例如:一個HelloDll.dll
其導出表信息如下:

該dll有4個函數;
用.def的方式導出;
其中有個匿名函數;

1.分析
導入dll中的函數有兩種方式:
1】通過序號
2】通過函數名
例如:顯式鏈接dll時使用的庫函數“GetProcAddress”實現了用函數的查找;
myPlus = (lpPlus)GetProcAddress(hModule, "_Plus@8");
其中的參數hModule實際上是pe文件拉伸后的起始位置ImageBase;
2.通過函數名導入
思路:
遍歷名字表,獲取函數名,與目標函數名比對,如果有相同的函數名,獲得該函數名在名字表中的索引;
注意:名字表中儲存的是函數名的內存鏡像中相對ImageBase的偏移地址,需要轉換成文件鏡像偏移地址;
用獲得的索引在序號表中找到該函數對應的序號;
以序號為索引在函數地址表中找到函數的地址;
注意:函數地址表中儲存的是函數內存鏡像的偏移地址,需要轉換為文件鏡像的偏移地址;
用函數指針接收函數的地址;
使用函數;
函數:
GetFunctionAddrByName(FileBuffer指針,函數名指針)
代碼:
#include "stdafx.h"
#include "PeTool.h"
#include "string.h"
#define SRC "C:\\Users\\Administrator\\Desktop\\DllHello.dll"
typedef int (__stdcall *lpPlus)(int,int);
typedef int (__stdcall *lpSub)(int,int);
typedef int (__stdcall *lpMul)(int,int);
typedef int (__stdcall *lpDiv)(int,int);
//通過函數名找dll導出函數
LPVOID GetFunctionAddrByName(LPVOID pFileBuffer, LPSTR funName){
//定義頭結構指針
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; //導出表指針
//初始化頭指針
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 NULL;
}
LPVOID pFun = NULL;
//1.循環從名字表中找與目標函數名相同的;如有有返回該名字在表中的索引
int ordIndex = -1;
for(int i=0;i<exportDir->NumberOfNames;i++){
DWORD nameOffset = *((LPDWORD)((DWORD)pFileBuffer + (DWORD)((LPDWORD)(exportDir->AddressOfNames)+i)));
LPSTR nameAddr =(LPSTR) ((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer,nameOffset));
if(!strcmp(nameAddr, funName)){
ordIndex = i;
break;
}
}
if(ordIndex < 0){
printf("沒有該名字的函數\n");
return NULL;
}
//2.用獲得的索引從序號表中找函數的序號
WORD ord = *(LPWORD)((DWORD)pFileBuffer + (DWORD)((exportDir->AddressOfNameOrdinals)+ordIndex));
//3.以序號表中查出來的序號為索引從函數地址表中找函數地址
DWORD addr = (DWORD)pFileBuffer + (DWORD)((LPDWORD)(exportDir->AddressOfFunctions) +ord);
DWORD offset = *((LPDWORD)addr);
//5.因為地址表中保存的是內存鏡像地址,需要轉換為文件鏡像地址
offset = RvaToFileOffset(pFileBuffer,offset);
pFun = (LPVOID)((DWORD)pFileBuffer + offset);
return pFun;
}
//調用dll中的導出函數
void getDllFun(){
//讀取文件到緩沖區
LPVOID pFileBuffer = NULL;
DWORD fileSize = ReadPEFile(SRC, &pFileBuffer);
if(!fileSize){
printf("讀取文件失敗\n");
return;
}
//獲取函數指針
lpPlus myPlus =(lpPlus) GetFunctionAddrByName(pFileBuffer, "Plus");
printf("1+2=%d\n", myPlus(1,2));
lpMul myMul = (lpMul) GetFunctionAddrByName(pFileBuffer, "Plus");
printf("2X3=%d\n", myMul(2,3));
//釋放內存
free(pFileBuffer);
}
int main(int argc, char* argv[])
{
getDllFun();
getchar();
}
結果:

3.用序號導入函數
思路:
序號 - Base = 函數地址在地址表中的索引;
用索引在地址表中找到函數地址,注意內存鏡像地址轉文件鏡像地址;
用函數指針接收函數地址;
使用函數;
有的函數以匿名導出,不能通過函數名找到,可以用這種方式;
函數:
GetFunctionAddrByOrdinals(FileBuffer指針,函數名導出序號)
實現:
#include "stdafx.h"
#include "PeTool.h"
#include "string.h"
#define SRC "C:\\Users\\Administrator\\Desktop\\DllHello.dll"
typedef int (__stdcall *lpPlus)(int,int);
typedef int (__stdcall *lpSub)(int,int);
typedef int (__stdcall *lpMul)(int,int);
typedef int (__stdcall *lpDiv)(int,int);
//通過函數序號找dll導出函數
LPVOID GetFunctionAddrByOrdinals(LPVOID pFileBuffer, DWORD ord){
//定義頭結構指針
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; //導出表指針
//初始化頭指針
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 NULL;
}
LPVOID pFun = NULL;
//1.函數地址索引 = 序號 - Base
DWORD index = ord - exportDir->Base;
//2.利用索引從函數地址表中找函數地址
DWORD addr = (DWORD)pFileBuffer + (DWORD)((LPDWORD)(exportDir->AddressOfFunctions) + index);
DWORD offset = *((LPDWORD)addr);
//5.因為地址表中保存的是內存鏡像地址,需要轉換為文件鏡像地址
offset = RvaToFileOffset(pFileBuffer,offset);
pFun = (LPVOID)((DWORD)pFileBuffer + offset);
return pFun;
}
//調用dll中的導出函數
void getDllFun(){
//讀取文件到緩沖區
LPVOID pFileBuffer = NULL;
DWORD fileSize = ReadPEFile(SRC, &pFileBuffer);
if(!fileSize){
printf("讀取文件失敗\n");
return;
}
//獲取函數指針
lpSub mySub =(lpSub) GetFunctionAddrByOrdinals(pFileBuffer, 15);
printf("2-1=%d\n", mySub(2,1));
//釋放內存
free(pFileBuffer);
}
int main(int argc, char* argv[])
{
getDllFun();
getchar();
}
結果:
