1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > Windows核心编程_PE文件格式解析

Windows核心编程_PE文件格式解析

时间:2020-08-04 18:45:34

相关推荐

Windows核心编程_PE文件格式解析

接上一篇的理论知识:PE文件格式

这一篇是实战,其实读取非常简单,WIndows也为我们提供了内存对齐的结构体:

//DOS头PIMAGE_DOS_HEADER//NT头(包括PE标识+Image_File_Header+OptionHeader)PIMAGE_NT_HEADERS//标准PE头PIMAGE_FILE_HEADER

其实PIMAGE_NT_HEAD_ERS里的IMAGE_FILE_HEADER已经包含了PIMAGE_FILE_HEADER结构体

可以根据个人所需读取字节来定义想要的结构体大小

具体参见理论篇

这里我们编写一个函数用于解析指定PE文件格式:

第一步先声明:

int JyPe(const char* file){}

第二步将所需结构体声明出来

//DOS头PIMAGE_DOS_HEADER pImageDosHeader;//NT头(包括PE标识+Image_File_Header+OptionHeader)PIMAGE_NT_HEADERS pImageNtHeaders;//标准PE头PIMAGE_FILE_HEADER pImageFileHeader;

第三步,将文件映射到内存,这里你也可以直接在文件里按字节对齐的方式读取:

//打开文件HANDLE hFile;hFile = CreateFile(file, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0);if (hFile == NULL){printf("打开文件失败\n");system("pause");return 0;}//创建映射关系hMapObject = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);if (hMapObject == NULL){printf("创建文件映射内核对对象失败\n");system("pause");return 0;}//获取映射内存首地址uFileMap = (PUCHAR)MapViewOfFile(hMapObject, FILE_MAP_READ, 0, 0, 0);if (uFileMap == NULL){printf("映射到进程地址空间失败\n");system("pause");return 0;}

第四步读取dos头,理论知识里说过,dos的hand就在pe文件起始位置,在内存映射里我们直接在首地址按结构体字节对齐读取就可以了:

上面说的Dos结构体的定义:

PIMAGE_DOS_HEADER

这个定义是个指针

我们通过它直接指向内存映射的首地址就可以了,节省空间便于操作,这就是指针的好处

pImageDosHeader = (PIMAGE_DOS_HEADER)uFileMap;if (pImageDosHeader->e_magic != IMAGE_DOS_SIGNATURE){printf("不是PE结构\n");system("pause");return 0;}

在理论知识里说过,e_magic是指向dos标识的,其标志是mz,Windows给我们提供了对比宏,直接比对就可以知道是不是正确指向了。

定位到NT PE的头,这里涉及到一个rva地址到虚拟地址转换的过程

注意e_lfanew立马存放着PE HAND的偏移地址,但是这只是偏移地址,俗称rva,虚拟偏移地址,不是逻辑地址,逻辑地址是真实物理地址转换时的一个别名,这里是虚拟地址转换

uFileMap指向文件映射内存的首地址,也就是基地址,完整虚拟地址公式是:基地址+RAV地址

//定位到NT PE头pImageNtHeaders = (PIMAGE_NT_HEADERS)((PUCHAR)uFileMap + pImageDosHeader->e_lfanew);

第二步获取资源目录管理器里的导入表虚拟地址,也就是EAT的地址,里面包含着此程序用的导入API

//导入表的相对虚拟地址(RVA)ULONG rva_ofimporttable = pImageNtHeaders->OptionalHeader.DataDirectory[1].VirtualAddress;

这一步,我们要通过偏移得到实际的导入API地址,所以我们要进行一个偏移计算,写一个函数用于将RVA地址转化为相对偏移地址

ULONG RvaToOffset(IMAGE_NT_HEADERS * pNtHeader, ULONG Rva){}

第一个参数是Nt头,第二个参数是要转换的Rva地址

注意,这里我们是通过节表来转化的,详细可以参见理论知识

