Windows PE数据目录表和导出表解析
起因
五一假期没有回家,是因为在四月份已经向提了离职.估计五月中旬应该就能办理离职手续,在公司呆了6年多,合同由3(2次)年换成无固定期限劳动合同.因为家庭原因,最终还是要回去的.不知不觉间北漂了8年,在公司工作期间经历我人生的大事(结婚和生子).关于PE相关的内容,在前面也写过 学习PE 和 在解析PE遇到的问题,这里也不多的介绍了.
在看正文之前,先看看图,对下边具体要做的事情,有一个大概的认知.
关系图(省略节表部分)

数据目录表
在 学习PE 这边博文中,已经对可选PE头进行了解析. 在IMAGE_OPTIONAL_HEADER结构体中这个字段DataDirectory,就是我们通常所说的数据目录表.重新看看这个可选PE头结构.#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16 //数据目标表的长度
//可选PE头
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// Standard fields.
//
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;
//
// NT additional fields.
//
DWORD ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //数据目录表
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32
//数据目录表结构
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; //在内存中的相对地址
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
先将数据目录表信息打印出来,在用PETool进行对比,看看是否是有问题.
//打印出 数据目录表 信息
void print_data_dir(char* base)
{
PIMAGE_DOS_HEADER dos_header = (PIMAGE_DOS_HEADER)base;
PIMAGE_NT_HEADERS nt_header = (PIMAGE_NT_HEADERS)((unsigned long)base + dos_header->e_lfanew);
PIMAGE_FILE_HEADER file_header = (PIMAGE_FILE_HEADER)((unsigned long)nt_header + 4); //4为nt头中Signature DWORD
PIMAGE_OPTIONAL_HEADER optional_header = (PIMAGE_OPTIONAL_HEADER)((unsigned long)file_header + IMAGE_SIZEOF_FILE_HEADER);
for (int i = 0; i < IMAGE_NUMBEROF_DIRECTORY_ENTRIES; i++)
{
IMAGE_DATA_DIRECTORY data_dir = optional_header->DataDirectory[i];
printf("%d data:%08x size:%x\n", i, data_dir.VirtualAddress, data_dir.Size);
}
}
int main(int argc, char* argv[])
{
char* filename = "DllExportTable.dll"; //以动态库为例
int size = file_size(filename);
if (size > 0)
{
char* buf;
file_to_memory(filename, size, &buf);
print_data_dir(buf);
}
else
{
printf("file not found!\n");
}
return 0;
}
0 | 导出表 |
1 | 导入表 |
2 | 资源表 |
3 | 异常表 |
4 | 安全证书表 |
5 | 重定位表 |
6 | 调试信息表 |
7 | 版权所有表 |
8 | 全局指针表 |
9 | TLS(线程本地存储表) |
10 | 加载配置表 |
11 | 绑定导入表 |
12 | IAT(导入地址表) |
13 | 延迟导入表 |
14 | COM表 |
15 | 保留(这个暂时没用) |
导出表
从数据目录表中,知道了第一个表是导出表,并且导出表的VirtualAddress.这个时候要了解这两个RVA和FOA知识点.RVA(相对虚拟地址):VirtualAddress就是RVA.
FOA(文件偏移地址):
如果解析PE时候,如果不进行内存拉伸,VirtualAddress就需要用RVA转FOA的转换,这个转换是拿导出表的VirtualAddress在节表中查找每个节的VirtualAddress的区域中.然后根据节中的PointerToRelocation(文件中的偏移)
//相对虚拟地址转换为文件偏移地址
int rva_to_foa(char* base, unsigned long* va)
{
unsigned long rva = *va;
PIMAGE_DOS_HEADER dos_header = (PIMAGE_DOS_HEADER)base;
PIMAGE_NT_HEADERS nt_header = (PIMAGE_NT_HEADERS)((unsigned long)base + dos_header->e_lfanew);
PIMAGE_FILE_HEADER file_header = (PIMAGE_FILE_HEADER)((unsigned long)nt_header + 4);
PIMAGE_OPTIONAL_HEADER optional_header = (PIMAGE_OPTIONAL_HEADER)((unsigned long)file_header + IMAGE_SIZEOF_FILE_HEADER);
//1. 获取节表
PIMAGE_SECTION_HEADER section_headersection_header = (PIMAGE_SECTION_HEADER)((unsigned long)optional_header + file_header->SizeOfOptionalHeader);
DWORD alignment = optional_header->SectionAlignment; //内存对齐大小 4096
//2. 获取节表的个数
int sections = nt_header->FileHeader.NumberOfSections;
int offset = -1;
for (int i = 0; i < sections; i++)
{
PIMAGE_SECTION_HEADER psection = section_headersection_header + i;
//3. 获取节的RVA
DWORD section_start = psection->VirtualAddress;
if (rva < section_start)
{
offset = rva;
return offset;
}
int block_count = psection->SizeOfRawData / alignment;
block_count += psection->SizeOfRawData % alignment ? 1 : 0;
//4. 判断导出表的相对虚拟地址rva 是否在这个节中
if (rva >= section_start && rva < section_start + block_count * alignment)
{
//5. 获取节的文件中偏移加上导出表的rva减去节的rva (真正在文件中的偏移地址)
offset = psection->PointerToRawData + rva - section_start;
return offset;
}
}
return offset;
}
//打印出 数据目录表
void print_data_dir(char* base)
{
PIMAGE_DOS_HEADER dos_header = (PIMAGE_DOS_HEADER)base;
PIMAGE_NT_HEADERS nt_header = (PIMAGE_NT_HEADERS)((unsigned long)base + dos_header->e_lfanew);
PIMAGE_FILE_HEADER file_header = (PIMAGE_FILE_HEADER)((unsigned long)nt_header + 4); //4为nt头中Signature DWORD
PIMAGE_OPTIONAL_HEADER optional_header = (PIMAGE_OPTIONAL_HEADER)((unsigned long)file_header + IMAGE_SIZEOF_FILE_HEADER);
for (int i=0;i< IMAGE_NUMBEROF_DIRECTORY_ENTRIES;i++)
{
IMAGE_DATA_DIRECTORY data_dir = optional_header->DataDirectory[i];
printf("%d data:%08x size:%x\n", i, data_dir.VirtualAddress, data_dir.Size);
}
IMAGE_DATA_DIRECTORY export_dir = optional_header->DataDirectory[0];
int offset = rva_to_foa(base, &export_dir.VirtualAddress);
//根据导出表的rva,进行foa转换
//获取导出表
PIMAGE_EXPORT_DIRECTORY export_table = (PIMAGE_EXPORT_DIRECTORY)(base + offset);
}
//IMAGE_EXPORT_DIRECTORY结构体 主要的字段
//DWORD Name;
//DWORD Base;
//IMAGE_EXPORT_DIRECTORY 导出表
//DWORD NumberOfFunctions; //导出函数的个数
//DWORD NumberOfNames; //函数名称的函数个数
//DWORD AddressOfFunctions; 导出函数的地址表 rva 个数用NumberOfFunctions
//DWORD AddressOfNames; // 导出函数名称表 rva 个数用NumberOfNames
//DWORD AddressOfNameOrdinals; // 导出序号表 rva 个数用NumberOfNames
//函数名字 通过AddressOfNames(将地址rva到foa) 查到之后,去AddressOfNameOrdinals 获取序号,然后在根据序号去AddressOfFunctions表找到函数的地址
//序号 通过序号 减去base 得到的下标,直接去AddressOfFunctions
上面拿到导出表,通过导出表解析,可以实现GetProcAddress功能.
动态库头文件
#ifdef __cplusplus
extern "C"
{
#endif
int add(int a, int b);
int sub(int a, int b);
#ifdef __cplusplus
}
#endif
动态库源文件
_declspec (dllexport) int add(int a, int b)
{
return a + b;
}
_declspec (dllexport) int sub(int a, int b)
{
return a - b;
}
看看如何实现:
//获取函数的调用地址
void* my_proc_address(char* base, char* func_name)
{
PIMAGE_DOS_HEADER dos_header = (PIMAGE_DOS_HEADER)base;
PIMAGE_NT_HEADERS nt_header = (PIMAGE_NT_HEADERS)((unsigned long)base + dos_header->e_lfanew);
PIMAGE_FILE_HEADER file_header = (PIMAGE_FILE_HEADER)((unsigned long)nt_header + 4);
PIMAGE_OPTIONAL_HEADER optional_header = (PIMAGE_OPTIONAL_HEADER)((unsigned long)file_header + IMAGE_SIZEOF_FILE_HEADER);
PIMAGE_SECTION_HEADER section_headersection_header = (PIMAGE_SECTION_HEADER)((unsigned long)optional_header + file_header->SizeOfOptionalHeader);
IMAGE_DATA_DIRECTORY export_table = optional_header->DataDirectory[0];
PIMAGE_EXPORT_DIRECTORY export = (PIMAGE_EXPORT_DIRECTORY)(base + export_table.VirtualAddress);
unsigned long names = export->NumberOfNames;
unsigned long* address_names = (unsigned long*)(base + export->AddressOfNames);
unsigned short* address_name_ordinals = (unsigned short*)(base + export->AddressOfNameOrdinals);
unsigned long* address_fuctions = (unsigned long*)(base + export->AddressOfFunctions);
for (int i = 0; i < names; i++)
{
char* name = (char*)(base + address_names[i]);
if (strcmp(name, func_name) == 0)
{
int ordinal = address_name_ordinals[i];
void* func_addr = (void*)(base + address_fuctions[ordinal]);
return func_addr;
}
}
return NULL;
}
int main(int argc, char* argv[])
{
typedef int(*Add)(int a, int b);
HMODULE hmodule = LoadLibraryEx("DllExportTable.dll", NULL, DONT_RESOLVE_DLL_REFERENCES);
//my_proc_address 实现GetProcAddress()功能
Add add = (Add)my_proc_address(hmodule, "add");
int sum = add(100, 100);
printf("sum=%d\n", sum);
}
上面实现GetProcAddress函数的代码,为什么没有进行RVA到FOA的转换呢? 因为LoadLibraryEx函数已经dll文件加载到内存上进行了拉伸操作.所以不需要进行转换.
通过序号查找函数的调用地址
//通过序号,查找函数在内存中地址
//1. 获取导出表在内存中的位置
//2. 序号减去导出表中起始函数序号(Base),得到的下标就是
void* my_proc_ordinal(char* base, int ordinal)
{
if (ordinal > 0)
{
PIMAGE_DOS_HEADER dos_header = (PIMAGE_DOS_HEADER)base;
PIMAGE_NT_HEADERS nt_header = (PIMAGE_NT_HEADERS)((unsigned long)base + dos_header->e_lfanew);
PIMAGE_FILE_HEADER file_header = (PIMAGE_FILE_HEADER)((unsigned long)nt_header + 4);
PIMAGE_OPTIONAL_HEADER optional_header = (PIMAGE_OPTIONAL_HEADER)((unsigned long)file_header + IMAGE_SIZEOF_FILE_HEADER);
PIMAGE_SECTION_HEADER section_headersection_header = (PIMAGE_SECTION_HEADER)((unsigned long)optional_header + file_header->SizeOfOptionalHeader);
IMAGE_DATA_DIRECTORY export_table = optional_header->DataDirectory[0]; //导出表 内存中相对地址
PIMAGE_EXPORT_DIRECTORY export = (PIMAGE_EXPORT_DIRECTORY)(base + export_table.VirtualAddress); //导出表 在内存的地址
//这里获取函数个数,要特殊处理一下
//正常情况下,用NumberOfFunctions就能获取到
//其他情况下,如在def文件,让个别函数没有名称或者指定序号
unsigned long numbers = export->Base + export->NumberOfFunctions - 1; //起始序号加个数 在减一
if (ordinal > numbers)
{
return NULL;
}
unsigned long* address_fuctions = (unsigned long*)(base + export->AddressOfFunctions);
unsigned long base_count = export->Base;
return (void*)(base + address_fuctions[ordinal - base_count]);
}
return NULL;
}
秋风
2020-05-03