如何删除windows的pe选择(PE数据目录表和导出表解析)

起因

五一(2020年,迁移文章)假期没有回家,是因为在四月份已经向公司提了离职.估计五月中旬应该就能办理离职手续,在公司呆了6年多,合同由3(2次)年换成无固定期限劳动合同.因为家庭原因,最终还是要回去的.不知不觉间北漂了8年,在公司工作期间经历我人生的大事(结婚和生子).

关于PE相关的内容,在前面也写过 <<学习PE文件结构>> 和 <<在解析PE遇到的问题>>,这里也不多的介绍了.

在看正文之前,先看看图,对下边具体要做的事情,有一个大概的认知.

关系图(省略节表部分)

如何删除windows的pe选择(PE数据目录表和导出表解析)(1)

从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:x 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; }

如何删除windows的pe选择(PE数据目录表和导出表解析)(2)

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. 获取节的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:x 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); }

如何删除windows的pe选择(PE数据目录表和导出表解析)(3)

通过导出表获取函数的调用地址,实现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; }

个人能力有限,如果您发现有什么不对,请私信我

如果您觉得对您有用的话,可以点个赞或者加个关注,欢迎大家一起进行技术交流

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页