μthenticode:一款用于验证 Windows 文件签名的跨平台工具,用于在没有Windows设备的情况下在Windows PE二进制文件上验证Authenticode签名。开发人员还将其集成到Winchecksec的最新版本中,以便你日后可以使用它来验证Windows可执行文件上的签名。
μthenticode旨在轻松集成,它是用跨平台的现代C ++编写的,避免了它所取代的CryptoAPI接口的复杂性(即WinVerifyTrust和CertVerifyCertificateChainPolicy)。你现在可以使用它来代替SignTool的许多功能,并且还有更多功能正在使用中。
Authenticode快速入门
Authenticode是Microsoft的代码签名技术,其功能与Apple Gatekeeper相当。Authenticode为已签名的程序提供许多属性:
1. 真实性:具有有效Authenticode签名的程序包含足以验证该签名的证书链,所述链最终植根于存储在用户的受信任发布者存储中的证书,从而防止了未经用户明确选择就无法自行签名的证书。
2. 完整性:每个Authenticode签名都包含已签名二进制文件的加密哈希,将此哈希与加载时二进制文件的内存表示形式进行比较,以防止恶意修改。
3. Authenticode还可以为每页内存嵌入加密哈希,它们与强制完整性签名一起使用,这对于Windows内核驱动程序是必需的,并且需要一个特殊的Microsoft交叉签名的“软件发布者证书”,而不是自签名或独立受信任的证书颁发机构(CA)。
4. 时效性:Authenticode支持嵌入来自时间戳授权(TSA)的反签名,从而使二进制文件上的签名有可能超过其签名证书的到期日期。这样的签名也可以防止有效签名的回溯,从而使攻击者更难以重用过期的签名证书。
像所有代码签名技术一样,Authenticode不能对程序做出某些保证:
1. 也有漏洞:任何人都可以编写有漏洞的软件并使用自签名证书或通过从Microsoft交叉签名的CA购买证书来对其进行签名。
2. 它只运行自身的代码,众所周知,Windows执行契约是出了名的宽松(例如,桌面应用程序的DLL加载规则),许多应用程序支持某种形式的代码执行作为一种特性(脚本、插件、生病的WinAMP皮肤,等等)。Authenticode无法验证在初始签名二进制文件之外执行的代码的完整性或意图。
因此,Authenticode与所有PKI实现一样,容易受到以下因素影响:
1. 错位的信任:CA希望出售尽可能多的证书,因此对检查从其购买的实体的合法性的动机有限,任何人都可以花几百美元在美国建立有限责任公司。
2. 证书被盗:代码签名和HTTPS证书是盗窃的主要目标,许多真实的活动利用窃取的证书欺骗用户相信恶意代码。公司定期到源代码控制系统检查他们的秘密材料,代码签名证书也不例外。
3. 欺诈性证书:臭名昭著的Flame利用了对MD5的一种新颖的选择前缀攻击来模拟一个意外被信任用于代码签名的Microsoft证书。对SHA-1的类似攻击现在在价格上对民族国家和有组织犯罪都是合理的。
总而言之,Authenticode(以及所有其他形式的代码签名)为二进制文件添加了有用的真实性和完整性检查,前提是你信任签名者及其存储密钥材料的能力。
解析Authenticode签名:PKCS#7
PKCS#7,也叫做加密消息的语法标准,由RSA安全体系在公钥加密系统中交换数字证书产生的一种加密标准。PKCS#7描述数字证书的语法和其他加密消息——尤其是,数据加密和数字签名的方法,也包含了算法。当使用PKCS#7进行数字签名时,结果包含签名证书(一列相关证书撤回列表)和已证明路径上任何其他证书。如果使用PKCS#7加密数据,通常包含发行者的参考消息和证书的序列号,它与用于解密已加密数据的公共密钥相关。
对于2000年代的Microsoft而言,这是一个不寻常的举动,大多数Authenticode格式实际上都已记录在案并可以下载。少数部分明显定义不足或标记为“超出范围”:我们将在下面介绍其中的一些。
Authenticode的核心包括两个组件:
证书表:其中包含一个或多个条目,每个条目可以是一个SignedData。
SignedData对象:通常是普通的PKCS#7容器(根据RFC 2315标有SignedData的内容类型)。
证书表
证书表是将Authenticode签名嵌入到PE文件中的机制,它具有一些有趣的属性:
1. 访问证书表涉及读取数据目录表中的证书表目录,与数据目录表中的所有其他条目不同,证书目录的RVA字段不是虚拟地址,而是直接文件偏移量。这反映了Windows加载程序的行为,它实际上并未将证书加载到程序的地址空间中。
2. 尽管如此,现实世界中的工具在证书表的放置和后续解析方面似乎并不灵活。 Microsoft的工具始终将证书表放在PE的末尾,许多第三方工具会天真地寻求证书表的偏移量并进行解析直到EOF,从而使攻击者可以轻易附加附加证书。
一旦定位,解析证书表就很简单了,它是一个8字节对齐的WIN_CERTIFICATE结构的Blob:
有一些有趣的域:
1.wRevision:WIN_CERTIFICATE的“修订”。MSDN最近才修复了这个字段的文档:WIN_CERT_REVISION_2_0=0x0200是Authenticode签名的当前版本。 WIN_CERT_REVISION_1_0 = 0x0100用于“旧版”签名,我无法在野外找到后者。
2.wCertificateType:封装的证书数据的种类。MSDN记录了wCertificateType的四个可能值,但开发人员只对其中一个感兴趣:WIN_CERT_TYPE_PKCS_SIGNED_DATA。
3.bCertificate:实际的证书数据。对于WIN_CERT_TYPE_PKCS_SIGNED_DATA,这是上面提到的PKCS#7 SignedData。
如你所料,证书表的结构允许多个独立的Authenticode签名。这对于跨多个Windows版本部署程序很有用,尤其是那些可能在受信任发布者存储中具有旧证书或由于某种原因不信任特定CA的版本。
Authenticode的SignedData
微软提供了可视化的签名数据结构:
这几乎是一个正常的PKCS#7 SignedData,但有几个关键的偏差:
1. Authenticode SignedData的contentInfo类型不是RFC 2315内容类型之一,而是SPC_INDIRECT_DATA_OBJID类型,微软将其定义为1.3.6.1.4.1.311.2.1.43。
2. 与此对象标识符(OID)对应的结构被记录为SpcIndirectDataContent,微软也提供了它的ASN.1定义:
请注意,自定义AlgorithmIdentifier实际上只是X.509的AlgorithmIdentifier,详细情况,请参阅RFC 3279及其更新。
根据上面的ASN.1定义,开发人员可以使用OpenSSL的(软硬且完全未记录的)ASN.1宏来解析Microsoft的自定义结构:
检查实际签名
有了适当的结构后,开发人员可以使用OpenSSL的)奇热未记录的PKCS#7 API来解析SignedData和间接数据内容:
然后验证它们:
开发人员通过了PKCS7_NOVERIFY,因为开发人员不一定有权访问整个证书链,只有在其受信任的发布者存储区中具有相关证书的Windows用户才能访问整个证书链。
计算并检查Authenticode哈希
现在开发人员有了真实性(以根证书为模),让开发人员进行完整性检查。
首先,让我们获取嵌入在Authenticode签名中的哈希,以进行最终比较:
接下来,开发人员需要计算二进制文件的实际哈希值,这有点复杂。
1. 每个PE都有一个32位的校验和字段,用于基本完整性目的(即意外损坏)。计算哈希值时,该字段需要跳过,因为它是对整个文件计算得出的,并且会随着证书的添加而变化。
2. 需要跳过证书数据目录条目本身,因为重新定位或修改证书表的大小不需要对现有签名进行任何更改。
3. 当然,证书表和组成签名本身不能作为哈希的输入的一部分。
4. 为了确保一致的哈希,Authenticode规定按升序根据每个节标头的PointerToRawData的值对节进行哈希,而不是按照节标头本身的顺序。这不是特别麻烦,但需要一些额外的操作。
μthenticode对Authenticode哈希过程的实现时间太长,无法在下面重复,但使用伪代码:
1. 从空缓冲区开始;
2. 将所有PE头(DOS、COFF、可选、节)插入缓冲区;
3. 按顺序从缓冲区中删除证书表目录条目和校验和字段,以避免重新计算前者的偏移量。
4. 使用pe-parse的IterSec API构造节缓冲区列表,从#129开始,IterSec以文件偏移顺序生成节。
5. 跳过证书表并将尾随数据添加到缓冲区(如果存在的话);
6. 使用从签名中检索到的NID创建并初始化新的OpenSSL消息摘要上下文;
7. 将缓冲区放入EVP_DigestUpdate中,并使用EVP_DigestFinal结束。
8. 将结果与Authenticode提供的哈希进行比较。
现在,我们将接着讨论Authenticode剩下的两个主要功能:页面哈希和时间戳签名。
页面哈希
如上所述,页面哈希明显没有在Authenticode规范中记录,并且被描述为存储在“[…]二进制结构中[这]不在本文的讨论范围内”。
有关上述结构的在线信息仅限于以下几种资源:
VirtualBox源代码引用了两个不同版本的页面哈希结构的OID:
SPC_PE_IMAGE_PAGE_HASHES_V1_OBJID:1.3.6.1.4.1.311.2.3.1
SPC_PE_IMAGE_PAGE_HASHES_V2_OBJID:1.3.6.1.4.1.311.2.3.2
尽管这些OID确实出现在Wintrust.h中,但未在Microsoft的OID参考或OID存储库中列出。
osslsigncode至少有一个分支支持生成和验证页面哈希,并授予我们进一步的洞察力:
V1 OID表示SHA-1页面哈希值; V2代表SHA2-256。
每个SpcSerializedObject的serializedData是一个ASN.1集合,其中的每个成员都是一个ASN.1序列,其作用如下所示:
上面的定义是我根据get_page_hash_link的主体进行的重构; osslsigncode混淆地将SpcAttributeTypeAndOptionalValue类型重用于Impl_SpcPageHash并手动构造SpcSerializedObject的其余内容。
据我所知,osslsigncode只为整个PE插入一个Impl_SpcPageHash,它在pe_calc_page_hash中计算该值。该函数中的代码非常密集,但似乎会生成如下结构表:
其中IMPL_PAGE_HASH_SIZE由所使用的哈希算法(即,由Impl_SpcPageHash.type)确定,并且表中的第一个条目是仅对具有page_offset = 0的PE标头进行空填充的“页面哈希”。此表未分配ASN.1定义,而是直接插入Impl_SpcPageHash.pageHashes中。
时间戳签名
与页面哈希不同,Authenticode的时间戳签名格式在官方来源和第三方来源中都有相对较好的记录。
就像Authenticode SignedData主要是一个正常的PKCS#7签名数据一样,Authenticode的时间戳格式主要是正常的PKCS#9签名。
向时间戳颁发机构(TSA)发出时间戳请求(TSR)时,该请求采用HTTP 1.1 POST的形式,其中包含DER编码,然后是base64编码的ASN.1消息:
其中countersignatureType是自定义的Microsoft OID 1.3.6.1.4.1.311.3.2.1(即SPC_TIME_STAMP_REQUEST_OBJID),内容是原始的Authenticode PKCS#7 ContentInfo。
TSA响应是PKCS#7 SignedData,从中提取SignerInfo并将其嵌入到主Authenticode SignedData中。来自TSA响应的证书类似地作为未经身份验证的属性嵌入到证书列表中。
总结
我们已经讨论了Authenticode的四个主要组件:验证签名,对照已验证的哈希值检查文件的完整性,计算页面哈希值以及验证时间戳的签名。
μthenticode本身仍在开发中,目前仅支持签名和主要的Authenticode哈希。你可以通过提供对页面哈希解析和验证以及时间戳签名验证的支持来帮助开发人员!
μthenticode的API已被完全记录和托管,并且大多数可以立即与peparse :: parsed_pe *:一起使用:
请查看svcli命令行工具中的应用示例,包括检索嵌入式Authenticode哈希。
Μthenticode工具是完全从头编写的,并使用Microsoft提供的官方Authenticode文档作为底本。