五一(2020年,迁移文章)假期没有回家,是因为在四月份已经向公司提了离职.估计五月中旬应该就能办理离职手续,在公司呆了6年多,合同由3(2次)年换成无固定期限劳动合同.因为家庭原因,最终还是要回去的.不知不觉间北漂了8年,在公司工作期间经历我人生的大事(结婚和生子).
关于PE相关的内容,在前面也写过 <<学习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 DWORDPIMAGE_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;}
PE中的数据目录表
导出表 | |
1 | 导入表 |
2 | 资源表 |
3 | 异常表 |
4 | 安全证书表 |
5 | 重定位表 |
6 | 调试信息表 |
7 | 版权所有表 |
8 | 全局指针表 |
9 | TLS(线程本地存储表) |
10 | 加载配置表 |
11 | 绑定导入表 |
12 | IAT(导入地址表) |
13 | 延迟导入表 |
14 | COM表 |
15 | 保留(这个暂时没用) |
正常我们只需要关注重点表: 导出表/导入表/重定位/IAT
导出表从数据目录表中,知道了第一个表是导出表,并且导出表的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. 获取节的RVADWORD 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 DWORDPIMAGE_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 __cplusplusextern "C"{#endifint 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);}
通过导出表获取函数的调用地址,实现GetProAddress功能
上面实现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;}
个人能力有限,如果您发现有什么不对,请私信我
如果您觉得对您有用的话,可以点个赞或者加个关注,欢迎大家一起进行技术交流