例如:一个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();
}
结果: