2020年1月15日,微软公布了1月份的补丁更新列表,其中存在一个位于CryptoAPI椭圆曲线密码(ECC)证书检测绕过相关的漏洞(CVE-2020-0601),该漏洞为NSA发现并汇报给微软。攻击者可以利用这个漏洞,使用伪造的代码签名证书对恶意的可执行文件进行签名,并以此恶意文件来进行攻击。该漏洞又称为CurveBall或Chain of Fools,攻击者利用该漏洞可以创建自己的加密证书,这些证书看起来是来自于Windows信任的合法证书。
Windows CryptoAPI包含多个不同的库,该漏洞位于crypt32.dll
中。研究人员对该DLL的补丁和原始DLL进行差异分析,结果如下所示:
图 1. 使用BinDiff对crypt32.dll
文件和补丁文件进行差异分析
对比这两个DLL文件,研究人员发现有5个新的函数加入了最新的补丁中,还有5个原有的函数发生了变化。
首先,可以看到修改的函数名:
图 2. crypt32.dll
文件中修改的函数名
可以看出CCertObject
类的constructor
和destructor
都发生了微小的变化,但最大的变化位于ChainGetSubjectStatus()
和CCertObjectCache::FindKnownStoreFlags()
中。
然后,看一下新加入的函数名:
图 3. crypt32.dll
中新加入的函数名ChainLogMSRC54294Error()
函数就是一个新加入的函数用来帮助Windows event logging对潜在的漏洞利用尝试进行记录。其目的可以通过以下内容来确定:
图 4. 表明函数ChainLogMSRC54294Error()
的块
该块传递了一个含有与该漏洞相关的CVE编号的字符串到外部库函数CveEventWrite
中,这是一个相对较新的Event Tracing API函数,会将CVE相关的事件写入Windows Event Log中。
利用这些信息,研究人员分析了该函数的交叉引用,发现了关于事件记录的环境情况。在本例中,只有ChainLogMSRC54294Error()
的直接引用是在函数ChainGetSubjectStatus()
中,该函数在更新的几个函数中变化最大:
图 5. 函数ChainGetSubjectStatus()
检查ChainLogMSRC54294Error()
的引用情况
查看到新记录函数的调用环境可以发现,该函数是根据调用CryptVerifyCertificateSignatureEx()
和ChainComparePublicKeyParametersAndBytes()
的特定结果才调用的,后者是补丁中新加入的一个函数。因此,ChainGetSubjectStatus()
是一个很好的分析目标。
为了理解ChainGetSubjectStatus()
参与的过程,首先要了解一个典型的程序如何使用CryptoAPI来处理证书。
本文中研究人员用Powershell的Invoke-Webrequest cmdlet
来进行分析。Powershell加载后,到含有可信证书的系统证书库的handler会通过调用CertOpenStore
来获取,所以存储会在必要的时间使用。然后,这些证书库会加入到“collection”中,collection
就像一个大的合并证书库一样。
当Invoke-Webrequest
这样的命令用于用来发送TLS上的Https请求到服务器时,服务器就会呈现一个含有终端证书和其他证书的TLS证书握手消息,这些证书会用来验证证书链。在接收到证书后,其他内存中的库就会通过调用CertOpenStore()
来创建。然后,接收到的证书会通过函数 CertAddEncodedCertificateToStore()
添加到新的库中,该函数会常见一个含有到证书库的handle的CERT_CONTEXT
结构,一个指向原始编码证书的指针,一个指向CERT_INFO
结构的指针,其中CERT_INFO
结构与证书的ASN.1结构对应。
比如,下面这些结构就是通过调用到终端证书的CertAddEncodedCertificateToStore()
创建的:
图 6. 调用CertAddEncodedCertificateToStore()
创建的结构
通过对CVE-2020-0601漏洞的分析,研究人员得出以下结论:
终端证书的签名是用伪造的root证书和其他包含的椭圆曲线参数验证的。
伪造的root证书签名是用自签名的证书验证的,还说使用其中包含的椭圆曲线参数。
伪造的root证书匹配的证书位于系统证书库中,伪造的和合法的root证书都保存在这里。
伪造的和合法的root证书的公钥哈希值都会被检查,如果哈希值匹配,就不再验证伪造的root证书,导致伪造终端证书的成功验证。
那么微软是如何处理该漏洞的呢?在图5中,可以看出加入了一个到新函数ChainComparePublicKeyParametersAndBytes()的
调用,替换了原来的发行者和可信root公钥哈希之间的简单比较,会比较可信root证书和用来验证终端证书签名的证书的公钥参数和字节。如果比较失败,就会调用CryptVerifySignatureEx()
来重新验证终端证书签名。
本文作者:ang010ela
本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/123102.html