65.9K
CodeProject 正在变化。 阅读更多。
Home

一组可重用的 PE 文件格式扫描函数

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.21/5 (15投票s)

2006年7月31日

CPOL

5分钟阅读

viewsIcon

60061

downloadIcon

830

用于从 PE 文件检索信息的有用函数

引言

本文伴随一组命令行示例应用程序,这些应用程序封装了我的一些通用代码。这些通用代码可用于从 PE 格式文件(PE 格式代表可移植可执行格式)中提取各种信息。这四个示例分别命名为 **bitness**、**pefileuses**、**dotnetsearch** 和 **pdbget**。

**bitness** 接受文件名作为命令行参数,并将告诉你传入的参数文件是 32 位还是 64 位 PE 文件。它封装了以下通用代码函数:

BOOL IsFile64BitPEFileW(LPCWSTR szFile, PBOOL pbIs64Bits);
BOOL IsFile64BitPEFileA(LPCSTR szFile, PBOOL pbIs64Bits);

参数应该很直观。如果函数成功,它将返回一个非零值。如果失败,返回值将是 FALSE,并且可以通过 GetLastError 获取扩展错误信息。成功时,out 参数 pbIs64Bits 将包含一个非零值,如果作为参数 szFile 传入的 PE 文件是 64 位的。

**pefileuses** 用于确定给定的 PE 文件是否链接到某个 DLL 或使用给定 DLL 中的函数。它需要 3 个命令行参数,可选地还有第四个参数。第一个参数是一个介于零和 2 之间的数字。此数字决定是扫描导入表还是延迟加载函数的表,或者两者都扫描。“0”表示扫描两个表。“1”表示仅扫描导入表,“2”表示仅扫描延迟加载表的。“第二个参数是要扫描的 PE 文件。第三个参数表示应扫描其名称的 DLL。最后,第四个参数是一个可选的函数名称。应用程序将在 stdout 上打印出指定的二进制文件是否链接到给定的 DLL,甚至是否使用可选的函数名。此工具封装了以下通用代码函数:

BOOL __stdcall PeFileUsesImportA(LPCSTR szPeFile, LPCSTR szDllName, 
                                 LPCSTR szFunction, 
                                 PBOOL pbUse, DWORD dwFlags);
BOOL __stdcall PeFileUsesImportW(LPCWSTR szPeFile, LPCWSTR szDllName, 
                                 LPCWSTR szFunction, PBOOL pbUse, 
                                 DWORD dwFlags);

要传递给此函数的标志是传递给 pefiluses.exe 的第一个参数,并定义如下:

#define PUI_USE_IMPORT_ONLY    0x1
#define PUI_USE_DELAYLOAD_ONLY 0x2

如上所述,将 0L 作为 dwFlags 参数传递会扫描两个表。其他参数应该很直观。如果函数成功,它将返回一个非零值。如果失败,返回值将是 FALSE,并且可以通过 GetLastError 获取扩展错误信息。

**dotnetsearch** 是一个工具,用于扫描整个目录树并评估找到的每个 DLL 和 EXE 文件,以确定它是否为 .NET 二进制文件。我编写此工具是为了查看 Windows Vista 的每个新版本,并找出整个 Vista 硬盘上有多少文件使用了 .NET 框架。一个使用 .NET 框架的二进制文件很容易识别,因为它链接到 mscoree.dll。dotnetsearch 工具封装了以下通用代码函数:

BOOL __stdcall BinaryUsesDotNetA(LPCSTR szFileName, PBOOL pbUse);
BOOL __stdcall BinaryUsesDotNetW(LPCWSTR szFileName, PBOOL pbUse);

同样,参数应该很直观。如果函数成功,它将返回一个非零值。如果失败,返回值将是 FALSE,并且可以通过 GetLastError; 获取扩展错误信息。

**pdbget** 检查给定的 PE 文件以查找其调试信息的所在位置。如今,如果您使用 Microsoft 编译器,CodeView 调试信息位于 PDB 文件中。为了让调试器找到给定 PE 文件的 PDB 文件,其文件路径将作为 ANSI 字符串嵌入 PE 文件中。pdbget 示例应用程序将要检查的 PE 文件的路径作为命令行参数,并将关联的 PDB 文件的位置打印到 stdout。PDB 2.0(VC 6)和 PDB 7.0(Visual Studio .NET 及更高版本)这两种格式的调试信息都将被正确检测,此工具封装了以下通用代码函数:

BOOL __stdcall GetPDBFileA(LPCSTR szPeFile, LPSTR szPdbFile, PDWORD pdwSize);
BOOL __stdcall GetPDBFileW(LPCWSTR szPeFile, LPWSTR szPdbFile, PDWORD pdwSize);

这些函数的第一参数是要检查的 PE 文件的路径。第二个参数是可选参数,指向内存缓冲区,函数会将 PDB 文件的路径以空终止字符串的形式写入其中。第三个参数是以字符为单位的内存缓冲区的大小,它充当输入/输出参数。内存缓冲区必须由调用者分配,但可以将 szPdbFile 参数指定为 NULL,在这种情况下,只有所需大小会写入 pdwSize。这样,您可以先将第二个参数指定为 NULL 调用这些函数,然后使用通过第三个参数检索到的大小分配所需的内存。使用此分配的内存,您可以再次调用这些函数来检索 PDB 文件的路径。

如果函数成功,它将返回一个非零值。如果失败,返回值将是 FALSE,并且可以通过 GetLastError 获取扩展错误信息。

Using the Code

该代码在 Visual Studio 2005 中,对于 x86 和 x64 目标,在 UNICODE 和 ANSI 构建中都能干净地编译。您还可以使用 Visual Studio 2008 并将项目和解决方案文件转换为其较新格式。

致谢

我在此通用代码中使用的函数大多是从 Matt Pietrek 和一个随 MSDN 杂志文章附带的示例中“借鉴”来的。我所做的只是将功能更好地分离成可重用的块,删除了原始代码中全局使用的变量,并对事物进行了更好的重新组织,以便在 x86 和 x64 构建以及最新的头文件中正确使用。如果在使用此代码时,您发现任何方面效果很好,那完全归功于 Matt Pietrek,如果代码出现故障或损坏,那将是我的错误。关于 PE 文件中 CodeView 2.0 调试信息的信息来自 debuginfo.com 的优秀人士的文章

历史

  • 1st 版本:2006/07/30:初始版本
  • 2nd 版本:2010/04/13:移除了 VC6 支持,添加了 pdbget
  • 3rd 版本:2010/05/05:GetPDBFile 函数现在也可以从 x64 二进制文件中确定 PDB 位置,PeFileUsesImportW 不再将 PE 文件参数转换为 MBCS
© . All rights reserved.