C++PE文件格式解析类(轻松制作自己的PE文件解析器)

PE是Portable Executable File Format(可移植的运行体)简写,它是眼下Windows平台上的主流可运行文件格式。

PE文件里包括的内容非常多,详细我就不在这解释了,有兴趣的能够參看之后列出的參考资料及其它相关内容。

近期我也在学习PE文件格式,參考了很多资料。用C++封装了一个高效方便的PE文件格式解析的类。

该类对想学PE文件结构的朋友可算一份可贵的资料。代码均非常易懂,考虑较全面,具有一定的通用性。

同一时候该类也能够让想创建自己的PE文件解析软件的朋能够轻松在此基础上实现。

最后。错误在所难免。假设大家发现有错误,欢迎大家指正。

下面是该类中接口函数的定义代码(完整代码附于之后下载链接中):

<pre name="code" class="cpp">class CPeFile
{
public:
	CPeFile();
	~CPeFile();

	// 下面函数无特殊说明不论什么时候均可用
public:
	//将PE文件附加到对象,成功返回IMAGE_DOS_SIGNATURE、IMAGE_OS2_SIGNATURE、IMAGE_OS2_SIGNATURE_LE、IMAGE_NT_SIGNATURE之中的一个;失败返回0UL(未知类型),1UL(文件操作失败),2(其它错误),【仅在没有PE文件附加到类对象时可用】
	DWORD Attach(LPCTSTR lpszFilePath);
	//若已有PE文件附加到类对象则释放关联
	void Detach();
	//获取Attach信息,成功返回IMAGE_DOS_SIGNATURE、IMAGE_OS2_SIGNATURE、IMAGE_OS2_SIGNATURE_LE、IMAGE_NT_SIGNATURE之中的一个;未成功返回0UL
	DWORD GetAttachInfo() const;

	// 下面函数无特殊说明Attach成功后均可用
public:
	//获取文件句柄(注:不应在外部运行CloseHandle等有副作用的操作)
	HANDLE GetFileHandle() const;
	//获取内存映射文件头部地址
	DWORD_PTR GetMappedFileStart() const;
	//获取内存映射文件头部指定偏移位置(不应大于文件大小。否则返回的指针可能引起错误)
	DWORD_PTR GetMappedFileOffset(DWORD dwFoa) const;
	//获取DOS头
	const IMAGE_DOS_HEADER* GetDosHeader() const;
	//获取DOS的入口地址
	DWORD GetDosEntryPoint() const;

	// 下面函数无特殊说明仅用于IMAGE_NT_SIGNATURE
public:
	//获取PE文件头(假设是64位程序,返回的实际是const IMAGE_NT_HEADER64*)【类型为IMAGE_OS2_SIGNATURE、IMAGE_OS2_SIGNATURE_LE时仍可用,但操作需小心】
	const IMAGE_NT_HEADERS32* GetNtHeader() const;
	//返回文件是否为64位(PE32+)
	BOOL Is64Bit() const;
	//获取PE文件头中的载入基地址ImageBase(64位程序返回的是ULONGLONG类型。32位程序可将返回值转换成DWORD类型)
	ULONGLONG GetImageBase() const;
	//获取PE文件头中的DataDirectory
	const IMAGE_DATA_DIRECTORY* GetDataDirectory() const;
	//获取各DataDirectory入口的RVA
	DWORD GetDataDirectoryEntryRva(DWORD dwIndex) const;
	//获取节表(节表的数量可由lpSectionNum传出)
	const IMAGE_SECTION_HEADER* GetSectionHeader(LPWORD lpSectionNum = NULL) const;
	//将RVA转换为FOA(可由lpFoa传出FOA、可由lpSection传出节序号。不在节中节序号为-1)
	BOOL RvaToFoa(DWORD dwRva, LPDWORD lpFoa = NULL, LPWORD lpSection = NULL) const;
	//将FOA转换为RVA(可由lpRva传出RVA、可由lpSection传出节序号。不在节中节序号为-1)
	BOOL FoaToRva(DWORD dwFoa, LPDWORD lpRva = NULL, LPWORD lpSection = NULL) const;
	//将VA转换为RVA(VA应大于ImageBase,函数中不检查)
	DWORD VaToRva(DWORD dwVa) const;
	DWORD VaToRva(ULONGLONG ullVa) const;
	//将RVA转换为VA(64位程序返回的是ULONGLONG类型,32位程序可将返回值转换成DWORD类型)
	ULONGLONG RvaToVa(DWORD dwRva) const;

	// 下面函数无特殊说明仅用于IMAGE_NT_SIGNATURE
public:
	//读取PE中导出表
	BOOL ReadExport();
	//读取PE中导入表
	BOOL ReadImport();
	//读取PE中资源表
	BOOL ReadResource();
	//读取PE中异常表
	BOOL ReadException();
	//读取PE中属性证书表
	BOOL ReadSecurity();
	//读取PE中基址重定位表
	BOOL ReadBaseRelocation();
	//读取PE中调试数据
	BOOL ReadDebug();
	//读取PE中线程局部存储表
	BOOL ReadTLS();
	//读取PE中载入配置表
	BOOL ReadLoadConfig();
	//读取PE中绑定导入表
	BOOL ReadBoundImport();
	//读取PE中延迟载入导入表
	BOOL ReadDelayImport();
	//清理PE中导出表
	void ClearExport();
	//清理PE中导入表
	void ClearImport();
	//清理PE中资源表
	void ClearResource();
	//清理PE中异常表
	void ClearException();
	//清理PE中属性证书表
	void ClearSecurity();
	//清理PE中基址重定位表
	void ClearBaseRelocation();
	//清理PE中调试数据
	void ClearDebug();
	//清理PE中线程局部存储表
	void ClearTLS();
	//清理PE中载入配置表
	void ClearLoadConfig();
	//清理PE中绑定导入表
	void ClearBoundImport();
	//清理PE中延迟载入导入表
	void ClearDelayImport();
	//清理全部
	void ClearAll();
	//返回是否读取了PE中导出表
	BOOL IsReadExport() const;
	//返回是否读取了PE中导入表
	BOOL IsReadImport() const;
	//返回是否读取了PE中资源表
	BOOL IsReadResource() const;
	//返回是否读取了PE中异常表
	BOOL IsReadException() const;
	//返回是否读取了PE中属性证书表
	BOOL IsReadSecurity() const;
	//返回是否读取了PE中基址重定位表
	BOOL IsReadBaseRelocation() const;
	//返回是否读取了PE中调试数据
	BOOL IsReadDebug() const;
	//返回是否读取了PE中线程局部存储表
	BOOL IsReadTLS() const;
	//返回是否读取了PE中载入配置表
	BOOL IsReadLoadConfig() const;
	//返回是否读取了PE中绑定导入表
	BOOL IsReadBoundImport() const;
	//返回是否读取了PE中延迟载入导入表
	BOOL IsReadDelayImport() const;

	// 下面函数无特殊说明仅用于IMAGE_NT_SIGNATURE且ReadExport成功
public:
	//获取导出表
	const IMAGE_EXPORT_DIRECTORY* GetExportDirectory() const;
	//获取导出表中各导出函数地址数组(数量可由lpFuncNum传出)
	const DWORD* GetExportFunction(LPDWORD lpFuncNum = NULL) const;
	//获取导出表中被定义了名称的各导出函数名称地址数组(数量可由lpNameNum传出)
	const DWORD* GetExportName(LPDWORD lpNameNum = NULL) const;
	//获取导出表中被定义了名称的各导出函数的索引(数量可由lpNameNum传出)
	const WORD* GetExportNameOrdinal(LPDWORD lpNameNum = NULL) const;
	//解析导出函数地址数组中dwIndex项,返回值小于NumberOfNames为按名称导出(数值为序号),返回值等于NumberOfNames则为按序号导出
	DWORD ParseExportFunction(DWORD dwIndex) const;

	// 下面函数无特殊说明仅用于IMAGE_NT_SIGNATURE且ReadImport成功
public:
	//获取各导入表(导入表数量可由lpImportDescriptorNum传出)
	const IMAGE_IMPORT_DESCRIPTOR* GetImportDescriptor(LPDWORD lpImportDescriptorNum = NULL) const;
	//获取第iImpoert个导入表中的IMAGE_THUNK_DATA32结构(64位程序实际是IMAGE_THUNK_DATA64)(数量可由lpCount传出)
	const IMAGE_THUNK_DATA32* GetImportThunkData(DWORD iImport, LPDWORD lpCount = NULL) const;
	//解析某个IMAGE_THUNK_DATA32结构(64位程序实际是IMAGE_THUNK_DATA64),返回结果:1表示按序号导入(lpParam可传出序号);2表示按名称导入(lpParam可传出相应IMAGE_IMPORT_BY_NAME的FOA);0失败【仅仅须要IMAGE_NT_SIGNATURE就可以用】
	int ParseThunkData(const IMAGE_THUNK_DATA32* lpThunk, LPDWORD lpParam = NULL) const;

	// 下面函数无特殊说明仅用于IMAGE_NT_SIGNATURE且ReadResource成功
public:
	//获取第一层资源的ID,返回1表示第一层是文件夹,返回2表示第一层是数据。返回0表示无资源
	int GetFirstResourceId(PIDTYPE lpFirstID) const;
	//获取下一层资源的ID,返回1表示下一层是文件夹,返回2表示下一层是数据。返回0表示无下一层
	int GetNextResourceId(IDTYPE Id, DWORD iRes, PIDTYPE NextID) const;
	//解析Id相应的文件夹层,lpEntryNum可传出数组数量。lpLevel可传出第几级文件夹,lpResourceEntry传出本层相应的IMAGE_RESOURCE_DIRECTORY_ENTRY数组
	const IMAGE_RESOURCE_DIRECTORY* ParseResourceDirectory(IDTYPE Id, LPDWORD lpEntryNum = NULL, LPDWORD lpLevel = NULL, IMAGE_RESOURCE_DIRECTORY_ENTRY** lpResourceEntry = NULL) const;
	//解析dwId相应的数据层
	const IMAGE_RESOURCE_DATA_ENTRY* ParseResourceData(IDTYPE Id) const;
	//解析某个IMAGE_RESOURCE_DIRECTORY_ENTRY结构中Name成员,返回结果:1(dwParam为ID);2(dwParam为相应IMAGE_RESOURCE_DIR_STRING_U的FOA)【仅仅须要IMAGE_NT_SIGNATURE就可以用】
	int ParseResourceDirectoryEntry(const IMAGE_RESOURCE_DIRECTORY_ENTRY* lpEntry, LPDWORD dwParam) const;

	// 下面函数无特殊说明仅用于IMAGE_NT_SIGNATURE且ReadException成功
public:
	//获取异常表(数量可由lpRuntimeFunctionNum传出)
	const IMAGE_RUNTIME_FUNCTION_ENTRY* GetRuntimeFunction(LPDWORD lpRuntimeFunctionNum = NULL) const;

	// 下面函数无特殊说明仅用于IMAGE_NT_SIGNATURE且ReadSecurity成功
public:
	//获取属性证书表(数量可由lpCertificateNum传出)
	const WIN_CERTIFICATE* const* GetCertificate(LPDWORD lpCertificateNum = NULL) const;

	// 下面函数无特殊说明仅用于IMAGE_NT_SIGNATURE且ReadBaseRelocation成功
public:
	//获取各基址重定位表(数量可由lpBaseRelocationNum传出)
	const IMAGE_BASE_RELOCATION* const* GetBaseRelocation(LPDWORD lpBaseRelocationNum = NULL) const;
	//获得某个基址重定位表中的重定位块(数量可由lpCount传出,包含对齐用的)
	const WORD* GetBaseRelocationBlock(const IMAGE_BASE_RELOCATION* lpBaseRelocation, LPDWORD lpCount = NULL) const;
	//解析某个基址重定位表后的某一项,返回的是高4位的值,低12位的值可由lpParam传出【不论什么时候均后可用】
	static WORD ParseBaseRelocationBlock(WORD wBaseRelocationBlock, LPWORD lpParam = NULL);

	// 下面函数无特殊说明仅用于IMAGE_NT_SIGNATURE且ReadDebug成功
public:
	//获取调试数据(数量可由lpDebugDirectoryNum传出)
	const IMAGE_DEBUG_DIRECTORY* GetDebugDirectory(LPDWORD lpDebugDirectoryNum = NULL) const;
	//获取第dwIndex项调试信息起始地址,未获取到返回NULL
	LPCVOID GetDebugInfoStart(DWORD dwIndex);

	// 下面函数无特殊说明仅用于IMAGE_NT_SIGNATURE且ReadTLS成功
public:
	//获取线程局部存储表(假设是64位程序,返回的实际是const IMAGE_TLS_DIRECTORY64*)
	const IMAGE_TLS_DIRECTORY32* GetTLSDirectory() const;
	//获取线程局部存储表回调函数数组的指针(假设是64位程序,返回的实际是const ULONGLONG*)(数量可由lpCallbackNum传出)
	const DWORD* GetTLSCallback(LPDWORD lpCallbackNum = NULL) const;

	// 下面函数无特殊说明仅用于IMAGE_NT_SIGNATURE且ReadLoadConfig成功
public:
	//获取载入配置表(假设是64位程序,返回的实际是const IMAGE_LOAD_CONFIG_DIRECTORY64*)
	const IMAGE_LOAD_CONFIG_DIRECTORY32* GetLoadConfigDirectory() const;

	// 下面函数无特殊说明仅用于IMAGE_NT_SIGNATURE且ReadBoundImport成功
public:
	//获取绑定导入表(数量可由lpBoundImportNum传出)
	const IMAGE_BOUND_IMPORT_DESCRIPTOR* const* GetBoundImportDescriptor(LPDWORD lpBoundImportNum = NULL) const;
	//获取第iBoundImpoert个绑定导入表(数量可由lpRefNum传出)
	const IMAGE_BOUND_FORWARDER_REF* GetBoundImportForwarderRef(DWORD iBoundImport, LPDWORD lpRefNum = NULL) const;

	// 下面函数无特殊说明仅用于IMAGE_NT_SIGNATURE且ReadDelayImport成功
public:
	//获取延迟载入导入表(数量可由lpDelayImportNum传出)
	const IMAGE_DELAYLOAD_DESCRIPTOR* GetDelayImportDescriptor(LPDWORD lpDelayImportNum = NULL) const;

	/*--------------------------------------------------------------------------------------------------------------------*/
	//其它私有成员略

};

