目录
导出表定位以及解析(手动)
代码定位导出表并打印其数据
通过函数名查找导出函数地址
通过导出函数序号查找函数地址
移动导出表
PE中的导出表通常存在于动态链接库文件里.有些EXE也会存在导出表.导出表的主要作用是将PE中存在的函数引出到外部,以便其他人可以使用这些函数,实现代码的重用.导出表的存在可以让程序的开发者很容易清楚PE中到底有多少可以使用的函数.
Windows装载器在进行PE装载时,将与进程相关的DLL加载到对应虚拟地址空间.会根据导入表中登记的与该动态链接库相关的由INT指向的名称或编号来遍历DLL所在虚拟地址空间,通过函数名或编号查找导出表结构,从而确定该导出函数在虚拟地址空间中的起始地址VA,并将VA覆盖导入表的IAT相关项.
导出数据所在的节通常被命名为.edata,它包含了一些可被其他EXE程序访问的符号的相关信息,比如导出函数和资源等.这些符号通常出现在DLL中,但DLL也可以包含导入符号,而且在某些EXE中也可以有导出符号.
IMAGE_EXPORT_DIRECTORY
结构及成员含义如下:
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics; // 未使用
DWORD TimeDateStamp; // 时间戳
WORD MajorVersion; // 未使用
WORD MinorVersion; // 未使用
DWORD Name; // 指向该导出表的文件名字符串RVA
DWORD Base; // 导出函数的起始序号
DWORD NumberOfFunctions; // 所有导出函数的个数
DWORD NumberOfNames; // 以函数名字导出的函数个数
DWORD AddressOfFunctions; // 导出函数地址表RVA
DWORD AddressOfNames; // 导出函数名称表RVA
DWORD AddressOfNameOrdinals; // 导出函数序号表RVA
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
定位导出表:
IMAGE_OPTIONAL_HEADER -> DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT(0)].VirtualAddress + ImageBase.(如果在文件中解析只需将RVA转换为FOA加上当前文件首地址即可定位导出表).
通过WinHex工具查看PE文件默认属性
上述解析得知:
内存对齐 = 1000h
文件对齐 = 200h
导出表RVA = 0x00116640
转换为FOA = 0x000C8E40(RVA与FOA转换讲解)
成员含义:
Characteristics;// 未使用
TimeDateStamp;// 时间戳
MajorVersion;// 未使用
MinorVersion;// 未使用
Name;// 指向该导出表的文件名字符串,该值为RVA
Base;// 导出函数起始序号.(默认导出序号从1开始,但是通过xxx.def文件可自定义函数导出序号).
NumberOfFunctions;// 所有导出函数的个数.该值并不一定是真正导出函数个数.计算公式为:导出最大序号 - 导出最小序号 + 1.
这个DLL我自己编写测试的.实际导出函数为4个.序号为2,4,5,6.(6 - 2 + 1就是NumberOfFunctions结果).
NumberOfNames;// 以函数名字导出的函数个数.(通过xxx.def文件可以定义函数不导出名字).
测试DLL .def定义如下:
LIBRARY
EXPORTS
Add @2
Sub @6
Mul @4 NONAME
Div @5
AddressOfFunctions;// 导出函数地址表,该值为RVA,该成员相当于指向元素大小为4字节的数组,元素个数由NumberOfFunctions提供,每项元素为导出函数地址的RVA(加上ImageBase才是函数真正的地址).
AddressOfNames;// 导出函数名称表,该值为RVA,该成员相当于指向元素大小为4字节的数组,元素个数由 NumberOfNames提供,每项元素为导出函数名的RVA.
AddressOfNameOrdinals;// 导出函数序号表,该值为RVA,该成员相当于指向元素大小为2字节的数组,元素个数由 NumberOfNames提供,元素值加上Base为函数导出序号.通过函数名查找函数地址需要通过此值.
上述解析全解如下图:
导出表相对简单,只需要了解三张表结构以及含义很容易理解其对应关系.
读取文件代码
PVOID FileToMem(IN PCHAR szFilePath, OUT LPDWORD dwFileSize)
{
//打开文件
FILE* pFile = fopen(szFilePath, "rb");
if (!pFile)
{
printf("FileToMem fopen Fail \r\n");
return NULL;
}
//获取文件长度
fseek(pFile, 0, SEEK_END); //SEEK_END文件结尾
DWORD Size = ftell(pFile);
fseek(pFile, 0, SEEK_SET); //SEEK_SET文件开头
//申请存储文件数据缓冲区
PCHAR pFileBuffer = (PCHAR)malloc(Size);
if (!pFileBuffer)
{
printf("FileToMem malloc Fail \r\n");
fclose(pFile);
return NULL;
}
//读取文件数据
fread(pFileBuffer, Size, 1, pFile);
//判断是否为可执行文件
if (*(PSHORT)pFileBuffer != IMAGE_DOS_SIGNATURE)
{
printf("Error: MZ \r\n");
fclose(pFile);
free(pFileBuffer);
return NULL;
}
if (*(PDWORD)(pFileBuffer + *(PDWORD)(pFileBuffer + 0x3C)) != IMAGE_NT_SIGNATURE)
{
printf("Error: PE \r\n");
fclose(pFile);
free(pFileBuffer);
return NULL;
}
if (dwFileSize)
{
*dwFileSize = Size;
}
fclose(pFile);
return pFileBuffer;
}
输出文件代码
VOID MemToFile(IN PCHAR szFilePath, IN PVOID pFileBuffer, IN DWORD dwFileSize)
{
//打开文件
FILE* pFile = fopen(szFilePath, "wb");
if (!pFile)
{
printf("MemToFile fopen Fail \r\n");
return;
}
//输出文件
fwrite(pFileBuffer, dwFileSize, 1, pFile);
fclose(pFile);
}
定位并输出导出表数据
VOID PrintExportInfo()
{
//读取文件二进制数据
DWORD dwFileSize = 0;
PCHAR pFileBuffer = FileToMem(FILE_PATH_IN, &dwFileSize);
if (!pFileBuffer)
{
return;
}
//定位结构
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pFileBuffer;
PIMAGE_NT_HEADERS pNth = (PIMAGE_NT_HEADERS)(pFileBuffer + pDos->e_lfanew);
PIMAGE_FILE_HEADER pFil = (PIMAGE_FILE_HEADER)((PUCHAR)pNth + 4);
PIMAGE_OPTIONAL_HEADER pOpo = (PIMAGE_OPTIONAL_HEADER)((PUCHAR)pFil + IMAGE_SIZEOF_FILE_HEADER);
//判断该PE文件是否有导出表
if (!pOpo->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)
{
printf("该PE文件不存在导出表 \r\n");
return;
}
PIMAGE_EXPORT_DIRECTORY pExp = (PIMAGE_EXPORT_DIRECTORY)(pFileBuffer + RvaToFoa(pFileBuffer, pOpo->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress));
//基本导出表信息
printf("IMAGE_EXPORT_DIRECTORY.Characteristics -> [0x%08x] \r\n", pExp->Characteristics);
printf("IMAGE_EXPORT_DIRECTORY.TimeDateStamp -> [0x%08x] \r\n", pExp->TimeDateStamp);
printf("IMAGE_EXPORT_DIRECTORY.MajorVersion -> [0x%04x] \r\n", pExp->MajorVersion);
printf("IMAGE_EXPORT_DIRECTORY.MinorVersion -> [0x%04x] \r\n", pExp->MinorVersion);
printf("IMAGE_EXPORT_DIRECTORY.Name -> [%s] \r\n", pFileBuffer + RvaToFoa(pFileBuffer, pExp->Name));
printf("IMAGE_EXPORT_DIRECTORY.Base -> [0x%08x] \r\n", pExp->Base);
printf("IMAGE_EXPORT_DIRECTORY.NumberOfFunctions -> [0x%08x] \r\n", pExp->NumberOfFunctions);
printf("IMAGE_EXPORT_DIRECTORY.NumberOfNames -> [0x%08x] \r\n", pExp->NumberOfNames);
//函数地址表
printf("IMAGE_EXPORT_DIRECTORY.AddressOfFunctions -> [0x%08x] \r\n", pExp->AddressOfFunctions);
LPDWORD pAddFunc = (LPDWORD)(pFileBuffer + RvaToFoa(pFileBuffer, pExp->AddressOfFunctions));
for (size_t i = 0; i < pExp->NumberOfFunctions; i++)
{
printf("AddressOfFunctions[%d] -> [0x%08x] \r\n", i, pAddFunc[i]);
}
//函数名称表
printf("IMAGE_EXPORT_DIRECTORY.AddressOfNames -> [0x%08x] \r\n", pExp->AddressOfNames);
LPDWORD pAddName = (LPDWORD)(pFileBuffer + RvaToFoa(pFileBuffer, pExp->AddressOfNames));
for (size_t i = 0; i < pExp->NumberOfNames; i++)
{
printf("AddressOfNames[%d] -> [%s] \r\n", i, pFileBuffer + RvaToFoa(pFileBuffer, pAddName[i]));
}
//函数序号表
printf("IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals -> [0x%08x] \r\n", pExp->AddressOfNameOrdinals);
LPWORD pAddOrdi = (LPWORD)(pFileBuffer + RvaToFoa(pFileBuffer, pExp->AddressOfNameOrdinals));
for (size_t i = 0; i < pExp->NumberOfNames; i++)
{
printf("AddressOfNameOrdinals[%d] -> [0x%04x] \r\n", i, pAddOrdi[i]);
}
//PE工具解析如下
pAddFunc = (LPDWORD)(pFileBuffer + RvaToFoa(pFileBuffer, pExp->AddressOfFunctions));
pAddName = (LPDWORD)(pFileBuffer + RvaToFoa(pFileBuffer, pExp->AddressOfNames));
pAddOrdi = (LPWORD)(pFileBuffer + RvaToFoa(pFileBuffer, pExp->AddressOfNameOrdinals));
size_t i, j;
BOOL bFlag = FALSE;
for (i = 0; i < pExp->NumberOfFunctions; i++)
{
//函数地址表中值为空直接跳过本次循环
if (pAddFunc[i] == NULL)
{
printf("Ordinal[%04x] RVA[0x%08x] Name[NULL] \r\n", i + pExp->Base, pAddFunc[i]);
continue;
}
//判断导出方式
bFlag = FALSE;
for ( j = 0; j < pExp->NumberOfNames; j++)
{
if (pAddOrdi[j] == i)
{
//通过函数名导出
bFlag = TRUE;
printf("Ordinal[%04x] RVA[0x%08x] Name[%s] \r\n", pAddOrdi[j] + pExp->Base, pAddFunc[pAddOrdi[j]], pFileBuffer + RvaToFoa(pFileBuffer, pAddName[j]));
break;
}
}
if (!bFlag)
{
//通过序号导出
printf("Ordinal[%04x] RVA[0x%08x] Name[NULL] \r\n", i + pExp->Base, pAddFunc[i]);
}
}
}
//通过函数名查找导出函数地址
PVOID GetFunctionAddrByName(PCHAR pFileBuffer, PCHAR szName)
{
//定位结构
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pFileBuffer;
PIMAGE_NT_HEADERS pNth = (PIMAGE_NT_HEADERS)(pFileBuffer + pDos->e_lfanew);
PIMAGE_FILE_HEADER pFil = (PIMAGE_FILE_HEADER)((PUCHAR)pNth + 4);
PIMAGE_OPTIONAL_HEADER pOpo = (PIMAGE_OPTIONAL_HEADER)((PUCHAR)pFil + IMAGE_SIZEOF_FILE_HEADER);
//判断该PE文件是否有导出表
if (!pOpo->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)
{
printf("该PE文件不存在导出表 \r\n");
return;
}
PIMAGE_EXPORT_DIRECTORY pExp = (PIMAGE_EXPORT_DIRECTORY)(pFileBuffer + RvaToFoa(pFileBuffer, pOpo->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress));
//定位地址表,名称表,序号表
LPDWORD pAddFunc = (LPDWORD)(pFileBuffer + RvaToFoa(pFileBuffer, pExp->AddressOfFunctions));
LPDWORD pAddName = (LPDWORD)(pFileBuffer + RvaToFoa(pFileBuffer, pExp->AddressOfNames));
LPWORD pAddOrdi = (LPWORD)(pFileBuffer + RvaToFoa(pFileBuffer, pExp->AddressOfNameOrdinals));
for (size_t i = 0; i < pExp->NumberOfNames; i++)
{
if (strcmp(szName,(PCSTR)(pFileBuffer + RvaToFoa(pFileBuffer, pAddName[i]))) == 0)
{
//此值+ImageBase为真正函数地址
return pAddFunc[pAddOrdi[i]];
}
}
return NULL;
}
//通过导出函数序号查找函数地址
PVOID GetFunctionAddrByOrdinals(PCHAR pFileBuffer, DWORD dwOrdinal)
{
//定位结构
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pFileBuffer;
PIMAGE_NT_HEADERS pNth = (PIMAGE_NT_HEADERS)(pFileBuffer + pDos->e_lfanew);
PIMAGE_FILE_HEADER pFil = (PIMAGE_FILE_HEADER)((PUCHAR)pNth + 4);
PIMAGE_OPTIONAL_HEADER pOpo = (PIMAGE_OPTIONAL_HEADER)((PUCHAR)pFil + IMAGE_SIZEOF_FILE_HEADER);
//判断该PE文件是否有导出表
if (!pOpo->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)
{
printf("该PE文件不存在导出表 \r\n");
return;
}
PIMAGE_EXPORT_DIRECTORY pExp = (PIMAGE_EXPORT_DIRECTORY)(pFileBuffer + RvaToFoa(pFileBuffer, pOpo->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress));
//定位地址表,名称表,序号表
LPDWORD pAddFunc = (LPDWORD)(pFileBuffer + RvaToFoa(pFileBuffer, pExp->AddressOfFunctions));
//判断序号是否正常
if (dwOrdinal - pExp->Base > pExp->NumberOfFunctions)
{
return NULL;
}
return pAddFunc[dwOrdinal - pExp->Base];
}
VOID MoveExportTable()
{
//读取文件二进制数据
DWORD dwFileSize = 0;
PCHAR pFileBuffer = FileToMem(FILE_PATH_IN, &dwFileSize);
if (!pFileBuffer)
{
return;
}
//新增节存储导出表
pFileBuffer = AddNewSection(pFileBuffer, 0x2000, &dwFileSize);
//定位结构
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pFileBuffer;
PIMAGE_NT_HEADERS pNth = (PIMAGE_NT_HEADERS)(pFileBuffer + pDos->e_lfanew);
PIMAGE_FILE_HEADER pFil = (PIMAGE_FILE_HEADER)((PUCHAR)pNth + 4);
PIMAGE_OPTIONAL_HEADER pOpo = (PIMAGE_OPTIONAL_HEADER)((PUCHAR)pFil + IMAGE_SIZEOF_FILE_HEADER);
PIMAGE_SECTION_HEADER pSec = (PIMAGE_SECTION_HEADER)((PUCHAR)pOpo + pFil->SizeOfOptionalHeader);
//判断该PE文件是否有导出表
if (!pOpo->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)
{
printf("该PE文件不存在导出表 \r\n");
return;
}
PIMAGE_EXPORT_DIRECTORY pExp = (PIMAGE_EXPORT_DIRECTORY)(pFileBuffer + RvaToFoa(pFileBuffer, pOpo->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress));
//导出地址表,名称表,序号表大小
DWORD dwSizeOfAddrFunc = 4 * pExp->NumberOfFunctions;
DWORD dwSizeOfAddrName = 4 * pExp->NumberOfNames;
DWORD dwSizeOfAddrOrdi = 2 * pExp->NumberOfNames;
//修复可选PE头中目录项指向
PUCHAR pCurrent = pFileBuffer + pSec[pFil->NumberOfSections - 1].PointerToRawData;
pOpo->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress = FoaToRva(pFileBuffer, pCurrent - pFileBuffer);
//拷贝导出表结构
PIMAGE_EXPORT_DIRECTORY pNewExp = (PIMAGE_EXPORT_DIRECTORY)pCurrent;
memcpy(pCurrent, pExp, sizeof(IMAGE_SECTION_HEADER));
pCurrent += sizeof(IMAGE_SECTION_HEADER);
//拷贝导出表结构中Name成员
DWORD dw = RvaToFoa(pFileBuffer, pExp->Name);
dw += (DWORD)pFileBuffer;
PUCHAR pOldDllName = pFileBuffer + RvaToFoa(pFileBuffer, pExp->Name);
DWORD dwDllNameLength = strlen(pOldDllName);
memcpy(pCurrent, pOldDllName, dwDllNameLength);
//修复导出表结构中Name指向
pNewExp->Name = FoaToRva(pFileBuffer, pCurrent - pFileBuffer);
pCurrent = pCurrent + dwDllNameLength + 1;
//拷贝地址表数据
PUCHAR pOldAddrFunc = pFileBuffer + RvaToFoa(pFileBuffer, pExp->AddressOfFunctions);
memcpy(pCurrent, pOldAddrFunc, dwSizeOfAddrFunc);
//修复导出表结构中AddressOfFunctions指向
pNewExp->AddressOfFunctions = FoaToRva(pFileBuffer, pCurrent - pFileBuffer);
pCurrent += dwSizeOfAddrFunc;
//拷贝名称表数据
PDWORD pOldAddrName = pFileBuffer + RvaToFoa(pFileBuffer, pExp->AddressOfNames);
memcpy(pCurrent, pOldAddrName, dwSizeOfAddrName);
//修复导出表结构中AddressOfNames指向
pNewExp->AddressOfNames = FoaToRva(pFileBuffer, pCurrent - pFileBuffer);
//指向当前导出名称表
PDWORD pNewAddrName = pCurrent;
pCurrent += dwSizeOfAddrName;
for (size_t i = 0; i < pExp->NumberOfNames; i++)
{
//遍历并修复导出表名称表的指向
//默认函数地址表中RVA转FOA并获取文件中地址
PUCHAR pOldNameAddr = pFileBuffer + RvaToFoa(pFileBuffer, pOldAddrName[i]);
//获取函数名长度
DWORD dwFunNameSize = strlen(pOldNameAddr) + 1;
//拷贝函数名
memcpy(pCurrent, pOldNameAddr, dwFunNameSize);
//修正新导出名称表中地址
pNewAddrName[i] = FoaToRva(pFileBuffer, pCurrent - pFileBuffer);
pCurrent += dwFunNameSize;
}
//拷贝序号表数据
PUCHAR pOldAddrOrdi = pFileBuffer + RvaToFoa(pFileBuffer, pExp->AddressOfNameOrdinals);
memcpy(pCurrent, pOldAddrOrdi, dwSizeOfAddrOrdi);
//修复导出表结构中AddressOfNameOrdinals指向
pNewExp->AddressOfNameOrdinals = FoaToRva(pFileBuffer, pCurrent - pFileBuffer);
pCurrent += dwSizeOfAddrOrdi;
//将二进制数据输出到文件
MemToFile(FILE_PATH_OUT, pFileBuffer, dwFileSize);
}
查看移动后导出表位置
移动后解析数据无误
获取函数地址成功