欢迎来到 黑吧安全网 聚焦网络安全前沿资讯,精华内容,交流技术心得!

内核中的代码完整性:深入分析ci.dll

来源:本站整理 作者:佚名 时间:2020-03-16 TAG: 我要投稿

在某些场景中,如果我们希望在允许某个进程进行特定动作前,以一种可靠的方式确认该进程是否可信,那么验证该进程的Authenticode签名是一个不错的方式。用户模式下的DLL wintrust提供了专门用于此目的的API。但是,如果我们需要在内核模式下以一种可靠的方式来进行身份验证,这时应该如何进行呢?在以下的情况中,我们可能会遇到这样的场景:1、应用程序用户模式部分不可用,可能是由于正处于开发过程的早期阶段,也可能是由于运行失败或配置出现问题。2、我们希望获得对进程操作的内联访问权限,以便在进程未验证的情况下阻止它们。3、最典型的一种情况是Windows内核在加载驱动程序时对驱动程序进行验证,显然这一过程必须要在内核模式下完成。尽管在不少论坛上,都有人多次提问应该如何操作,但我们还没有在公开的地方找到解决该问题的任何实现。其中一些方案建议我们自行实现,一些方案则建议将OpenSSL源导入到我们的项目中。而另外一种方案则将这个任务委托给用户模式下的代码。但是,上述所有替代方案都有明显的缺点:1、在解析复杂的ASN1结构时容易出现错误;2、不适合将大量源代码导入驱动程序,因为OpenSSL中的每一个漏洞修复都会导致重新导入该代码。3、进入用户模式可能无效,并且用户模式并非始终都可用。实际上,Microsoft内核模式库ci.dll中,就包含对文件进行身份验证的功能。j00ru的研究表明,ntoskrnl通过CiInitialize()函数初始化CI模块,该函数以回调列表填充函数指针结构。如果我们可以使用这些函数或者其他CI导出来验证正在运行的进程或文件的完整性和真实性,这将会成为内核驱动程序的一个最佳方案。除了ntoskernel.exe之外,我们还发现了两个驱动程序,它们都链接到ci.dll,并使用其导出文件:

链接到ci.dll的驱动程序

链接到ci.dll的驱动程序驱动程序可以链接到这个模块,并且调用一些关键的函数,例如CiValidateFileObject()。从函数名称就可以看出,这样的方式完全可以满足我们的需求。在本文中,我们将通过一个代码示例来详细分析CI,可以以此作为进一步研究的基础。
 
背景信息
我们建议各位读者在详细分析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])。而其中的一个函数,CiValidateImageHeader(),将会被ntoskernel.exe用于加载驱动程序以验证签名的过程:

调用堆栈以在加载过程中验证驱动程序签名在我们的研究中,利用了导出的函数CiCheckSignedFile()以及与之交互的数据结构。稍后我们将看到,这些数据结构也出现在其他CI函数中,我们也可以将研究范围扩展到这些其他的函数。
CiCheckSignedFile()
CiCheckSignedFile()可以接收8个参数,但目前我们还不清楚这些参数的名称是什么。但是,通过检查内部函数,我们可以推断出其参数。例如,我们可以检查MinCryptGetHashAlgorithmFromWinCertificate():

检查WIN_CERTIFICATE的结构成员我们发现,对于WIN_CERTIFICATE结构来说,常量0x200和2是比较常见的值,该结构为我们提供了第四个和第五个参数。我们可以通过类似的方式找到其余的输入参数。而对于输出参数来说,则方法完全不同,我们将在后文中详细描述。进行一些逆向之后,我们得到了函数签名:
NTSTATUS CiCheckSignedFile(
    __In__ const PVOID digestBuffer,
    __In__ int digestSize,
    __In__ int digestIdentifier,
    __In__ const LPWIN_CERTIFICATE winCert,
    __In__ int sizeOfSecurityDirectory,
    __Out__ PolicyInfo* policyInfoForSigner,
    __Out__ LARGE_INTEGER* signingTime,
    __Out__ PolicyInfo* policyInfoForTimestampingAuthority
);
该函数的工作方法如下:1、调用方位函数提供文件摘要(缓冲区和算法类型),以及指向Authenticode签名的指针。2、该函数通过以下方式验证签名和摘要:(1)遍历文件签名,并获取使用特定摘要算法的签名;(2)验证签名(和证书),并提取其中显示的文件摘要;(3)将提取的摘要与调用方提供的摘要进行比较。3、除了验证文件签名之外,该函数还为调用方提供有关已验证签名的各种详细信息。该函数后面一部分的工作原理非常值得关注,因为仅仅知道文件已经经过正确签名是不够的,我们还需要知道是由谁进行签名的。在下一节中,我们将解决这一问题。
PolicyInfo结构
到目前为止,我们已经将所有输入参数输入到CiCheckSignedFile()并且能够进行调用。但是,我们除了其大小(在Windows 10 x64上为0x30)之外,对于PolicyInfo结构几乎一无所知。作为输出参数,我们希望该结构能以某种方式提供有关签名者身份的提示。因此,我们调用该函数,并对内存进行检查,以确认哪些数据填充到PolicyInfo之中。在内存中,似乎包含一个地址和一些较大的数字。该结构正在内部函数MinCryptVerifyCertificateWithPolicy2()中填充:

[1] [2] [3]  下一页

【声明】:黑吧安全网(http://www.myhack58.com)登载此文出于传递更多信息之目的,并不代表本站赞同其观点和对其真实性负责,仅适于网络安全技术爱好者学习研究使用,学习中请遵循国家相关法律法规。如有问题请联系我们,联系邮箱admin@myhack58.com,我们会在最短的时间内进行处理。
  • 最新更新
    • 相关阅读
      • 本类热门
        • 最近下载