凝视部分已经具体说了各功能。主要操作例如以下:

1、调用Attach成员函数使类对象附加到一个PE文件

2、通过ReadXXX 读取须要的文件夹(包含导入表、导出表、资源表、基址重定位....)

3、调用相关处理函数获取对应信息(依据读取内容的不同而不同,详细參看凝视)

4、将获得的数据做你想要的操作(如显示出来等)

5、ClearXXX释放资源(可选,对象析构时会自己主动调用)

6、Detach释放对该文件的关联(可选。对象析构时会自己主动调用)

眼下基本数据文件夹中的大部分都能够获取。.Net部分(IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR)还未实现,之后会继续完好

之后的链接中附有一个Demo演示怎样使用该类,如仍有不清楚的朋友能够回复寻问我

最后发一个自己写的获取PE文件信息的软件,能够Dump出PE文件的各信息,该软件是用该PE解析类实现的,详细代码较多,初看并不易懂,所以在这不提供了国。

该软件我会附载之后的下载链接里。能够方便获取PE文件里各信息,因为精力有限,写的是控制台下的。有兴趣的朋友能够自己实现一个GUI版本号的~

软件中,仅仅需在控制台下输入PE文件路径。或者将文件拖拽进窗体(插入.lnk快捷方式也行)。程序会将须要的信息输出来。

