题主似乎碰到了不得了的东西。
这段日志表明,这个程序试图利用一个漏洞,通过内核内存补丁的方式关闭 Windows Vista/Windows 7 的强制驱动程序签名(Driver Signature Enforcement,简称 DSE),然后将一个可能没有签名的驱动程序加载进系统内核。但是这个驱动程序可能格式错误或者已经损坏,而没能加载成功。
我会在下文大致描述发生的事情,假设读者有一定的编程语言和操作系统知识。如果对其中的原理不感兴趣,请直接跳过本回答剩余的部分。
我不是信息安全工作者。以下内容基于的信息全部来自互联网,不能作为严肃的技术参考或安全指导规范。如果有专业人士发现以下内容有错误,请不吝指正。
Windows 的驱动程序运行在内核态,主要用于操作硬件,拥有非常大的权限。如果能够在驱动程序上做手脚,在用户态就可以通过与驱动程序的输入输出(I/O)交互,实质上获得系统内核的权限。这是恶意软件的开发目标之一。
微软在 Windows 中提供了驱动程序签名机制,通过基于证书加密的方式来验证驱动程序是否由证书列表中的受信任实体发布。只使用受信任的驱动程序可以大大降低被恶意软件利用的风险。
一般情况下,默认的证书列表只信任由微软签名的驱动程序。驱动程序开发后要提交给微软进行测试,测试通过后才能得到微软的签名。
仅仅是安装时发出警告并不能劝退一心想要装上一切自己看到的东西的用户。于是微软在 Windows 的后续版本中加强了安全机制。
微软从 Windows Vista 开始引入了一个叫作代码完整性[1](Code Integrity,简称 CI)的机制。根据微软文档[2]的说法:
Code Integrity is a feature that improves the security of the operating system by validating the integrity of a driver or system file each time it is loaded into memory. Code Integrity detects whether an unsigned driver or system file is being loaded into the kernel, or whether a system file has been modified by malicious software that is being run by a user account with administrative permissions. On x64-based versions of the operating system, kernel-mode drivers must be digitally signed.
代码完整性是一个通过每次加载驱动程序或系统文件到内存中时验证其完整性来提高操作系统安全性的功能。代码完整性检测是否有未签名的驱动程序或系统文件正要被加载到内核中,或者是否有系统文件已经被具有管理员权限的用户账户正在运行的恶意软件修改。在基于 x64 版本的操作系统上,内核模式驱动程序必须有数字签名。
这个机制的另一个名字叫作强制驱动程序签名(DSE,全称见上述结论部分)。
早期某些小厂商开发的硬件,以及一些需要进行内核态操作的软件(比如 VirtualBox),使用了未签名或签名已过期的驱动程序。用户在安装驱动程序的过程中就会收到错误提示,因为驱动程序没有有效的签名而无法安装。网络上发布的绕过这种问题的方法通常是重启电脑,开机时按 F8 键进入“高级启动选项”菜单,然后以“禁用驱动程序签名强制”方式启动操作系统。
随着各软硬件厂商对驱动程序签名的重视,用户无法正常安装使用驱动程序的问题逐渐得到了改善。
2012 年,有内核研究者发现了一种恶意软件,最早出现可以追溯到 2010 年。这种恶意软件被命名为 WinNT/Turla 或 Uroburos rootkit[3],得名自使用这个恶意软件的一个可溯源到俄罗斯的高级长期威胁(Advanced Persistent Threat,简称 APT)实体 Turla[4][5]使用的不同的名字。
这款恶意软件利用了 VirtualBox 支持驱动程序 VBoxDrv.sys
的 1.6.2 版本中的漏洞(现已修复)。这个版本驱动程序的部分操作并未对传入的地址进行范围检查,因此可以通过这个驱动程序向内核内存空间植入 shellcode(一种短小的原生代码),然后触发代码对内核中的特定内存地址进行写入,关闭代码完整性机制,最后将未签名的驱动程序加载到内核,实行恶意操作。
这显示出驱动程序签名机制的问题:如果一个驱动程序得到了签名,并且签名还没有过期,但是其中有漏洞没有被微软发现,却被恶意软件利用,那么即使有代码完整性机制也无法保证安全。
在 Windows Vista 和 Windows 7 中,代码完整性是否开启是通过 Windows NT 内核程序 ntoskrnl.exe
中的布尔变量 g_CiEnabled
决定的。这就是题主截图中的 nt!g_CiEnabled
。每次要加载驱动程序时,Windows 内核都会直接检查这个变量是 TRUE(相当于开启)还是 FALSE(相当于关闭),以决定是否验证驱动程序的签名。如果能够把这个变量设置为 FALSE,那么就可以直接关闭代码完整性功能。
问题在于,g_CiEnabled
并没有导出符号,无法使用查找导出表的方法找到这个变量在内存中的位置。WinNT/Turla 使用特殊的方法解决了这个问题:内核对代码完整性的初始化是在一个叫作 SepInitializeCodeIntegrity
的内部函数中进行的,这个函数访问了 g_CiEnabled
变量,而且是唯一调用了代码完整性模块 ci.dll
中 CiInitialize
的函数。所以可以通过下列流程找到 g_CiEnabled
变量的地址:
NtQuerySystemInformation
获取当前系统内核进程的映像名称和加载基址;ntoskrnl.exe
,从而可以在无管理员权限的条件下进行分析;ci.dll
;ci.dll
部分可以得到 CiInitialize
函数的导入项,搜索对这个导入项的引用即可得到内核程序的未导出函数 SepInitializeCodeIntegrity
的位置;SepInitializeCodeIntegrity
函数中的引用,即可得到 g_CiEnabled
变量加载后的偏移地址。加上之前得到的当前内核模块加载基址就可以得到当前系统内核进程中 g_CiEnabled
变量所在的内核地址。把这个地址传到对有漏洞的 VirtualBox 支持驱动程序的 DeviceIoControl
调用,触发之前植入的 shellcode,就可以为内核内存进行修补,修改代码完整性的开启状态了。
这就是题主的截图中前两行日志的含义:找到内核中的 g_CiEnabled
变量,修改变量以关闭代码完整性(即强制驱动程序签名)功能。
从 Windows 8 开始,代码完整性功能的开启状态从 ntoskrnl.exe
中的 g_CiEnabled
变量改成了 ci.dll
的 g_CiOptions
变量,原来的变量被删除。新的 g_CiOptions
变量是一个组合旗标,当代码完整性功能开启时值为 6,关闭时值为 0。这个变量到最新版本的 Windows 10 也是如此。
CiInitialize
函数会调用 ci.dll
中的未导出函数 CipInitialize
,而 CipInitialize
函数一上来就对 g_CiOptions
变量进行了写入。那么利用相似的原理就可以找到系统内核进程中 g_CiOptions
变量的位置,然后传给驱动程序进行修改。
2005 年,微软为 x64 版本的 Windows XP 和 Windows Server 2003 SP1 引入了内核补丁保护(Kernel Patch Protection,简称 KPP)机制。这个机制对 x64 版本 Windows 的内核修补行为进行了技术上的限制(32 位 x86 则没有这个限制)。
内核补丁保护在开机操作系统启动时将受保护的内核数据生成加密的备份,并不定期对内核中受保护的数据与加密的备份进行比较。如果检测到受保护的内核数据被修改,就会触发名叫 CRITICAL_STRUCTURE_CORRUPTION
的缺陷检查(俗称蓝屏死机)。
从 Windows 8.1 开始,微软将上述的 g_CiOptions
变量纳入内核补丁保护之下[6]。为了避免被内核补丁保护机制检测到修改,恶意软件需要快速关闭代码完整性功能,载入未签名的驱动程序,然后立刻重新启动代码完整性功能。题主截图中的日志第三行和第四行就分别是加载驱动和重新启动代码完整性功能。然而风险依然存在:如果在上述过程中内核补丁保护机制正好在进行内核数据检查,那么就有可能触发缺陷检查导致死机。这种加载未签名驱动程序的方式变得不稳定。
随着 VirtualBox 旧版驱动程序签名的证书到期,加上微软更新过的内核补丁保护,这个漏洞已经无法以上述方式稳定利用了。
根据 @经过鲁 评论(已推荐置顶)中提供的信息,题主截图中的日志很可能是由一个叫 gdrv-loader 的工具输出的。源代码中可以看到与题主截图中的日志相同的字符串。
这个工具利用了技嘉(GIGABYTE)系统管理工具 GIGABYTE APP Center v1.05.21(及以前)、AORUS GRAPHICS ENGINE v1.33(及以前)、XTREME GAMING ENGINE v1.25(及以前)以及 OC GURU II v2.08(及以前)使用的内核驱动程序 gdrv.sys
中相似的漏洞,通过驱动程序 I/O 控制写入任意内核内存地址。这个漏洞已经随上述工具的版本更新修复。
gdrv-loader 寻找并写入 g_CiEnabled
或 g_CiOptions
的方法借用了利用 WinNT/Turla 原理制作的演示工具 DSEFix 的实现,同样采用了通过修改内核中相同的变量临时关闭强制驱动签名,然后加载未签名的驱动程序的方式。
题主截图中的日志第三行是指加载驱动程序时发生了错误:
Failed to load target driver: C0000263 加载目标驱动程序失败:C0000263
这个错误编码的格式是 Windows NT 内核状态码。在 Windows SDK 的 ntstatus.h
中查询这个状态码,可以看到以下内容:
// // MessageId: STATUS_DRIVER_ENTRYPOINT_NOT_FOUND // // MessageText: // // {Driver Entry Point Not Found} // The %hs device driver could not locate the entry point %hs in driver %hs. // #define STATUS_DRIVER_ENTRYPOINT_NOT_FOUND ((NTSTATUS)0xC0000263L)
大意是没有找到驱动程序的入口点。有可能这个程序试图加载的文件不是格式正确的驱动程序,或者驱动程序发生了损坏。
以上内容仅仅是 WinNT/Turla 复杂攻击技术的很小一部分。以我有限的知识水平,很难具体全面地展现 APT 实体万花筒般的攻击手段。但是从题主截图中的寥寥数行基本可以肯定,这个应该是对上述漏洞的利用。
题主可能是使用了某个演示如何利用上述漏洞的示例程序(见更新部分)。如果题主不是要利用这个漏洞做些什么的话,强烈建议将这个程序删除。如果这个程序真的加载了有恶意功能的未签名驱动程序,被恶意软件的某个用户态模块利用,那么题主的电脑可能会陷入危险。
以上内容也说明了为什么存储重要数据的联网电脑要使用新版本的 64 位 Windows 操作系统。x64 版本的 Windows 有更加严格的安全要求,实现了更多安全相关的功能,之前能够利用的漏洞在内核层面得到封堵。而 32 位的 x86 版本 Windows 则没有这些要求,只要有管理员权限,就可以进行未签名驱动程序的加载以及内核内存修补。如果电脑不幸被像 Turla 这样的攻击者视作定向攻击目标(当代攻击者使用复杂的工具通常只用于定向攻击),那么用户可能会遭受数据、财产、隐私甚至人身安全的损失。