PESignAnalyzer 是一个用于分析和验证 Windows PE 文件 Authenticode 签名的命令行工具。它支持嵌入式签名和 Catalog 目录签名,并能自动查找系统中已经安装的 Catalog。
验证流程不会导入或调用
Wintrust.dll 中的任何 API。PE 摘要计算、Catalog 成员查找、CMS 签名校验、时间戳验证、证书链构建以及可选的吊销检查,均由 PE 结构解析及 Kernel32、Crypt32、Advapi32 提供的 API 完成。当前进展
| 功能 | 状态 |
|---|---|
| 嵌入式 Authenticode 元数据解析 | 已支持 |
| 多签名及嵌套签名元数据解析 | 已支持 |
| 自动查找系统 Catalog | 已支持,且不使用 WinTrust |
| 显式指定 Catalog | 已支持,使用 --catalog |
| Authenticode 内容摘要验证 | 已支持 |
| CMS/PKCS#7 签名验证 | 已支持 |
| RFC 3161 时间戳 | 已支持 |
| 旧式 PKCS#9 countersignature | 已支持 |
| 证书链策略验证 | 已支持 |
| 缓存或在线吊销检查 | 已支持 |
| x86、x64 发布二进制 | 已包含在 dist/ 中 |
发布二进制已经检查,导入表中只有
kernel32.dll、crypt32.dll 和 advapi32.dll。验证原理
PESignAnalyzer 不使用
WinVerifyTrust 或 CryptCATAdmin*,验证流程如下:- 解析 PE 头并计算 Authenticode 镜像摘要,排除校验和字段、安全目录项以及证书表。
- 对嵌入式签名,从 PKCS#7 内容中提取已签名摘要,并与计算结果比较。
- 对 Catalog 签名,计算候选摘要,扫描 Windows CatRoot,解析 Catalog DER 成员并查找匹配的成员摘要。
- 使用
CryptMsgControl和CMSG_CTRL_VERIFY_SIGNATURE_EX验证 CMS 签名者。 - 验证 RFC 3161 时间戳或旧式 countersignature。
- 构建签名证书链和时间戳证书链,并应用 Authenticode 证书链策略。
- 根据选项使用本地缓存或在线 CRL/OCSP 执行吊销检查。
自动查找时先使用轻量 DER 成员摘要筛选,再确认摘要确实存在于候选项的 CMS 内容中;启用
--verify 后还会验证该候选项的 CMS 签名和证书链。由于不使用 Windows WinTrust Catalog 索引,首次扫描或没有匹配项时会比系统索引查询更慢。命令行
TEXT
Usage: PESignAnalyzer.exe [options] <file>
Options:
-c, --catalog <file> Use a specific catalog as fallback.
--embedded-only Require an embedded signature.
--verify Verify the Authenticode signature.
--revocation <mode>
Revocation mode: none, cache, or online.
-h, --help, /? Show this help and exit.
-V, --version Show version information and exit.
-- Stop processing options.
原有的仅分析元数据调用方式保持兼容:
POWERSHELL
.\dist\bin_x64.exe "C:\Program Files\Git\cmd\git.exe"
验证嵌入式签名:
POWERSHELL
.\dist\bin_x64.exe --verify `
"C:\Program Files\Git\cmd\git.exe"
自动查找并验证系统文件对应的 Catalog:
POWERSHELL
.\dist\bin_x64.exe --verify `
"C:\Windows\System32\notepad.exe"
显式指定 Catalog:
POWERSHELL
.\dist\bin_x64.exe --verify `
--catalog "C:\Windows\System32\CatRoot\{GUID}\package.cat" `
"C:\Windows\System32\notepad.exe"
启用在线吊销检查(
--revocation 隐含启用 --verify):POWERSHELL
.\dist\bin_x64.exe --revocation online `
"C:\Windows\System32\notepad.exe"
在 PowerShell 中,用于续行的反引号必须是该行最后一个字符。
验证输出
| 字段 | 含义 |
|---|---|
contentDigest | PE 摘要与嵌入式签名或 Catalog 成员记录匹配 |
cmsSignature | CMS/PKCS#7 密码学签名有效 |
certificateChain | 签名证书链满足所选 Authenticode 策略 |
timestamp | RFC 3161 或旧式时间戳有效 |
revocation | not_checked、good、revoked 或 unknown |
overall | valid、invalid 或 indeterminate |
indeterminate 表示密码学签名和证书链可能有效,但无法确认所要求的吊销状态。在严格安全策略中,不能把它视为等同于 valid。退出码
| 代码 | 含义 |
|---|---|
0 | 分析或验证成功 |
1 | 未找到可读取的签名 |
2 | 命令行参数错误 |
3 | 签名验证失败 |
4 | 验证结果无法确定 |
编译
推荐使用 CMake 作为统一构建入口。以下命令可在 Visual Studio 开发人员命令提示符中重新生成
dist/ 内发布的两个可执行文件:CMD
cmake -S . -B .build\cmake-x64 -A x64
cmake --build .build\cmake-x64 --config Release --parallel
copy /Y .build\cmake-x64\Release\PESignAnalyzer.exe dist\bin_x64.exe
cmake -S . -B .build\cmake-x86 -A Win32
cmake --build .build\cmake-x86 --config Release --parallel
copy /Y .build\cmake-x86\Release\PESignAnalyzer.exe dist\bin_x86.exe
仓库仍在
msvc/ 目录保留旧版 Visual C++ 工程文件:msvc/vs2013.vcxprojmsvc/vs2015.vcxproj
MSBuild 示例:
CMD
MSBuild msvc\vs2015.vcxproj /p:Configuration=Release /p:Platform=x64
MSBuild msvc\vs2015.vcxproj /p:Configuration=Release /p:Platform=Win32
实现代码按职责放在
src/,公开 API 位于 include/analyzer/。模块划分与依赖边界详见 docs/architecture.md。两套构建系统都会链接 Crypt32.lib 和 Advapi32.lib,不需要、也不会链接 Wintrust.lib。测试
可以分别对
dist/ 中的两个发布架构运行冒烟测试:POWERSHELL
.\tests\smoke.ps1 -Executable .\dist\bin_x64.exe
.\tests\smoke.ps1 -Executable .\dist\bin_x86.exe
测试覆盖命令行行为、嵌入式签名验证、篡改检测、Catalog 自动查找、Catalog 验证以及
--embedded-only 行为。当前限制
- 因为刻意不使用 WinTrust 索引,Catalog 自动查找需要扫描本机 Windows CatRoot;冷扫描或没有匹配项时耗时可能更长。
- 自动发现仅检查本机已经安装的系统 Catalog。其他 Catalog 文件需要通过
--catalog指定。 - 吊销检查依赖本机缓存、网络环境以及 CA 的 CRL/OCSP 可用性;无法获取状态时会返回
indeterminate。 - 元数据分析支持多签名及嵌套签名;当前验证汇总针对消息中选定的主签名者。
- 本项目定位为诊断工具。在把它用作安全强制边界前,应结合部署环境的安全要求和签名样本进行验证。
基于 OpenSSL 的跨平台版本
基于 OpenSSL 的跨平台 Windows PE Authenticode 解析和验证库。使用 C++17、OpenSSL libcrypto 和 libcurl,目标平台为 Windows、Linux、macOS。输入为 Windows PE 文件(exe、dll、sys 等),不需要在 Windows 上运行。
跨平台版本使用独立 CMake 入口。根目录 Windows CryptoAPI 版本及 dist 发布文件保留原构建方式;跨平台版本不调用 WinTrust 或 CryptCATAdmin。
功能
| 原 Windows 版本功能 | OpenSSL 版本 |
|---|---|
| PE32 / PE32+、全部 WIN_CERTIFICATE | 有界读取、8 字节对齐 |
| 多签名者、多记录、嵌套签名 | 解析并逐个验证,保留父签名索引 |
| 签名、证书、算法、摘要、CRL URL 元数据 | 支持文本及 UTF-8 JSON |
| Authenticode 内容摘要 | 按原 Windows 文件顺序排除校验和、安全目录项、证书表,覆盖节间填充 |
| CMS/PKCS#7 验签 | 内容及 signed attributes,RSA、RSA-PSS、ECDSA、DSA |
| RFC 3161 时间戳 | CMS、TSTInfo、签名值绑定、ESS 签者身份及链,额外 ESS 约束可严格检查 |
| PKCS#9 countersignature | 签名值绑定及时间戳证书链 |
| 证书链 | 构链、有效期、代码签名/TSA 用途、密钥用途、系统及显式根 |
| 吊销检查 | none / cache / online,签者及 TSA 非根证书逐级检查 |
| Catalog | 显式文件、递归目录查找、已签名 CTL 成员摘要匹配 |
| Windows 系统 Catalog 自动发现 | CatRoot,兼容 32 位进程 Sysnative |
| 退出码 | 0 成功、1 无签名、2 参数错误、3 失败、4 无法确定 |
默认解析元数据并离线构建签名者证书链,从签名证书集合、系统证书库、显式 CA 和本地发行者缓存补齐根证书。只有启用 --verify、--revocation 或 --strict-timestamps 才执行验证。certificates 是内嵌证书集合;signingTime 和时间戳元数据本身是声明。只有通过签名绑定、TSA 信任和有效期检查的时间戳才能决定历史链验证时间,签名者自述的 signingTime 不能恢复过期证书的信任。
构建
依赖 CMake 3.20+、C++17 编译器、OpenSSL 3.0+、libcurl 7.68+ 开发包。建议 GCC 9+、Clang 9+、Apple Clang 12+ 或 Visual Studio 2019+。Python 3 可选,用于本地 HTTP 测试。
Linux(Ubuntu / Debian)
SH
sudo apt-get install build-essential cmake libssl-dev libcurl4-openssl-dev
cmake -S openssl -B .build/openssl-linux -DCMAKE_BUILD_TYPE=Release
cmake --build .build/openssl-linux --parallel
ctest --test-dir .build/openssl-linux --output-on-failure
macOS(Homebrew)
SH
brew install cmake openssl@3 curl
cmake -S openssl -B .build/openssl-macos -DCMAKE_BUILD_TYPE=Release \
-DOPENSSL_ROOT_DIR="$(brew --prefix openssl@3)" \
-DCURL_ROOT="$(brew --prefix curl)"
cmake --build .build/openssl-macos --parallel
ctest --test-dir .build/openssl-macos --output-on-failure
Windows(Visual Studio + vcpkg)
POWERSHELL
$vcpkgRoot = 'C:\dev\vcpkg'
& "$vcpkgRoot\vcpkg.exe" install openssl:x64-windows curl:x64-windows
cmake -S openssl -B .build/openssl-windows -A x64 `
"-DCMAKE_TOOLCHAIN_FILE=$vcpkgRoot/scripts/buildsystems/vcpkg.cmake" `
-DVCPKG_TARGET_TRIPLET=x64-windows
cmake --build .build/openssl-windows --config Release --parallel
ctest --test-dir .build/openssl-windows -C Release --output-on-failure
也可指定 OPENSSL_ROOT_DIR 和 CURL_ROOT 使用匹配架构的开发包。动态构建需要 libcrypto、libcurl 及其运行时依赖;vcpkg CMake 集成处理依赖复制。32 位 Windows 可使用 x86-windows triplet 和 -A Win32;两种架构均解析 PE32/PE32+。
本机 Windows x64 Release 运行包位于 .build/openssl-native/package/windows-x64,包含程序及所需 DLL;解压后保留同目录 DLL 即可运行。
当前包使用静态 MSVC 运行库,无需单独安装 VC Runtime。OpenSSL 编译输出及压缩包仅放在 Git 忽略的 .build 下,不持久化到 dist 或纳入版本控制。
Windows 链接 Crypt32,macOS 链接 Security/CoreFoundation 读取系统信任来源;验签和构链均由 OpenSSL 执行。使用 -DBUILD_TESTING=OFF 关闭测试,或 cmake --install <build-dir> --prefix <目录> 安装。
使用
SH
# 元数据解析和本地证书链
.build/openssl-linux/PESignAnalyzerOpenSSL --json sample.exe
# 验证嵌入式签名,明确选择信任根
.build/openssl-linux/PESignAnalyzerOpenSSL --verify --embedded-only \
--no-system-roots --ca-file roots.pem sample.exe
# 显式 Catalog 或指定目录查找
.build/openssl-linux/PESignAnalyzerOpenSSL --verify --catalog package.cat sample.exe
.build/openssl-linux/PESignAnalyzerOpenSSL --verify --catalog-dir catalogs sample.exe
# 在线检查,然后使用同一缓存离线检查
.build/openssl-linux/PESignAnalyzerOpenSSL --revocation online \
--revocation-cache cache sample.exe
.build/openssl-linux/PESignAnalyzerOpenSSL --revocation cache \
--revocation-cache cache sample.exe
完整参数查看 --help。还支持 --ca-dir(PEM/DER 根目录)、--crl-file、--crl-dir、--network-timeout 1..300(每链累计网络预算,默认 10 秒)、--version、--。--catalog-dir 可重复指定;--embedded-only 与 Catalog 选项冲突。Catalog 是没有嵌入式签名时的回退。
Windows 默认扫描已安装的 CatRoot;Linux/macOS 需提供 Catalog 或目录,可直接验证从 Windows 复制的文件和 Catalog。
Catalog 的内容类型 1.3.6.1.4.1.311.10.1 表示 CTL,属于正常格式;其成员摘要不在 SpcIndirectDataContent 中,文本输出不显示 imageDigestAlgorithm 和 embeddedDigest。JSON 的 digestAlgorithm 和 embeddedDigest 元数据仍为空,验证结果由 verification.contentDigest 表示。
Windows 上默认显示系统文件的签名者完整链及根证书;加上 --verify 执行验证:
POWERSHELL
.\PESignAnalyzerOpenSSL.exe C:\Windows\notepad.exe
.\PESignAnalyzerOpenSSL.exe --verify C:\Windows\notepad.exe
Windows 程序位于 .build/openssl-windows/Release/PESignAnalyzerOpenSSL.exe。CLI 使用宽字符入口,库使用 std::filesystem::path,支持 Unicode 路径。JSON 非法 UTF-8 字节替换为 Unicode 替换字符;日期为 UTC,时间戳小数秒保留精度。
信任和吊销策略
- Windows:当前用户和计算机 ROOT、Disallowed、根证书用途限制。
- Windows HTTPS 在支持 CURLSSLOPT_NATIVE_CA 的 libcurl 上也使用系统 CA,避免依赖 DLL 原安装目录中的 CA bundle;当前 Windows 发布包已验证该行为。
- macOS:系统锚点及用户、管理员、系统信任设置,应用用途和显式拒绝。
- Linux:OpenSSL 默认 CA 文件/目录,取决于安装环境及 OpenSSL 配置。
- --ca-file / --ca-dir 增加显式根,--no-system-roots 仅使用这些根。不把签名内嵌的自签证书自动视为可信。
- 签者链检查代码签名用途;RFC 3161 TSA 叶证书要求唯一的 timeStamping EKU,PKCS#9 叶证书也必须允许时间戳用途。兼容 Microsoft 非 critical EKU 的时间戳。可信时间戳允许在历史时刻验证签者链。
- Microsoft Authenticode 时间戳 OID 默认使用原 Windows 兼容语义:ESS 第一项必须绑定实际 TSA 签名证书,额外 ESS 证书/属性证书引用不强制决定信任。此类兼容情况在 details 中明确输出。--strict-timestamps 要求全部额外引用满足约束,不匹配或无法验证时失败。标准 RFC 3161 时间戳 OID 始终执行严格 ESS 检查。
- none 不查吊销、不联网、不读取本工具的中间证书缓存;cache 读取本地 CRL/OCSP 和缓存中间证书、不联网;online 允许 AIA 补链及 CRL/OCSP 请求,保存经验证的响应。
- CRL 优先,无法给出状态时使用 OCSP;校验证书标识、签名、授权、有效期和状态。缺失、过期、不可信或无法处理的响应返回 unknown;要求吊销检查时汇总为 indeterminate。确认吊销为 revoked,汇总为 invalid。certificateChain 的 trusted 表示路径信任,吊销状态单独输出。
- 缓存属于本工具,不直接复用 CryptoAPI 缓存。默认 Windows %LOCALAPPDATA%/PESignAnalyzer/revocation、macOS ~/Library/Caches/PESignAnalyzer/revocation、Linux $XDG_CACHE_HOME/PESignAnalyzer/revocation 或 ~/.cache/PESignAnalyzer/revocation。
各平台根库和策略不同,结果不保证逐位等同 Windows Authenticode 策略。Linux/macOS CA 集合可能缺少 Microsoft 根;通过 --ca-file 提供明确受信任的根,不应直接把全部内嵌证书当作根导入。
输出及库 API
JSON schemaVersion 为 2,parseStatus 为 ok 或 partial。顶层 verification 是汇总字符串;verificationResult 包含 contentDigest、cmsSignature、certificateChain、timestamp、revocation、overall、errorCode、verificationTime、实际 chain 及 details。
每个 signature/signer 都有独立结果,汇总取第一条主签名的第一个签者。未验证时 verification 为 not_performed,退出 0 仅表示解析成功。
signers[].certificateChain 包含 status、certificates、details。certificates 按叶证书、中间证书、根证书排列,并包含完整的主题、发行者、序列号、指纹、有效期及 CRL 地址。
built 表示链到达经自签名确认的根;partial 表示发行者缺失或达到限制;unavailable 表示无法获取签名者证书。
built 表示链到达经自签名确认的根;partial 表示发行者缺失或达到限制;unavailable 表示无法获取签名者证书。
构链状态不代表信任,不检查有效期、用途、吊销或签名文件摘要。此过程只读取本地来源,不下载证书。--verify 的信任结论仍位于 verification.certificateChain,通过在线 AIA 补齐的链位于 verification.chain。
signatures[].certificateRecord 指向记录;parentSignature、parentSigner 标识嵌套来源,顶层为 -1;signers[].certificateIndex 指向该消息的证书集合。读取、PE 结构、解析或验证失败返回 3;无签名返回 1;无法确认吊销状态返回 4。
CPP
#include <pesign/analyzer.h>
#include <iostream>
pesign::Options options;
options.verify = true;
options.ca_file = "roots.pem";
auto result = pesign::analyze_file("sample.exe", options);
std::cout << pesign::to_json(result) << '\n';
链接 CMake 目标 pesign::openssl,或安装后的静态库及 OpenSSL、libcurl 和平台信任库。公开头文件不暴露 OpenSSL 或 Windows 类型。
文件重载及带 Options 的字节重载默认离线构链;Options 可配置 Catalog、根、CRL、缓存及库级验证时间。analyze_bytes(bytes) 只解析嵌入式元数据,不读取本地证书库,其 certificateChain.status 为 not_built。
测试与边界
pe_reader_tests.cpp 检查 PE32/PE32+、目录、节和证书表边界。
pkcs7_tests.cpp 检查真实 ASN.1、证书匹配、嵌套、两类时间戳、资源限制。verification_tests.cpp 生成根、中间证书、签者/TSA、PE、Catalog、CRL/OCSP,覆盖离线根证书补齐、链不完整、多签者/嵌套构链,以及正确结果、篡改、过期、用途错误、不可信根、错误成员和时间戳绑定。
可选 Python 使用本地 HTTP 服务检查 AIA、CRL、OCSP、缓存及断网行为,不依赖外部 CA 服务或系统信任库。CI 配置包括 Windows x64/x86、Linux、macOS。
| 源文件 | 职责 |
|---|---|
| pe_reader.cpp / pkcs7_parser.cpp | 有界 PE 和签名元数据 |
| digest.cpp / catalog.cpp | PE 摘要、CTL 成员、Catalog 查找 |
| verification.cpp | 签名、时间戳、逐签者汇总 |
| trust.cpp / network.cpp | 信任来源、构链、吊销、缓存、HTTP |
| report.cpp / main.cpp | 文本/JSON、CLI 和退出码 |
输入上限 1 GiB、4096 记录;单条 PKCS#7 最大 64 MiB、嵌套深度 8,签名总数 64、每条 128 签者和 512 证书、每签者 64 时间戳。
解析元数据预算 16 MiB(最坏 JSON 转义估算),字段最大 4 KiB。Catalog 扫描最多 100000 文件;网络仅 HTTP/HTTPS、最多 3 次重定向、每链最多 32 请求,响应大小及累计时间受限。
文件当前整体读取。支持完整 direct IDP 范围和 base/delta CRL 配对、freshestCRL 检索及认证的 hold/release。分区理由、间接 CRL 或无法安全确认的扩展返回 unknown。

暂无评论