如何删除windows的pe选择(PE数据目录表和导出表解析)
五一(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 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;
}
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);
}
通过导出表获取函数的调用地址,实现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