DEV Community

MachineHunter
MachineHunter

Posted on

How to Reverse UEFI modules (DXE Driver)

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
  2. UEFI Protocols
  3. 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.

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.



EFI_STATUS EFIAPI UefiMain(
  IN EFI_HANDLE ImageHandle,
  IN EFI_SYSTEM_TABLE *SystemTable
)


Enter fullscreen mode Exit fullscreen mode

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
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 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.

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.

But mostly in DXE drivers, you'll see gBS->RegisterProtocolNotify used as a set with gBS->CreateEvent.
Image description
gBS->RegisterProtocolNotify 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.

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
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 or gBS->InstallMultipleProtocolInterfaces.

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

  • 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
you have to look at below's address 0x800034CC.
(My Ghidra's ImageBase is 0x80000000)
Image description

Then, if you jump to that address, there will be the content of SubmitCommand as below.
Image description

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).
(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
(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
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
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
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
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
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

After

Image description

(By the way, EFI_STATUS values can be found referring UefiBaseType.h and Base.h of EDK2.)

Top comments (0)