应优先调用 WinVerifyTrust 验证签名并提取证书链,传 WINTRUST_ACTION_GENERIC_VERIFY_V2 动作,设置 WTD_CHOICE_FILE 且 hWVTStateData 为 NULL;验证成功后通过 WTHelperProvDataFromStateData 和 WTHelperGetProvCertFromChain 获取证书链。

用 WinVerifyTrust 验证签名并提取证书链
Windows 文件数字签名信息不在文件元数据里,而是通过嵌入的 Authenticode 签名 + 证书链 + 信任链验证得到。直接读取 PE 文件的 .sig 或 IMAGE_DIRECTORY_ENTRY_SECURITY 只能拿到原始 PKCS#7 blob,不解析就无法获取证书主体、颁发者、有效期等字段。真正可靠的做法是调用系统信任验证 API,让 WinVerifyTrust 帮你把签名解包、校验完整性、构建证书链——它内部会调用 CryptQueryObject 解析 CMS 结构。
关键点:必须传入 WINTRUST_ACTION_GENERIC_VERIFY_V2 动作,并在 WINTRUST_DATA 中设置 dwUnionChoice = WTD_CHOICE_FILE 和 hWVTStateData = NULL(否则可能跳过证书链提取)。验证成功后,证书链会存于 CRYPT_PROVIDER_DATA 中,但该结构体未公开,需用 WTHelperProvDataFromStateData + WTHelperGetProvCertFromChain 间接访问。
- 务必用
SetThreadErrorMode(SEM_FAILCRITICALERRORS)防止弹出系统错误对话框 - 验证前检查文件是否被其他进程独占打开(如杀软实时扫描),否则
WinVerifyTrust可能返回TRUST_E_NOSIGNATURE(实际有签名) - 若目标是驱动或内核模块,需额外指定
WTD_REVOKE_NONE并禁用吊销检查,否则易因网络不可达失败
用 CryptQueryObject 解析签名 blob(绕过验证直接读)
当只需要证书信息、不关心签名是否受信任时,可跳过 WinVerifyTrust,直接从 PE 文件末尾的安全目录读取原始签名数据,再用 CryptQueryObject 解析。这是更“底层”的路径,但风险在于:若签名损坏或格式异常,解析会失败,且无法得知签名是否对应当前文件内容(即完整性未校验)。
步骤是:先用 ImageLoad 或手动解析 IMAGE_NT_HEADERS 找到 IMAGE_DIRECTORY_ENTRY_SECURITY 的 RVA 和大小;然后用 SetFilePointer/ReadFile 读取该偏移处的 WIN_CERTIFICATE 结构;最后将其中 bCertificate 字段传给 CryptQueryObject,指定 CERT_QUERY_OBJECT_BLOB 和 CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED。
立即学习“C++免费学习笔记(深入)”;
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
-
CryptQueryObject成功后,用CertGetCertificateContextProperty获取CERT_NAME_ATTR_TYPE、CERT_SHA1_HASH_PROP_ID等属性 - 注意
WIN_CERTIFICATE的wRevision必须为WIN_CERT_REVISION_2_0,旧版(1.0)不支持 Authenticode - 某些打包工具(如 Inno Setup)会在签名后追加数据,导致读取的 blob 长度不准,应以
dwLength字段为准,而非文件末尾硬算
证书信息提取:从 CERT_CONTEXT 到可读字符串
拿到 PCCERT_CONTEXT 后,不能直接 printf 打印,必须用 Windows 证书 API 转换。常见需求是主体名(Subject)、颁发者(Issuer)、序列号、有效期——这些都属于证书属性,不是上下文本身字段。
例如获取主体名字符串:CertGetNameStringA 是最简方式,传入 CERT_NAME_SIMPLE_DISPLAY_TYPE;但若需结构化字段(如只取 CN=xxx 部分),得用 CertFindSubjectInStore + CertGetNameStringA 配合 CERT_NAME_EMAIL_TYPE 等类型。序列号要用 CertGetCertificateContextProperty 取 CERT_SHA1_HASH_PROP_ID,再转十六进制字符串。
- 所有
Cert*函数返回的字符串是 UTF-16,用CertGetNameStringW+WideCharToMultiByte转 ANSI(如需日志输出) - 证书链中可能有多个
CERT_CONTEXT,索引 0 是叶子证书(签名者),后续是中间 CA,根 CA 通常不包含在 blob 中 - 别忘了调用
CertFreeCertificateContext释放每个上下文,否则内存泄漏
常见错误码和静默失败场景
WinVerifyTrust 和 CryptQueryObject 失败时不抛异常,只返回错误码,且很多码含义模糊。比如 TRUST_E_NOSIGNATURE 不一定代表没签名,可能是文件被修改、签名目录被覆盖、或权限不足读取安全目录;CRYPT_E_NO_MATCH 常因传错 dwObjectType(误用 CERT_QUERY_OBJECT_FILE 查内存 blob)。
-
CRYPT_E_ASN1_BADTAG:签名 blob 被截断或格式非法,检查读取长度是否等于WIN_CERTIFICATE::dwLength -
TRUST_E_EXPLICIT_DISTRUST:证书在系统“不受信任的发布者”列表里,此时仍可提取证书信息,只是验证失败 - 32/64 位程序混用问题:用
ImageLoad解析 PE 时,32 位程序无法正确加载 64 位 PE 的 NT 头,建议统一用MapViewOfFile+ 手动解析
最麻烦的是:某些企业签名使用交叉证书或自定义策略 OID,WinVerifyTrust 默认策略不识别,需用 WTD_STATEACTION_AUTO_CACHE + 自定义策略 DLL,这种场景下,直接解析 blob 反而更可控。


















