Reverse engineering EFI executables is a bit of a pain in the arse due to the way linking works with EFI. All EFI executables are statically linked, and there is no dynamic linking/loading in the traditional sense. Much of the core EFI functionality is exposed through several “tables” - the System Table, Boot Services Table and Runtime Services Table. These are just structs containing a bunch of pointers to functions and data that are accessed by executables running in the EFI environment. I won’t go further into the details of the tables - if you’re reading this you’re probably well across it already, but if not, check out the EFI spec.
When an EFI binary is executed by the DXE or BDS phase of boot, the binary is just loaded into memory and the entry point is
called like any other function. The EFI System Table is passed as a parameter to the entry point function, along with a reference to the Image Handle, and the Boot Services and Runtime Services tables are retrieved from the System Table. From here, EFI functionality is accessed from within the executable by making calls to function pointers in the various tables - for example, the Boot Services function
AllocatePool() for allocating memory on the heap.
Since there are no references in the binary to external symbols, reversing an EFI binary becomes a bit more difficult. Calling a Boot Services function might look like this:
mov rax, cs:qword_whatever call qword ptr [rax+150h]
Not particularly useful. What we want to see is something more like this:
mov rax, cs:gBootServices call [rax+EFI_BOOT_SERVICES.UninstallMultipleProtocolInterfaces]
Manually changing all the references to EFI tables into struct offsets is painful, so I’ve written some IDAPython code to make it a bit easier. The Github repo is here.
The first task is finding global variables where the various EFI tables are stored. This can be done (for the few binaries I’ve tested with) by the
rename_tables() function in
efiutils.py. Once that’s done, the
update_structs() function will find xrefs to these globals and rename any references to these structures.
Before running the script, one binary (a free hug to the first person to tell me what it is) looked like this:
After, something much more useful:
The next step in making EFI reversing easier is going to be locating and renaming function pointers within EFI protocol blocks. I guess I’ll get to it.
Update: OK, I’ve added searching for and renaming of GUIDs with the
And we can see some of the protocol usage starting to take shape:
I extracted a pretty large list of guids (470 of them) from the TianoCore source, which should be found and renamed in any data(ish) segment in the binary.