I presented my research into EFI rootkits at Ruxcon 2012 in Melbourne, Australia on Saturday. You can find the slides right here.
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.
rEFInd is a fork of the rEFIt EFI boot manager. I ran into some issues building rEFInd on Mac OS X with the EDKII using the Makefile(s) provided by the author, which are intended to be used for building on Linux. A number of
ld options used are incompatible with the Mac OS X toolchain. Rather than do the right thing and figure out what the appropriate options are, I’ve just dumped the rEFInd source into a new Package in the EDKII and added DSC and DEC files to get it to build on OS X.
The build information files can be found on github.
Prepare and build your EDKII environment (outside of the scope of this document).
Clone the repository into the root directory of the EDKII.
$ cd /path/to/edk2 $ git clone https://github.com/snarez/refind-edk2.git RefindPkg
Download the latest version of the rEFInd source into the RefindPkg directory and unpack it.
$ cd RefindPkg $ wget http://downloads.sourceforge.net/project/refind/0.4.5/refind-src-0.4.5.zip $ unzip refind-src-0.4.5.zip
Create a symlink so that the path referred to in the DSC file makes sense.
$ ln -s refind-0.4.5 refind
Build the package.
$ cd .. $ source edksetup.sh $ build -p RefindPkg/RefindPkg.dsc
This works for me on Mac OS X 10.7.4 with Xcode 4.4.1 and its command line tools. Drop me a line if you have any problems.
Hello internet dudes. I am privileged to be presenting my EFI rootkit research at Black Hat USA this year in scorching Las Vegas. If you’re going to be at the conference come along and check out my talk on Thursday July 26, and/or hit me up on twitter. I’ll be in town for DEF CON as well, of course.
I’ll be talking about some of the same stuff that I talked about at SyScan - how EFI can be used in a Mac OS X rootkit, how the kernel payload can work, etc - but I’ll also be talking about and demonstrating a pretty sweet new attack, so stay tuned. I’ll upload the slides for the presentation and the white paper as soon as I’ve finished presenting and have had 2 beers.
In my tinkering with EFI I attempted to flash some backdoored firmware to a test MacBook that was kindly donated to science by a friend of mine. This resulted in the bastard doing the S.O.S. beeps and not booting, and it didn’t seem to be recoverable using the Firmware Restore CDs from Apple. I decided that since it was dead anyway I might as well try and recover it by re-flashing the firmware manually using the nifty Bus Pirate that I impulse-bought not long ago, and a copy of flashrom.
First things first - an appropriate beer:
Next, I disassembled the MacBook with the help of the iFixit MacBook take apart guide (wasn’t exactly the right model, but close enough). Here’s the remains of the machine after I removed the logic board:
I had to hunt around on the board a bit to find the flash that contains the EFI firmware, but knowing the model number from when I bricked it helped. Found it!
Now that I’d found the flash I had to wire up the Bus Pirate and hope the chip would be programmable in-circuit without any hassles:
After a few false starts and some confusion with wiring between Bus Pirate versions,
flashrom detected the chip:
# flashrom -V -p buspirate_spi:dev=/dev/tty.usbserial-A800KC47 flashrom v0.9.5.2-r1515 on Darwin 11.3.0 (x86_64), built with libpci 3.1.7, LLVM Clang 3.1 (tags/Apple/clang-318.0.58), little endian flashrom is free software, get the source code at http://www.flashrom.org Calibrating delay loop... OS timer resolution is 1 usecs, 1619M loops per second, 10 myus = 10 us, 100 myus = 101 us, 1000 myus = 1015 us, 10000 myus = 9863 us, 4 myus = 3 us, OK. Initializing buspirate_spi programmer SPI speed is 8MHz Raw bitbang mode version 1 Raw SPI mode version 1 The following protocols are supported: SPI. Probing for AMIC A25L05PT, 64 kB: RDID byte 0 parity violation. probe_spi_rdid_generic: id1 0x00, id2 0x00 <snip> Probing for SST SST25VF016B, 2048 kB: probe_spi_rdid_generic: id1 0xbf, id2 0x2541 Chip status register is 1c Chip status register: Block Protect Write Disable (BPL) is not set Chip status register: Auto Address Increment Programming (AAI) is not set Chip status register: Bit 5 / Block Protect 3 (BP3) is not set Chip status register: Bit 4 / Block Protect 2 (BP2) is set Chip status register: Bit 3 / Block Protect 1 (BP1) is set Chip status register: Bit 2 / Block Protect 0 (BP0) is set Chip status register: Write Enable Latch (WEL) is not set Chip status register: Write In Progress (WIP/BUSY) is not set Resulting block protection : all Found SST flash chip "SST25VF016B" (2048 kB, SPI) on buspirate_spi. Probing for SST SST25VF032B, 4096 kB: probe_spi_rdid_generic: id1 0xbf, id2 0x2541 Found SST flash chip "SST25VF016B" (2048 kB, SPI). No operations were specified. Raw bitbang mode version 1 Bus Pirate shutdown completed.
Looking good! So next I read back the dodgy firmware to make sure it looked like everything was working OK:
# flashrom -V -p buspirate_spi:dev=/dev/tty.usbserial-A800KC47,spispeed=8M -r macbook_buspirate.rom <snip> Found SST flash chip "SST25VF016B" (2048 kB, SPI). Some block protection in effect, disabling Missing status register write definition, assuming EWSR is needed Reading flash... done. Raw bitbang mode version 1 Bus Pirate shutdown completed.
This took a good half hour plus, maybe 45 minutes. Apparently there are some recent Bus Pirate speedups for
flashrom but I didn’t wanna mess with it since it was working. A quick look at the firmware that was read back, and it looks OK compared to the original one that I read before flashing the dodgy one:
# hexdump macbook_buspirate.rom|head -5 0000000 00 00 00 00 00 00 00 00 bc e5 c8 87 00 00 00 00 0000010 d9 54 93 7a 68 04 4a 44 81 ce 0b f6 17 d8 90 df 0000020 00 00 19 00 00 00 00 00 5f 46 56 48 ff 8e ff ff 0000030 48 00 fd de 00 00 00 01 19 00 00 00 00 00 01 00 0000040 00 00 00 00 00 00 00 00 25 71 52 11 b2 78 3e 4d # hexdump dump_from_macbook.fd|head -5 0000000 00 00 00 00 00 00 00 00 bc e5 c8 87 00 00 00 00 0000010 d9 54 93 7a 68 04 4a 44 81 ce 0b f6 17 d8 90 df 0000020 00 00 19 00 00 00 00 00 5f 46 56 48 ff 8e ff ff 0000030 48 00 fd de 00 00 00 01 19 00 00 00 00 00 01 00 0000040 00 00 00 00 00 00 00 00 25 71 52 11 b2 78 3e 4d
Time to write the original firmware back to the flash chip:
# flashrom -V -p buspirate_spi:dev=/dev/tty.usbserial-A800KC47,spispeed=8M -w dump_from_macbook.fd flashrom v0.9.5.2-r1515 on Darwin 11.3.0 (x86_64), built with libpci 3.1.7, LLVM Clang 3.1 (tags/Apple/clang-318.0.58), little endian flashrom is free software, get the source code at http://www.flashrom.org Calibrating delay loop... OS timer resolution is 1 usecs, 1597M loops per second, 10 myus = 11 us, 100 myus = 100 us, 1000 myus = 1001 us, 10000 myus = 10081 us, 4 myus = 4 us, OK. Initializing buspirate_spi programmer SPI speed is 8MHz Raw bitbang mode version 1 Raw SPI mode version 1 The following protocols are supported: SPI. Probing for AMIC A25L05PT, 64 kB: RDID byte 0 parity violation. probe_spi_rdid_generic: id1 0x00, id2 0x00 <snip> Found SST flash chip "SST25VF016B" (2048 kB, SPI). Some block protection in effect, disabling Missing status register write definition, assuming EWSR is needed Reading old flash chip contents... done. Erasing and writing flash chip... Trying erase function 0... 0x000000-0x000fff:S, 0x001000-0x001fff:S, 0x002000-0x002fff:S, <snip> 0x1fe000-0x1fefff:S, 0x1ff000-0x1fffff:S Erase/write done. Verifying flash... VERIFIED. Raw bitbang mode version 1 Bus Pirate shutdown completed.
This took about 3 times as long as the read, as it had to read the flash back, erase the chip (which was pretty quick), write the new firmware, and then read the firmware back again to verify the write. After reassembling the machine:
It booted first go! I was honestly pretty surprised that I didn’t destroy something. Unfortunately the screen backlight is broken (which is why it was donated to science in the first place), so it’s a bit hard to see that it still works:
flashrom rules. Bus Pirate rules.