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();
}
結果:
