Wednesday, January 11, 2017

Attacking UEFI Runtime Services and Linux

Attackers with physical access are able to attack the firmware on many fully patched computers with DMA - Direct Memory Access. Once code execution is gained in UEFI/EFI Runtime Services it is possible to use this foothold to take control of a running Linux system.

The Linux 4.8 kernel fully randomizes the physical memory location of the kernel. There is a high likelyhood that the kernel will be randomized above 4GB on computers with sufficient memory. This means that DMA attack hardware only capable of 32-bit addressing (4GB), such as PCILeech, cannot reach the Linux kernel directly.

Since the EFI Runtime Services are usually located below 4GB they offer a way into Linux on high memory EFI booting systems.

Please see the video below for an example of how an attack may look like.



What are the EFI Runtime Services?
UEFI on PCs, EFI on macs, is the modern day BIOS. UEFI is short for Unified Extensible Firmware Interface. UEFI is responsible for detecting hardware and configuring devices so that the control can be handed over to the operating system that should be loaded.

Two main components of UEFI are the Boot Services and the Runtime Services. Very early on the operating system calls ExitBootServices. After this Boot Services are no longer in use.

The EFI Runtime Services lives on even after the operating system is loaded and running though. They provide various functionality that the operating system can call into. The UEFI specification specifies a fixed set of functions that the Runtime Services should provide to the operating system, as seen below.
The UEFI Runtime Services according to the UEFI specification.
Initially the location of the Runtime Services functionality is communicated to the operating system via the Runtime Services table. The physical address for each function is 64-bit/8-byte long and is stored in memory as little endian. The memory addresses are however in the 32-bit range on all systems looked at so far. The physical memory addresses also seems to be completely static, i.e. no randomization occurs between reboots.

Linux later on maps these addresses into virtual address space and overwrites the initial addresses in the table with the corresponding virtual addresses. An example of the initial table and the table altered by Linux is shown side-by-side below.

The EFI Runtime services table, original to the left, modified by Linux to the right.
The Attack
What if we overwrite the EFI Runtime Services with our own code by using DMA? Or even better what if we overwrite function pointers in the EFI Runtime Services table to point to previously inserted attack code?

Code execution is gained on the target system when the operating system calls into EFI Runtime Services - for example whenever it's reading an EFI variable. Code execution is gained in a special context, in which pages in lower memory are mapped 1:1 between virtual and physical addresses. The Linux kernel is reachable at its normal virtual addresses. Execution is gained in ring0 / supervisor mode.

It is however not possible to call into all functions in the Linux kernel. Some functions will fail due to the special EFI context Linux have set up for Runtime Services to execute in. The way around this is to patch a "random" hook function in the Linux kernel. When a "normal" kernel thread hits that hook "normal" kernel code execution is gained...

The target system running Ubuntu 16.10 with the PCILeech attack hardware connected (to the left). Detailed view of PCILeech (to the right).

The Targets
This have been tested on a Lenovo T430 with 8GB memory and on an Intel NUC Skull Canyon with 32GB memory. The ExpressCard slot was used to gain DMA access on the T430 On the NUC the Thunderbolt mode was set to "Legacy" in the "BIOS" settings - enabling DMA access via Thunderbolt3/USB-C.

The T430 is very straight forward. Just insert the PCILeech device and issue the pcileech.exe kmdload -kmd linux_x64_efi command. PCILeech will search for the EFI Runtime Services table and hook it. The user will be asked to do something resulting in a call to the Runtime Services - triggering the hook.

The NUC is different. PCILeech will encounter unreadable memory before the target is found and will fail. Luckily the location of the Runtime Services table is static and won't change between reboots. This goes for all tested systems. The easiest way to find out the location is by USB-booting a live Linux in EFI mode on the same system, or on a similar system. Once booted issue the command cat /sys/firmware/efi/runtime to see the physical address of the Runtime Services table. Once the address is known as 0x3b294e18 we may issue the command pcileech.exe kmdload -kmd linux_x64_efi -min 0x3b294000 -max 0x3b295000 .
The NUC setup. Displaying the NUC EFI Runtime Services table on the Surface attack computer.

Notes
To try this out yourself please have a look at PCILeech at Github.

The attack is not 100% stable in the sense that the exploit always works. Sometimes the search for the Runtime Services table will fail and sometimes it's hard to trigger a call into the Runtime Services. Target system crashes are rare though.


Conclusions
Linux 4.8 based operating systems, such as Ubuntu 16.10, are no longer completely secure from PCILeech.

Even though your laptop may not have an ExpressCard slot chances are that it will have a mini-PCIe or M.2 slot for a WiFi card if the back cover is unscrewed ...

Operating systems cannot protect themselves from DMA attacks by putting everything of interest in memory above the 32-bit / 4GB address limit. 32-bit hardware such as PCILeech may attack firmware code and data residing below 4GB. Other malicious hardware may also reach into 64-bit address space.

Operating systems should protect both themselves and firmware, such as the Runtime Services, from malicious devices by enabling VT-d.