首先定义一个节表类型,并指向NtHand里的节地址

//PE节IMAGE_SECTION_HEADER *p_section_header;

//取得第一个节表项p_section_header = (IMAGE_SECTION_HEADER *)((BYTE *)pNtHeader + sizeof(IMAGE_NT_HEADERS));

以NtHand为基,指向偏移量

然后我们在取得表的数目

//取得节表项数目ULONG sNum;sNum = pNtHeader->FileHeader.NumberOfSections;

这里我需要说一下,为什么要用节表来取得偏移地址,上面拿到的是RVA地址,相对的虚拟地址,也不可以直接基址+RVA地址,因为导入表在节表里,我们拿到的EAT导入表是存在于节表地址里的,而节表又不止这一个地址,所以我们需要找到节表的基地址,然后循环遍历直到找到我们自己的EAT表:

for (int i = 0; i<sNum; i++){printf("PE 节名称: %s\n",p_section_header->Name);if ((p_section_header->VirtualAddress <= Rva) && Rva<(p_section_header->VirtualAddress + p_section_header->SizeOfRawData)){return Rva - p_section_header->VirtualAddress + p_section_header->PointerToRawData;}p_section_header++;}

这里判断虚拟地址是否小于等于我们EAT表的地址并且EAT表的地址小于当前表虚拟地址加上整个表与磁盘文件对应的大小

这样就把控我们表的范围在EAT表之内了

(p_section_header->VirtualAddress <= Rva) && Rva<(p_section_header->VirtualAddress + p_section_header->SizeOfRawData)

公式转换:

return Rva - p_section_header->VirtualAddress + p_section_header->PointerToRawData;

RVA是相对的EAT虚拟地址减去当前节的虚拟地址加上位于磁盘文件中的偏移地址,就是在内存中的偏移地址

RAW(磁盘地址) = RVA(相对虚拟地址) - VirtualAddress + PointerToRawData

RVA(相对虚拟地址

) = VA(虚拟地址) - ImageBase(基址)

下一步我们在取的刚刚获取到的磁盘地址,然后加上内存基地址,就是位于内存中的实际偏移地址:

//取得导入表的地址IMAGE_IMPORT_DESCRIPTOR *pImportTable = (IMAGE_IMPORT_DESCRIPTOR *)((char*)uFileMap + offset_importtable);

这里我们声明一个结构体:

IMAGE_IMPORT_DESCRIPTOR null_iid;memset(&null_iid, 0, sizeof(null_iid));

因为我们等下要用链表的形式递增加直到结束区段

//每个元素代表了一个引入的DLL。for (int i = 0; memcmp(pImportTable + i, &null_iid, sizeof(null_iid)) != 0; i++){char *dllName = (char*)(uFileMap + RvaToOffset(pImageNtHeaders, pImportTable[i].Name));//拿到了DLL的名字printf("模块[%d]: %s\n", i, (char*)dllName);PIMAGE_THUNK_DATA32 pThunk = (PIMAGE_THUNK_DATA32)(uFileMap + RvaToOffset(pImageNtHeaders, pImportTable[i].FirstThunk));while (pThunk->u1.Ordinal != NULL){PIMAGE_IMPORT_BY_NAME pname = (PIMAGE_IMPORT_BY_NAME)(uFileMap + RvaToOffset(pImageNtHeaders, pThunk->u1.AddressOfData));printf("函数编号: %d 名称: %s\n", pname->Hint, pname->Name);pThunk++;}}

这里巧妙的运用memcmp来判断结构体指针指向的地方与当前结构体字节数量是否一致,如果不是一致代表已经指向别的内存区了,就可以return掉了。

运行结果:

完整代码:

