POSTS
Undocumented Fastboot Oem Commands
Introduction
Printers are cool because they are, but I have friends that remind me at least weekly that hacking a printer is not useful. Every time it’s mostly the same thing all over again, find a 1990 type bug, write a stupid script, done. Some months ago, I started a new project, a wild ride inside Google’s humongous codebase. While most of Google code, especially Android, is open source, some parts are not. I’ll try to talk about one such case, the bootloader of a Pixel 3 phone.
A bit of context
What you are about to read mostly applies to Android 10 and later versions. Much has changed, but the exciting part for us is that now fastboot can run in userspace mode.
Here you can find the nitty-gritty details, the TL;DR is something like this:
- Recovery and Fastboot partitions are now the same thing. One less partition, more code reuse. Nice.
- The mechanisms to start Recovery or Fastboot is now slightly different, but this is taken care of by the bootloader.
- Userspace Fastboot can’t issue some types of commands, namely OEM.
Because Userspace fastboot can’t issue OEM commands, we will have to dig into the bootloader code.
Let’s start
Ok, we got a binary, let’s see what’s inside.
➜ strings -n 12 bootloader-sargo-b4s4-0.2-6066691.img | head
b4s4-0.2-6066691
partition table
A19F205F-CCD8-4B6D-8F1E-2D9BC24CFFB1
8a09a857-0ead-42a8-88f0-e3ead7f08788
DEA0BA2C-CBDD-4805-B4F9-F428251C3E98
332e0f87-b798-4b5f-a843-bce64d333a92
77036CD4-03D5-42BB-8ED1-37E5A88BAA34
b0a31f69-8cbe-4481-afa6-14e525b9aa08
xbl_config_a
5A325AE4-4276-B66D-0ADD-3494DF27706A
As soon as I saw that, I started wondering when phones started using UEFI. UEFI and EDK2 are cool, but when did they end up being used in phones?
Here there are some notes on the history of why QC uses UEFI. If I understand it correctly, it’s because they want to support Windows on ARM.
Now that we know it’s UEFI, and we satisfied our curiosity on why it’s UEFI, we can start working on it.
Getting the code - aka don’t binwalk uefi
Binwalk on the bootloader image will yield precisely zero. UEFI images are very structured, support compression, and allow for nested volumes. Ether, you know what you are looking at, or you are not going to get far.
Get uefi-firmware-parser from pip with
python -m pip install --user uefi-firmware-parser
uefi-firmware-parser it’s more of a framework and can be used in scripts. Fortunately, they provide a super nice and handy wrapper. The script can brute force -b
any unknown image type, searching for known UEFI magic numbers.
➜ uefi-firmware-parser -b bootloader-sargo-b4s4-0.2-6066691.img
Found volume magic at 0x885a8
Firmware Volume: 8c8ce578-8a3d-4f1c-9935-896185c32dd3 attr 0x000cfeff, rev 2, cksum 0xc520, size 0x270000 (2555904 bytes)
Firmware Volume Blocks: (4992, 0x200)
File 0: ffffffff-ffff-ffff-ffff-ffffffffffff type 0xf0, attr 0x00, state 0x07, size 0xfa0 (4000 bytes), (ffs padding)
File 1: 8af09f13-44c5-96ec-1437-dd899cb5ee5d type 0x03, attr 0x28, state 0x07, size 0x28018 (163864 bytes), (security core)
Section 0: type 0x19, size 0xf34 (3892 bytes) (Raw section)
Section 1: type 0x12, size 0x270cc (159948 bytes) (Terse executable (TE) section)
File 2: dde58710-41cd-4306-dbfb-3fa90bb1d2dd type 0x02, attr 0x00, state 0x07, size 0x261e (9758 bytes), (freeform)
Section 0: type 0x15, size 0x1e (30 bytes) (User interface name section)
Name: uefiplat.cfg
Section 1: type 0x19, size 0x25e6 (9702 bytes) (Raw section)
File 3: 9e21fd93-9c72-4c15-8c4b-e77f1db2d792 type 0x0b, attr 0x00, state 0x07, size 0x19938a (1676170 bytes), (firmware volume image)
Section 0: type 0x02, size 0x199372 (1676146 bytes) (Guid Defined section)
Guid-Defined: 1d301fe9-be79-4353-91c2-d23bc959ae0c offset= 0x18 attrs= 0x1 (PROCESSING_REQUIRED)
Found volume magic at 0x7125e8
Firmware Volume: 8c8ce578-8a3d-4f1c-9935-896185c32dd3 attr 0x0003feff, rev 2, cksum 0x1cd9, size 0xfd000 (1036288 bytes)
Firmware Volume Blocks: (2024, 0x200)
File 0: 9e21fd93-9c72-4c15-8c4b-e77f1db2d792 type 0x0b, attr 0x00, state 0x07, size 0x4019d (262557 bytes), (firmware volume image)
Section 0: type 0x02, size 0x40185 (262533 bytes) (Guid Defined section)
Guid-Defined: ee4e5898-3914-4259-9d6e-dc7bd79403cf offset= 0x18 attrs= 0x1 (PROCESSING_REQUIRED)
Section 0: type 0x19, size 0x4 (4 bytes) (Raw section)
Section 1: type 0x17, size 0x14ea04 (1370628 bytes) (Firmware volume image section)
Firmware Volume: 8c8ce578-8a3d-4f1c-9935-896185c32dd3 attr 0x0003feff, rev 2, cksum 0xb874, size 0x14ea00 (1370624 bytes)
Firmware Volume Blocks: (21416, 0x40)
File 0: ffffffff-ffff-ffff-ffff-ffffffffffff type 0xf0, attr 0x00, state 0x07, size 0x2c (44 bytes), (ffs padding)
File 1: 8d40eae1-aebd-4c76-8929-62d04d3e0882 type 0x09, attr 0x00, state 0x07, size 0x5e038 (385080 bytes), (application)
Section 0: type 0x15, size 0x1c (28 bytes) (User interface name section)
Name: LinuxLoader
Section 1: type 0x10, size 0x5e004 (385028 bytes) (PE32 image section)
File 2: ff725d98-21d5-4e79-80bc-82c71defec81 type 0x07, attr 0x00, state 0x07, size 0x13050 (77904 bytes), (driver)
Section 0: type 0x10, size 0x13004 (77828 bytes) (PE32 image section)
Section 1: type 0x15, size 0x34 (52 bytes) (User interface name section)
Name: FastbootTransportUsbDxe
File 3: 61f96920-c022-4b2b-aa80-1c364a4f64b1 type 0x09, attr 0x00, state 0x07, size 0x7e040 (516160 bytes), (application)
Section 0: type 0x15, size 0x24 (36 bytes) (User interface name section)
Name: B1c1FastbootApp
Section 1: type 0x10, size 0x7e004 (516100 bytes) (PE32 image section)
File 4: d9b2d43d-e02f-4a86-a445-bd1474b33671 type 0x02, attr 0x00, state 0x07, size 0x32be (12990 bytes), (freeform)
Section 0: type 0x15, size 0x2a (42 bytes) (User interface name section)
Name: RedWarning1440.bmp
Section 1: type 0x19, size 0x327a (12922 bytes) (Raw section)
Note that there are more files on the second volumes. I omitted them for conciseness. After extracting the files with the -e
option, we can start reversing. Nice.
(I later found out that Anestis Bechtsoudis had already written an image unpacker. I understand that this tool can unpack file 3 in volume 1, which is the main bootloader code. It’s nice, but it’s not necessary for the scope of this post.)
Reversing UEFI PE binaries
Super simple. Get Ghidra, load the PE, look at it very intensively, shout “yes” when you finally figured it out.
From above, we can see that there are two interesting files, FastbootTransportUsbDxe
and B1c1FastbootApp
:
- Fastboot DXE driver. At least a part of this driver is open source you can find on EDK2 GitHub repository. It’s useful to see the code, but not essential to understand what this module does.
- Fastboot application handler. Closed source and most likely implemented by google.
B1c1FastbootApp
handles the command coming fromFastbootTransportUsbDxe
.
The binary is stripped, but there are good strings to understand what’s happening.
2k strings. Nice. We want to understand how OEM commands work. I know that I get this error if I use a nonexisting OEM command:
➜ fastboot oem asd
FAILED (remote: 'Invalid oem command asd')
fastboot: error: Command failed
If we are right and this is the binary handling the fastboot, we should find that string. Lo and behold:
This string is used in a big function. We would expect some loop through a list of commands. From the screenshot, it’s evident that the while loop is performing some operation on 36 elements.
command_handler is initialized with a pointer to something in the bss. If we go there, at first it’s quite ugly:
But with some Ghidra magic (aka declaring a structure), we can make it ok:
By looking at the memory access, see picture below, it’s fairly simple to guess that the first two members are 8 bytes pointers. As you can see in the red box, a pointer is loaded into r23, and later another pointer is loaded in r23 with the value of the previous pointer minus 8 bytes (green box in the disassembly view). While Ghidra decompiler output might not be the greatest (it tries to display the operation as array access, but ends up using a negative index), the assembly is pretty straightforward.
This is an educated guess of the structure in C:
typedef struct fb_oem_handler_s{
char* command_name;
int64_t* command_handler
int32_t unk0
int32_t unk1
} fb_oem_handler_t
We found the OEM commands! After defining this structure, with a simple Ghidra script, it’s possible to get a nice list:
Successfully compiled: ListBootloaderCommands.java
ListBootloaderCommands.java> Running...
ListBootloaderCommands.java> 0 "setbrightness"
ListBootloaderCommands.java> 1 "get_config"
ListBootloaderCommands.java> 2 "set_config"
ListBootloaderCommands.java> 3 "rm_config"
ListBootloaderCommands.java> 4 "get_platform_info"
ListBootloaderCommands.java> 5 "set_platform_info"
ListBootloaderCommands.java> 6 "select-display-panel"
ListBootloaderCommands.java> 7 "esim_erase"
ListBootloaderCommands.java> 8 "esim_atp"
ListBootloaderCommands.java> 9 "esim_eid"
ListBootloaderCommands.java> 10 "psim_info"
ListBootloaderCommands.java> 11 "uart"
ListBootloaderCommands.java> 12 "off-mode-charge"
ListBootloaderCommands.java> 13 "sha1sum"
ListBootloaderCommands.java> 14 "ramdump"
ListBootloaderCommands.java> 15 "ramdump_sahara"
ListBootloaderCommands.java> 16 "rma"
ListBootloaderCommands.java> 17 "dump-chipid"
ListBootloaderCommands.java> 18 "check-hw-security"
ListBootloaderCommands.java> 19 "HALT"
ListBootloaderCommands.java> 20 "set_display_power_mode"
ListBootloaderCommands.java> 21 "citadel"
ListBootloaderCommands.java> 22 "enable-factory-lock"
ListBootloaderCommands.java> 23 "factory-lock"
ListBootloaderCommands.java> 24 "ddrtest"
ListBootloaderCommands.java> 25 "continue-factory"
ListBootloaderCommands.java> 26 "dmesg"
ListBootloaderCommands.java> 27 "pmic-dump"
ListBootloaderCommands.java> 28 "pmic-write"
ListBootloaderCommands.java> 29 "check-dp"
ListBootloaderCommands.java> 30 "allow-flash-super"
ListBootloaderCommands.java> 31 "silentota"
ListBootloaderCommands.java> 32 "barcode"
ListBootloaderCommands.java> 33 "display_color"
ListBootloaderCommands.java> 34 "enter-shipmode"
ListBootloaderCommands.java> 35 "verify-erased"
ListBootloaderCommands.java> Finished!
Conclusions
There is no documentation for the OEM commands, but it’s reasonably trivial to understand what parameters are required when thinking about the context where they are used. For example, pmic-dump
dumps the configuration of the Power Manager IC, barcode
shows every device identification number as a barcode on the screen. It’s easy to get confused at first, but it’s just codenames and domain-specific language.
Be aware that some commands rely on other UEFI drivers to work. UEFI drivers are loaded at a fixed address, so it’s not super tricky to map each driver handler in memory and see how it’s used.
I couldn’t find this information anywhere on the net, and I’ll have to admit it was pretty nice to dig into how android bootloaders operate.