我从一开始就在那里 - 初步理解 Windows UEFI Bootkit 开发

admin 2025年1月2日17:29:13评论9 views字数 2552阅读8分30秒阅读模式

I was always there from the start

我一直想学习引导程序工具包 (bootkit) 并编写一个。这篇博客解释了什么是引导程序工具包以及我们编写的工具包是如何工作的。

引导程序工具包

引导程序工具包是一种在系统引导过程中感染系统的恶意软件,通常在引导加载程序之前加载,使其能够修补或钩住其之后的任何内容。在本例中,我们的目标是修补 Windows 内核ntoskrnl.exe。引导程序工具包本质上是隐蔽的,使其非常难以被检测到,因此也使得防病毒软件更难检测到它们或它们可能对系统做出的任何更改。

UEFI

统一可扩展固件接口 (UEFI) 是为替代 BIOS 而创建的,它充当操作系统和固件之间的接口,为引导加载程序或预引导应用程序提供标准环境。

UEFI 应用程序(如引导加载程序或驱动程序)是存储在 FAT32 分区上的可移植可执行文件 (PE),在启动期间由固件加载。虽然 UEFI 应用程序和驱动程序相似,但它们在关键方面有所不同。UEFI 应用程序只运行一次,退出后从内存中卸载,而 UEFI 驱动程序即使在操作系统初始化后仍保留在内存中。

UEFI 提供两种类型的服务:引导服务和运行时服务。

引导服务

引导服务是仅在操作系统初始化之前可用的功能。一旦调用ExitBootServices,这些服务就无法访问。它们包括检索系统内存映射、访问图形输出协议 (GOP) 等操作。由于它们仅一次性可用,引导服务主要由引导加载程序等 UEFI 应用程序使用。

运行时服务

运行时服务是即使在操作系统完全初始化后仍然可访问的功能。这些服务包括获取或设置系统时间、关闭或重置系统以及访问固件的环境变量等任务。与引导服务不同,运行时服务在调用ExitBootServices后仍然可用,这使得它们对需要与操作系统交互的 UEFI 驱动程序特别有用。

我们从哪里开始?

我们的目标是在系统引导过程中获得控制权,所以我们想要钩住引导加载程序本身。在内存中寻找引导加载程序并修补它以调用钩子是很繁琐的。幸运的是有一个更好的方法。我们可以钩住ExitBootServices

ExitBootServices

ExitBootServices 是一个由引导加载程序在将控制权转交给操作系统之前调用的函数。此时,内核和所有必要的依赖项都已加载到内存中并准备执行。引导加载程序的唯一剩余任务是将控制权交给内核并传递LOADER_PARAMETER_BLOCK

我从一开始就在那里 - 初步理解 Windows UEFI Bootkit 开发

Winload 在将控制权交给内核之前调用 ExitBootServices

钩住ExitBootServices很简单。我们禁用写保护,覆盖全局引导服务对象中的ExitBootServices函数指针,然后重新启用写保护。

我从一开始就在那里 - 初步理解 Windows UEFI Bootkit 开发

钩住 ExitBootServices

钩子函数很简单,只捕获调用者的返回地址,该地址指向OslFwpKernelSetupPhase1内部。之后,它调用原始的ExitBootServices以继续引导过程。

我们遍历winload.efi并使用移动签名来获取LOADER_PARAMETER_BLOCK的指针。然而,我们遇到了一个问题,尝试访问LOADER_PARAMETER_BLOCK会导致崩溃,因为我们尚未切换到 NT 使用的正确地址空间。

我从一开始就在那里 - 初步理解 Windows UEFI Bootkit 开发显示加载器块位置的反汇编

EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE

引导程序工具包的下一步涉及在驱动程序入口点注册一个处理函数,该函数在事件触发时被调用。此事件在新地址空间设置完成后发生,使我们能够访问LOADER_PARAMETER_BLOCK和内核。要定位LOADER_PARAMETER_BLOCK,我们将搜索代码移到处理程序中,并使用前面提到的加载签名。一旦我们有了LOADER_PARAMETER_BLOCK,我们就解析它以找到ntoskrnl.exe并提取其基地址。我从一开始就在那里 - 初步理解 Windows UEFI Bootkit 开发虚拟地址更改事件处理程序

修补内核

现在我们有了内核基地址,我们可以扫描内核以找到任何函数并根据需要修改它。在这种情况下,我们的目标是修补在引导过程早期调用的函数。一个这样的函数IoInitSystem作为理想目标脱颖而出。我们钩住的部分是在核心系统驱动程序(如文件系统、ACPI 等)加载之后,但在任何引导驱动程序加载之前调用的。

我从一开始就在那里 - 初步理解 Windows UEFI Bootkit 开发

原始 IoInitSystem 函数的反汇编

通过执行另一个签名搜索,我们定位到IoInitSystem的地址,在那里我们修补一个 retpoline 跳转。修补过程如下

-> 首先,我们从函数中复制原始代码。

-> 接下来,我们准备 retpoline 的 shellcode 如下

    mov  r10, IoInitSystempush r10 ; Keep the original as ourreturn address    mov  r10, IoinitSystemHook    jmp  r10

在钩子函数结束时,我们恢复原始代码,由于栈上的最后一个地址是原始函数,当我们返回时执行会正常继续。

我从一开始就在那里 - 初步理解 Windows UEFI Bootkit 开发

修补后的 IoInitSystem 函数的反汇编

由于我们的钩子函数位于 UEFI 驱动程序中,我们必须使用ConvertPointer来获取 NT 使用的地址空间中的地址。

一旦进入钩子函数,我们就获得了内核级别的控制权。我们可以解析内核的IMAGE_EXPORT_DIRECTORY来查找函数地址,这使我们能够使用任何 NT 函数。

我从一开始就在那里 - 初步理解 Windows UEFI Bootkit 开发

Bootkit 工作流程图

演示

演示视频展示了 bootkit 如何在启动过程中修补 NT,使用ZwDisplayString显示 Hello World。

完整代码可以在这里[1]找到。 

结论

通过这个项目,我们 (NSG650[2]Pdawg[3]) 学到了很多关于 UEFI 和 Windows 启动过程的知识。这也提醒我们这类恶意软件是多么可怕和强大,以及为什么需要像安全启动这样的安全措施。

参考资料

[1] 

这里: https://github.com/Pdawg-bytes/UEFIBootKit

[2] 

NSG650: https://github.com/NSG650

[3] 

Pdawg: https://github.com/Pdawg-bytes

原文始发于微信公众号(securitainment):我从一开始就在那里 - 初步理解 Windows UEFI Bootkit 开发

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年1月2日17:29:13
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   我从一开始就在那里 - 初步理解 Windows UEFI Bootkit 开发http://cn-sec.com/archives/3584321.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息