起因五一(2020年,迁移文章)假期没有回家,是因为在四月份已经向公司提了离职.估计五月中旬应该就能办理离职手续,在公司呆了6年多,合同由3(2次)年换成无固定期限劳动合同.因为家庭原因,最终还是要回去的.不知不觉间北漂了8年,在公司工作期间经历我人生的大事(结婚和生子).关于PE相关的内容,在前面也写过<<学习PE文件结构>>和<<在解析PE遇到的问题>>,这里也不多的介绍了.在看正文之前,先看

五一(2020年,迁移文章)假期没有回家,是因为在四月份已经向公司提了离职.估计五月中旬应该就能办理离职手续,在公司呆了6年多,合同由3(2次)年换成无固定期限劳动合同.因为家庭原因,最终还是要回去的.不知不觉间北漂了8年,在公司工作期间经历我人生的大事(结婚和生子).
关于PE相关的内容,在前面也写过 <<学习PE文件结构>> 和 <<在解析PE遇到的问题>>,这里也不多的介绍了.
在看正文之前,先看看图,对下边具体要做的事情,有一个大概的认知.
关系图(省略节表部分)从PE中解析导出表,根据导出表的地址,进行获取函数调用的地址
数据目录表在 <<学习PE文件结构>> 这边博文中,已经对可选PE头进行了解析. 在IMAGE_OPTIONAL_HEADER结构体中这个字段DataDirectory,就是我们通常所说的数据目录表.重新看看这个可选PE头结构.
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES16//数据目标表的长度//可选PE头typedef struct _IMAGE_OPTIONAL_HEADER {//// Standard fields.//WORDMagic;BYTEMajorLinkerVersion;BYTEMinorLinkerVersion;DWORDSizeOfCode;DWORDSizeOfInitializedData;DWORDSizeOfUninitializedData;DWORDAddressOfEntryPoint;DWORDBaseOfCode;DWORDBaseOfData;//// NT additional fields.//DWORDImageBase;DWORDSectionAlignment;DWORDFileAlignment;WORDMajorOperatingSystemVersion;WORDMinorOperatingSystemVersion;WORDMajorImageVersion;WORDMinorImageVersion;WORDMajorSubsystemVersion;WORDMinorSubsystemVersion;DWORDWin32VersionValue;DWORDSizeOfImage;DWORDSizeOfHeaders;DWORDCheckSum;WORDSubsystem;WORDDllCharacteristics;DWORDSizeOfStackReserve;DWORDSizeOfStackCommit;DWORDSizeOfHeapReserve;DWORDSizeOfHeapCommit;DWORDLoaderFlags;DWORDNumberOfRvaAndSizes;//数据目录表IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32//数据目录表结构typedef struct _IMAGE_DATA_DIRECTORY {DWORDVirtualAddress;//在内存中的相对地址DWORDSize;} 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)basedos_header->e_lfanew);PIMAGE_FILE_HEADER file_header = (PIMAGE_FILE_HEADER)((unsigned long)nt_header4);//4为nt头中Signature DWORDPIMAGE_OPTIONAL_HEADER optional_header =(PIMAGE_OPTIONAL_HEADER)((unsigned long)file_headerIMAGE_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;}
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)basedos_header->e_lfanew);PIMAGE_FILE_HEADER file_header = (PIMAGE_FILE_HEADER)((unsigned long)nt_header4);PIMAGE_OPTIONAL_HEADER optional_header = (PIMAGE_OPTIONAL_HEADER)((unsigned long)file_headerIMAGE_SIZEOF_FILE_HEADER);//1. 获取节表PIMAGE_SECTION_HEADER section_headersection_header = (PIMAGE_SECTION_HEADER)((unsigned long)optional_headerfile_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_headeri;//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_startblock_count * alignment){//5. 获取节的文件中偏移加上导出表的rva减去节的rva (真正在文件中的偏移地址)offset = psection->PointerToRawDatarva - 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)basedos_header->e_lfanew);PIMAGE_FILE_HEADER file_header = (PIMAGE_FILE_HEADER)((unsigned long)nt_header4);//4为nt头中Signature DWORDPIMAGE_OPTIONAL_HEADER optional_header = (PIMAGE_OPTIONAL_HEADER)((unsigned long)file_headerIMAGE_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)(baseoffset);}
//IMAGE_EXPORT_DIRECTORY结构体 主要的字段//DWORDName;//DWORDBase;//IMAGE_EXPORT_DIRECTORY 导出表//DWORDNumberOfFunctions;//导出函数的个数//DWORDNumberOfNames;//函数名称的函数个数//DWORDAddressOfFunctions;导出函数的地址表 rva个数用NumberOfFunctions//DWORDAddressOfNames;// 导出函数名称表 rva个数用NumberOfNames//DWORDAddressOfNameOrdinals;// 导出序号表 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)intadd(int a, int b){return ab;}_declspec (dllexport) intsub(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)basedos_header->e_lfanew);PIMAGE_FILE_HEADER file_header = (PIMAGE_FILE_HEADER)((unsigned long)nt_header4);PIMAGE_OPTIONAL_HEADER optional_header = (PIMAGE_OPTIONAL_HEADER)((unsigned long)file_headerIMAGE_SIZEOF_FILE_HEADER);PIMAGE_SECTION_HEADER section_headersection_header = (PIMAGE_SECTION_HEADER)((unsigned long)optional_headerfile_header->SizeOfOptionalHeader);IMAGE_DATA_DIRECTORY export_table = optional_header->DataDirectory[0];PIMAGE_EXPORT_DIRECTORYexport = (PIMAGE_EXPORT_DIRECTORY)(baseexport_table.VirtualAddress);unsigned long names = export->NumberOfNames;unsigned long* address_names = (unsigned long*)(baseexport->AddressOfNames);unsigned short* address_name_ordinals = (unsigned short*)(baseexport->AddressOfNameOrdinals);unsigned long* address_fuctions = (unsigned long*)(baseexport->AddressOfFunctions);for (int i = 0; i < names; i){char* name = (char*)(baseaddress_names[i]);if (strcmp(name, func_name) == 0){int ordinal = address_name_ordinals[i];void* func_addr = (void*)(baseaddress_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)basedos_header->e_lfanew);PIMAGE_FILE_HEADER file_header = (PIMAGE_FILE_HEADER)((unsigned long)nt_header4);PIMAGE_OPTIONAL_HEADER optional_header = (PIMAGE_OPTIONAL_HEADER)((unsigned long)file_headerIMAGE_SIZEOF_FILE_HEADER);PIMAGE_SECTION_HEADER section_headersection_header = (PIMAGE_SECTION_HEADER)((unsigned long)optional_headerfile_header->SizeOfOptionalHeader);IMAGE_DATA_DIRECTORY export_table = optional_header->DataDirectory[0];//导出表 内存中相对地址PIMAGE_EXPORT_DIRECTORYexport = (PIMAGE_EXPORT_DIRECTORY)(baseexport_table.VirtualAddress);//导出表 在内存的地址//这里获取函数个数,要特殊处理一下//正常情况下,用NumberOfFunctions就能获取到//其他情况下,如在def文件,让个别函数没有名称或者指定序号unsigned long numbers = export->Baseexport->NumberOfFunctions - 1;//起始序号加个数 在减一if (ordinal > numbers){return NULL;}unsigned long* address_fuctions = (unsigned long*)(baseexport->AddressOfFunctions);unsigned long base_count = export->Base;return (void*)(baseaddress_fuctions[ordinal - base_count]);}return NULL;}
个人能力有限,如果您发现有什么不对,请私信我
如果您觉得对您有用的话,可以点个赞或者加个关注,欢迎大家一起进行技术交流
