lordmilko/PESpy
GitHub: lordmilko/PESpy
PESpy 是一个用 C#/PowerShell 编写的逆向工程库,用于解析、分析和可视化微软编译器生成的各类二进制文件和符号格式。
Stars: 3 | Forks: 0
# PESpy
[](https://ci.appveyor.com/project/lordmilko/pespy)
[](https://www.nuget.org/packages/PESpy/)
[](https://liberapay.com/lordmilko/donate)
PESpy 是一个用于逆向工程、分析和可视化 Microsoft 编译器生成的文件格式的 C#/PowerShell 库。
给定一个文件,PESpy 旨在
* 理解*该文件中每一个字节*的含义
* 支持解析*所有已知实体*,无论它们多么冷门
* 尽量减少抽象,并在尽可能的地方映射原生类型名称
* 在保证易用性的同时保持高性能。内存分配必须尽可能低!
* 支持*所有已知的符号格式*;COFF、OMF、CodeView、SYM、DBG、PDB 文件、DNDRB、NB00-NB10、RSDS - 只要存在符号,PESpy 就会读取并展示给你
* 真正实现*触手可及的信息*。整个文件层级通过属性暴露出来;只需打开一个文件,然后在 Locals 窗口中查看即可
* 支持从远程调试目标中读取预先不知道大小的 PE 文件
* 提供用于执行各种文件操作的工具,包括
* 检测文件类型
* 定位符号文件(不再需要 `symsrv.dll`!)
* 解析 RPC 服务器
* 操作符号键 (Symbol Keys)
* 解析 vftables
* 还原符号名
* 读取并解压 Windows 安装介质中包含的文件
* 对 NativeAOT 高度友好
PESpy 能够与以下文件类型进行交互
| 名称 | 描述
|--------|---------------
| PE | Portable Executable 文件,最早出现于 Windows NT 3.1 |
| PDB | “旧式” (JG 1.0)、MSF (JG 2.0, DS 7.0) 和 Portable PDB 文件 |
| OBJ | 原则上我们主要关注 `*.obj` 文件,但严格来说任何使用 COFF 的文件(如 `*.exp`、`*.iobj` 等)都可以被打开 |
| DOS | 带有 `IMAGE_DOS_HEADER` 和可能存在的尾部 CodeView 数据的简单 DOS 文件 |
| NE | 16 位 New Executable 文件,常见于 16 位 Windows 以及在较小程度上见于 Windows 9x |
| LE | 32 位 Linear Executable 文件;具体而言,即 VxD 驱动文件使用的格式 |
| DBG | 基于 COFF 的文件,包含从主可执行文件中分离出来的调试元数据 |
| LIB | 链接器使用的基于 COFF 的 Archive 库,其中可能潜藏有对象文件 |
| OMF | DOS 时代旧编译器工具链生成的 `*.obj` 文件,它们使用 COFF 的前身——Object Module Format |
| OMFLIB | DOS 时代旧编译器工具链生成和使用的 `*.lib` 文件,它们使用 OMF |
| OMFDBG | 旧式 `*.dbg` 文件,其全部内容即为原始的 OMF 样式 CodeView 段 |
| SYM | 由 `mapsym.exe` 或编译器解析 `*.map` 文件生成的 `*.sym` 文件 |
## 安装说明
```
Install-Package PESpy
```
PESpy 在 [nuget.org](https://www.nuget.org/packages/PESpy/) 和 [PowerShell Gallery](https://www.powershellgallery.com/packages/PESpy/) 上均可用。PESpy 提供了针对 .NET 9.0 和 .NET Standard 的目标,并且兼容 SourceLink。为了从 PowerShell Gallery 安装 PESpy,你必须运行 PowerShell 5.1+。PESpy 兼容 Windows PowerShell 和 PowerShell Core。
## 快速入门
PESpy 的主要卖点在于,只要有可能,它都会尽力向你展示文件中数据的真实形态。以下代码片段展示了 PESpy 核心功能的各个入口点。有关 PESpy 所有功能的极其详尽的文档,请参阅 [wiki](https://github.com/lordmilko/PESpy/wiki)。
### 枚举所有导入
```
/* Retrieving locals in native code involves traversing the IMAGE_IMPORT_DESCRIPTOR entities, resolving various RVAs
* traversing a list of IMAGE_THUNK_DATA entities followed, checking various bit fields, resolving
* even more RVAs, before finally retrieving the strings you're after. That is what the data looks like. PESpy provides
* many mechanisms to simplify complex lookups, but it will never hide the underlying shape of the data to "make it easy" */
using var peFile = PEFile.FromFile("C:\\Windows\\system32\\kernel32.dll");
ImageImportDescriptor[]? importTable = peFile.ImportTable;
if (importTable != null)
{
foreach (var imageImportDescriptor in importTable)
{
/* Any field that is an RVA to another entity is modelled as a field of type RVA. This type
* provides access to the original RVA that was listed in the field, whether the RVA could actually
* be resolved to a valid address, and the actual value that was read from that address */
RVA dllName = imageImportDescriptor.Name;
if (!dllName.IsValid)
continue;
RVA originalFirstThunk = imageImportDescriptor.OriginalFirstThunk;
if (!originalFirstThunk.IsValid)
continue;
/* A custom collection type prevents us from having to allocate a large array to access all
* of the thunks in the section. Note that the trailing "null" IMAGE_THUNK_DATA is also included
* as the last item in this list */
foreach (ImageThunkData entry in originalFirstThunk.Value)
{
//IMAGE_THUNK_DATA is defined as a union of four possible fields. PESpy tries to figure out
//which logical type the thunk represents, and stores this in an added Kind field
if (entry.Value == 0)
continue; //This is the trailing "null" entry which marks the end of this import's thunks
if (entry.Kind == ImageThunkData.DataKind.Name)
{
RVA thunkName = entry.Name;
if (!thunkName.IsValid)
Console.WriteLine($"{dllName}: Invalid Name (0x{thunkName.ListedOffset})");
else
Console.WriteLine($"{dllName}: {thunkName}");
}
}
}
}
```
### 定位符号文件
PESpy 的 `Locator` 类提供了 mspdbcore 中 `LOCATOR` 类的托管实现,这也是 DIA 背后的技术支撑
* `Locator` 可以定位各种符号;PDB(无论是常规的、Portable、Embedded 还是 NGEN 的)、`*.dbg` 文件(可能反过来指向 `*.pdb` 文件),甚至是旧式的 `*.sym` 文件
* 它知道如何读取你的符号路径;如果未设置 `_NT_SYMBOL_PATH`,它会自动使用包含 `msdl.microsoft.com` 的符号路径
* 它可以从远程 HTTP 服务器下载符号,并将它们级联存储到你的符号路径中
* 为各种不同的场景提供了多个入口点,同时支持同步和异步模式
* 允许指定一个回调来接收进度通知
* 历经重重优化以实现尽可能低的内存分配
* 完全可移植,完全不依赖 `symsrv.dll`
```
var pdbPath = Locator.LocatePDB("C:\\Windows\\system32\\ntdll.dll");
```
`Locator` 只是 PESpy 功能表面的冰山一角,但我很惊讶自己竟然如此频繁地使用它;它出乎意料地成为了 PESpy 对我来说最棒的功能之一!
### 枚举所有符号
```
/* PEFile provides various members (SymStoreKeys, GetSymStoreKey()) that provide identifiers for files
* that you can lookup on a symbol server. If you're writing unit tests for a diagnostic application that analyzes
* a certain DLL, you can potentialy "bookmark" that DLL by hardcoding its SymStoreKey, and then have your test re-download
* that file as needed so your test always produces the same result! */
var key = new SymStoreKey("coreclr.pdb/75099299D3D948A68B594FC4439DFA521/coreclr.pdb");
var pdbPath = Locator.LocatePDB(key);
/* The PDBFile class provides access to every single piece of functionality you might see in an MSF based PDB File.
* Every hash, every lookup, every struct since the introduction of MSF in Visual C++ 2.0 (1994) */
using var pdbFile = PDBFile.FromFile(pdbPath);
/* The native representation of a symbol is a SYMTYPE*. SymType is a zero cost abstraction over a pointer, but unlike
* a native SYMTYPE*, SymType uses insane debugger magic to show you all of the symbol's fields in the Locals window
* without you having to write any code */
foreach (SymType symType in pdbFile.EnumerateSymbols())
{
/* A SymType can be cast to a more specific symbol type (e.g. ProcSym32) based on the `SYM_ENUM_e` of its `rectyp`,
* or you can use extension methods that replicate the behavior of the various getters seen on `IDiaSymbol` */
if (symType.TryGetFramePointerPresent(out var framePointerPresent))
{
if (symType.rectyp == SYM_ENUM_E.S_GPROC32)
{
var pubSym32 = (PubSym32) symType;
/* Modern PDBs contain UTF-8 null terminated strings. But older PDBs use length prefixed "ST" strings.
* PESpy can use magic to figure out that the expected string format is, or you can just provide the PDBFile.
* ProcSym32's "name" property provides easy access to the symbol's name, but for high performance access
* you'll want to use the GetName method */
SymString name = pubSym32.GetName(pdbFile);
}
}
}
```
### 可视化文件
```
/* In two lines of code, you can visualize the entire contents of a file: view all sections, the regions
* within those sections, how code and data intertwine, and the xrefs between everything. Explore
* the entire structure of a file right from within your debugger. Query offsets, RVAs and VAs to find
* exactly what is located at that address. Strings are automatically detected, and an interface is provided
* to facilitate tagging disassembled code */
using var peFile = PEFile.FromFile("C:\\Windows\\system32\\kernel32.dll");
/* Unless you say otherwise, GetView will automatically attempt to download symbols,
* so the first time you call this you may need to wait while symbols are downloaded.
* Secify a progress callback to receive notice of what is going on. See the wiki for
* more information on interfacing with views */
var view = peFile.GetView();
```
有关使用 PESpy 的更多信息,请参阅 [wiki](https://github.com/lordmilko/PESpy)
标签:AI合规, PDB解析, PE文件, 二进制分析, 云安全运维, 云资产清单, 逆向工程