導出表


exe程序中通常會使用動態鏈接庫dll中的函數;
dll相當於一個獨立的模塊,dll中的代碼並不會編譯到exe程序中;
這就產生了一個問題:exe怎么知道dll中的代碼在什么位置;
這就需要dll提供一個清單,這個清單中能清晰說明有多少個函數、它們的名字、地址;
導出表就是這樣的一個清單;
 
exe和dll都可以提供函數給其它模塊使用;
如果要這么做就必須提供一個清單,也就是導出表;
一般情況下exe都沒有導出表,因為exe通常不需要給其它模塊提高函數,但這不是絕對的;
 
1.定位導出表
數據目錄項的第一個結構,就是導出表.                    
typedef struct _IMAGE_DATA_DIRECTORY {                    
    DWORD   VirtualAddress;                    
    DWORD   Size;                    
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;         
VirtualAddress  導出表的RVA                    
Size 導出表大小      ->size只是參考,導出表有自己的方式約定自己的大小;修改了size並不影響程序;
 
2、導出表結構                                    
上面的結構,只是說明導出表在哪里,有多大,並不是真正的導出表.                                    
如何在FileBuffer中找到這個結構呢?在VirtualAddress中存儲的是RVA,如果想在FileBuffer中定位                                    
必須要先將該RVA轉換成FOA.                                    
真正的導出表結構如下:                                    
typedef struct _IMAGE_EXPORT_DIRECTORY {                                    
    DWORD   Characteristics;                // 未使用                    
    DWORD   TimeDateStamp;                // 時間戳                    
    WORD    MajorVersion;                // 未使用                    11
    WORD    MinorVersion;                // 未使用                    12
    DWORD   Name;                // 指向該導出表文件名字符串                    13
    DWORD   Base;                // 導出函數起始序號                    15
    DWORD   NumberOfFunctions;                // 所有導出函數的個數                    17
    DWORD   NumberOfNames;                // 以函數名字導出的函數個數                    
    DWORD   AddressOfFunctions;     // 導出函數地址表RVA                                    
    DWORD   AddressOfNames;         // 導出函數名稱表RVA                                    
    DWORD   AddressOfNameOrdinals;  // 導出函數序號表RVA                                    
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;              
 
地址表AddressOfFunctions    ->由導出函數的個數NumberOfFunctions決定;
名字表NumberOfNames    ->由NumberOfNames決定,因為可能有的函數用隱藏函數名的方式導出;
序號表AddressOfNameOrdinals    ->和名字表一樣大,也是由NumberOfNames決定;
 
導出函數有兩種方式:
    1】名字導出;
        ->遍歷名字表,用名字表中的地址找字符串,與目標字符串比對;
        ->找到字符串一樣的之后,得到該處的索引;
        ->按照相同的索引號從序號表中找序號值;
        ->用序號值為索引,從地址表中找到目標函數的地址;
    2】序號導出;
        ->用目標序號-BASE,得到一個值;
        ->直接用這個值為索引,從地址表中找函數的地址;
        ->不需要查序號表;
    
3、AddressOfFunctions說明                               
該表中元素寬度為4個字節                                
該表中存儲所有導出函數的地址                                
該表中個數由NumberOfFunctions決定                                
該表項中的值是RVA, 加上ImageBase才是函數真正的地址                                
定位:IMAGE_EXPORT_DIRECTORY->AddressOfFunctions 中存儲的是該表的RVA 需要先轉換成FOA                                
 
4、AddressOfNames說明                
該表中元素寬度為4個字節                
該表中存儲所有以名字導出函數的名字的RVA                
該表項中的值是RVA, 指向函數真正的名稱                
特別說明:            
    1、函數的真正的名字在文件中位置是不確定的            
    2、但函數名稱表中是按名字排序的            
    也就是說,A開頭的函數在AddressOfNames排在最前面.            
    但AXXXXXX這個真正的名字,可能排在BXXXXX后面            
    3、如果想打印名字,要先將AddressOfNames轉換為FOA     
 
5、AddressOfNameOrdinals說明       
該表中元素寬度為2個字節                
該表中存儲的內容 + Base = 函數的導出序號  
     
 
6.總結
1)為什么要分成3張表?                                
    函數導出的個數與函數名的個數未必一樣.所以要將函數地址表和函數名稱表分開. 
    比如有些函數可以用隱藏函數名的方式導出;
                               
2)函數地址表是不是一定大於函數名稱表?                                
    未必,一個相同的函數地址,可能有多個不同的名字.  
 
3)如何根據函數的名字獲取一個函數的地址?                
 
4)如何根據函數的導出序號獲取一個函數的地址?                
 
