导语:Windows中,要从内核驱动程序进行数字签名检查并不简单。原因在于,所有已记录且易于使用的wintrust.h API都位于用户模式下,这为我们增加了不少的困难。
概述
在Windows系统中,从内核驱动程序进行数字签名检查并不是一项容易的工作。原因在于,所有已记录且易于使用的wintrust.h API都位于用户模式下,这为我们增加了不少的困难。
第一种选择是使用OpenSSL,但是如果我们构建针对Windows 1903版本或以上的驱动程序,WHQL进程似乎需要提交其他日志,例如SDV等。OpenSSL项目中存在着较多的问题,我们会因为内存未对齐而收到很多警告,可能达到1000+ Warining。更重要的是,使用这种方式所生成的加密摘要并不是我们所期望的,很可能由于在实现过程中出现错误而导致最终的结果出错。另外,如果我们完整阅读代码,并搜索“hack”、“todo”关键词,得到的结果会让我们感到不安。最后,使用OpenSSL编译的驱动程序通常为250KB,而使用本地方法仅为30KB,如果我们的空间非常有限,那么就需要谨慎考虑是否选择这种方法。
第二种选择是使用从代码完整性DLL加载到System进程中的未记录导出。尽管这并非是在客户端计算机上发布驱动程序地最为安全的方法,但这种方式实现起来相对较为简单。Ido Moshe和Liron Zuarets在这篇文章中(https://medium.com/cybereason/code-integrity-in-the-kernel-66b3f5cce5f)进行了非常好的解释。
最后,第三个选项是使用Bcrypt,我们可以从C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\shared\bcrypt.h中的内核驱动程序链接到这个头部文件,同时也可以链接到内核库ksecdd.lib。我们可以找到各种有趣的导出方式,例如BCryptOpenAlgorithmProvider、BCryptImportKeyPair、BCryptGenerateSymmetricKey、BCryptCreateHash、BCryptHashData、BCryptDecrypt、BCryptEncrypt、BCryptExportKey、BCryptVerifySignature等等,但是其中的很多都是由其他函数创建并作为HANDLE传递的结构,现在必须由我们来人工制作并传递。可以通过以下方式来查看导出:
dumpbin.exe /exports "C:\Program Files (x86)\Windows Kits\10\Lib\10.0.18362.0\km\x64\ksecdd.lib"
Authenticode认证码
Authenticode是一种Microsoft的代码签名技术,它使用具有公钥和私钥的非对称加密。在驱动程序的不可执行部分,包含一个嵌入式DER编码的X509证书,或者也可以使用目录文件(分离的签名)来验证驱动程序的完整性。2008年,Microsoft曾在Authenticode_PE.docx文档中对Authenticode进行了完整的说明,但并非所有部分都做了及时更新,例如,文档中指出证书的指纹是MD5哈希值,但现在实际上指纹(DER编码后证书的哈希值)已经变为SHA-1哈希值。
添加单个证书或添加同一个链上的多个证书都不会影响二进制签名,因为证书表自身、证书表指针和文件校验都不属于证书签名的一部分,如下图所示。
PE文件和Authenticode签名格式的概述:
在这里,我们不难发现一个安全问题。如果将任意数据添加到具有签名的二进制文件的Certificate条目中,该哈希值将仍然有效。幸运的是,.cat文件解决了这一问题,但并非所有的驱动程序都经过目录签名。因此,如果我们是红队成员,我们可以不再使用Mimikatz这类工具,只需要添加一个新的第三方根CA,就可以不会再引起人们的关注。
我们列举出这一过程中的一些关键术语。
1、摘要/消息摘要:哈希函数的输出。
2、加密摘要:签名者的私钥用于加密摘要信息,以生成签名。
3、签名消息:计算摘要和加密的整个过程。
4、公钥:仅包含一个模数和指数。
5、PKCS #7:加密数据的标准格式,包括签名数据、证书和证书吊销列表(CRL)。
签名过程的基本步骤包括:
1、生成PE映像的哈希值,特别是上图白色背景部分所展示的内容。
2、使用签名者的私钥对上述摘要进行加密(例如使用RSA 2048),从而生成加密摘要。
3、将加密摘要、证书和哈希算法结合在一起,创建签名块。
4、将签名块插入到PE文件中。
当用户验证嵌入的Authenticode签名时,基本步骤包括:
1、从嵌入式证书提取公钥。
2、使用公钥解密嵌入的加密摘要。
3、在PE代码/数据等部分,再次运行用于创建原始摘要的相同哈希算法(例如SHA-256)以创建摘要。
4、比较步骤2和步骤3得到的摘要,如果相同,则公钥与用于对原始代码进行签名的私钥相匹配,证明该代码未被篡改。
证书链
RDN是以0个或多个逗号分隔的组件,称为“相对专有名称”(Relative Distinguished Name,RDN),会形成一个专有名称(DN)。我们以本地上传的Adobe Reader为例,查看二进制文件的专有名称。如下图所示,我们可以看到AcroRd32.exe标题字段的10个相对专有名称。
Adobe Reader的证书标题:
证书路径:
检查证书链返回其根的过程,仅仅是将子颁发者与父标题进行比较。在上图的上方展示了路径信息,下表说明了子级(颁发者字段)和父级(标题字段)之间的对应联系。几乎可以肯定,我们的计算机上已经安装了指纹为[5fb7ee..]的DigiCert根CA,如下图所示。
AcroRd32.exe
颁发者:
CN = DigiCert EV Code Signing CA (SHA2)
OU = www.digicert.com
O = DigiCert Inc
C = US
标题:
CN = Adobe Inc.
OU = Acrobat DC
O = Adobe Inc.
L = San Jose
S = ca
C = US
SERIALNUMBER = 2748129
2.5.4.15 = Private Organization
1.3.6.1.4.1.311.60.2.1.2 = Delaware
1.3.6.1.4.1.311.60.2.1.3 = US
指纹:
1bfc555fd6489422bc2baba036b31aa75dc0aa17
序列号:
0ee3f1c8f451cbf21203341a53f23e71
签名算法:
sha256RSA
DigiCert EV Code Signing CA (SHA2)
颁发者:
CN = DigiCert High Assurance EV Root CA
OU = www.digicert.com
O = DigiCert Inc
C = US
标题:
CN = DigiCert EV Code Signing CA (SHA2)
OU = www.digicert.com
O = DigiCert Inc
C = US
指纹:
60ee3fc53d4bdfd1697ae5beae1cab1c0f3ad4e3
序列号:
03f1b4e15f3a82f1149678b3d7d8475c
签名算法:
sha256RSA
DigiCert High Assurance EV Root CA
颁发者:
CN = DigiCert High Assurance EV Root CA
OU = www.digicert.com
O = DigiCert Inc
C = US
标题:
CN = DigiCert High Assurance EV Root CA
OU = www.digicert.com
O = DigiCert Inc
C = US
指纹:
5fb7ee0633e259dbad0c4c9ae6d38f1a61c7dc25
序列号:
02ac5c266a0b409b8f0b79f2ae462577
签名算法:
sha1RSA
证书位置
根证书可以在注册表的HKLM键下找到。一开始,我们认为这里存在着安全问题,管理员用户可以对主机进行内核调试,或者将其附加到lsass.exe并窃取缓存的凭据,或者将psexec以SYSTEM权限覆盖到受保护的文件夹。同时,管理员也通过上述位置添加或删除系统证书。下表列举了所有类型证书在Windows证书管理器中的存储位置。
证书存储的注册表路径:
[用户上下文]
HKCU:\SOFTWARE\Microsoft\SystemCertificates\ 存储用户专用公钥的物理位置
HKCU:\SOFTWARE\Policies\Microsoft\SystemCertificates\ 存储活动目录(AD)组策略对象(GPO)安装的用户专用公钥的物理位置
[计算机上下文]
HKLM:\SOFTWARE\Microsoft\SystemCertificates\ 存储计算机所需公钥的物理位置
HKLM:\SOFTWARE\Microsoft\Cryptography\Services\ 存储与特定服务相关的密钥的物理位置
HKLM:\SOFTWARE\Policies\Microsoft\SystemCertificates\ 存储组策略对象(GPO)安装的计算机所需公钥的物理位置
HKLM:\SOFTWARE\Microsoft\EnterpriseCertificates\ 存储由AD域中Enterprise PKI容器安装的计算机所需公钥的物理位置
证书存储的文件夹:
[用户上下文]
C:\user\abc\APPDATA\Microsoft\SystemCertificates\ 用户专用公钥和私钥指针的物理存储位置
C:\user\abc\APPDATA\Microsoft\Crypto\ 用户专用私钥容器的物理存储位置
[计算机上下文]
C:\ProgramData\Microsoft\Crypto\ 计算机专用私钥容器的物理存储位置
证书存储的类型包括:
1、AddressBook:其他用户和资源的证书存储;
2、AuthRoot:第三方证书颁发机构(CA)的证书存储;
3、CertificationAuthority:中间证书颁发机构(CA)的证书存储;
4、Disallowed:证书存储区中已经被撤销的证书;
5、My:用户使用的个人证书存储,这是大多数自定义证书的存储位置;
6、Root:信任的证书颁发机构(CA)的证书存储;
7、TrustedPeople:信任的用户和资源的证书存储;
8、TrustedPublisher:信任的应用程序发布者的证书存储。
我们的“DigiCert High Assurance EV Root CA”是第三方CA,因此我们可以在以下的注册表键中,找到计算机所需的系统证书:
HKLM\SOFTWARE\Microsoft\SystemCertificates\AuthRoot\Certificates
DigiCert根证书的指纹是5fb7ee0633e259dbad0c4c9ae6d38f1a61c7dc25。在下图中,我们可以在注册表中找到该证书。在Windows中,我们可以通过指纹来有效地搜索所需证书。
计算机上全部非Microsoft颁发的根证书:
要查看单个证书的详细信息,另一种方式是从签名的二进制文件中手动提取证书,我们可以在链中将任何证书“Copy to file”。随后,我们就可以使用certutil工具转储大量数据,或者使用certutil查看有关完整二进制文件的详细信息。
OID
OID表示法是指以点分隔的数字字符串,例如1.2.840.113549.1.9.4,以十六进制表示为06 09 2A 86 48 86 F7 0D 01 09 04。我编写了一个Python3脚本,来对其进行转换:https://github.com/AstralVX/oidhex_to_dot 。messageDigest的OID可以拆分为:
1.2.840.113549.1.9.4 – messageDigest
1.2.840.113549.1.9 – PKCS-9签名
1.2.840.113549.1 – PKCS
1.2.840.113549 – RSADSI
1.2.840 – USA
1.2 – ISO成员
1 – ISO分配的OID
TLV
每个ASN.1数据都被编码为标签长度值(TLV)三元组。标签定义了对象类型,例如整型、布尔型、字符串或序列等。TLV也可以包含子TLV。
示例ASN.1数据:30 0D 0C 05 48 45 4C 4C 4F 02 01 1E 01 01 00
我们对TLV进行解释,下面是ASN.1序列和C样式结构:
标签:30
长度:0D
说明:序列长度为13。
标签:OC
长度:05
值:48 45 4C 4C 4F
说明:UTF8字符串长度为6,其值为HELLO。
标签:02
长度:01
值:1E
说明:整型长度为1,且值为0x1E(十进制30)。
标签:01
长度:01
值:00
说明:布尔型长度为1,值为0(FALSE)。
//ASN.1 sequence Sequence { Name::= UTF8 String Age::= Integer isSmoker::= [0] Boolean OPTIONAL } //C struct struct person { CHAR[5] Name, INT age, BOOL isSmoker}
ASN.1
ASN.1是描述结构的通用方法。Authenticode数字签名中包含Authenticated属性(包含有用的部分)和Unauthenticated属性(时间戳签名,MS签署)。在下图中,只展示了一些具有可读名称的OID,并没有完成所有转换,并且漏掉了一些重要的OID。我们在身份验证属性中看到的是:
1.2.840.113549.1.9.3 = contentType,包含messageDigest OID。(1.3.6.1.4.1.311.2.1.4)
1.3.6.1.4.1.311.2.1.11 = SPC_STATEMENT_TYPE_OBJID
1.3.6.1.4.1.311.2.1.12 = SpcSpOpusInfo,包含:programName [可选]描述,字段 [可选]和一个URL。
1.2.840.113549.1.9.4 = messageDigest
其中, 1.3.6.1.4.1.311.2.1.4 == SPC_INDIRECT_DATA_OBJID (06 0a 2b 06 01 04 01 82 37 02 01 04)是一个SpcIndirectDataContent结构:
SpcIndirectDataContent (struct) { Data - SpcAttributeTypeAndOptionalValue (struct) { Type - OID: SPC_PE_IMAGE_DATAOBJ [1.3.6.1.4.1.311.2.1.15] (06 0a 2b 06 01 04 01 82 37 02 01 0f) Value - SpcPeImageData (struct) { Flags - File - SPCLink (struct) { URL [0]: Moniker [1]: SpcSerializedObject (struct) { SpcUuid - 10 byte const GUID (a6 b5 86 d5 b4 a1 24 66 ae 05 a2 17 da 8e 60 d6) SerializedData - struct of page hashes if present } } File [2]: SpcString (struct) containing Unicode string "<<>>" } } MessageDigest - DigestInfo (struct) { DigestAlgorithim - must be same as SignerInfo.DigestAlgorithim Digest - message digest value } }
Adobe Reader的数字签名标签:
实现
考虑到版权问题,我们无法发布包含数千行代码的完整加密代码。但从较高的层次上来看,可以执行以下操作:
1、读取目标二进制文件的PE头,以进行数字签名检查,解析其中的DOS/NT部分,并提取嵌入式Authenticode证书。
2、解析TLV:解析SignedData序列,随后对另一个摘要算法TLV进行解析,从而获得ContentInfo结构和SignerInfo结构。
3、在证书之间循环,检查中间证书的序列号、有效日期范围、哈希算法、EKU代码签名等。
4、验证最高父级根证书,可以使用Bcrypt生成最高级别证书的指纹,然后在根证书存储中检查该指纹。
5、随后,通过使用适当的摘要算法,对实际二进制文件进行哈希处理,然后将其与嵌入式AuthenticodeInfo签名者消息摘要进行比较,以验证消息摘要。
6、接下来,验证加密的摘要,确认是否属于签名原始消息摘要的签名者。这一过程是通过生成已签名属性结构的摘要,并将其公钥应用于嵌入式加密摘要以获得未签名摘要来实现的。如果二者匹配,则表明签名者是使用其私钥进行了签名。在这里,需要大量的Bcrypt,这也是最棘手的部分,使用人工构造的pRsaKeyBlob,它由[BCRYPT_RSAKEY_BLOB, pbPublicExponent(大端字节序), pbRawPublicKey(大端字节序)]组成。然后,我们可以在BCryptImportKeyPair(BCRYPT_RSAPUBLIC_BLOB)和BCryptEncrypt(BCRYPT_PAD_NONE)中使用精心构造的结构,该结构使用公钥对未签名的摘要进行验证。
7、最后是对映像摘要进行验证,并将生成的哈希值与存储在authenticode的映像摘要进行比较。
遗憾的是,在上述所有过程中,都包含大量的TLV解析、Bcrypt、缓冲区溢出检查、黑名单证书检查、证书固定检查。要实现上述过程,不仅仅需要几次Bcrypt调用和一些解析,而是有数千行需要开展的工作。但是,至少目前我们已经有了实现方式的思路。下图展示了一些我们需要创建的标头。
与Authenticode处理有关的结构:
样本值
以下是我们文章中提到的Adobe二进制文件的示例值,如果大家需要进行比较,可以以此作为参考。
公钥:
30 82 01 0a 02 82 01 01 00 b3 cc 04 ee e8 24 99 23 43 3b 6b 34 ce e6 ee 80 c5 8b ab 38 a4 28 f4 55 8f ab 8b e5 ab cb 44 62 25 c8 71 24 1f f6 15 03 35 76 a9 b0 9b a5 db 28 8a 24 45 fc e6 e4 fd 22 fe b7 17 8b d6 d3 77 11 42 bb 1a e3 6f f9 42 d2 b5 9b 41 d6 b1 5d 8f f9 87 18 fb 55 fd 5e 1a 51 3d bc 44 ac 96 ba d5 97 9d 88 e4 59 cf e7 21 f1 07 28 d7 a5 ef 17 00 2c 5e 1e 1d 67 c5 b4 a9 83 8f 2e 2c 08 51 64 e4 14 89 91 2e 6e e4 30 f5 24 e9 b3 0e c3 08 98 a0 9c f1 c9 68 7a 3d dd d6 f9 9c 3b db 2c 96 a2 7f 17 97 87 f8 16 5f ed 64 fb 1d 72 43 ff 1a 95 3d 91 28 90 68 56 76 2b 66 4e ff a8 ee 36 9f 49 cb a6 ee 87 9c 76 22 1d fb 98 48 9e 72 2e d1 1b b1 d8 2d 52 6b eb fa 62 00 9a c8 83 04 b6 64 ed 45 4c 21 34 f4 8d 36 3b 8b b4 52 47 b3 a9 4e 2b 54 94 0f b2 b7 06 54 25 23 1f 36 fb 07 ba d4 a3 ea b7 02 03 01 00 01
图片摘要 - 原始:
ef 64 7e 10 ca 06 df 3d 22 d1 10 fb 78 17 92 b4 f2 2c 0e f7 1e c1 95 cf 0c 33 59 a4 b8 b2 ff cb
内容信息(Bcrypt标头+图片摘要):
30 5c 06 0a 2b 06 01 04 01 82 37 02 01 04 a0 4e 30 4c 30 17 06 0a 2b 06 01 04 01 82 37 02 01 0f 30 09 03 01 00 a0 04 a2 02 80 00 30 31 30 0d 06 09 60 86 48 01 65 03 04 02 01 05 00 04 20 ef 64 7e 10 ca 06 df 3d 22 d1 10 fb 78 17 92 b4 f2 2c 0e f7 1e c1 95 cf 0c 33 59 a4 b8 b2 ff cb
摘要算法(_DIGEST_ALGO_IDENTIFIER_SHA256):
60 86 48 01 65 03 04 02 01
签名者信息 - 签名摘要(ALGO_IDENTIFIER_RSA):
2a 86 48 86 f7 0d 01 01 01
消息摘要,例如SHA-256(内容信息):
a7 ee 55 49 55 11 15 df f5 ea 6a b4 10 44 9f de 24 66 c5 f4 3f 8e 5c e4 de 01 20 4c 55 ab ca 6e
加密摘要:
33 d9 b3 76 1d a7 37 70 a1 b1 5b 3a 11 e8 12 34 03 43 b2 18 b4 b1 af 60 ad 2f cb 41 33 f5 6d 13 f5 6c 3f 84 d6 ef a1 77 07 b1 55 2c 9e 12 d5 1b 1f 3c 0e 6b 5e 9a d9 2f 5f d7 8b b2 a0 a1 62 c1 80 60 4f 8f ad 2c 0d 90 0e 1a 96 aa 13 24 8f ca d8 05 c1 aa 98 1d df df 93 85 ea eb 7e 0c 7f 94 df d0 36 e5 ca df fc 64 d1 a0 7b 30 5a 87 a0 38 7b cb ea e1 6c d1 53 ad ab 48 3f 7f 73 90 66 39 bc b1 6a 8a b2 42 1c 7b bb 63 9f 3c 03 e6 82 f5 3a 0e fc 22 d1 9c eb a5 da 2b be 93 b3 9d 88 a3 43 d0 0c 70 90 06 84 0d 33 ff 3a 8e 3a b6 8f a6 7d 54 4a ea 59 f6 b4 7f 95 51 49 dc 96 0a ba 29 8e a1 71 36 d5 a9 d2 72 39 0d 54 15 b8 62 5c f1 aa 3a 2b f4 18 16 5b 8b 8b 62 49 02 89 c6 7d ea 29 15 0e c3 a9 15 ee 7f bd da 96 6d 35 6f 4d 2b 7e e1 04 f0 de be 7b 01 15 a4 07 a8 09 df 69 d9
签名者信息 - 签名属性:
31 81 9a 30 19 06 09 2a 86 48 86 f7 0d 01 09 03 31 0c 06 0a 2b 06 01 04 01 82 37 02 01 04 30 1c 06 0a 2b 06 01 04 01 82 37 02 01 0b 31 0e 30 0c 06 0a 2b 06 01 04 01 82 37 02 01 15 30 2e 06 0a 2b 06 01 04 01 82 37 02 01 0c 31 20 30 1e a0 1c 80 1a 00 41 00 64 00 6f 00 62 00 65 00 20 00 41 00 63 00 72 00 6f 00 62 00 61 00 74 30 2f 06 09 2a 86 48 86 f7 0d 01 09 04 31 22 04 20 a7 ee 55 49 55 11 15 df f5 ea 6a b4 10 44 9f de 24 66 c5 f4 3f 8e 5c e4 de 01 20 4c 55 ab ca 6e
本文翻译自:https://astralvx.com/index.php/2020/03/20/authenticode-certificates-and-checks-from-a-km-driver/如若转载,请注明原文地址: