跨版本二进制签名和 Windows PE 函数的 RVA 偏移量
pe-signgen 是一款面向逆向工程师和安全研究人员的工具,可自动生成:
核心思想 是提供一种 系统化、可靠的方式来访问未导出函数,跨越 Windows 10/11 构建版本。它利用:
⚠️ Windows 版本支持
pe-signgen仅支持 Windows 10 和 Windows 11。 这是一个经过深思熟虑的设计选择:Winbindex 并未为较旧版本提供完整数据。
未导出的 Windows 内部函数
为 LdrpInitializeTls、RtlpInsertInvertedFunctionTableEntry 等函数生成签名。
游戏外挂 / 反作弊研究 生成在游戏更新后仍稳定的签名。
安全研究 跨越 Windows 构建版本定位安全关键例程。
自动化 为整组内部 API 提供可脚本化的签名和偏移量生成。
pe-signgen 针对不同用例提供三种不同的输出格式:
适用于自动化、脚本编写以及与其他工具集成的结构化数据。```bash pe-signgen --signature ntdll!NtCreateFile -o ntcreatefile.json --output-format json
**输出结构:**```json
{
"dll_name": "ntdll",
"function_name": "NtCreateFile",
"architecture": "x64",
"generated": "2024-12-11T15:30:00.123456",
"total_builds": 1247,
"unique_signatures": 3,
"signature_groups": [
{
"matched_symbol": "NtCreateFile",
"signature": "4C 8B DC 49 89 5B 08 49 89 6B 10 49 89 73 18 ...",
"length": 48,
"build_count": 845,
"versions": [
{ "major": 10240, "minor": 16384, "build": "10240.16384" },
{ "major": 10586, "minor": 0, "build": "10586.0" }
]
}
]
}
注意:
major和minor是从构建字符串中通过分割第一个点得到的。
示例:"10240.16384" → major = 10240, minor = 16384。build是内部使用的原始构建字符串键。紧凑的、运行时就绪的二进制格式,针对嵌入式系统和低开销扫描进行了优化。
这些匹配write_wsig()和write_woff()中实现的磁盘布局。
魔数: WSO\0 (0x57 0x53 0x4F 0x00)
当前版本: 1
用途: 存储带有通配符掩码和相关Windows构建版本的二进制签名。
┌─────────────────────────────────────┐ │ Header (36 bytes) │ ├─────────────────────────────────────┤ │ DLL Name (variable) │ ├─────────────────────────────────────┤ │ Function Name (variable) │ ├─────────────────────────────────────┤ │ Signature / Mask / Build blobs │ ← Arbitrary order, see notes ├─────────────────────────────────────┤ ← Aligned to 4 bytes │ Groups Table (24 × N bytes) │ └─────────────────────────────────────┘
**重要布局说明(与 `write_wsig` 匹配)**
* 在头部之后,DLL 和函数名称以 UTF‑8 字节形式写入。
* 对于每个签名组,依次写入模式字节和掩码字节,然后是该组的构建数组。
* 这些每组区域**不**按类型全局分组:模式、掩码和构建数组可能交错排列。
* 构建器在每个构建数组之前以及组表之前会进行 **4 字节** 对齐。这可能会引入填充。
* 消费者**始终**必须遵循头部和组条目中的偏移量;**不要**依赖概念图来获得物理连续性。
##### 头部布局(36 字节)```c
// Packed as: "<4sIIIIIIII" (little-endian)
typedef struct {
char magic[4]; // "WSO\0" (WSIG_MAGIC)
uint32_t version; // FORMAT_VERSION (currently 1)
uint32_t arch; // Architecture code (1=x64, 2=ARM64, 3=WoW64)
uint32_t dll_off; // Offset to DLL name string
uint32_t dll_len; // Length of DLL name in bytes
uint32_t func_off; // Offset to function name string
uint32_t func_len; // Length of function name in bytes
uint32_t group_count;// Number of signature groups
uint32_t groups_off; // Offset to groups table
} wsig_header_t; // 36 bytes
每个签名组表示一个独特的模式,适用于一个或多个Windows构建版本。```c // Packed as: "<IIIIII" (little-endian)
typedef struct { uint32_t sig_off; // Offset to signature pattern bytes uint32_t sig_len; // Length of signature pattern (in bytes) uint32_t mask_off; // Offset to wildcard mask bytes uint32_t mask_len; // Length of wildcard mask (≈ ceil(sig_len/8)) uint32_t builds_off; // Offset to build version array uint32_t build_cnt; // Number of builds using this signature } wsig_group_t; // 24 bytes
##### 构建版本条目(8 字节)
每个构建条目标识一个使用此签名的特定 Windows 版本。```c
typedef struct {
uint32_t major; // e.g. 19041
uint32_t minor; // e.g. 1234
} wsig_build_t; // 8 bytes
major 和 minor 来自构建字符串的拆分 ("A.B" → A, B)。原始构建字符串不存储在二进制格式中;如果需要,请将其保留在外部(它存在于 JSON 输出中)。
该掩码是一个 位掩码,其中每个位对应签名模式中的一个字节:
示例:``` Signature: 4C 8B DC 49 89 ?? 08 49 Mask bits: 1 1 1 1 1 0 1 1 (MSB first within each byte) Mask byte: 0xBF (binary: 10111111)
掩码字节以**小端位序**在每个字节内存储和解释(与C辅助函数和`parse_signature`中的用法完全相同):```c
uint8_t bit = (mask_bytes[byte_index >> 3] >> (byte_index & 7)) & 1u;
*_len 确定长度;不要读取超过该长度。魔数: WOF\0 (0x57 0x4F 0x46 0x00)
当前版本: 1
目的: 存储跨 Windows 构建版本的函数的直接 RVA 和文件偏移
┌─────────────────────────────────────┐ │ Header (36 bytes) │ ├─────────────────────────────────────┤ │ DLL Name (variable) │ ├─────────────────────────────────────┤ │ Function Name (variable) │ ├─────────────────────────────────────┤ │ Matched Symbol Names (variable) │ ← One UTF‑8 string per entry ├─────────────────────────────────────┤ ← Aligned to 4 bytes │ Entries Table (32 × N bytes) │ └─────────────────────────────────────┘
布局细节(与 `write_woff` 匹配):
* 在头部占位符之后,DLL 和函数名称以 UTF‑8 字节写入。
* 对于每个构建,匹配的符号名称以 UTF‑8 字符串写入(无终止符)。这些组成一个简单的字符串池。
* 写入器随后进行 4 字节对齐,并写入固定大小的条目表。
* 每个条目包含指向该字符串池的偏移量(`matched_off`、`matched_len`)。
##### 头部布局(36 字节)```c
// Packed as: "<4sIIIIIIII" (little-endian)
typedef struct {
char magic[4]; // "WOF\0" (WOFF_MAGIC)
uint32_t version; // FORMAT_VERSION (currently 1)
uint32_t arch; // Architecture code (1=x64, 2=ARM64, 3=WoW64)
uint32_t dll_off; // Offset to DLL name string
uint32_t dll_len; // Length of DLL name in bytes
uint32_t func_off; // Offset to function name string
uint32_t func_len; // Length of function name in bytes
uint32_t entry_cnt; // Number of offset entries
uint32_t entries_off;// Offset to entries table
} woff_header_t; // 36 bytes
每个条目将一个 Windows 构建版本映射到该构建版本中函数的位置。```c // Packed as: "<IIQQII" (little-endian)
typedef struct { uint32_t major; // Windows major version (e.g., 19041) uint32_t minor; // Windows minor version (e.g., 1234) uint64_t rva; // Relative Virtual Address in the DLL uint64_t file_offset; // Raw file offset in the DLL on disk uint32_t matched_off; // Offset to matched symbol name string uint32_t matched_len; // Length of matched symbol name } woff_entry_t; // 32 bytes
##### 使用说明
* **RVA** 是 DLL 以其首选基址加载时的内存偏移量。
* **文件偏移量** 是磁盘上 PE 文件中的原始位置。
* **匹配的符号** 可能与请求的函数不同(例如,转发的导出)。
该字符串在字符串池中只存储一次;`matched_off`/`matched_len` 引用它。
* 条目按**构建版本**(先 `major`,后 `minor`)排序,以提高查找效率。
---
#### 架构代码
两种二进制格式使用相同的架构编码(通过 `ARCH_CODE_MAP`):
| 代码 | 架构 | 描述 |
| ---- | ------------ | ---------------------------- |
| 1 | x64 | 64位 AMD64/Intel64 |
| 2 | ARM64 | 64位 ARM (AArch64) |
| 3 | WoW64 | 64位 Windows 上的 32位 x86 |
未知的架构字符串内部默认使用 `1` (x64);CLI 将值限制在支持的集合内。
---
### 3. **C 头文件格式**
可直接编译的 C 头文件,包含类型安全的结构体和数据数组。
`pe-signgen` 可以输出两种*类型*的 C 头文件:
* **WSIG 头文件** – 用于签名和掩码数据(来自 `write_wsig_header`)。
* **WOFF 头文件** – 用于直接的 RVA/文件偏移量表(来自 `write_woff_header`)。
`--output-format cheader` 选项选择 C 头文件;结合 `--offsets` 可在 WSIG 和 WOFF 变体之间切换。
#### WSIG C 头文件```bash
pe-signgen --signature ntdll!RtlpInitializeThreadActivationContextStack \
-o rtlp_init_actx.h --output-format cheader
生成的头部结构(简化,匹配 write_wsig_header):```c
/* Auto-generated WSIG header for ntdll ! RtlpInitializeThreadActivationContextStack ! x64. */
#ifndef WSIG_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_H
#define WSIG_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_H
#include <stdint.h> #include <stddef.h>