跨版本二进制签名和 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>
#define WSIG_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_DLL_NAME "ntdll" #define WSIG_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_FUNCTION_NAME "RtlpInitializeThreadActivationContextStack" #define WSIG_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_ARCH "x64"
/* Per-version build identifier. */ typedef struct { uint32_t major; uint32_t minor; } WSIG_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_version_t;
/* Signature group entry. */ typedef struct { const uint8_t *pattern; const uint8_t *mask; uint32_t length; uint32_t build_count; const WSIG_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_version_t *versions; } WSIG_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_group_t;
/* One pattern/mask/versions triple per group. / static const uint8_t WSIG_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_group0_pattern[] = { / ... / }; static const uint8_t WSIG_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_group0_mask[] = { / ... / }; static const WSIG_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_version_t WSIG_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_group0_versions[] = { { 10240u, 16384u }, / 10240.16384 / / ... */ };
static const WSIG_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_group_t WSIG_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_GROUPS[] = { { WSIG_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_group0_pattern, WSIG_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_group0_mask, (uint32_t)(sizeof(WSIG_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_group0_pattern) / sizeof(WSIG_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_group0_pattern[0])), (uint32_t)(sizeof(WSIG_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_group0_versions) / sizeof(WSIG_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_group0_versions[0])), WSIG_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_group0_versions }, /* group 0 (RtlpInitializeThreadActivationContextStack) / / ... */ };
static const size_t WSIG_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_GROUP_COUNT = sizeof(WSIG_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_GROUPS) / sizeof(WSIG_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_GROUPS[0]);
#endif /* WSIG_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_H */
**集成示例(已修正以匹配生成的类型):**```c
#include "rtlp_init_actx.h"
static inline int match_byte(uint8_t want, uint8_t got,
const uint8_t *mbits, uint32_t i) {
uint8_t bit = (mbits[i >> 3] >> (i & 7)) & 1u;
return bit ? (want == got) : 1;
}
static const uint8_t *
find_signature(const uint8_t *base, size_t size,
const uint8_t *pattern,
const uint8_t *mbits,
uint32_t sig_len) {
if (!base || !pattern || !mbits || sig_len == 0)
return NULL;
if (size < sig_len)
return NULL;
// Find first non-wildcard byte as anchor
uint32_t anchor = sig_len;
for (uint32_t i = 0; i < sig_len; ++i) {
if ((mbits[i >> 3] >> (i & 7)) & 1u) {
anchor = i;
break;
}
}
if (anchor == sig_len)
return base; // all wildcards
const uint8_t anchor_val = pattern[anchor];
const size_t last_pos = size - (size_t)sig_len;
for (size_t pos = 0; pos <= last_pos; ++pos) {
if (base[pos + anchor] != anchor_val)
continue;
uint32_t i = 0;
for (; i < sig_len; ++i) {
if (!match_byte(pattern[i], base[pos + i], mbits, i))
break;
}
if (i == sig_len)
return base + pos;
}
return NULL;
}
static void
fetch_signature(uint32_t major, uint32_t minor,
const WSIG_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_group_t *groups,
size_t group_len,
const uint8_t **signature_dest,
const uint8_t **mask_dest,
uint32_t *signature_len_dest) {
*signature_dest = NULL;
*mask_dest = NULL;
*signature_len_dest = 0;
const WSIG_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_group_t *closest = NULL;
uint32_t best_distance = 0xFFFFFFFFu;
for (size_t gi = 0; gi < group_len; ++gi) {
const WSIG_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_group_t *g = &groups[gi];
for (uint32_t vi = 0; vi < g->build_count; ++vi) {
uint32_t m = g->versions[vi].major;
uint32_t n = g->versions[vi].minor;
uint32_t distance = (m > major ? m - major : major - m) * 10000u +
(n > minor ? n - minor : minor - n);
if (distance < best_distance) {
best_distance = distance;
closest = g;
}
if (m == major && n == minor) {
*signature_dest = g->pattern;
*mask_dest = g->mask;
*signature_len_dest = g->length;
return;
}
}
}
if (closest) {
*signature_dest = closest->pattern;
*mask_dest = closest->mask;
*signature_len_dest = closest->length;
}
}
然后你可以将其接入你自己的加载器特定代码(例如使用 GetModuleHandleA、遍历 PE 节等)。该头文件故意仅提供数据;辅助函数由使用者自行实现。
对于仅需偏移量的使用场景,write_woff_header 会生成一个小型头文件,描述一个已排序的 (major, minor, rva, file_offset) 条目表。
布局(与 write_woff_header 匹配):```c
/* Auto-generated WOFF header for ntdll ! RtlpInitializeThreadActivationContextStack ! x64. */
#ifndef WOFF_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_H
#define WOFF_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_H
#include <stdint.h> #include <stddef.h>
#define WOFF_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_DLL_NAME "ntdll" #define WOFF_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_FUNCTION_NAME "RtlpInitializeThreadActivationContextStack" #define WOFF_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_ARCH "x64"
/* Per-build offset entry. */ typedef struct { uint32_t major; uint32_t minor; uint64_t rva; uint64_t file_offset; } WOFF_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_entry_t;
static const WOFF_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_entry_t WOFF_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_ENTRIES[] = { { 10240u, 16384u, 0x5B195ULL, 0x5A595ULL }, /* 10240.16384 (RtlpInitializeThreadActivationContextStack) / / ... (sorted by major, then minor) ... */ };
static const size_t WOFF_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_ENTRY_COUNT = sizeof(WOFF_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_ENTRIES) / sizeof(WOFF_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_ENTRIES[0]);
#endif /* WOFF_NTDLL_RTLPINITIALIZETHREADACTIVATIONCONTEXTSTACK_X64_H */
当您信任偏移量本身且不需要模式匹配时,此功能非常有用。
---
## 安装
### 先决条件
* Python 3.8+
* Git
* 互联网连接(首次运行)
* 约10GB磁盘空间用于完整缓存
### 通过Pip安装```bash
pip install pe-signgen
git clone https://github.com/forentfraps/pe-signgen.git cd pe-signgen pip install -r requirements.txt pip install -e .
---
## Quick Start
### Generate a Signature```bash
pe-signgen --signature ntdll!LdrLoadDll
pe-signgen --signature kernel32!CreateFileW --offsets
### 保存为 JSON```bash
pe-signgen --signature ntdll!NtCreateFile -o out.json --output-format json
pe-signgen --signature DLL!FUNCTION [OPTIONS]
### 架构```bash
--arch x64 # default
--arch arm64
--arch wow64
--os-version win10 # Only Windows 10 --os-version win11 # Only Windows 11 --min-version 10.0 # Minimum version --max-version 11.0 # Maximum version
### 签名长度控制```bash
--min-length 32 # Minimum signature length
--max-length 64 # Maximum signature length
-o, --output PATH # Output file path --output-format FORMAT # json | binary | cheader --offsets # Generate offsets instead of signatures
### 性能```bash
--workers 16 # Parallel workers (default: CPU count)
--no-cache # Disable caching
--no-git-update # Skip Winbindex updates
--verbose # Detailed output --quiet # Minimal output --no-progress # Disable progress bars
---
## 缓存
### 缓存布局```text
~/.cache/pe-signgen/
│
├── dlls/ # Downloaded DLLs
├── pdbs/ # Downloaded PDBs
├── signatures/ # Generated signatures
└── winbindex_data/ # Winbindex metadata
pe-signgen --signature ntdll!NtCreateFile --no-cache
rm -rf ~/.cache/pe-signgen
export PE_SIGNGEN_CACHE=/custom/path pe-signgen --signature ntdll!NtCreateFile
---
## 性能
**示例性能(12 核 CPU,100 Mbps):**
| 操作 | 时间 |
| ---------------------------- | ----------- |
| 首次运行(无缓存) | 5–10 分钟 |
| 缓存运行 | < 1 秒 |
| 每构建分析 | 0.1–0.5 秒 |
| 完整运行(1000 个构建,8 个工作者) | 2–4 分钟 |
**资源需求:**
* **磁盘:** 完整 DLL/PDB 缓存约 10 GB
* **内存:** 峰值使用约 500 MB
* **网络:** 首次运行数 GB
---
## 已知限制
* **Windows 版本覆盖:** 仅限 Windows **10 和 11**(Winbindex 限制)
* **构建可用性:** 并非所有 Win10/11 构建都存在于 Winbindex 中
---
## 开发```bash
git clone https://github.com/forentfraps/pe-signgen.git
cd pe-signgen
pip install -e ".[dev]"
# Code formatting
black pe_signgen/
# Type checking
mypy pe_signgen/
MIT 许可证 – 参见 LICENSE。
灵感来源于对内部 Windows API 进行稳健、自动化签名生成的需求。
欢迎贡献!请: