PE是Portable Executable File Format(可移植的执行体)简写,它是目前Windows平台上的主流可执行文件格式。
PE文件中包含的内容很多,具体我就不在这解释了,有兴趣的可以参看之后列出的参考资料及其他相关内容。
最近我也在学习PE文件格式,参考了许多资料,用C++封装了一个高效方便的PE文件格式解析的类。
该类对想学PE文件结构的朋友可算一份可贵的资料,代码均很易懂,考虑较全面,具有一定的通用性。
同时该类也可以让想创建自己的PE文件解析软件的朋可以轻松在此基础上实现。
最后,错误在所难免,如果大家发现有错误,欢迎大家指正。
以下是该类中接口函数的定义代码(完整代码附于之后下载链接中):
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快捷方式也行),程序会将需要的信息输出来。
以下附上软件的一部分截图:
哈,啰嗦了这么多,接下来就留给各位去体验吧,其实PE文件没想像那么难~
相关资源下载地址:
(内含PE文件解析类源码文件、简单DEMO、以及自已实现的控制台PE文件查看器)
参考书籍:
《Windows PE权威指南》戚利
《加密与解密(第三版)》段钢
《软件加密技术内幕》看雪学院
《Windows环境下32位汇编语言程序设计(典葳版)》罗云彬
《加密与解密》吴强
《逆向工程核心原理》李承远
参考软件:
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!
微信
支付宝