背景信息
我们建议读者在详细分析ci.dll之前先熟悉以下相关主题: 1. PE安全目录:PE中包含Authenticode签名的部分; 2. WIN_CERTIFICATE结构:Authenticode签名之前的标头; 3. PKCS 7. SignedData结构:Authenticode的基本结构; 4. X.509证书结构; 5.证书时间戳:通过使证书过期或吊销来延长签名生命周期的方法。
研究过程
在Windows 10 上,CI 导出以下函数:
CI 导出函数如上所述,调用CiInitialize() 将返回一个名为g_CiCallbacks 的结构体,该结构体包含更多函数(详细信息请参阅[1][2][5])。 ntoskernel.exe 将使用其中一个函数CiValidateImageHeader() 来加载驱动程序以验证签名:
在加载期间验证驱动程序签名的调用堆栈在我们的研究中,我们利用了导出的函数CiCheckSignedFile() 及其与之交互的数据结构。稍后我们会看到这些数据结构也出现在其他CI 函数中,我们也可以将研究范围扩展到这些其他函数。
CiCheckSignedFile()可以接收8个参数,但不清楚这些参数的名称是什么。然而,通过检查内部函数,我们可以推断出它的参数。例如,我们可以检查MinCryptGetHashAlgorithmFromWinCertificate():
检查WIN_CERTIFICATE的结构体成员我们发现常量0x200和2是WIN_CERTIFICATE结构体的常用值,它为我们提供了第四个和第五个参数。我们可以用类似的方式找到其余的输入参数。对于输出参数,方法完全不同,我们稍后会详细介绍。经过一些逆向工程,我们得到了函数签名:
NTSTATUS CiCheckSignedFile( __In__ const PVOID摘要缓冲区, __In__ int摘要大小, __In__ int摘要标识符, __In__ const LPWIN_CERTIFICATE winCert, __In__ int sizeOfSecurityDirectory, __Out__ PolicyInfo*policyInfoForSigner, __Out__LARGE_INTEGER*signingTime, __Out__PolicyInfo*policyInfoForTimestampingAuthority);该函数的工作方法如下: 1. 调用azimuth函数提供文件摘要(缓冲区和算法类型),以及指向Authenticode签名的指针。 2. 该函数通过以下方式验证签名和摘要: (1) 遍历文件签名,并使用特定的摘要算法获取签名; (2) 验证签名(和证书)并提取其中显示的文件摘要; (3) 转换将提取的摘要与调用者提供的摘要进行比较。 3. 除了验证文件签名之外,该函数还向调用者提供有关已验证签名的各种详细信息。该函数的后一部分如何工作很有趣,因为仅仅知道文件已正确签名是不够的,我们还需要知道是谁签名的。在下一节中我们将解决这个问题。
PolicyInfo结构
到目前为止,我们已将所有输入参数输入到CiCheckSignedFile() 中并且能够进行调用。然而,除了其大小(Windows 10 x64 上为0x30)之外,我们对PolicyInfo 结构几乎一无所知。作为输出参数,我们希望该结构以某种方式提供有关签名者身份的提示。因此,我们调用该函数并检查内存以确认PolicyInfo中填充了哪些数据。在内存中,它似乎包含一个地址和一些更大的数字。该结构正在内部函数MinCryptVerifyCertificateWithPolicy2() 中填充:
填充PolicyInfo 结构该函数中的某些代码似乎正在检查该值是否在特定范围内。对于证书验证过程,我们推测这个范围就是证书有效的时间范围,事实证明这是正确的:
检查证书有效性这将导致以下结构:
typedef struct _PolicyInfo {int structSize; NTSTATUS验证状态; int 标志; PVOID 一些缓冲区; //后来称为certChainInfo; FILETIME 撤销时间; FILETIME notBeforeTime; FILETIME notAfterTime;}PolicyInfo, *pPolicyInfo;虽然证书的有效期非常值得关注,但这并不会直接转到签名者那里。稍后我们会发现,大部分信息都位于成员certChainInfo中,我们稍后会讨论。
CertChainInfo缓冲区
当检查PolicyInfo的内存时,我们可以看到它指向动态分配的缓冲区的结构——之外的内存位置。分配位于I_MinCryptAddChainInfo() 中,其函数名称指示缓冲区的用途。我们通过检查其内存布局来对该缓冲区进行逆向工程: 1. 在前几个字节中,有指向缓冲区内各个位置的指针。 2. 在这些指向位置中,存在重复模式和指向缓冲区内其他位置的指针。 3. 在最后指出的位置中,我们发现一些看起来像证书摘要的文本。该缓冲区包含有关整个证书链的数据,包括解析格式(位于子结构中)和原始数据格式(包含证书、密钥、EKU 的ASN.1 证书)。此部分允许调用者轻松查看证书的主题、颁发者、证书链的组成以及用于创建每个证书的哈希算法。为了更好地解释该缓冲区的格式以及从中获得的子结构,我们将在32 位计算机上分析其内存布局。如果使用32位计算机,混乱就会减少,并且可以利用较少的填充字节来满足对齐要求。下面是Microsoft 签名的Notepad.exe 的示例:
这里我们可以看到CertChainInfo 缓冲区的内存视图: 1. 缓冲区顶部有两个4 字节数字。其中一个表示在哪里可以找到一系列CertChainMember类型结构体的地址,另一个是一个计数器,表示其中有多少个结构体。 2. 第一个CertChainMember 位于地址0x89BF45C8(以黑色标记)。我们将其格式化如下:(1)在CertChainMembers的末尾,在蓝色标记的地址0x89BF4688处,有纯文本格式的主题名称。 (2) 在橙色标记的地址0x89BF4699处,有纯文本格式的发行人名称。 (3) 在红色箭头所指的地址0x89BF46BE 处,包含实际证书的ASN.1 blob 的开头。内存以小端对齐的4 字节组显示,因此证书的前两个字节实际上是0x3082,而不是如图所示的0x3131。
typedef struct _CertChainMember{ int 摘要标识符; //例如0x800c 表示SHA256 intdigestSize; //例如0x20 表示SHA256 字节摘要缓冲区[64]; //包含摘要本身CertificatePartyName subjectName; //指向主题名称的指针CertificatePartyName IssuerName; //指向颁发者名称Asn1BlobPtr 证书的指针; //指向ASN.1 中实际证书的指针} CertChainMember, * pCertChainMember;这就是我们之前提到的解析后的数据。我们无需自己解析证书即可获取主题或颁发者。该结构中的最后一个字节指向缓冲区的更深处。接下来的96 个字节包含第二个CertChainMember,出于可读性原因未对其进行标记。其中包含有关链中下一个证书的信息。公钥和EKU(扩展密钥用法)有一组类似的指针和结构。换句话说,CI 从证书中获取一些关键数据,并以子结构的形式提供给调用者。然而,如果调用者需要其他东西,它可能包括未解析的原始数据。注意:PolicyInfo 和CertChainInfo 结构都以结构的大小开头。由于这些结构在操作系统版本之间是可扩展的,因此在尝试访问其他结构成员之前必须检查其大小。 CertChainInfo 缓冲区和各种子结构的完整细分可以在存储库的文件ci.h 中找到。
该函数将释放PolicyInfo的certChainInfo缓冲区,该缓冲区由CiCheckSignedFile()和填充PolicyInfo结构的其他CI函数分配。该函数还重置其他结构成员。这里,必须调用它以避免内存泄漏。
CiFreePolicyInfo() 的实现由于此函数在内部检查是否有可用内存,因此即使未填充PolicyInfo,也可以安全地调用它。
如前所述,在调用CiCheckSignedFile() 之前需要完成一些工作。调用者必须计算文件哈希并解析PE,以向函数提供签名的位置。然而,函数CiValidateFileObject() 可以为调用者完成这部分工作。我们不需要从头开始,因为它与CiCheckSignedFile() 共享一些参数:
NTSTATUS CiValidateFileObject( __In__ struct _FILE_OBJECT* fileObject, __In__ int a2, __In__ int a3, __Out__ PolicyInfo*policyInfoForSigner, __Out__PolicyInfo*policyInfoForTimestampingAuthority, __Out__ LARGE_INTEGER*signingTime, __Out__BYTE*digestBuffer, __Out__int*digestSize, __Out__int*digestIdentifier) ;这个函数是在内核空间中映射文件并提取其签名:
通过CiValidateFileObject() 在系统空间中映射文件。该函数还计算文件摘要,如果提供了足够长的非空缓冲区,则将用摘要填充它。注:由于该功能仅在最新的Windows版本上添加,因此我们没有重点研究该功能。如果我们要继续我们的研究,我们将专注于分析其验证策略。这里,使用了比CiCheckSignedFile() 更严格的策略,这意味着它可能无法验证先前已由CiCheckSignedFile() 验证过的PE。这可能会受到第二个和第三个参数值的影响,但我们还没有扭转它。
与CI链接
64位
1. 使用dumpbin 实用程序从dll 中获取导出的函数:
转储bin /导出c:windowssystem32ci.dll2。创建一个.def 文件,如下所示:LIBRARY ci.dllEXPORTSCiCheckSignedFileCiFreePolicyInfoCiValidateFileObject3。使用lib 实用程序生成.lib 文件:
lib /def:ci.def /machine:x64 /out:ci.lib
32位
这里的情况比较棘手,因为在32 位系统上该函数反映了参数的总和(以字节为单位),例如:
CiFreePolicyInfo@4 但是ci.dll会导出没有这部分的函数,所以我们需要创建一个.lib文件来执行这样的转换,所以我们使用了[3]和[4]文章中描述的方法。 1. 按照步骤1 和2 中所述为64 位创建.def 文件。 2. 使用具有相同签名的伪装实体的函数存根创建一个C++ 文件。我们基本上可以模仿供应商从代码中导出函数时所做的事情。例如:
extern 'C' __declspec(dllexport) PVOID _stdcall CiFreePolicyInfo(PVOID policyInfoPtr){ return nullptr;} 3. 将其编译为OBJ 文件。 4. 使用lib 实用程序生成.lib 文件,这次使用OBJ 文件:
Lib /def:ci.def /machine:x86 /out:ci.lib 位于GitHub 存储库中,包含存根的代码。
总结
参考文章
[1] Microsoft Windows FIPS 140 验证安全策略文档(https://csrc.nist.gov/csrc/media/projects/cryptographic-module-validation-program/documents/security-policies/140sp3093.pdf) [2 ] Windows 驱动程序签名绕过(作者:derusbi)(https://www.sekoia.fr/blog/windows-driver-signing-bypass-by-derusbi/) [3] 如何创建32 位导入库(https://qualapps.blogspot.com /2007 /08/how-to-create-32-bit-import-libraries.html) [4] Q131313: 如何在没有.OBJ 或源代码的情况下创建32 位导入库(https://jeffpar.github.io/kbarchive/kb /131/Q131313 /) [5] j00ru 关于CI 的博客文章(https://j00ru.vexillium.org/2010/06/insight-into-the-driver-signature-enforcement/)
原文链接:https://www.anquanke.com/post/id/200478
关于深入剖析内核代码完整性:ci.dll详解和的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。
相关问答
答: ci.dll 是 Windows 操作系统的关键组件,它负责管理和保护系统内核中运行的代码完整性。如果 ci.dll 被恶意软件攻击或破坏,可能会导致系统崩溃、数据泄露甚至安全漏洞被利用。因此,保障 ci.dll 的安全性至关重要,可以有效防止系统遭受恶意攻击和病毒感染。
129 人赞同了该回答
答: 维护 ci.dll 的安全性需要多种方法配合实施,例如定期更新 Windows 系统、谨慎下载未知来源的应用程序以及安装可靠的网络安全软件。此外,还可以通过使用虚拟化技术或白名单机制来限制不信任进程对 ci.dll 的访问权限,进一步增强系统的防护能力。
56 人赞同了该回答
答: ci.dll 主要负责在 Windows 内核中确保代码的完整性和安全性。它的工作原理是通过一系列复杂的算法和机制来验证正在运行的代码是否经过合法授权,并检测是否存在恶意行为或篡改痕迹。一旦发现异常情况,ci.dll 会采取相应的措施,例如隔离恶意程序或终止其执行,从而保护系统免受损害。
292 人赞同了该回答
答: 简而言之,可以将 ci.dll 比作一个严格的守门员,负责审查和检查所有试图进入内核的代码。它确保只有经过授权、安全的代码才能顺利运行,防止潜在威胁对系统造成破坏。
171 人赞同了该回答
答: 为保障 ci.dll 的安全性,我们可以采取多种措施:首先,要保持 Windows 系统的稳定性和最新状态,及时安装官方发布的安全补丁;其次,谨慎下载和安装应用程序,尤其要注意来源不明或安全评价较低的软件;此外,建议使用可靠的杀毒软件和防火墙来防御恶意攻击。
196 人赞同了该回答
答: 还可以考虑使用虚拟化技术或白名单机制来限制对 ci.dll 的访问权限,仅允许授权程序进行操作。 同时,定期对系统进行备份也是非常重要的,以防意外事件导致 ci.dll 损坏时能够快速恢复数据。
240 人赞同了该回答