前言
具有物理访问权限的攻击者能够在许多具有DMA-直接内存访问的完全修补的计算机上攻击固件。一旦在UEFI/EFI运行时服务中执行代码,就可以使用脚本来控制正在运行的Linux系统。
Linux 4.8内核完全随机化了内核的物理内存地址。如果计算机拥有足够大的内存,内核很有可能随机化到4G以上的地址空间上。这意味着DMA硬件攻击仅能够攻击32位的地址(4GB),如PCILeech,不能直接攻击Linux内核。
由于EFI运行时服务通常位于4GB以下,因此它们在高内存EFI引导系统上提供了一种进入Linux的方法。
什么是EFI运行时服务?
PC上的UEFI,mac上的EFI是现代化的BIOS。UEFI是统一的可扩展固件接口的缩写。UEFI负责检测硬件和配置设备,以便于将控制移交给正在加载的操作系统。
UEFI的两个主要组件是引导服务和运行时服务。操作系统上很早就调用ExitBootServices。随后引导服务就不再使用。
即使在操作系统加载和运行后,EFI Runtime Services仍然保持运行。它们提供操作系统可以调用的各种功能。UEFI规范指定了运行时服务应该向操作系统提供的一组固定的函数,如下所示。
UEFI运行时服务
最初,运行时服务功能的位置通过运行时服务表传送到操作系统。每个函数的物理地址是64位/8字节长,并以小端存储的形式保存在内存中。然而,到目前为止,所有系统的内存地址都在32位范围内。物理内存地址似乎是完全不变的,即在重新启动之间不发生随机化。
Linux随后将这些地址映射到虚拟地址空间,并用相应的虚拟地址覆盖表中的初始地址。初始表和被Linux修改后的表的如下所示。
EFI Runtime服务表,原始在左边,Linux修改的为右边。
攻击
如果我们使用DMA用自己的代码覆盖EFI运行时服务表会发生什么?或者,如果我们修改EFI运行时服务表的指针,让它指向之前插入的攻击代码,这又会发生什么?
当操作系统调用EFI运行时服务时,会在目标系统上执行代码-例如当它读取一个EFI变量。在特殊上下文中获得执行代码,其中较低的内存页面在虚拟地址与物理地址之间以1:1映射。在正常的虚拟地址中是可以访问到Linux内核的。在ring0/supervisor模式下执行。
然而,调用Linux内核中的所有函数是不可能的。一些函数会调用失败,因为Linux已经为运行时服务设置了特殊的EFI上下文。针对这种情况,可以在Linux内核中patch 一个“随机”的钩子函数。当“正常”内核线程命中该钩子时,被hook的内核代码将会被执行.
目标系统运行Ubuntu 16.10,连接着攻击硬件的PCILeech。右视图为PCILeech
目标
已经在一台具有8GB内存的联想T430和一台具有32G内存的 Intel NUC Skull Canyon做过了测试。T430的ExpressCard插槽用于获得DMA访问,在NUC上,在“BIOS”中设置Thunderbolt模式为“Legacy” – 允许通过Thunderbolt3 / USB-C进行DMA访问。
T430非常简单,只需插入PCILeech设备并调用pcileech.exe kmdload -kmd linux_x64_efi命令。PCILeech将搜索EFI运行时服务表并hook它。然后要求用户执行某些操作来触发已hook过的服务。
NUC是不同的。PCILeech在找到目标之前如果遇到不可读的内存就会失败。幸运的是,运行时服务表的地址是静态的,在重新启动之间不会改变。这适用于所有已测试的系统。找到地址最简单的方法是通过在同一系统或相似系统上以EFI模式用USB引导启动Linux。一旦启动就调用命令cat /sys/firmware/efi/runtime来查看运行时服务表的物理地址。一旦知道地址是0x3b294e18,我们可以调用命令
- pcileech.exe kmdload -kmd linux_x64_efi -min 0x3b294000 -max 0x3b295000
显示被攻击的运行时服务表
备注
如果你也想试试,可以查看Github上的PCILeech。
虽然漏洞总是在运作,但是攻击并不是100%稳定的。有时,搜索运行时服务表会失败,有时很难触发对运行时服务表的调用。有时目标系统也会崩溃。
结论
在Linux 4.8操作系统上,如Ubuntu 16.10。由于可以利用PCILeech,所以不再是完全安全的。 即使你的笔记本电脑可能没有一个ExpressCard插槽,但是如果拧开后盖,它可能会有一个mini-PCIe或M.2插槽。
操作系统将重要的数据放在高于32-bit/4GB的地址上并不能阻止DMA攻击。32位硬件(如PCILeech)会攻击低于4GB的代码和数据。其他恶意硬件也可能攻击多达到64位地址空间。
操作系统应该启用VT-d来保护自身和固件(例如运行时服务)免受恶意设备的攻击。