<--

Exploiting Qualcomm EDL Programmers (1): Gaining Access & PBL Internals

By Roee Hay (@roeehay) & Noam Hadad
January 22, 2018
* *

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:

  1. We describe the Qualcomm EDL (Firehose) and Sahara Protocols. (Part 1)
  2. We created firehorse, a publicly available research framework for Firehose-based programmers, capable of debugging/tracing the programmer (and the rest of the bootloader chain, including the Boot ROM itself, on some devices). (Part 3 & Part 4)
  3. We obtained and reverse-engineered the PBL of various Qualcomm-based chipsets (MSM8994/MSM8917/MSM8937/MSM8953/MSM8974) using the Firehose programmers and our research framework. (Part 3)
  4. We obtained the RPM & Modem PBLs of Nexus 6P (MSM8994). (Part 3)
  5. We managed to unlock & root various Android Bootloaders, such as Xiaomi Note 5A, using a storage-based attack only. (Part 2)
  6. We managed to manifest an end-to-end attack against our Nokia 6 device running Snapdragon 425 (MSM8937). We believe this attack is also applicable for Nokia 5, and might be even extensible to other devices, although unverified. (Part 5)

Qualcomm Secure Boot

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

Emergency Download Mode (EDL)

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)
 

Gaining EDL Access

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:

Test Points found in Xiaomi Note 5a

Similarly on Nokia 6: Test Points found in 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.

EDL cable

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

PBL Internals

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:

  • It sets VBAR to 0x100000
  • It resets the stacks to 0x205400.
  • It resets the MMU and some other system registers, in a function we named reset_MMU_and_other_stuff, located at 0x110004.
  • It maintains 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).

  • It iterates over a list of routines (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.

Loading of the SBL by the PBL & EDL Mode Arbitration

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;
}

Inside Qualcomm Firehose Programmers

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!

Exploring the Firehose Programmer Capabilities

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;
      }
[...]
}

Upcoming Next: Attacking the Firehose Programmer

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:

  1. Storage-based attacks (exploiting program/update & read). These are covered in Part 2.
  2. Memory-based attacks (exploiting peek and poke). These are covered in Part 3, Part 4 & Part 5.