众所周知,攻击者将合法的数字证书应用于其恶意软件,这大概是为了躲避基本的签名验证程序。Petya勒索病毒
便是这种情况。作为一名逆向工程师或red team
的开发者,了解哪些合法的签名可以被应用到其他的未签名代码以及攻击者提供的代码的方法。这篇博客将会给出代码签名机制、二进制数字签名,以及对数字证书应用于未签名的PE文件的相关技术背景的介绍。不久,你将会在我下个月发布的研究中看到相关技术的联系。
对PE文件(如exe、dll、sys等文件)进行签名是什么意思呢?一个比较简明的答案就是选择相应的PE文件,右键单击之后,查看文件属性,如果存在“数字签名”选项卡,则表示已经该文件已经被签名。当你看到文件属性中的这个“数字签名”选项卡的时候,实际上意味着该PE文件是Authenticode签名
的,在这个文件中有一个二进制的数据包,其中包括一个证书和这个文件的签名哈希值(更具体点说,Authenticode哈希值
就是在散列计算中去除PE头的那部分数据的哈希值)。存储Authenticode签名
的格式在PE代码签名规范
【见附件1】中有详细说明。
然而,很多本应该被签名的文件(以notepad.exe
为例),事实上却并没有我们所说的“数字签名”
选项卡。那这是否意味着该文件没有签名,并且微软也运行了这些未签名的代码呢?这个要视情况而定。虽然notepad.exe
本身没有嵌入Authenticode签名
,但实际上它通过目录签名这种方式进行过签名。Windows包含由许多目录文件组成的目录存储库,基本上只是Authenticode
的散列表。然后,每个目录文件都被签名,以证明任何具有匹配哈希的文件源自目录文件的签名者(基本上都是Microsoft
的程序)。因此,当Explorer UI
不尝试查找目录签名时,几乎其他的所有签名验证工具都将执行目录查找。在PowerShell
和Sysinternals Sigcheck
中获取可以Authenticode签名
。
注:目录文件存储在%windir%\System32\CatRoot{F750E6C3-38EE-11D1-85E5-00C04FC295EE}
在上述屏幕截图中,SignatureType
属性指示notepad.exe
是目录签名(”Catalog”)
。还值得注意的是IsOSBinary
属性。虽然具体的实现没有文档化,但如果签名链接到几个已知的,散列的Microsoft
根证书之一,则将显示“True”
。有兴趣了解更多有关如何工作的人可以逆向CertVerifyCertificateChainPolicy
函数【https://msdn.microsoft.com/en-us/library/windows/desktop/aa377163(v=vs.85).aspx 】。
Sigcheck工具
使用“-i”
参数,执行目录证书验证,并显示包含匹配的Authenticode哈希
的目录文件路径。 “-h”
参数还将计算并显示PE文件的SHA1
和SHA256
形式的Authenticode散列
(分别为PESHA1
和PE256
):
理解Authenticode哈希
的概念会使你比较容易的在目录文件中找到相应的条目。你也可以双击目录文件来查看其它条目。我还编写了CatalogTools
【https://github.com/mattifestation/CatalogTools 】,这是用于解析目录文件的PowerShell
模块。在“hint”
元数据字段说明了notepad.exe
确实是相应的条目:
现在你已经了解了可以对PE文件进行签名的方法(Authenticode签名
和目录签名
),那么对签名的二进制格式有所了解也是十分必要的。无论是Authenticode签名
还是目录签名
,其签名都存储PKCS#7
【https://tools.ietf.org/html/rfc2315 】签名数据中,这种数据是一种ASN.1格式
的二进制数据。 ASN.1
只是一个标准,用于说明应该存储不同数据类型的二进制数据。在观察/解析数字签名的字节之前,你必须首先知道它如何存储在文件中。目录文件很简单,因为文件本身由原始的PKCS#7
数据组成。 网上有在线的ASN.1
解析器【https://lapo.it/asn1js/ 】解析ASN.1
数据,并以直观的方式呈现出来。例如,尝试将包含notepad.exe
的哈希的目录文件加载到解析器中,你将可以看到一个层次分明的数据布局的视图。 以下是解析输出的代码段:
ASN.1
编码数据中的每个属性都以对象标识符(OID,object identifier
)开头,这是一个唯一的数字序列,用于标识以下数据的类型。上述代码段中值得注意的OID
如下:
建议想要深入理解的读者花点时间探索数字签名中包含的所有字段。然而,图中的字段都不在本博客的讨论范围之内。这里【https://support.microsoft.com/en-us/help/287547/object-ids-associated-with-microsoft-cryptography 】列出了附加的加密/签名相关OID
。
具有嵌入式Authenticode签名
的PE文件中的数字签名数据附加在文件末尾(这里说的是格式正常的的PE文件)。操作系统显然需要一些信息来检索嵌入式签名的确切偏移量和大小。 给大家介绍一款我最喜欢PE解析/编辑工具CFF Explorer
【http://www.ntcore.com/exsuite.php 】,下面是使用CFF Explorer
来解析kernel32.dll
:
嵌入式数字签名的偏移量和大小存储在PE的可选头内的“security directory”
数组中的“data directories”
偏移量中。 数据目录包含PE文件中的各种结构的偏移量和大小——导出表,导入表,重定位表等。数据目录中的所有偏移量都是相对虚拟地址(RVA),这意味着它们是加载时PE相应部分的在内存中的偏移量。只有一个例外——安全目录表将其偏移量存储为文件偏移量,这是因为Windows加载程序实际上并没有在内存中加载安全目录表的内容。
安全目录表的二进制数据是WIN_CERTIFICATE
结构【https://msdn.microsoft.com/en-us/library/windows/desktop/dn582059(v=vs.85).aspx 】的。下面是kernel32.dll
在010Editor
中解析的结果【https://gist.github.com/mattifestation/d10f91859bd0dffd4a539945ae02eccb, 相应文件见附件2 】(文件偏移是0x000A9600):
PE文件的Authenticode签名
应始终具有WIN_CERT_TYPE_PKCS_SIGNED_DATA
的wRevision
。后续的字节数组是与目录文件内容相同的PKCS#7
以及ASN.1
编码签名数据。唯一的区别是你应该找不到1.3.6.1.4.1.311.12.1.1
这样的OID,因为这表明存在的目录散列签名。
在网上在线的ASN.1
解码器解析出的原始bCertificate
数据证实我们正在处理正确的PKCS#7
的数据:
现在你已经了解了数字签名的二进制格式和存储位置的基本概念,接下来可以开始将现有签名应用于未签名的代码。
将嵌入的Authenticode签名
从签名文件应用到未签名的PE文件是非常简单的。虽然这个过程可以自动化完成,这里我将用十六进制编辑器和CFF Explorer
来讲解如何手动实现。
步骤1: 识别要窃取的Authenticode签名
。在实例中,我将使用kernel32.dll中的一个签名作为演示。
步骤2: 识别“security directory”
中的WIN_CERTIFICATE
结构的偏移量和大小。
可以看到上述截图中的文件偏移量为0x000A9600,大小为0x00003A68。
步骤3: 在一款十六进制编辑器中打开kernel32.dll
,从偏移量0xA9600
处选择0x3A68
字节的数据,然后复制这些数据。
步骤4:在十六进制编辑器中打开你的未签名PE(在此示例中为HelloWorld.exe),滚动到最后,粘贴从kernel32.dll复制的3A68字节的数据。 注意签名开头的文件偏移量(在我的机器环境下为0x00000E00)。粘贴完数据之后记得保存文件。
步骤5:在CFF Explorer中打开HelloWorld.exe,并更新安全目录以指向应用的数字签名:offset - 0x00000E00,size - 0x00003A68。 进行修改后保存文件。 忽略“无效”警告,这是因为CFF Explorer不将安全目录视为文件偏移量,并在尝试引用数据所在的位置时发出警告。
这就完成了所有操作。现在,签名验证实用程序将正确解析和显示签名。 唯一需要注意的是,它们将报告签名无效,因为所计算的文件的Authenticode与证书中存储的签名哈希的Authenticode不匹配。
现在,如果你想知道为什么SignerCertificate指纹符号不匹配,那么你是一个精明的读者。考虑到我们应用了相同的签名,为什么证书指纹不符合?这是因为Get-AuthenticodeSignature首先尝试对kernel32.dll进行目录文件查找。在这种情况下,它找到了kernel32.dll的目录条目,并显示目录文件的签名者的签名信息。kernel32.dll也是Authenticode签名的。 所以要验证Authenticode散列的指纹值是否相同,请临时停止CryptSvc服务,该服务负责执行目录哈希查询的服务。如此,你将看到指纹值匹配。 这表示目录哈希是使用不同的代码签名证书用于签署kernel32.dll本身的证书。
实际上,CryptSvc
一直在运行,并且将会执行目录查找。假设你想要注意OPSEC,并匹配用于签署目标二进制文件的相同证书。事实证明,您可以通过在WIN_CERTIFICATE
结构中交换bCertificate
的内容并相应地更新dwLength来将目录文件的内容实际应用于嵌入式PE签名。 可以跟着后续的实例一起操作。请注意,我们的目标(在本实例中)是将Authenticode签名
应用于我们的无符号二进制文件,与用于对包含的目录文件进行签名的二进制文件相同:本实例中,证书指纹为: AFDD80C4EBF2F61D3943F18BB566D6AA6F6E5033
。
步骤1:识别包含目标二进制文件的Authenticode
哈希的目录文件——kernel32.dll
。 如果一个文件是Authenticode
签名的,Sigcheck
工具将无法解析目录文件。然而,Signtool
(包括在Windows SDK
中)可以做到。
步骤2:在十六进制编辑器中打开目录文件,并获取文件大小为0x000137C7
步骤3:我们将在十六进制编辑器中手动构造一个WIN_CERTIFICATE
结构。我们来看看我们提供的每个成员:
在十六进制编辑器中制作数据时,请注意数据以小端格式存储。
步骤4:复制所有WIN_ERTIFICATE
的所有字节,附加到未签名的PE的末尾,并相应地更新安全目录的偏移量和大小。
现在,假设你的计算和对齐是正确的,看看与目录文件的指纹匹配情况!
这篇博文中提出的技术,希望可以引起一些人考虑如何检测数字签名的滥用。虽然我没有彻底研究清楚签名的启发,但我只是提出一系列问题,以期待能够激励他人开始调查和撰写检测潜在的签名异常:
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)