大约十年前我写 PESignAnalyzer 时,最初的目标其实很直接:给定一个 Windows PE 文件,希望能够分析它的数字签名,知道这个文件是谁签署的、使用了什么算法、对应什么证书、证书链是什么,以及签名有没有时间戳。
真正开始实现以后,事情很快变得复杂起来。
PE 文件的数字签名并不是简单地在文件末尾放一段 RSA 签名,然后对整个 EXE 做一次 SHA-256。Windows 的 Authenticode 把签名放进 PE 的 Certificate Table,而 Certificate Table 里面又是一份 PKCS#7/CMS 数据;PKCS#7 中包含 SignedData、SignerInfo、证书集合和各种 Attribute;SignerInfo 再通过 Issuer 和 Serial Number 与 X.509 证书关联起来。继续往下,还可能遇到 Counter Signature、RFC 3161 时间戳、Nested Signature,以及完全不同于 Embedded Signature 的 Catalog Signature。
因此,一个看起来很简单的问题:“这个 EXE 是谁签的?”
实际上涉及了多个不同层次的技术:
TEXT
PE
↓
Authenticode
↓
PKCS#7 / CMS
↓
SignerInfo
↓
X.509
↓
Certificate Chain
↓
Timestamp / Nested Signature
↓
Windows TrustPESignAnalyzer 当年的实现,基本就是沿着这条链一路往下做解析。
现在重新回头看这份代码,我觉得它比较适合作为一个实际案例,用来理解 Windows PE 数字签名到底是怎么工作的。这篇文章不会把重点放在“API 手册式”的介绍上,而是直接从 PESignAnalyzer 的实现路径出发,解释每一层数据是什么、为什么需要这一层,以及 Windows CryptoAPI 在其中究竟帮我们做了什么。
一、从一个 EXE 文件开始:PE 数字签名到底在哪里
理解 Authenticode,第一步不是看证书,也不是看 RSA,而是先回到 PE 文件本身。
一个 PE 文件的基本结构大致是 DOS Header、PE Header、Optional Header、Data Directory 和多个 Section。很多和 Windows PE 相关的功能都通过 Optional Header 中的 Data Directory 描述,例如导入表、资源表、重定位表等。
数字签名对应的是其中一个非常特殊的目录项:
IMAGE_DIRECTORY_ENTRY_SECURITY,它指向的是 PE 的 Attribute Certificate Table。这里有一个很重要的细节:IMAGE_DIRECTORY_ENTRY_SECURITY 与普通 PE Data Directory 的语义并不完全一样。普通目录中的地址通常是 RVA,也就是相对于 PE Image Base 的虚拟地址;而 Security Directory 中的 VirtualAddress 实际表示的是文件偏移量。
这意味着 Certificate Table 并不是 PE 映像正常加载后某个 Section 中的一块内存。它实际上是文件中的附加数据,通常位于 PE 文件末尾。
这也是 Windows Authenticode 一个有意思的设计。
假设一个 EXE 的结构是:
TEXT
DOS Header
PE Header
.text
.rdata
.data
.rsrc
...
Certificate TableCertificate Table 并不参与正常的代码执行,也不需要被映射成普通 PE Section。它只是作为文件的一部分保存签名相关数据。
Certificate Table 中的基本容器是 WIN_CERTIFICATE:
TEXT
typedef struct _WIN_CERTIFICATE {
DWORD dwLength;
WORD wRevision;
WORD wCertificateType;
BYTE bCertificate[1];
} WIN_CERTIFICATE;这里最容易产生误解的是 bCertificate 这个名字,它并不意味着这里直接存放了一张 X.509 Certificate。对于 Authenticode 来说,这里的数据通常是:PKCS#7 SignedData。
所以从文件结构的角度来看,可以把它理解成:
TEXT
PE 文件
└── Attribute Certificate Table
└── WIN_CERTIFICATE
└── PKCS#7 SignedData到了这里,PE 格式本身已经基本完成了它的任务。PE 只负责告诉 Windows:“这里有一块 Certificate Table 数据”。至于这块数据里面是什么,已经进入密码学和消息封装格式的世界。这也是为什么一个真正的 PE 签名分析器不能只会解析 PE Header。解析到 IMAGE_DIRECTORY_ENTRY_SECURITY 只是第一步,真正复杂的工作才刚刚开始。
二、从 CryptQueryObject 到 SignerInfo:PESignAnalyzer 如何解析 Authenticode
PESignAnalyzer 最核心的实现思路之一,是尽可能把标准数据结构交给 Windows 自己的 CryptoAPI 处理,而不是重新实现一遍 PKCS#7 和 X.509。这是一个非常现实的工程选择。因为 PKCS#7 本质上是 ASN.1 定义的数据结构。它不是一个简单的
Header Length Data Signature 格式,而是大量 SEQUENCE、SET、OID、Context-specific Tag 以及可变长度数据组合起来的嵌套结构。如果自己从零实现,会很快进入 ASN.1 编解码器、X.509 Parser、PKCS#7 Parser 这些完全不同的工作领域。
Windows 已经提供了这些能力。
PESignAnalyzer 使用的一个重要入口就是 CryptQueryObject。它的意义可以理解为:把文件中的编码对象交给 CryptoAPI,让系统识别和解析它,并返回后续操作需要的密码学对象。
对于 Authenticode 来说,最终最重要的两个对象分别是:
HCRYPTMSG、HCERTSTORE。HCRYPTMSG 可以理解成已经被 CryptoAPI 打开的 PKCS#7/CMS 消息对象,而 HCERTSTORE 则代表这个签名消息所携带的证书集合。这两个对象非常关键,因为接下来 PESignAnalyzer 不需要自己在 DER 数据中寻找 SignerInfo。它直接通过 CryptMsgGetParam 获取 CMSG_SIGNER_INFO:
TEXT
CryptMsgGetParam(
hCryptMsg,
CMSG_SIGNER_INFO_PARAM,
...
);这一步实际上完成了一次非常重要的抽象。
文件里面原本是一大块 ASN.1 DER:
TEXT
30 82 ...
06 ...
31 ...
30 ...经过 CryptoAPI 后,应用程序可以直接看到一个结构化的 CMSG_SIGNER_INFO。而 SignerInfo 正是理解 PKCS#7 签名的核心。从概念上看,它大致包含:
TEXT
SignerInfo
├── Version
├── SignerIdentifier
│ └── Issuer + Serial Number
├── DigestAlgorithm
├── Authenticated Attributes
├── SignatureAlgorithm
├── Signature
└── Unauthenticated Attributes其中最关键的是 SignerIdentifier、算法信息和 Attributes。
Issuer + Serial Number 用来确定“谁是这个签名者”。DigestAlgorithm 和 SignatureAlgorithm 描述签名所使用的密码学算法。而 Attributes 则是后面整个 Authenticode 世界变复杂的主要原因之一。
PESignAnalyzer 中封装的 MyCryptMsgGetParam 也体现了 CryptoAPI 一个很典型的使用方式:
第一次调用 API 获取需要的缓冲区大小,第二次真正分配内存并读取结构。这并不是 CryptoAPI 特有的技巧,而是 Windows C API 中非常常见的设计,因为 CMSG_SIGNER_INFO 后面还有数量不固定的属性数据,其实际大小无法通过一个固定的 sizeof(CMSG_SIGNER_INFO) 得到。
拿到 CMSG_SIGNER_INFO 以后,PESignAnalyzer 就真正从“PE 文件解析”进入了“签名消息解析”。
三、从 SignerInfo 到 X.509:签名者、证书和证书链是怎么关联起来的
这里是 PE 签名分析中非常容易被忽略的一步。很多人看到一个 PKCS#7 文件,会认为:“里面既然有签名者,那里面应该直接有签名者的证书”。从结果上看确实如此,但从数据结构上并不是简单的“SignerInfo 里面放一张 Certificate”。
SignerInfo 中的签名者标识通常是:Issuer 和 Serial Number,而 PKCS#7 SignedData 又包含一个证书集合。所以实际关系是:
TEXT
SignerInfo
│
├── Issuer
└── Serial Number
│
▼
Certificate Store
│
▼
X.509 Certificate这就是 PESignAnalyzer 后面需要使用 CertFindCertificateInStore 一类 API 的原因。它不是多余的查找步骤,而是在完成两个不同数据结构之间的关联。
拿到 PCCERT_CONTEXT 后,才真正进入 X.509 世界。这时候可以读取证书的 Subject、Issuer、Serial Number、Version、Validity、Thumbprint、Public Key 和 Signature Algorithm 等信息。PESignAnalyzer 为此设计了 CERT_NODE_INFO 结构:
TEXT
/// Per certificate node.
typedef struct _CERT_NODE_INFO {
std::string SubjectName;
std::string IssuerName;
std::string Version;
std::string Serial;
std::string Thumbprint;
std::string NotBefore;
std::string NotAfter;
std::string SignAlgorithm;
std::wstring CRLpoint;
} CERT_NODE_INFO, *PCERT_NODE_INFO;从这个结构能够非常清楚地看出当时希望最终得到什么信息:证书主体、颁发者、版本、序列号、Thumbprint、有效期、签名算法以及 CRL 地址等。
与此同时,项目又使用 SIGN_NODE_INFO 表示一个签名节点,并把证书信息组织成 CertChain。
TEXT
/// Per signature node.
typedef struct _SIGN_NODE_INFO {
std::string DigestAlgorithm;
std::string Version;
SIGN_COUNTER_SIGN CounterSign;
std::list<CERT_NODE_INFO> CertChain;
} SIGN_NODE_INFO, *PSIGN_NODE_INFO;这个数据结构设计实际上反映了一个很重要的概念:签名和证书不是同一个东西。
一个签名对应一个 SignerInfo;SignerInfo 关联一个签名者证书;签名者证书又可能通过 Issuer 继续找到它的上级 CA。
因此一个正常的软件签名可能形成:
TEXT
Code Signing Certificate
│
▼
Intermediate CA
│
▼
Root CA最下面的软件签名证书是 End Entity Certificate,它并不是信任链的根。
所以一个分析器真正有价值的结果,不应该只是:Subject = XXX Corporation,而应该能够继续告诉我们:这个证书由哪个 CA 签发,它是否能够构建到可信根,以及这个证书在签名时是否处于有效状态。
这里还必须区分几个非常容易混淆的概念,例如:
TEXT
SHA-256
RSA
sha256WithRSAEncryption它们不是三个不同名字的同一个东西。
- SHA-256 是摘要算法。
- RSA 是公钥密码算法。
- sha256WithRSAEncryption 是将 SHA-256 与 RSA 签名组合起来的算法标识。
PESignAnalyzer 中的 CalculateDigestAlgorithm 和 CalculateCertAlgorithm 就承担了把 OID 转换成人类可读名称的工作。
例如 PKCS#7 中可能看到:
1.2.840.113549.1.7.2,这是 signedData。而证书签名算法又可能出现:
1.2.840.113549.1.1.11,它对应 SHA-256 with RSA。如果直接把这些 OID 原样输出给用户,分析结果其实并不好用。
因此,PESignAnalyzer 做的事情不仅是“解析”,还包括把底层密码学数据重新组织成分析人员能够理解的信息。
四、Authenticode 最麻烦的部分:Nested Signature、Counter Signature 与 RFC 3161
如果 PESignAnalyzer 只需要找到一个 SignerInfo,整个项目其实不会复杂到现在这个程度。真正让 Authenticode 变得有意思的是 Unauthenticated Attributes。
签名本身还可以携带其他签名相关数据,最典型的就是 Nested Signature。
PESignAnalyzer 对
szOID_NESTED_SIGNATURE 进行了专门处理。Nested Signature 的本质是:一个签名结构里面又包含另一个签名结构。也就是说,结构可能变成:
TEXT
SignerInfo
└── Unauthenticated Attributes
└── Nested Signature
└── PKCS#7 SignedData
└── SignerInfo这时候,如果程序只解析最外层 PKCS#7,就会漏掉里面的签名。
PESignAnalyzer 的实现方式比较有代表性:先让 CryptoAPI 解析外层消息,然后自己遍历 UnauthAttrs,寻找 Nested Signature 的 OID。找到以后,再把属性中的内容作为新的 PKCS#7 数据交给 CryptoAPI 解析。
也就是说:
TEXT
CryptoAPI
↓
解析外层 PKCS#7
PESignAnalyzer
↓
发现 Nested Signature
CryptoAPI
↓
再次解析内层 PKCS#7这个模式实际上非常值得借鉴:不是所有数据都自己解析,也不是所有事情都交给系统 API,而是在两者之间找到合适的边界。
更麻烦的是 Nested Signature 并不一定只有一个。PESignAnalyzer 专门考虑了多个签名之间的排列和 8 字节对齐问题,因此不能简单地假设“这个属性就是一个完整的 PKCS#7,然后处理完就结束”。
这就是为什么代码里会出现 _8BYTE_ALIGN,以及对 DER 数据开头进行检查的逻辑。
Counter Signature 则是另外一种关系。它并不是“另一个并列的软件签名”,而是对已有签名进行进一步佐证。
PESignAnalyzer 会在 UnauthAttrs 中寻找:
szOID_RSA_counterSign,找到后,通过 CryptDecodeObject 按照 PKCS7_SIGNER_INFO 解码。因此结构变成:
TEXT
主 SignerInfo
│
└── Counter Signature
│
└── Counter SignerInfo这也解释了为什么签名者和时间戳服务提供者可以是不同的证书。
主签名回答:“是谁签署了这个软件?”
Counter Signature 或时间戳相关签名则回答:“这个签名在什么时间点已经存在?”
这两种身份在密码学结构上本来就是分开的。
到了 RFC 3161 时间戳,PESignAnalyzer 又必须向下走一层。
Windows CryptoAPI 可以很好地把 PKCS#7 解出来,但具体时间戳属性内部仍然是 ASN.1/DER 数据。
所以源码里出现了几个非常有意思的函数:
TEXT
ParseDERType
ParseDERSize
ParseDERFindType
SafeToReadNBytes它们并不是一个完整的 ASN.1 实现,而是一个针对当前分析需求的轻量级 DER 遍历器。这点非常重要。
如果目标是开发一个通用 ASN.1 库,当然不应该只写这么几个函数。但 PESignAnalyzer 的需求并不是“支持所有 ASN.1 类型”,而是:在已经由 CryptoAPI 找到的特定时间戳结构中,继续找到需要的字段。所以源码只实现了当前需要的 Tag、Length 和 Value 遍历。这也是二进制解析器中非常常见的一种工程思路。
DER 本质上采用 TLV:
Tag Length Value,解析器首先读取 Tag,再解析 Length,然后根据 Length 跳过或进入 Value。真正危险的地方是 Length,因为 Length 来自输入文件,所以绝对不能直接相信。如果代码直接:ptr += length,那么一个恶意构造的 PE 文件就可能让指针跳到 buffer 外。PESignAnalyzer 因此专门使用 SafeToReadNBytes 检查当前 buffer 是否还剩足够的数据,然后才继续读取。这部分虽然不像 RSA、X.509 那么“高深”,但对于实际二进制分析工具来说反而非常关键。因为密码学算法本身可能完全正确,真正导致安全问题的往往是解析器没有正确处理攻击者控制的长度字段。
五、Embedded Signature 之外:Catalog Signature 与 Authenticode Hash
前面讨论的是最直观的情况:
TEXT
PE
└── Certificate Table
└── Embedded Signature但 Windows 的文件信任并不只有这一种模式。另一个非常重要的机制是 Catalog Signature。
Catalog 的思路与 Embedded Signature 完全不同。
Embedded Signature 是:
TEXT
EXE
└── Signature而 Catalog Signature 更接近:
TEXT
EXE
│
└── Hash
│
▼
Catalog
│
└── Signature也就是说,文件本身可以没有 Embedded Signature,但 Windows 可以计算它的 Hash,然后在 Catalog 中找到对应的记录。
PESignAnalyzer 因此还使用了 Microsoft Catalog API,例如:
TEXT
CryptCATAdminAcquireContext
CryptCATAdminCalcHashFromFileHandle
CryptCATAdminEnumCatalogFromHash这也是为什么分析 PE 签名时,只检查 IMAGE_DIRECTORY_ENTRY_SECURITY 并不一定足够。
这时候就会涉及几个特别容易混淆的 Hash。
首先是普通的文件 Hash:SHA256(整个文件),它描述的是文件的完整字节内容。
然后是 Authenticode Hash。它不是简单地对整个文件做 SHA-256。如果签名本身参与 Hash,就会产生一个明显的问题:
TEXT
文件 Hash
↓
生成 Signature
↓
Signature 被写进文件
↓
文件内容发生变化
↓
Hash 发生变化
↓
原来的 Signature 失效因此 Authenticode 必须规定哪些 PE 区域参与 Hash,哪些区域不参与。其中最关键的就是 Certificate Table。这也是 PESignAnalyzer 需要自己计算 Hash 的原因之一。
项目中的 CalculateHashOfBytes 使用传统 Windows CryptoAPI 的
CryptAcquireContext、CryptCreateHash、CryptHashData、CryptGetHashParam 完成摘要计算。这里还有一个很重要的区分:Certificate Thumbprint 也不是 PE 文件 Hash。它通常是对 X.509 Certificate 的 DER 编码计算摘要,用于标识证书。
所以至少要区分:
- 文件 Hash
- Authenticode Hash
- Certificate Thumbprint
- Signature
它们看起来都可能是一串十六进制字符,但描述的是完全不同的对象。
六、CryptoAPI、CNG、WinTrust 与 OpenSSL:为什么它们不能简单互换
PESignAnalyzer 是一个 Windows 项目,因此使用 CryptoAPI 很自然。但如果今天重新实现一个类似的工具,一个很自然的问题就是:
能不能直接使用 OpenSSL?
答案是:可以替代其中相当一部分,但不能简单地“一比一替换”。
首先需要明确 CryptoAPI 和 OpenSSL 的定位并不完全一样。
OpenSSL 更像一个跨平台的密码学和协议工具箱。它能够处理:
TEXT
ASN.1 / DER
X.509
PKCS#7
CMS
RSA
ECDSA
SHA-256
...因此,如果手里已经有一份 PKCS#7:
TEXT
PKCS#7
↓
OpenSSL
↓
CMS / PKCS7
↓
SignerInfo
↓
X509这条路线非常自然。
而 Windows CryptoAPI 则不仅仅负责密码学和编码。它还深度集成了 Windows 的
Certificate Store、Certificate Chain、Catalog、Windows Trust 等系统能力。例如:HCERTSTORE、PCCERT_CONTEXT、HCRYPTMSG 这些都是 Windows 生态中的概念。更重要的是 WinVerifyTrust。很多人会把“验证 PKCS#7 签名”和“验证 Windows 文件是否可信”当成同一件事,实际上完全不是。
可以把它拆成四个层次。
第一层是格式解析:PKCS#7 和 X.509 能不能正确解析?
第二层是密码学验证:Signature 和公钥是否匹配?
第三层是证书链验证:签名者证书能不能建立到可信根?
第四层才是Windows Trust:Windows 根据自己的信任模型,是否认可这个代码签名?
OpenSSL 很适合处理前面几层,尤其是 ASN.1、X.509、CMS 和密码学验证。但 WinVerifyTrust 属于 Windows 的信任体系。
所以:
PKCS7_verify() 并不能简单地理解成 WinVerifyTrust() 的跨平台版本,这两个 API 解决的问题层次并不相同。另外,现代 Windows 还存在 CNG,也就是 Cryptography Next Generation。
传统 CryptoAPI 中经常见到:
TEXT
HCRYPTPROV
HCRYPTHASH
CryptAcquireContext
CryptCreateHash
CryptHashData现代 Windows 则更多使用:
TEXT
BCrypt*
NCrypt*CNG 在设计上更加现代化和模块化。
不过 Windows 的证书、Authenticode、Trust 等体系又有大量历史兼容,因此实际开发中经常会同时遇到:
TEXT
CryptoAPI
CNG
Certificate API
WinTrust
Catalog API这也是 Windows 安全开发长期以来比较复杂的原因之一。
如果今天把 PESignAnalyzer 做成跨平台版本,我会更倾向于把这些层次拆开:
TEXT
PESignAnalyzer
│
Authenticode Layer
│
┌────────┴────────┐
│ │
Windows Linux
│ |
CryptoAPI OpenSSL
│ │
└────────┬────────┘
│
X.509 Model
│
Trust Layer也就是说,OpenSSL 并不是 CryptoAPI 的简单替代品。
更准确的说法是:OpenSSL 可以替代 CryptoAPI 中与 ASN.1、X.509、PKCS#7/CMS 和密码学相关的相当一部分能力,但 Windows 特有的 Certificate Store、Catalog 和 Trust Model 仍然需要单独实现。
如果目标只是:“解析 PE 中的签名”,OpenSSL 已经非常有用。
但如果目标是:“得到与 Windows Explorer/WinVerifyTrust 基本一致的签名可信结果”,那就完全是另一件事情了。
七、回头看 PESignAnalyzer:解析、密码学验证与信任是三个不同的问题
重新看 PESignAnalyzer 的源码,你会发现它其实没有试图把所有问题混成一个“VerifySignature”函数。它实际上一直在处理三个不同层次的问题。
第一层是:解析。
从 PE 文件找到 Certificate Table,再从 Certificate Table 得到 PKCS#7,再得到 SignerInfo 和 X.509:
TEXT
PE
↓
Certificate Table
↓
PKCS#7
↓
SignerInfo
↓
Certificate第二层是:关联和分析。
把 SignerInfo 与证书关联起来,再建立证书链,同时解析 Nested Signature、Counter Signature、Timestamp 等 Authenticode 特有结构:
TEXT
SignerInfo
├── Certificate
├── Certificate Chain
├── Nested Signature
└── Counter Signature第三层才是:信任。
这涉及:
TEXT
Certificate Chain
↓
Root Trust
↓
Revocation
↓
Policy
↓
Windows Trust这三个层次如果混在一起,代码很容易变得非常难以理解。
而在 PESignAnalyzer 的实现中,实际上已经引入了这种分层意识:SIGN_NODE_INFO 描述签名,CERT_NODE_INFO 描述证书;CryptoAPI 负责标准格式解析;源码自己处理 Authenticode 中没有直接暴露出来的特殊结构;Catalog 则通过另一套 API 单独处理。
从今天的角度重新看这份大约十年前的代码,我认为其中最有价值的并不是某个具体 API,而是这种处理复杂二进制格式的思路:
先利用平台提供的能力把标准格式解析出来,再针对业务真正需要的部分深入底层。
如果完全自己实现 PKCS#7、X.509、ASN.1、证书链和 Trust,不仅工作量巨大,而且很容易重新踩密码学库已经解决过的问题。
但如果什么都交给系统 API,又会发现系统 API 并不一定直接暴露应用真正想分析的数据。
所以最终往往需要两者结合。
这也是 PESignAnalyzer 中最有代表性的地方:大部分 PKCS#7 和 X.509 工作交给 CryptoAPI,遇到 Nested Signature、特定 Timestamp 属性等结构时,再自己进入 DER 层进行解析。
从这个角度看,ParseDERType、ParseDERSize、ParseDERFindType 这些函数并不是 PESignAnalyzer 的“另一个解析器”,而更像是 CryptoAPI 和具体 Authenticode 数据之间的一层补充。
而如果今天重新设计这个项目,我会进一步把整个系统明确拆成:
TEXT
PE Parser
↓
Authenticode Parser
↓
CMS / PKCS#7 Parser
↓
Signer Model
↓
X.509 Model
↓
Certificate Chain
↓
Timestamp / Nested Signature
↓
Platform-specific TrustWindows 平台可以使用 CryptoAPI、CNG、WinTrust 和 Catalog API;其他平台则可以使用 OpenSSL 等库实现底层协议和密码学能力。
最终最重要的一点,是不要把下面几个概念混为一谈:
解析成功,不代表签名有效。
签名数学验证成功,不代表证书可信。
证书链能够建立,不代表 Windows 一定信任这个代码签名。
一个 PE 没有 Embedded Signature,也不代表 Windows 完全无法通过 Catalog 对它进行信任判断。
这几个层次,正是理解 Authenticode 最重要的地方。
结语
几年前写 PESignAnalyzer 的时候,我最开始只是想解决一个很具体的问题:从 PE 文件里把数字签名的信息读出来。
但真正开始处理以后,会发现这个问题背后其实连接了一整套 Windows 安全技术。
PE 负责提供 Certificate Table;Authenticode 定义代码签名相关的数据结构;PKCS#7/CMS 负责封装签名和签名者信息;ASN.1/DER 负责底层编码;X.509 负责身份和证书链;Counter Signature 和 RFC 3161 负责时间证明;Catalog 又提供了另一种文件签名机制;最终 WinTrust 才站在更高的层面回答:
Windows 是否信任这个签名?
所以,一个 EXE 文件右键属性里看起来非常简单的:“数字签名”,实际上背后可能是一条很长的技术链:
TEXT
PE
↓
IMAGE_DIRECTORY_ENTRY_SECURITY
↓
WIN_CERTIFICATE
↓
PKCS#7 / CMS
↓
SignedData
↓
SignerInfo
↓
X.509 Certificate
↓
Certificate Chain
↓
Counter Signature / RFC 3161
↓
Windows Trust而 PESignAnalyzer 所做的事情,本质上就是把这条隐藏在二进制文件中的链条重新还原出来。
所有你再来看这个早年的小工具时,会注意到它其实碰到了 Windows 软件安全领域几个非常核心的概念:PE、Authenticode、PKCS#7、X.509、ASN.1、CryptoAPI、Catalog 和 Trust。
如果把这些东西真正串起来理解,那么以后再面对一个“签名正常”的 Windows PE 文件时,我们看到的就不再只是 Explorer 中的一行提示,而是一套可以从文件字节、签名结构、证书身份,一直追踪到操作系统信任模型的完整技术体系。

暂无评论