#include "windows.h"#include <stdio.h>ULONG RvaToOffset(IMAGE_NT_HEADERS * pNtHeader, ULONG Rva){//PE节IMAGE_SECTION_HEADER *p_section_header;//取得第一个节表项p_section_header = (IMAGE_SECTION_HEADER *)((BYTE *)pNtHeader + sizeof(IMAGE_NT_HEADERS));//取得节表项数目ULONG sNum;sNum = pNtHeader->FileHeader.NumberOfSections;for (int i = 0; i<sNum; i++){printf("PE 节名称: %s\n",p_section_header->Name);if ((p_section_header->VirtualAddress <= Rva) && Rva<(p_section_header->VirtualAddress + p_section_header->SizeOfRawData)){return Rva - p_section_header->VirtualAddress + p_section_header->PointerToRawData;}p_section_header++;}return 0;}int JyPe(const char* file){//DOS头PIMAGE_DOS_HEADER pImageDosHeader;//NT头(包括PE标识+Image_File_Header+OptionHeader)PIMAGE_NT_HEADERS pImageNtHeaders;//标准PE头PIMAGE_FILE_HEADER pImageFileHeader;HANDLE hMapObject;//DOS头PUCHAR uFileMap;//打开文件HANDLE hFile;hFile = CreateFile(file, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0);if (hFile == NULL){printf("打开文件失败\n");system("pause");return 0;}//创建映射关系hMapObject = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);if (hMapObject == NULL){printf("创建文件映射内核对对象失败\n");system("pause");return 0;}//获取映射内存首地址uFileMap = (PUCHAR)MapViewOfFile(hMapObject, FILE_MAP_READ, 0, 0, 0);if (uFileMap == NULL){printf("映射到进程地址空间失败\n");system("pause");return 0;}pImageDosHeader = (PIMAGE_DOS_HEADER)uFileMap;if (pImageDosHeader->e_magic != IMAGE_DOS_SIGNATURE){printf("不是PE结构\n");system("pause");return 0;}//定位到NT PE头pImageNtHeaders = (PIMAGE_NT_HEADERS)((PUCHAR)uFileMap + pImageDosHeader->e_lfanew);//导入表的相对虚拟地址(RVA)ULONG rva_ofimporttable = pImageNtHeaders->OptionalHeader.DataDirectory[1].VirtualAddress;//根据相对虚拟(rva)地址计算偏移地址(offset)ULONG offset_importtable = RvaToOffset(pImageNtHeaders, rva_ofimporttable);if (!offset_importtable){printf("获取导入表偏移地址失败\n");system("pause");return 0;}//取得导入表的地址IMAGE_IMPORT_DESCRIPTOR *pImportTable = (IMAGE_IMPORT_DESCRIPTOR *)((char*)uFileMap + offset_importtable);IMAGE_IMPORT_DESCRIPTOR null_iid;memset(&null_iid, 0, sizeof(null_iid));//每个元素代表了一个引入的DLL。for (int i = 0; memcmp(pImportTable + i, &null_iid, sizeof(null_iid)) != 0; i++){char *dllName = (char*)(uFileMap + RvaToOffset(pImageNtHeaders, pImportTable[i].Name));//拿到了DLL的名字printf("模块[%d]: %s\n", i, (char*)dllName);PIMAGE_THUNK_DATA32 pThunk = (PIMAGE_THUNK_DATA32)(uFileMap + RvaToOffset(pImageNtHeaders, pImportTable[i].FirstThunk));while (pThunk->u1.Ordinal != NULL){PIMAGE_IMPORT_BY_NAME pname = (PIMAGE_IMPORT_BY_NAME)(uFileMap + RvaToOffset(pImageNtHeaders, pThunk->u1.AddressOfData));printf("函数编号: %d 名称: %s\n", pname->Hint, pname->Name);pThunk++;}}system("pause");}

我们取消打印节的字段,就可以看到当前程序使用哪些模块,模块对应的函数名

同时也可以获取地址:

知道了地址,我们在转换成off偏移地址,在去修改它,那么就实现了API HOOK,其余可以参见我的关于API HOOK的介绍,可以学习,在结合本篇文章可以轻松实EAT表方式的API HOOK

