This blog post was originally published on April 8, 2013 on the now-defunct Azimuth Security blog.
I recently spent some time dissecting the bootloader used on Motorola’s latest Android devices, the Atrix HD, Razr HD, and Razr M. The consumer editions of these devices ship with a locked bootloader, which prevents booting kernel and system images not signed by Motorola or a carrier. In this blog post, I will present my findings, which include details of how to exploit a vulnerability in the Motorola TrustZone kernel to permanently unlock the bootloaders on these phones.
These three devices are the first Motorola Android phones to utilize the Qualcomm MSM8960 chipset, a break from a long tradition of OMAP-based Motorola devices. Additionally, these three devices were released in both “consumer” and “developer” editions. The developer editions of these models support bootloader unlocking, allowing the user to voluntarily void the manufacturer warranty to allow installation of custom kernels and system images not signed by authorized parties. However, the consumer editions ship with a locked bootloader, preventing these types of modifications.
From the perspective of the user, unlocking the bootloader on a developer edition device is fairly straightforward. The user must boot into “bootloader mode” using a hardware key combination (usually Vol Up + Vol Down + Power) at boot. Next, the standard “fastboot” utility can be used to issue the following command:
fastboot oem get_unlock_data
In response to this command, an ASCII blob will be returned to the user. The user must then submit this blob to the Motorola bootloader unlock website. If the user’s device is supported by the bootloader unlocking program (i.e. if it’s a developer edition device), the website will provide a 20-character “unlock token”.
To complete the process, the user issues a final fastboot command:
fastboot oem unlock [token]
At this point, the bootloader unlocks, and the user may use fastboot to flash custom kernel and system images that have not been signed by Motorola or the carrier.
Much of Qualcomm’s security architecture is implemented using QFuses, which are software-programmable fuses that allow one-time configuration of device settings and cryptographic materials such as hashes or keys. Because of their physical nature, once a QFuse has been blown, it is impossible to “unblow” it to revert its original value.
If the FORCE_TRUSTED_BOOT QFuse is blown, as is the case
on all production Motorola devices, each stage of the boot chain is
cryptographically verified to ensure only authorized bootloader stages
may be run. In particular, the PBL (“Primary Bootloader”), which resides
in mask ROM, verifies the integrity of the SBL1 (“Secondary Bootloader”)
via a SHA1 hash. Each stage of the boot chain verifies the next stage
using RSA signatures, until finally Motorola’s APPSBL (“Application
Secondary Bootloader”), “MBM”, is loaded and run.
The entirety of the Android OS signature verification and bootloader unlocking process is implemented in MBM. To study the implementation, I examined the “emmc_appsboot.mbn” binary included in a leaked SBF update package for the Motorola Atrix HD. It’s also possible to pull this partition directly from a device via the /dev/block/mmcblk0p5 block device.
To start, because the binary blob is an undocumented format, I assisted IDA Pro in identifying entry points for disassembly. Next, I searched for cross-references to strings referring to unlocking the bootloader. After getting my bearings, I identified the function responsible for handling the “fastboot oem unlock” command. The reverse engineered pseudocode looks something like this:
int handle_fboot_oem_unlock(char *cmd)
{
char *token;
if ( is_unlocking_allowed() != 0xff )
{
print_console("INFO", "fastboot oem unlock disabled!");
return 3;
}
if ( is_device_locked() != 0xff )
{
print_console("INFO", "Device already unlocked!");
return 3;
}
token = cmd + 12; /* offset of token in "oem unlock [token]" */
if ( strnlen(token, 21) - 1 > 19)
{
print_console("INFO", "fastboot oem unlock [ unlock code ]");
return 0;
}
if ( !validate_token_and_unlock(token) )
{
print_console("INFO", "OEM unlock failure!");
return 3;
}
return 0;
}
Of particular note are the is_unlocking_allowed() and
is_device_locked() functions. Further reversing revealed
that these functions query values stored in particular QFuses by
accessing the QFPROM region, which represents the contents of the
QFuses, memory-mapped at physical address 0x700000. In
particular, these two functions invoke another function I called
get_mot_qfuse_value(), which queries the value stored in a
specific QFuse register.
Having reversed this behavior, the implementation of
is_unlocking_allowed() is simple: it returns the “success”
value (0xff) if get_mot_qfuse_value(0x85)
returns zero, indicating the value in the QFuse Motorola identifies with
0x85 is zero (this register happens to be mapped to
physical address 0x700439). In other words, by blowing this
particular QFuse, unlocking may be permanently disabled on these
devices. Fortunately, this has not been performed on any of the consumer
editions of these devices.
The logic behind is_device_locked() is a bit more
complex. It invokes a function I called get_lock_status(),
which queries a series of QFuse values to determine the status of the
device. Among others, it checks the QFuse values for identifiers
0x85 (“is unlocking disabled?”) and 0x7b (“is
the Production QFuse blown?”). If the Production bit isn’t set,
get_lock_status() returns a value indicating an unlocked
bootloader, but this QFuse has been blown on all released models.
Otherwise, if unlocking hasn’t been permanently disabled, the result is
based on two additional conditions.
If the QFuse with identifier 0x84, which is mapped to
physical address 0x700438, hasn’t been blown, the status is
returned as “locked”. It turns out this is the QFuse we’re looking for,
since blowing it will result in unlocking the bootloader! If this QFuse
has been blown, there is one final condition that must be satisfied
before get_lock_status() will return an “unlocked” status:
there must not be a signed token in the SP partition of the phone.
Further investigation revealed that this token is only added when the
user re-locks their bootloader using “fastboot oem lock”, so it does not
pose any obstacle when trying to unlock the bootloader.
If the bootloader has not already been unlocked, MBM will attempt to validate the token provided by the user. More reversing revealed that the following logic is used:
The CID partition is read from the device.
A digital signature on the CID partition is verified using a certificate stored in the CID partition.
The authenticity of the certificate is verified by validating a trust chain rooted in cryptographic values stored in blown QFuses.
The user-provided token is hashed together with a key blown into the QFuses using a variant of SHA-1.
This hash is compared against a hash in the CID partition, and if it matches, success is returned.
As a result, there is no way for a user to generate his or her own valid unlock token without either breaking RSA to violate the integrity of the CID partition, or by performing a pre-image attack against SHA-1, both of which are computationally infeasible in a reasonable amount of time.
Edit: The original post claimed the algorithm used was MD4. I mistakenly identified the algorithm because MD4 and SHA-1 evidently share some of the same constant values used during initialization. Thanks to Tom Ritter, Melissa Elliott, and Matthew Green for discussing this and inspiring me to take another look.
Having run into this dead-end, I examined what actually takes place
when a successful unlock token is provided. We already know that MBM
must somehow blow the QFuse with Motorola identifier 0x84
in order to mark the bootloader as “unlocked”.
It accomplishes this by calling a function that invokes an ARM
assembly instruction that may be unfamiliar to some: SMC
(Secure Monitor Call). This instruction is used to make a call to the
ARM TrustZone kernel running on the device.
TrustZone is an approach to security integrated into many modern ARM processors. TrustZone operates in what’s known in ARM parlance as the “Secure world”, a trusted execution mode whose security is enforced by the processor itself. Among other tasks, TrustZone may designate “Secure memory”, which cannot be read from the “Non-secure world”, regardless of privilege level. Even if code is running in SVC (kernel) mode, attempts to access a Secure memory region from a Non-secure execution context will cause the CPU to abort.
The Secure world stack is often implemented as a small trusted kernel. On these particular Motorola devices, the TrustZone kernel resides on the TZ partition of the device and is loaded at early boot, prior to MBM. The Non-secure world may issue requests to the Secure world using the privileged SMC instruction.
In this case, it became clear that MBM issues a specific SMC call to request that the TrustZone kernel blow the appropriate QFuse to unlock the bootloader on the device.
For a first naive attempt at unlocking the bootloader, I decided to
deconstruct the syntax of the SMC call made by MBM and make
an identical call from kernel mode in the Android OS.
Fortunately, Qualcomm-based Linux kernel trees have source code that
reveals the syntax of these SMC calls. Looking at
arch/arm/mach-msm/scm.c reveals that calls to Qualcomm-based TrustZone
kernels are expected to pass arguments using the following data
structure:
struct scm_command {
u32 len;
u32 buf_offset;
u32 resp_hdr_offset;
u32 id;
u32 buf[0];
};
Understanding this data structure clarified the SMC
calling convention I saw in MBM. In particular, when issuing the call to
unlock the bootloader, MBM sets an id of
0x3f801 and provides a buf array of four
words, containing the values 0x2, 0x4,
0x1, and 0x0, in that order. Based on looking
at additional MBM code, it appears that the 0x4 represents
a word offset within the QFuse bank beginning at 0x700428,
and 0x1 represents a bitmask indicating which bits within
that word should be blown. This makes sense, since this call would
result in blowing the QFuse at physical address 0x700438,
which we already determined would unlock the bootloader.
With this knowledge in hand, I threw together a kernel module that
would issue an identical SMC call to hopefully unlock the
bootloader. I loaded this module into the Android OS’s Linux kernel
and…it returned an error code of -1001.
To see what was going on here, I knew I would need to reverse
engineer portions of the TrustZone kernel running on the device. After
loading the tz.mbn binary blob contained in an SBF update package into
IDA Pro, rather than reversing the entire kernel I made a quick search
for the immediate value 0xfffffc17 (-1001) to see if I
could skip straight to the code I was interested in. Sure enough, there
was only one function containing this value.
Taking a look at this function revealed the following pseudocode:
int handle_smc(int code, int arg1, int arg2, int arg3)
{
int ret;
switch (code) {
...
case 2:
if (global_flag) {
ret = -1001;
}
else {
/* Perform unlock */
...
ret = 0;
}
break;
case 3:
global_flag = 1;
ret = 0;
break;
...
}
return ret;
}
Based on this code, it appears that the first word in the
SMC buffer represents a command code (in our case it’s
0x2). The reason TrustZone is returning an error on our
SMC call from the Android kernel is a particular one-way
global flag residing in TrustZone secure memory has apparently been set
by MBM before booting the Android OS, preventing all subsequent calls to
blow QFuses. Going back to the MBM code, I confirmed this by identifying
an SMC call with a command code of 0x3 invoked
immediately before booting Linux. Another dead end.
At this point, the end was in sight, but I knew I would need a
vulnerability in the TrustZone kernel in order to set this flag to zero,
allowing my SMC call to blow the QFuse required to unlock
the bootloader. Fortunately, I didn’t have to look long, since one of
the other SMC commands in the same section of the TrustZone
kernel contains a fairly obvious arbitrary memory write
vulnerability:
switch (code) {
...
case 9:
if ( arg1 == 0x10 ) {
for (i = 0; i < 4; i++)
*(unsigned long *)(arg2 + 4*i) = global_array[i];
ret = 0;
} else
ret = -2020;
break;
...
}
This SMC call, invoked with command code
0x9, is evidently intended to allow the Non-secure Linux
kernel to obtain values stored in a particular region of Secure memory.
The value provided as the second argument in the SMC buffer
is used as the physical address at which this memory is copied. This
code does not check that the provided physical address corresponds to a
Non-secure memory region, so it can be used to overwrite memory in a
Secure region, including our global flag.
At this point, I had everything I needed. As a test, I issued the
vulnerable SMC call while providing a physical address in
Non-secure memory that I could read back. Fortunately, it appears that
all but the first of the four words that are written by the vulnerable
code are zeroes, allowing me to clear our global flag preventing a
bootloader unlock. Finally, I put it all together by aiming the
arbitrary TrustZone memory write to zero the flag and issuing the
SMC call with command code 0x9, and then
finally issuing the SMC call with command code
0x2 to unlock the bootloader. Rebooting my test device into
bootloader mode and checking the bootloader status with “fastboot getvar
all” showed I had been successful, and the bootloader was now
unlocked!
As previously mentioned, this exploit will work on all Qualcomm-based Motorola Android phones, which includes the Razr HD, Atrix HD, and Razr M.