接上一篇的理论知识: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");
}

Windows核心编程_PE文件格式解析相关推荐

  1. Windows核心编程_PE文件格式详细介绍

    目录 (一)基本概念 (二)可执行文件头 (三)PE导出表 (四)PE导入表 (五)延迟导入表 (六)重定位 (一)基本概念 PE(Portable Execute)文件是Windows下可执行文件的 ...

  2. Windows核心编程_HOOK(续)_APIHOOK

    啰嗦啰嗦: 开始之前还是要啰嗦几句,看到自己博客粉丝增加,访问量也越来越多,感到非常开心,而且好评也是不少,指错也非常感谢,从错误中发现了很多问题,非常感谢,也高兴自己的文章能帮助到其它人. 就比如之 ...

  3. 《windows核心编程系列》二谈谈ANSI和Unicode字符集

    第二章:字符和字符串处理 使用vc编程时项目-->属性-->常规栏下我们可以设置项目字符集合,它可以是ANSI(多字节)字符集,也可以是unicode字符集.一般情况下说Unicode都是 ...

  4. 《windows核心编程系列》十八谈谈windows钩子

    windows应用程序是基于消息驱动的.各种应用程序对各种消息作出响应从而实现各种功能. windows钩子是windows消息处理机制的一个监视点,通过安装钩子能够达到监视指定窗体某种类型的消息的功 ...

  5. 《Windows核心编程(第5版•英文版)》暨《深入理解.NET(第2版•英文版)》有奖书评/读书笔记征集活动

    <Windows核心编程(第5版•英文版)>暨<深入理解.NET(第2版•英文版)>有奖书评/读书笔记征集活动 图灵公司自成立以来,得到了CSDN的很多专家和朋友的帮助.为了感 ...

  6. chHANDLE_DLGMSG(windows核心编程)讲解

    看完<Windows程序设计>后开始看<windows核心编程>, 结果看第一个案例的时候就很惊人的发现,Jeffery大牛的代码很深奥.乍一看好像没有包含<window ...

  7. C#学习路线:C#入门经典 -> CLR VIA C# -> WINDOWS核心编程

    C#入门经典:入门阶段 CLR VIA C#:理论基础 WINDOWS核心编程:理论提升

  8. 窗口消息——Windows核心编程学习手札之二十六

    窗口消息 --Windows核心编程学习手札之二十六 Windows允许一个进程至多建立10000个不同类型的用户对象(user object):图符.光标.窗口类.菜单.加速键表等,当一个线程调用一 ...

  9. 未处理异常和C++异常——Windows核心编程学习手札之二十五

    未处理异常和C++异常 --Windows核心编程学习手札之二十五 当一个异常过滤器返回EXCEPTION_CONTINUE_SEARCH标识符时是告诉系统继续上溯调用树,寻找另外的异常过滤器,但当每 ...

最新文章

  1. mysql备份psd文件没有数据_两套mysql备份脚本
  2. 计算机伦理问题案例分析,基于网络环境的案例教学在《计算机伦理学》中的实践研究...
  3. 上海交通大学2006年数学分析考研试题
  4. 从文件夹里面多个文件里面查找指定内容
  5. Flash AS3.0中文帮助下载
  6. Scala-trait
  7. 基于MVC的JavaScriptWeb富应用开发
  8. java webservice 开发总结
  9. java字符转换成16进制_java 16进制与字符串直接相互转换
  10. 渗透测试报告模板_渗透测试报告编写的几个小技巧
  11. 繁凡的ACM模板(满注释模板)
  12. usermod -a -G group user修改user用户信息,把user添加到组group中
  13. hp暗夜精灵2Pro(HP OMEN 15-ax219TX 暗影精灵 II 代Pro游戏本)驱动列表
  14. 正确获取星期几(Calendar.DAY_OF_WEEK)
  15. 关于黑苹果卡在[IGPU] Scheduler Throttle Cap=100ms的解决办法
  16. kubectl常用命令 和 配置
  17. 不留痕迹的清除部分history历史命令记录
  18. DIY开源mini桌面i3结构3D打印机--开篇
  19. elasticsearch es explain 用法,分析得分 score 情况
  20. 联发科为何否认收购傲视通?

热门文章

  1. 前端实现红包雨功能_微信隐藏的7个实用功能,你都知道吗?真的白玩这么久微信...
  2. 已解决:An error occurred at line: 1 in the generated java file The type java.io.ObjectInputStream canno
  3. The “data“ option should be a function that returns a per-instance value in component definitions.
  4. c++ 等待子线程结束_进程和线程
  5. kafka是什么_Kafka为什么快到根本停不下来?
  6. 谷歌为什登不上去github_同声传译被攻陷!谷歌发布Translatotron直接语音翻译系统...
  7. mysql上面waring删掉吗_MySQL经典练习题:数据插入,更新,删除
  8. vim 安装_vim实战:插件安装(Vundle,NerdTree)
  9. java expression 强制出现_Java中带有强制括号对的单行循环
  10. eclipse查看jar包源码(反编译)