修改后的完整代码:

#include <windows.h>#include <stdio.h>ULONG RvaToOffset(IMAGE_NT_HEADERS * pNtHeader, ULONG Rva){//PE节IMAGE_SECTION_HEADER *p_section_header;//取得第一个节表项p_section_header = (IMAGE_SECTION_HEADER *)((BYTE *)pNtHeader + sizeof(IMAGE_NT_HEADERS));//取得节表项数目ULONG sNum;sNum = pNtHeader->FileHeader.NumberOfSections;for (int i = 0; i<sNum; i++){//printf("PE 节名称: %s\n",p_section_header->Name);if ((p_section_header->VirtualAddress <= Rva) && Rva<(p_section_header->VirtualAddress + p_section_header->SizeOfRawData)){return Rva - p_section_header->VirtualAddress + p_section_header->PointerToRawData;}p_section_header++;}return 0;}int JyPe(const char* file){//DOS头PIMAGE_DOS_HEADER pImageDosHeader;//NT头(包括PE标识+Image_File_Header+OptionHeader)PIMAGE_NT_HEADERS pImageNtHeaders;//标准PE头PIMAGE_FILE_HEADER pImageFileHeader;HANDLE hMapObject;//DOS头PUCHAR uFileMap;//打开文件HANDLE hFile;hFile = CreateFile(file, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0);if (hFile == NULL){printf("打开文件失败\n");system("pause");return 0;}//创建映射关系hMapObject = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);if (hMapObject == NULL){printf("创建文件映射内核对对象失败\n");system("pause");return 0;}//获取映射内存首地址uFileMap = (PUCHAR)MapViewOfFile(hMapObject, FILE_MAP_READ, 0, 0, 0);if (uFileMap == NULL){printf("映射到进程地址空间失败\n");system("pause");return 0;}pImageDosHeader = (PIMAGE_DOS_HEADER)uFileMap;if (pImageDosHeader->e_magic != IMAGE_DOS_SIGNATURE){printf("不是PE结构\n");system("pause");return 0;}//定位到NT PE头pImageNtHeaders = (PIMAGE_NT_HEADERS)((PUCHAR)uFileMap + pImageDosHeader->e_lfanew);//导入表的相对虚拟地址(RVA)ULONG rva_ofimporttable = pImageNtHeaders->OptionalHeader.DataDirectory[1].VirtualAddress;//根据相对虚拟(rva)地址计算偏移地址(offset)ULONG offset_importtable = RvaToOffset(pImageNtHeaders, rva_ofimporttable);if (!offset_importtable){printf("获取导入表偏移地址失败\n");system("pause");return 0;}//取得导入表的地址IMAGE_IMPORT_DESCRIPTOR *pImportTable = (IMAGE_IMPORT_DESCRIPTOR *)((char*)uFileMap + offset_importtable);IMAGE_IMPORT_DESCRIPTOR null_iid;memset(&null_iid, 0, sizeof(null_iid));//每个元素代表了一个引入的DLL。for (int i = 0; memcmp(pImportTable + i, &null_iid, sizeof(null_iid)) != 0; i++){char *dllName = (char*)(uFileMap + RvaToOffset(pImageNtHeaders, pImportTable[i].Name));//拿到了DLL的名字printf("模块[%d]: %s\n", i, (char*)dllName);PIMAGE_THUNK_DATA32 pThunk = (PIMAGE_THUNK_DATA32)(uFileMap + RvaToOffset(pImageNtHeaders, pImportTable[i].FirstThunk));while (pThunk->u1.Ordinal != NULL){PIMAGE_IMPORT_BY_NAME pname = (PIMAGE_IMPORT_BY_NAME)(uFileMap + RvaToOffset(pImageNtHeaders, pThunk->u1.AddressOfData));printf("函数编号: %d 名称: %s\n 地址:%x\n", pname->Hint,pname->Name, pThunk->u1.AddressOfData);pThunk++;}}system("pause");}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。