There are many guides [1,2,3,4,5,6,7] across the Internet for ‘unbricking’ Qualcomm-based mobile devices. All of these guides make use of Emergency Download Mode (EDL), an alternate boot-mode of the Qualcomm Boot ROM (Primary Bootloader). To make any use of this mode, users must get hold of OEM-signed programmers, which seem to be publicly available for various such devices. While the reason of their public availability is unknown, our best guess is that these programmers are often leaked from OEM device repair labs. Some OEMs (e.g. Xiaomi) also publish them on their official forums.
A partial list of available programmers we managed to obtain is given below:
Device | SoC | Programmer | Tested | SHA256 |
---|---|---|---|---|
LG G4 | MSM8992 | prog_emmc_firehose_8992_lite.mbn | no | 73C51DE96B5F6F0EE44E40EEBC671322071BC00D705EEBDD7C60705A1AD11248 |
Nokia 6 (d1c) | MSM8937 | prog_emmc_firehose_8937_lite.mbn | yes | 74F3DE78AB5CD12EC2E77E35B8D96BD8597D6B00C2BA519C68BE72EA40E0EB79 |
Nokia 5 | MSM8937 | prog_emmc_firehose_8937_lite.mbn | no | D18EF172D0D45AACC294212A45FBA91D8A8431CC686B164C6F0E522D476735E9 |
Nexus 6 (shamu) | APQ8084 | programmer.mbn (non-Firehose) | yes | 9B3184613D694EA24D3BEEBA6944FDB64196FEA7056C833D38D2EF683FD96E9B |
Nexus 6P (angler) | MSM8994 | prog_emmc_firehose.mbn | yes | 30758B3E0D2E47B19EBCAC1F0A66B545960784AD6D428A2FE3C70E3934C29C7A |
Moto G4 Plus | MSM8952 | programmer.mbn (non-Firehose) | no | 8D417EF2B7F102A17C2715710ABD76B16CBCE8A8FCEB9E9803733E731030176B |
OnePlus 5 (cheeseburger) | MSM8998 | <embedded in an archive> | yes | 02FFDAA49CF25F7FF287CAB82DA0E4F943CABF6E6A4BFE31C3198D1C2CFA1185 |
OnePlus 3T | MSM8996 | prog_ufs_firehose_8996_lite.elf | yes | EEF93D29E4EDDA26CCE493B859E22161853439DE7B2151A47DAFE3068EE43ABE |
prog_ufs_firehose_8996_ddr.elf | yes | A1B7EB81C61525D6819916847E02E9AE5031BF163D246895780BD0E3F786C7EE | ||
OnePlus 3 | MSM8996 | prog_ufs_firehose_8996_lite.elf | yes | 97EFF4D4111DD90523F6182E05650298B7AE803F0EC36F69A643C031399D8D13 |
prog_ufs_firehose_8996_ddr.elf | yes | C34EC1FDDFAC05D8F63EED3EE90C8E6983FE2B0E4B2837B30D8619A29633649C | ||
OnePlus 2 | MSM8994 | prog_emmc_firehose_8994_lite.mbn | yes | 63A47E46A664CCD1244A36535D10CA0B97B50B510BD481252F786177197C3C44 |
OnePlus X | MSM8974 | prog_emmc_firehose_8974.mbn | yes | 964B5C486B200AA6462733A682F9CEAD3EBFAD555CE2FF3622FEA8B279B006EE |
OnePlus One | MSM8974 | prog_emmc_firehose_8974.mbn | no | 71C4F97535893BA7A3177320143AC94DB4C6584544C01B61860ACA80A477D4C9 |
ZTE Axon 7 | MSM8996 | prog_emmc_firehose_8996_ddr.elf | no | CB06DECBE7B1C47D10C97AE815D4FB2A06D62983738D383ED69B25630C394DED |
ZUK Z1 | MSM8974 | prog_emmc_firehose_8974.mbn | no | A27232BF1383BB765937AEA1EBDEE8079B8A453F3982B46F5E7096C373D18BB3 |
ZUK Z2 | MSM8996 | prog_emmc_firehose_8996_ddr.elf | no | 3FDAF99FC506A42FCBC649B7B46D9BB8DD32AEABA4B56C920B45E93A4A7080EA |
prog_emmc_firehose_8996_lite.elf | no | 48741756201674EB88C580DF1FDB06C7B823DC95B3FC89588A84A495E815FBD4 | ||
Xiaomi Note 5A (ugglite) | MSM8917 | prog_emmc_firehose_8917_ddr.mbn | yes | 8483423802d7f01bf1043365c855885b0eea193bf32ed25041a347bc80c32d6b |
Xiaomi Note 5 Prime (ugg) | MSM8937 | prog_emmc_firehose_8937_ddr.mbn | no | 5F1C47435A031331B7F6EC33E8F406EF42BAEF9A4E3C6D2F438A8B827DD00075 |
Xiaomi Note 4 (mido) | MSM8953 | prog_emmc_firehose_8953_ddr.mbn | yes | 5D45ECF8864DBBC741FB7874F878126E8F23EE9448A3EA1EDE8E16FE02F782C0 |
Xiaomi Note 3 (jason) | SDM660 | prog_emmc_firehose_Sdm660_ddr.elf | no | 1D4A7043A8A55A19F7E1C294D42872CD57A71B8F370E3D9551A796415E61B434 |
Xiaomi Note 2 (scorpion) | MSM8996 | prog_ufs_firehose_8996_ddr.mbn | no | BF4E25AE6108D6F6C8D9218383BD85273993262EC0EBA088F6C58A04FC02903B |
Xiaomi Mix (lithium) | MSM8996 | prog_ufs_firehose_8996_ddr.elf | no | 3DB3B7FD2664D98FD16F432E8D8AD821A85B85BD37701422F563079CB64D084C |
Xiaomi Mix 2 (chiron) | MSM8998 | prog_ufs_firehose_8998_ddr.elf | no | ADEB0034FC38C99C8401DCDBA9008EE5A8525BB66F1FC031EE8F4EFC22C5A1DF |
Xiaomi Mi 6 (sagit) | MSM8998 | prog_ufs_firehose_8998_ddr.elf | no | 67A7EA77C23FDD1046ECCE7628BFD5975E9949F66ADDD55BB3572CAF9FE97AEA |
Xiaomi Mi 5s (capicorn) | MSM8996 | prog_ufs_firehose_8996_ddr.elf | no | 2DDE12F09B1217DBBD53860DD9145326A394BF6942131E440C161D9A13DC43DD |
Xiaomi Mi 5s Plus (natrium) | MSM8996 | prog_ufs_firehose_8996_ddr.elf | no | 69A6E465C2F1E2CAABB370D398026441B29B45C975778E4682FC5E89283771BD |
Xiaomi Mi 5x (tiffany) | MSM8953 | prog_emmc_firehose_8953_ddr.mbn | no | 61135CB65671284290A99BD9EDF5C075672E7FEBA2A4A79BA9CFACD70CD2EA50 |
Xiaomi Mi 5 (gemini) | MSM8996 | prog_ufs_firehose_8996_ddr.elf | no | C215AC92B799D755AF0466E14C7F4E4DC53B590F5FBC0D4633AFAFE5CECC41C3 |
Xiaomi Mi 3 (cancro) | MSM8974 | prog_emmc_firehose_8974.mbn | no | 964B5C486B200AA6462733A682F9CEAD3EBFAD555CE2FF3622FEA8B279B006EE |
Xiaomi Mi A1 (tissot) | MSM8953 | prog_emmc_firehose_8953_ddr.mbn | no | A38C6F01272814E0A47E556B4AD17F999769A0FEE6D3C98343B7DE6DE741E79C |
Xiaomi Mi Max2 (oxygen) | MSM8953 | prog_emmc_firehose_8953_ddr.mbn | no | BB5E36491053118486EBCCD5817C5519A53EAE5EDA9730F1127C22DD6C1B5C2B |
Xiaomi Redmi Note 3 (kenzo) | MSM8976 | prog_emmc_firehose_8976_ddr.mbn | no | 5C9CCCF88B6AB026D8165378D6ADA00275A606B8C4AD724FBCA33E8224695207 |
Xiaomi Redmi 5A (riva) | MSM8917 | prog_emmc_firehose_8917_ddr.mbn | no | 67D32C753DDB67982E9AEF0C13D49B33DF1B95CC7997A548D23A49C1DD030194 |
Xiaomi Redmi 4A (rolex) | MSM8917 | prog_emmc_firehose_8917_ddr.mbn | no | 7F6CE28D52815A4FAC276F62B99B5ABEB3F73C495F9474EB55204B3B4E6FCE6D |
In this 5-part blog post we discuss the security implications of the leaked programmers. The first part presents some internals of the PBL, EDL, Qualcomm Sahara and programmers, focusing on Firehose. In Part 2, we discuss storage-based attacks exploiting a functionality of EDL programmers – we will see a few concrete examples such as unlocking the Xiaomi Note 5A (codename ugglite
) bootloader in order to install and load a malicious boot image thus breaking the chain-of-trust. Part 3, Part 4 & Part 5 are dedicated for the main focus of our research – memory based attacks. In Part 3 we exploit a hidden functionality of Firehose programmers in order to execute code with highest privileges (EL3
) in some devices, allowing us, for example, to dump the Boot ROM (PBL) of various SoCs. We then present our exploit framework, firehorse, which implements a runtime debugger for firehose programmers (Part 4). We end with a complete Secure-Boot bypass attack for Nokia 6 MSM8937
, that uses our exploit framework. We achieve code execution in the PBL (or more accurately, in a PBL clone), allowing us to defeat the chain of trust, gaining code execution in every part of the bootloader chain, including TrustZone, and the High Level OS (Android) itself.
The merit of our research is as follows:
MSM8994
/MSM8917
/MSM8937
/MSM8953
/MSM8974
) using the Firehose programmers and our research framework. (Part 3)MSM8994
). (Part 3)MSM8937
). We believe this attack is also applicable for Nokia 5, and might be even extensible to other devices, although unverified. (Part 5)An abstract overview of the boot process of Qualcomm MSM devices is as follows:
[Primary Bootloader (PBL)]
|
`---NORMAL BOOT---.
[Secondary Bootloader (SBL)]
|-.
| [Android Bootloader (ABOOT)]
| `-.
| [boot.img]
| |-- Linux Kernel
| `-- initramfs
| `-.
| [system.img]
|
`-[TrustZone]
The PBL
kicks-in from ROM after the device is powered-on. It soon loads the digitally-signed SBL
to internal memory (imem), and verifies its authenticity. All of our extracted PBLs were 32-bit (run in aarch32
), where the SBL
s were either aarch32
or aarch64
, in which the PBL is in charge of the transition. Some devices have an XBL
(eXtensible Bootloader) instead of an SBL
. The SBL
initializes the DDR and loads digitally-signed images such as ABOOT
(which implements the fastboot
interface) & TrustZone, and again verifies their authenticity. The signed certificates have a root certificate anchored in hardware.
ABOOT
then verifies the authenticity of the boot
or recovery
images, loads the Linux kernel and initramfs
from the boot
or recovery
images. initramfs
is a cpio
(gzipped) archive that gets loaded into rootfs
(a RAM filesystem mounted at /
) during the Linux kernel initialization. It contains the init
binary, the first userspace process. ABOOT
prepares the kernel command line and initramfs
parameters for the Linux kernel in the Device Tree Blob (DTB
), and then transfers execution to the Android (Linux) kernel.
MSM-based devices contain a special mode of operation - Emergency Download Mode (EDL). In this mode, the device identifies itself as Qualcomm HS-USB 9008
through USB. EDL is implemented by the PBL. Since the PBL is a ROM resident, EDL cannot be corrupted by software. The EDL mode itself implements the Qualcomm Sahara protocol, which accepts an OEM-digitally-signed programmer (an ELF
binary in recent devices, MBN
in older ones) over USB, that acts as an SBL. Modern such programmers implement the Firehose
protocol, analyzed next.
[Primary Bootloader (PBL)]
|
`---EDL---.
[Programmer (Firehose)]
`- Commands (through USB)
There are several ways to coerce that device into EDL.
Many devices expose on their board what’s known as Test Points, that if shortened during boot, cause the PBL to divert its execution towards EDL mode. (Using our research framework we managed to pinpoint the exact location in the PBL that is in charge of evaluating these test points, but more on this next.)
For example, here are the Test Points on our Xiaomi Note 5A board:
Similarly on Nokia 6:
In addition, if the PBL fails to verify the SBL, or fails to initialize the flash, it will fall-back into EDL, and again, by using our research tool we found the relevant code part in the PBL that implements this. Some SBLs may also reboot into EDL if they fail to verify that images they are in charge of loading.
We also encountered SBLs that test the USB D+/GND pins upon boot (e.g. Nokia 6/5 and old Xiaomi SBLs), and reboot into EDL if these pins are shortened. This is known as the EDL or ‘Deep Flashing’ USB cable. Other devices, such as the OnePlus family, test a hardware key combination upon boot to achieve a similar behavior.
Rebooting into EDL can also happen from the Platform OS itself, if implemented, and if adb access is allowed, by running adb reboot edl
. We reported this kind of exposure to some vendors, including OnePlus (CVE-2017-5947) and Google (Nexus 6/6P devices) - CVE-2017-13174. Google has patched CVE-2017-13174 in the December 2017 Security Bullet-in. (Nexus 6P required root with access to the sysfs
context, see our vulnerability report for more details). Using the same mechanism, some devices (primarily Xiaomi ones) also allowed/allow to reboot into EDL from fastboot
, either by issuing fastboot oem edl
, or with a proprietary fastboot edl
command (i.e with no oem
). Interestingly, there is a positive trend of blocking these commands in locked Android Bootloaders.
Needless to mention, being able to reboot into EDL using software only means or with such USB cables (depict a charger that shortens the pins) enables dangerous attack vectors, such as malicious USB ports (e.g. chargers).
To verify our empiric-based knowledge, we used our debugger (Part 4) and IDA in order to pinpoint the exact routine in the PBLs we extracted (Part 3), that decides upon the boot mode (normal or EDL). Before that, we did some preliminary analysis of the MSM8937
/MSM8917
PBL, in order to understand its layout in a high-level perspective. We believe other PBLs are not that different.
The reset handler (address 0x100094
) of the PBL roughly looks as follows (some pseudo-code was omitted for readability)
int init()
{
int (__fastcall *v5)(pbl_struct *); // r1
__mcr(15, 0, 0x100000u, 12, 0, 0);
if ( !(MEMORY[0x1940000] & 1) )
{
if ( !reset_MMU_and_other_stuff() )
infinite_loop();
memzero_some_address();
timer_memory_stuff();
init_pbl_struct();
v4 = 0;
while ( 1 )
{
v5 = *(&initVector + v4);
if ( v5 )
v3 = v5(&pbl);
if ( v3 )
pbl_error_handler("./apps/pbl_mc.c", 516, 66304, v3);
if ( ++v4 >= 0x14 )
{
while (1);
}
}
}
}
The init
function is in charge of the following:
VBAR
to 0x100000
0x205400
.reset_MMU_and_other_stuff
, located at 0x110004
.pbl_struct
, that saves PBL contextual data, and lives throughout the lifetime of the PBL. Parts of it are later passed to the SBL.This struct contains the following fields: (The shown symbols are of course our own estimates.)
00000000 pbl_struct struc ; (sizeof=0xA4, mappedto_26)
00000000
00000000 hwinitfunctioninput DCD ?
00000004 bootmode DCD ?
00000008 field_8 DCD ?
0000000C flashReadDstStart DCD ?
00000010 flashReadDstEnd DCD ?
00000014 flashReadSize DCD ?
00000018 callback18 DCD ?
0000001C field_1C DCD ?
00000020 field_20 DCD ?
00000024 sbl_entry DCD ?
00000028 flash_struct DCD ?
0000002C pbl2sbl_data DCD ?
00000030 bootmode_reason DCD ?
00000034 errorSet DCD ?
00000038 pt_initialized DCD ?
0000003C field_3C DCD ?
00000040 field_40 DCD ?
00000044 field_44 DCD ?
00000048 callback48 DCD ?
0000004C callback4C_innerauth DCD ?
00000050 callback50 DCD ?
00000054 field_54 DCD ?
00000058 timer0 DCD ?
0000005C timer5c DCD ?
00000060 flash_init_timer DCD ?
00000064 field_64 DCD ?
00000068 field_68 DCD ?
0000006C field_6C DCD ?
00000070 timer70 DCD ?
[...]
000000A4 pbl_struct ends
Some fields worth noting include sbl_entry
which is later set to the SBL’s entry point, and pbl2sbl_data
which contains parameters passed to the soon-to-be-jumped-to SBL (see next).
init_vector
), located at 0x10CE0C
.ROM:0010CE0C init_vector
ROM:0010CE0C
ROM:0010CE0C
ROM:0010CE10 DCD 0
ROM:0010CE14 DCD pbl_hw_init
ROM:0010CE18 DCD 0
ROM:0010CE1C DCD pbl_initialize_pagetables
ROM:0010CE20 DCD 0
ROM:0010CE24 DCD pbl_stack_init
ROM:0010CE28 DCD 0
ROM:0010CE2C DCD pbl_copy_some_data
ROM:0010CE30 DCD 0
ROM:0010CE34 DCD pbl_sense_jtag_test_points_edl
ROM:0010CE38 DCD 0
ROM:0010CE3C DCD pbl_flash_init
ROM:0010CE40 DCD 0
ROM:0010CE44 DCD pbl_load_elf_sahara_stuff
ROM:0010CE48 DCD 0
ROM:0010CE4C DCD pbl_mc_init5
ROM:0010CE50 DCD 0
ROM:0010CE54 DCD pbl_jmp_to_sbl
ROM:0010CE58 DCD 0
Each of these routines plays an important role in the operation of the PBL. Some of them will get our coverage throughout this series of blog posts.
The routine that probes whether or not to go into EDL is pbl_sense_jtag_test_points_edl
:
int __fastcall pbl_sense_jtag_test_points_edl(pbl_struct *pbl)
{
[...]
pbl->bootmode_reason = bootmode_reason_0;
if ( !(MEMORY[0xA601C] & 8) ) // jtag fuse
{
if ( MEMORY[0xA606C] & 0x8000 ) // check test points
{
pbl->bootmode = edl;
pbl->bootmode_reason = tp;
return 0;
}
v4 = MEMORY[0x193D100];
v5 = MEMORY[0x193D100] & 0xF;
switch ( v5 )
{
case 1:
v6 = bootmode_reason_2;
pbl->bootmode = edl;
break;
case 2:
pbl->bootmode = bootmode_80;
pbl->bootmode_reason = bootmode_reason_3;
goto LABEL_17;
case 3:
pbl->bootmode = bootmode_81;
v6 = bootmode_reason_4;
break;
default:
goto LABEL_17;
}
pbl->bootmode_reason = v6;
LABEL_17:
MEMORY[0x193D100] = v4 & 0xFFF0;
if ( pbl->bootmode_reason )
return 0;
}
v2 = (MEMORY[0xA602C] >> 1) & 7;
if ( v2 >= 8 )
pbl_error_handler("./apps/pbl_hw_init.c", 227, 262656, ((MEMORY[0xA602C] >> 1) & 7));
pbl->bootmode = v2;
return 0;
}
By tracing through this code, we concluded that address 0xA606C
contains the test points status (0x8000
<=> ‘shortened’). In addition, rebooting into EDL by software is done by asserting the LSB of the 0x193D100
register (also known as tcsr-boot-misc-detect
) The routine sets the bootmode
field in the PBL context. Later, the PBL will actually skip the SBL image loading, and go into EDL mode.
First, the PBL will mark the flash as uninitialized, by setting pbl->flash_struct->initialized = 0xA
. This is done inside some_sahara_stuff
which gets called if either pbl->bootmode
is edl
, or the flash initialization has failed:
int __fastcall pbl_flash_init(pbl_struct *pbl)
{
[...]
v3 = pbl->bootmode;
if ( v3 == edl )
{
if ( some_sahara_stuff(v1) )
pbl_error_handler("./apps/pbl_flash.c", 134, 66048, v2);
if ( !flashStructLocation )
pbl_error_handler("./apps/pbl_flash.c", 138, 66304, 0);
return 0;
}
[...]
v2 = pbl_flash_sdcc(pbl);
if ( v2 != 3 && some_sahara_stuff(pbl) )
pbl_error_handler("./apps/pbl_flash.c", 134, 66048, v2);
if ( !flashStructLocation )
pbl_error_handler("./apps/pbl_flash.c", 138, 66304, 0);
[...]
return 0;
}
int __fastcall some_sahara_stuff(pbl_struct *pbl)
{
[...]
if ( !pbl )
pbl_error_handler("./apps/pbl_sahara.c", 911, 819200, 0);
if ( !a->flash )
pbl_error_handler("./apps/pbl_sahara.c", 915, 819200, 0);
[...]
pbl->flash->initialized = 0xA;
return 0;
}
Later, when the PBL actually tries to load the SBL from the flash drive, it will consider the pbl->flash->initialized
field and use the Sahara protocol instead:
int __fastcall pbl_load_elf_sahara_stuff(pbl_struct *pbl)
{
pbl_struct *v1; // r4
int flashInitstatus; // r6
if ( !pbl )
pbl_error_handler("./apps/pbl_mc.c", 677, 98304, 0);
if ( !pbl->flash )
pbl_error_handler("./apps/pbl_mc.c", 681, 98304, 0);
flashInitstatus = pbl->flash->initialized;
v4 = flashInitstatus == 4;
if ( flashInitstatus != 4 )
v4 = flashInitstatus == 5;
if ( v4 )
{
v6 = pbl_elf_loader(&pbl->field_44);
pbl->sbl = *(pbl->field_54 + 0x18);
return v6;
}
if ( flashInitstatus != 10 )
pbl_error_handler("./apps/pbl_mc.c", 710, 66304, 0);
v6 = pbl_sahara(pbl);
if ( !v6 )
{
pbl->sbl = *(*(*(pbl->field_40 + 8) + 8) + 68);
}
return v6;
}
The PBL later jumps to the SBL entry point, with the aforementioned pbl2sbl_data
:
unsigned int __fastcall pbl_jmp_to_sbl(pbl_struct *pbl)
{
pbl_struct *v1; // r4
signed int v2; // r0
v1 = pbl;
if ( !pbl->sbl )
pbl_error_handler("./apps/pbl_mc.c", 420, 98304, 0);
pbl->pbl2sbl_data->timer = get_timer();
sub_10F364();
set_vbar(infinite_loop);
v2 = initialize_secondary_pt_for_sbl(pbl);
if ( v2 )
pbl_error_handler("./apps/pbl_mc.c", 100, v2 | 0x20000, 0);
(pbl->sbl)(pbl->pbl2sbl_data);
return -1;
}
As mentioned above, modern EDL programmers implement the Qualcomm Firehose protocol. Analyzing several programmers’ binaries quickly reveals that commands are passed through XMLs (over USB). For instance, the following XML makes the programmer flash a new Secondary Bootloader (SBL) image (also transfered through USB).
<?xml version="1.0" ?>
<data>
<program SECTOR_SIZE_IN_BYTES="512" file_sector_offset="0" filename="sbl1.bin"
label="sbl1" num_partition_sectors="1024" physical_partition_number="0"
size_in_KB="512.0" sparse="false"
start_byte_hex="0xc082000" start_sector="394256"/>
</data>
As one can see, the relevant tag that instructs the programmer to flash a new image is program
.
Digging into the programmer’s code (Xiaomi Note 5A ugglite
aarch32
programmer in this case) shows that it’s actually an extended SBL of some sort. Its main routine is as follows:
int __cdecl SBLMain(pbl2sbl_struct *pbl2sbl_data)
{
sbl_struct *vSbl; // r6
[...]
SBLStart(&off_805C070, vSbl);
sub_801D0B0(off_805C078);
sub_8040A2C(vSbl);
initSBLStruct(&sblStruct, &unk_80678A0);
sub_80408F4(&sblStruct);
[...]
return callImageLoad(&sblStruct, 0x15, imageLoad);
}
pbl2sbl_data
is the data passed from the PBL to the SBL at the very end of the pbl_jmp_to_sbl
function. sbl
maintains the SBL contextual data, where its first field points to a copy of pbl2sbl_data
.
ImageLoad
is the function that is in charge of loading the next bootloaders, including ABOOT:
int __fastcall ImageLoad(sbl_struct *sbl, image_load_struct *aImageLoad)
{
[...]
loop_callbacks(sbl, aImageLoad->callbacks);
[...]
if ( imageLoad->field_14 == 1 )
{
v5 = sub_801CCDC();
uartB("Image Load, Start");
v8 = imageLoad->field_C;
if ( v8 == 1 )
{
if ( !boot_pbl_is_flash() )
{
[...]
ERROR("sbl1_sahara.c", 816, 0x1000064);
while ( 1 )
;
}
[...]
boot_elf_loader(v9, &v27);
[...]
loop_callbacks(sbl, imageLoad->callbacks2);
[...]
return result;
}
ImageLoad
starts by calling (using the loop_callbacks
routine) a series of initialization functions:
LOAD:0805C0C8 callbacks DCD nullsub_35+1
LOAD:0805C0CC DCD boot_flash_init+1
LOAD:0805C0D0 DCD sub_801ACB0+1
LOAD:0805C0D4 DCD sub_804031C+1
LOAD:0805C0D8 DCD sub_803FF08+1
LOAD:0805C0DC DCD sub_803FCD0+1
LOAD:0805C0E0 DCD firehose_main+1
LOAD:0805C0E4 DCD sub_8040954+1
LOAD:0805C0E8 DCD clock_init_start+1
LOAD:0805C0EC DCD sub_801B1AC+1
LOAD:0805C0F0 DCD boot_dload+1
firehose_main
eventually falls into the main firehose loop, and never returns. Interestingly, in the actual SBL of ugglite
, this series of initialization callbacks looks as follows:
LOAD:0805B0C8 callbacks DCD nullsub_36+1
LOAD:0805B0CC DCD boot_flash_init+1
LOAD:0805B0D0 DCD sub_8018E8C+1
LOAD:0805B0D4 DCD sub_8039C80+1
LOAD:0805B0D8 DCD sub_803986C+1
LOAD:0805B0DC DCD sub_8039634+1
LOAD:0805B0E0 DCD nullsub_37+1
LOAD:0805B0E4 DCD sub_803A2B8+1
LOAD:0805B0E8 DCD clock_init_start+1
LOAD:0805B0EC DCD sub_8019388+1
LOAD:0805B0F0 DCD boot_dload+1
Therefore, they only differ in the firehose_main
callback!
Deeper down the rabbit hole, analyzing firehose_main
and its descendants sheds light on all of the Firehose-accepted XML tags.
void __fastcall firehose_main(sbl_struct *sbl)
{
firehoseInit();
while ( 1 )
{
handle_input();
OUT("logbuf@0x%08X fh@0x%08X");
}
}
int handle_input()
{
[...]
do
{
[...]
result = sub_802C764(&v3, SHIDWORD(v0), v0, SHIDWORD(v0), &v4);
}
while ( !v4 );
if ( !result )
{
v2 = sub_804A55E(&unk_806DCC0, v3);
if ( sub_8049CEC(v2) == 1 )
{
OUT("XML (%d bytes) not validated");
[...]
}
else if ( v4 <= 0x1000 )
{
[...]
do
result = parse_command();
while ( result != 1 );
}
else
{
OUT("XML file cannot fit in buffer");
result = sub_804A55E(&unk_806DCC0, 0);
}
}
return result;
}
int firehose_parse_command()
{
[...]
v1 = get_next(&xmlCommand);
memzero(&v4, 0x200u);
switch ( v1 )
{
case 0:
v2 = 1;
goto LABEL_46;
case 4:
sub_8026D40(&xmlCommand, &v4, 0x200uLL);
if ( get_element_name(&xmlCommand, "data") )
{
v2 = 1;
goto LABEL_46;
}
break;
case 1:
if ( get_element_name(&xmlCommand, "configure") )
{
v2 = sub_80157B4();
goto LABEL_46;
}
if ( get_element_name(&xmlCommand, "program") )
{
v2 = fh_program();
goto LABEL_46;
}
[...]
if ( get_element_name(&xmlCommand, "read") )
{
v2 = fh_read();
goto LABEL_46;
}
if ( get_element_name(&xmlCommand, "getstorageinfo") )
{
v2 = fh_getstorageinfo();
goto LABEL_46;
}
if ( get_element_name(&xmlCommand, "erase") )
{
v2 = fh_erase();
goto LABEL_46;
}
[...]
if ( get_element_name(&xmlCommand, "peek") )
{
v2 = fh_peek();
goto LABEL_46;
}
if ( get_element_name(&xmlCommand, "poke") )
{
v2 = fh_poke();
goto LABEL_46;
}
[...]
}
Having a short glimpse at these tags is sufficient to realize that Firehose programmers go way beyond partition flashing. Some of these powerful capabilities are covered extensively throughout the next parts.
Concretely, in the next chapters we will use and continue the research presented here, to develop: