How to Reverse UEFI modules (DXE Driver)
Tags: uefi, dxe, tpm, ghidra
Published: 2022-11-09 16:27:36

Unlike executables running above OS, UEFI modules doesn't have import functions or systemcalls which is usually the important factor in reverse engineering. Therefore, things to focus on when reversing UEFI modules and executables above OS are quite different.
In this post, I'll explain what factors am I focusing on when reversing UEFI modules.

## Abstract
The important factors, which I think should be focused on are below.
1. **UEFI Services**
1. **UEFI Protocols**
1. **Register and those IO types info of devices**

**UEFI Services and Protocols are the most valuable information** (just like Windows API and system call when reversing above OS executables) and this can be auto-analyzed by plugins of decompilers such as Ghidra and IDA. Therefore, I'll mostly focus around device-specific register here. 


## Environment Setup
UEFI Services and Protocols can be auto-analyzed by below plugins. **I'll be using Ghidra+efiseek** in this post.
* [efiseek](https://github.com/DSecurity/efiSeek) (for Ghidra)
* [efiXplorer](https://github.com/binarly-io/efiXplorer) (for IDA Pro)


## UEFI Services
While Protocols are mostly for accessing devices' functionality, UEFI Services are for offering more general functionality. Therefore, if you want to reverse **malware** that infect boot chains or **UEFI applications** (not DXE drivers or the modules before DXE), UEFI Services are the most important factor to focus on.

### Entrypoint of UEFI modules
Most UEFI modules derives EFI_SYSTEM_TABLE as a second parameter of entrypoint.
```c
EFI_STATUS EFIAPI UefiMain(
  IN EFI_HANDLE ImageHandle,
  IN EFI_SYSTEM_TABLE *SystemTable
)
```
UEFI Services can be accessed from this EFI_SYSTEM_TABLE.
* SystemTable->BootServices
* SystemTable->RuntimeServices

In most cases, these BootServices and RuntimeServices are stored in a global variable like below. (When developing UEFI modules in EDK2, people mostly include `<Library/UefiBootServicesTableLib.h>` or `<Library/UefiRuntimeServicesTableLib.h>` and these offeres `gBS` and `gRT`)
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1jf5ym8fjrzsk9m28c03.png)
You also can see HOBs and DXE Service Tables are stored in global variables `DAT_8000f120` and `DAT_8000f128`. These can be seen in DXE driver's entrypoint. Main function is the function that has the above feature (refer ghidra comment).

### Analyzing UEFI Services
For this, just google the UEFI Service you want to analyze and rename arguments. In this post, I'll just explain below.
* **Memory Allocation (AllocatePool, AllocatePages)**
* **Protocol (LocateProtocol, InstallMultipleProtocolInterfaces)**
* **Events (CreateEvent, CreateEventEx, RegisterProtocolNotify)**

I'll explain Protocol in the Protocol section so I'm going to explain the other two.

#### Memory Allocation
This is like UEFI version of heap allocation.
In UEFI, memory is seperated into several regions and these can be referenced by `gBS->GetMemoryMap`. You can specify where in this region to allocate by [EFI_MEMORY_TYPE] (https://dox.ipxe.org/UefiMultiPhase_8h.html#a0e2cdd0290e753cca604d3977cbe8bb9) argument of below function.
* gBS->AllocatePool (for small 8B aligned buffer)
* gBS->AllocatePages (for large 4KB aligned buffer)

#### Events
UEFI Events are created by these 2 BootServices.
* [gBS->CreateEvent](https://dox.ipxe.org/UefiSpec_8h.html#a136cb7690540176a224528745133f019)
* [gBS->CreateEventEx](https://dox.ipxe.org/UefiSpec_8h.html#a78fd81bb75ad0d912af92ecae5ae968d) (Ex can specify event groups named by GUID)

For both functions, **it will execute the 3rd argument (EFI_EVENT_NOTIFY) function when the events specified by last 
argument (EFI_EVENT) is signaled**. 

There are actually 2 types of event `EVT_NOTIFY_SIGNAL(0x200)` and `EVT_NOTIFY_WAIT(0x100)` which can be specified in the 1st argument. The way of signaling the event differs among this argument as follows.
* EVT_NOTIFY_SIGNAL(0x200): [gBS->SignalEvent](https://dox.ipxe.org/UefiSpec_8h.html#aa3ff2b58c5fcbf0d37c87d336d1475e1)
* EVT_NOTIFY_WAIT(0x100): [gBS->CheckEvent](https://dox.ipxe.org/UefiSpec_8h.html#adf8e6c79edb3a20647fc489d22e329a2), [gBS->WaitForEvent](https://dox.ipxe.org/UefiSpec_8h.html#aaba96f688dc14bb2154ce5aceb793493)

But mostly in DXE drivers, you'll see `gBS->RegisterProtocolNotify` used as a set with `gBS->CreateEvent`.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iuiku91lyer8u27uv3nm.png)
**[gBS->RegisterProtocolNotify](https://dox.ipxe.org/UefiSpec_8h.html#afd363625c194ad93f8786cee87a877f7) is a function that signals 2nd argument (EFI_EVENT) when protocol specified with the 1st argument (EFI_GUID) is installed**. 
More detailed information about UEFI Events can be found [here](https://edk2-docs.gitbook.io/edk-ii-uefi-driver-writer-s-guide/5_uefi_services/51_services_that_uefi_drivers_commonly_use/515_event_services).


## UEFI Protocols
If you want to analyze some device specific functions or DXE driver, the most important factor is UEFI Protocol.

### Analyzing Protocol's function usage
If you want to see the use of Protocol's function, you can search by the GUID of protocol, and use xref to find `gBS->LocateProtocol`.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ug3dqm1fsq52pioxrpo0.png)
Then the handle of that protocol is stored in the last argument of LocateProtocol, so you can see if any function call is made like `handleOfProtocol[offset](args...)`. 


### Analyzing Protocol's functions
If you want to analyze specific function of a protocol, you first have to look for the **installation of that protocol**. This can be done by searching binary with protocol's GUID and refer to that xref to find the [gBS->InstallProtocolInterface](https://dox.ipxe.org/UefiSpec_8h.html#a83a71b62d2e3a933d91031e98741aeca) or [gBS->InstallMultipleProtocolInterfaces](https://dox.ipxe.org/UefiSpec_8h.html#a8f07efa92b97aba461dd125c132214c3).

In most cases, you can find a list of protocols that the DXE driver produces by looking for `InstallMultipleProtocolInterfaces` called mostly in the driver's entrypoint.

For example, if we have below lines, it means,
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j58t2f4d9mcuzmkbw7t5.png)
* DAT_80000518: function list of EFI_TREE_PROTOCOL
* DAT_80000550: function list of Protocol that has GUID in DAT_800003f0
* DAT_80000560: function list of Protocol that has GUID in DAT_80000400
* DAT_80000590: function list of EFI_TCG_PRIVATE_INTERFACE

(Actually, efiseek is a bit old. It's not TreeProtocol which is for TPM1.2 but it's Tcg2Protocol for TPM2.0)

So, if we want to analyze this `SubmitCommand` of Tcg2Protocol,
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bgx4d0wluzsu7q8yszbj.png)
you have to look at below's address `0x800034CC`.
(My Ghidra's ImageBase is `0x80000000`)
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/27zr0g0zmkf4c2pdwqk0.png)

Then, if you jump to that address, there will be the content of SubmitCommand as below.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gmftt64rqqc2ctlfu1re.png)


## Register and those IO types info of devices
When you want to see the actual interaction with the device, you first need to know what kind of **register** does that device has, and how can we access to that register (the **IO type**).
As an example, I will use TPM and analyze the above SubmitCommand function (Tcg2Protocol's function).

### Knowing Device's registers
You have to refer to that device's specification for this (though, some devices specify the way to IO in their spec). For TPM, the list of registers are defined in [here (PTP spec)](https://trustedcomputinggroup.org/wp-content/uploads/PC-Client-Specific-Platform-TPM-Profile-for-TPM-2p0-v1p05p_r14_pub.pdf#page=47).
(There are FIFO and CRB columns but you can ignore CRB)

Each register's explanaitions are written in the following page of the link above. There are tons of registers so you can refer to this when you actually see the access to that register.

### Knowing Device's IO types
For this, you actually have to see the platform (SoC) specification not the device's specification. Because the "connection" between device and the cpu is defined in SoC like below.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g5tc9x1d8oti2berbk31.png)
(This is from "Intel® Pentium® and Celeron® Processor N- and J- Series Datasheet")

In this SoC specification, the IO of every device is listed like below.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g5w95ud0vp8lj54odxa4.png)
We can see here that, TPM's IO type is **MMIO (Memory Mapped IO)**.  MMIO is an IO that maps device's register to the specific memory range, so that you can read/write register values by read/write to that memory address.

We also can see from the specification that the TPM is mapped to the memory address `0xFED40000` (We have 2 TPM in the spec but ignore the `0xFED41000` one).


### Reversing SubmitCommand
Now we know that the TPM's register is mapped to `0xFED40000` and we know where to look at for each register's definition. Then you can reverse the function below.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eu0hk1awhfl9wxevqsee.png)
We see the exact same address above. Let's see inside `FUN_8000d1bc` to see how this address specified in the first argument is used.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ds01lb0s1cz340a35baz.png)
We can see a lots of `*TpmMmioBase & 0x20` here. `*TpmMmioBase` means it's referring the first content of the mapped register so let's look at the PTP spec to find out this register.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/52lvtjxz562ehm56453a.png)
It's `TPM_ACCESS_0` so `*TpmMmioBase & 0x20` is equivalent to `TpmMmioBase->TPM_ACCESS_0 & 0x20`. `0x20 = 0b00100000` so it's checking the 6th bit of TPM_ACCESS_0 register is set or not.
Therefore, let's look at the 6th bit of TPM_ACCESS_0 register in the same spec.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d34u93yqzz1tbns44970.png)
Now We can see that it's checking if this `activeLocality` is set or not.

You can create struct refering to the spec to make decompile more readable. As an example, it will get clean like this.

#### Before
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rx34xv15gqk8quwyvzz5.png)

#### After
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/91ghv7epogct53wba91r.png)

(By the way, EFI_STATUS values can be found referring [UefiBaseType.h](https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Uefi/UefiBaseType.h#L110) and [Base.h](https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Base.h#L962-L1165) of EDK2.)