下面附上软件的一部分截图:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" >

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" >

哈。啰嗦了这么多,接下来就留给各位去体验吧。事实上PE文件没想像那么难~

相关资源下载地址:

点击打开链接

(内含PE文件解析类源代码文件、简单DEMO、以及自已实现的控制台PE文件查看器)

參考书籍:

《Windows PE权威指南》戚利

《加密与解密(第三版)》段钢

《软件加密技术内幕》看雪学院

《Windows环境下32位汇编语言程序设计(典葳版)》罗云彬

《加密与解密》吴强

《逆向project核心原理》李承远

參考软件:

Stud_PE

LordPE

StudyPE+

eXeScope-ha

CFF Explorer

Resource Hacker

PEID

PEview

ExeinfoPe

OllyDbg

IDA Pro

參考源代码:

PEDump

libpe-master

The Portable Executable File Format from Top to Bottom

PE文件格式官方文档:Microsoft PE and COFF Specification

在线PEDump工具:PEdump - dump your PE!

同一时候參考了CSDN、看雪学院、吾爱破解上等资料,在此一并感谢!

时间: 2025-01-03 23:09:56

C++PE文件格式解析类(轻松制作自己的PE文件解析器)的相关文章

支持各种特殊字符的 CSV 解析类 (.net 实现)(C#读写CSV文件)

CSV是一种十分简洁的数据结构,在DOTNET平台实际使用中发现微软官方并没有提供默认的方法,而网上好多例子发现实现并不严谨甚至一些含有明显错误,所以后面自己实现了一个读写工具类,这里发出来希望方便后面朋友(难免还是会有考虑不到的地方,可随时邮件联系) 使用该工具可对csv文件进行读写(甚至不用去了解CSV的各种规范) 直接以List<List<string>> 形式输出,方便进一步处理 因为工具类需要读取文件资源读取完毕后如果确认不会再次读取,建议立即Dispose,以释放文件句

深入理解 Win32 PE 文件格式

深入理解 Win32 PE 文件格式 Matt Pietrek 这篇文章假定你熟悉C++和Win32. 概述 理解可移植可执行文件格式(PE)可以更好地了解操作系统.如果你知道DLL和EXE中都有些什么东西,那么你就是一个知识渊博的程序员.这一系列文章的第一部分,讨论最近这几年PE格式所发生的变化. 这次更新后,作者讨论了PE格式如何适应于用.NET开发的应用程序,包括PE节,RVA,数据目录,以及导入函数.附录中包含了相关的映像头结构以及它们的描述. 很早以前,我为微软系统期刊(现在叫做MSD

PE文件格式详解(下)

作者:MSDN译者:李马 预定义段 一个Windows NT的应用程序典型地拥有9个预定义段,它们是.text..bss..rdata..data..rsrc..edata..idata..pdata和.debug.一些应用程序不需要所有的这些段,同样还有一些应用程序为了自己特殊的需要而定义了更多的段.这种做法与MS-DOS和Windows 3.1中的代码段和数据段相似.事实上,应用程序定义一个独特的段的方法是使用标准编译器来指示对代码段和数据段的命名,或者使用名称段编译器选项-NT——就和Wi

C#命令行参数解析类以及使用实例

http://blog.csdn.net/jackxinxu2100/article/details/6642694 编写命令行程序时如何进行命令行参数解析至关重要,下面将引用codeproject里面的一个命令行参数解析类并阐述如何使用来说明C#命令行参数解析的过程. 先看参数解析类,分为CommandLine类以及CommandArgs类,前者负责解析,后者负责结果封装,解析的结果分为三类:即 a=b 对应的key/value类型,-a b 对应的option与option value(可省

PE文件格式详解(上)

作者:MSDN 译者:李马 摘要 Windows NT 3.1引入了一种名为PE文件格式的新可执行文件格式.PE文件格式的规范包含在了MSDN的CD中(Specs and Strategy, Specifications, Windows NT File Format Specifications),但是它非常之晦涩.  然而这一的文档并未提供足够的信息,所以开发者们无法很好地弄懂PE格式.本文旨在解决这一问题,它会对整个的PE文件格式作一个十分彻底的解释,另外,本文中还带有对所有必需结构的描述

HTML解析类 ,让你不使用正则也能轻松获取HTML相关元素 -C# .NET

功能: 1.轻松获取指元素HTML元素. 2.可以根据属性标签进行筛选 3.返回的都是Llist强类型无需转换 用过XElement的都知道 用来解析XML非常的方便,但是对于HTML的格式多样化实在是没办法兼容. 所以我就写了这么一个类似XElement的 XHTMLElement 用法: string filePath = Server.MapPath("~/file/test.htm"); //获取HTML代码 string mailBody = FileHelper.FileT

PE文件格式学习之PE头移位

以前刚开始学网络安全,是从免杀开始的.记得那时候杀毒软件还很弱.金山江民瑞星还存在. 那会什么原理也不懂,就一直瞎鼓捣.(后来转入渗透行列了) 这段时间一直在学PE格式,突然想起来以前很古老的PE文件头移位. 网上搜了搜,看大家虽然做了视频,但是竟然没人讲原理.借着刚好在学PE格式的知识,就做个PE文件头移位的笔记.(不喜勿喷,刚学PE头文件格式,难免出错请提点,谢谢) 当然现在这种很古老的免杀方式对于杀软来说根本是不堪一击了.纯属做笔记. PE文件的基本结构如图示: IMAGE_DOS_HEA

使用 DiskMaker X 轻松制作 Yosemite 安装 U 盘(引)

使用 DiskMaker X 轻松制作 Yosemite 安装 U 盘 由于帮人在MacBook上装Windows, 用pe格式化了一下分出来的Windows分区, 搞得Mac系统也瘫掉了, 无奈之下需要重装Mac系统, 又不愿意费时等它自己下载安装, 就只好自己做U盘启动了, 可没想到居然这么费劲!!! 想懒省事,结果搞得倒费劲了. 尝试了N次, 出现N次之后, 终于是把启动盘做出来了. 留个念, 以备下次. 首先,需要一款被称为 DiskMaker X 的小软件.下面是下载地址. 链接: h

PE文件解析 基础篇

PE文件解析 基础篇 来源 https://bbs.pediy.com/thread-247114.htm 前言 之前学习了PE格式,为了更好的理解,决定写一个类似LoadPE的小工具. 编译器是VS2015,采用MFC框架. 此系列文章采用边介绍知识点,边写代码的形式,以免变的无聊丧失兴趣. PE知识请参照<加密与解密>第10章 文章有错误或则不清楚的地方还请您指出. PE文件格式 1.PE文件基本概念 PE文件是windows系统中遵循PE结构的文件,比如以.exe   .dll为后缀名的