7.打印導出表信息
RVA轉FOA:
//將內存偏移轉換為文件偏移                                                                
DWORD RvaToFileOffset(IN LPVOID pFileBuffer,IN DWORD dwRva){
    //定義文件頭
    PIMAGE_DOS_HEADER dosHeader = NULL;        //dos頭指針
    PIMAGE_NT_HEADERS ntHeader = NULL;        //nt頭指針
    PIMAGE_FILE_HEADER peHeader = NULL;        //pe頭指針
    PIMAGE_OPTIONAL_HEADER32 opHeader = NULL;    //可選pe頭指針
    PIMAGE_SECTION_HEADER sectionHeader = NULL;    //節表指針
 
    //找到文件頭
    dosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
    ntHeader = (PIMAGE_NT_HEADERS) ((DWORD)pFileBuffer + dosHeader->e_lfanew);
    peHeader = (PIMAGE_FILE_HEADER) ((DWORD)ntHeader + 4);
    opHeader = (PIMAGE_OPTIONAL_HEADER32) ((DWORD)peHeader + IMAGE_SIZEOF_FILE_HEADER);
    sectionHeader = (PIMAGE_SECTION_HEADER) ((DWORD)opHeader + peHeader->SizeOfOptionalHeader);
 
    //1.判斷是哪個節
    int sec = -1;
    for(int i=0;i<peHeader->NumberOfSections;i++){
        DWORD va = (sectionHeader+i)->VirtualAddress;
        DWORD size = (sectionHeader+i)->Misc.VirtualSize;
        if(dwRva >= va && dwRva<=(va+size) ){
            sec = i;
            printf("在第%d個節\n",sec);
            break;
        }    
    }
    if(sec<0){
        printf("內存偏移不在任何一個節\n");
        return 0;
    }
 
    //2.轉換
    DWORD secOffset = dwRva - (sectionHeader + sec)->VirtualAddress;
    DWORD foa = (sectionHeader + sec)->PointerToRawData + secOffset;
 
    return foa;
}
 
輸出導出表:
#include "stdafx.h"
#include "PeTool.h"
 
#define SRC "C:\\Users\\Administrator\\Desktop\\DllHello.dll"
 
void printExportDir(){
    //定義頭結構指針
    PIMAGE_DOS_HEADER dosHeader = NULL;        //dos頭指針
    PIMAGE_FILE_HEADER peHeader = NULL;        //pe頭指針
    PIMAGE_OPTIONAL_HEADER32 opHeader = NULL;    //可選pe頭指針
    PIMAGE_DATA_DIRECTORY dataDir = NULL;        //數據目錄指針
    PIMAGE_EXPORT_DIRECTORY exportDir = NULL;    //導出表指針
 
    //1.讀取文件到緩沖區
    LPVOID pFileBuffer = NULL;
    DWORD fileSize = ReadPEFile(SRC, &pFileBuffer);
    if(!fileSize){
        printf("讀取文件失敗\n");
        return;
    }
 
    //2.初始化頭指針
    dosHeader = (PIMAGE_DOS_HEADER) pFileBuffer;
    peHeader = (PIMAGE_FILE_HEADER) ((DWORD)dosHeader + dosHeader->e_lfanew + 4);
    opHeader = (PIMAGE_OPTIONAL_HEADER32) ((DWORD)peHeader + IMAGE_SIZEOF_FILE_HEADER);
    dataDir = opHeader->DataDirectory;
    exportDir = (PIMAGE_EXPORT_DIRECTORY) ((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer,dataDir->VirtualAddress));
    if(!exportDir){
        printf("該文件沒有導出表\n");
        free(pFileBuffer);
        return;
    }
    //3.輸出信息
    LPSTR expName = (LPSTR) ((DWORD)pFileBuffer + exportDir->Name);    //注意導出表中的name保存的是:dll名的地址相對於首地址的偏移
    printf("導出表文件名Name:%s\n",expName);
    printf("導出函數起始序號Base:%d\n",exportDir->Base);
    printf("所有導出函數的個數NumberOfFunctions:%d\n",exportDir->NumberOfFunctions);
    printf("以函數名導出的函數個數NumberOfNames:%d\n",exportDir->NumberOfNames);
    printf("地址表內存偏移AddressOfFunctions:%x\n",exportDir->AddressOfFunctions);
    printf("名字表內存偏移AddressOfNames:%x\n",exportDir->AddressOfNames);
    printf("序號表內存偏移AddressOfNameOrdinals:%x\n",exportDir->AddressOfNameOrdinals);
 
    printf("***************地址表*********************\n");
    LPDWORD pFunAddr = (LPDWORD)(exportDir->AddressOfFunctions);
    for(int i=0;i<exportDir->NumberOfFunctions;i++){
        DWORD funAddr = *(LPDWORD)((DWORD)pFileBuffer + (DWORD)(pFunAddr+i));
        printf("函數地址[%d]-%x:%x\n", i, pFunAddr+i, funAddr);    
    }
    printf("*************名字表****************\n");
    LPDWORD pNameAddr = (LPDWORD)((DWORD)pFileBuffer + exportDir->AddressOfNames);
    for(int j=0;j<exportDir->NumberOfNames;j++){
        DWORD nameOffset = *(pNameAddr + j);
        LPSTR funName = (LPSTR)((DWORD)pFileBuffer + nameOffset);
        printf("函數名[%d]-%x:%s\n", j, nameOffset, funName);
    }
    printf("*********序號表***************\n");
    LPWORD pOrdinals = (LPWORD) (exportDir->AddressOfNameOrdinals);
    for(int l=0;l<exportDir->NumberOfNames;l++){
        WORD ord = ((LPWORD)((DWORD)pFileBuffer + (DWORD)pOrdinals))[l];
        printf("序號[%d]-%x:%d\n",l, pOrdinals+l,ord);
    }
    
    //釋放內存
    free(pFileBuffer);
 
}
 
int main(int argc, char* argv[])
{
    printExportDir();
    getchar();
}
 
 
結果:
       
 
  
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM