amlogic-usbdl : unsigned code loader for Amlogic BootROM
Posted on Wed 10 February 2021 in Tool
In previous posts, we explained how to reverse the USB stack in the Exynos bootROM, which led to the discovery of a critical bug. After reproducing this methodology on Amlogic bootROM recently dumped, a similar vulnerability has been discovered in the USB stack that can be exploited to run arbitrary code.
The following targets are known to be vulnerable :
- Khadas VIM3L (S905D3) (Secure boot is disabled anyway)
- Chromecast with Google TV (S905D3G)
The difference between S905D3 & S905D3G is not clear at this moment; it may be related to the gold certification this component received for Premium Content processing.
amlogic-usbdl, an open source tool available on Github, exploits this vulnerability to load and run unsigned code.
Vulnerability details
USB Download protocol
Amlogic BootROM implements a custom USB protocol that allows USB host to execute a set of commands on target device i.e. read/write memory, run code, read/write registers. A common use-case for this protocol is to upload a bootloader and run it. On Secure Boot-enabled devices like Chromecast, a signature check is performed first before executing any uploaded code.
Among these commands, USB host can use AM_REQ_WR_LARGE_MEM to upload data into device memory.
First, USB host sends an USB CONTROL transfer with some infos regarding the payload :
URB setup
bmRequestType: 0x40 : Direction: Host-to-device - Type: Vendor (0x2) - Recipient: Device (0x00)
bRequest: 17 (0x11) : Write memory command AM_REQ_WR_LARGE_MEM
wValue: 0x1000 : size of each BULK transfer
wIndex: 16 (0x0010) : count of BULK transfers
wLength: 16 : size of data in this CONTROL transfer
Data Fragment: 0000faff000010000000000000000000
The data fragment follows the following structure :
struct dldata_s {
u_int32_t addr; // address where to write payload i.e. 0xfffa0000
u_int32_t size; // payload size i.e. 0x00010000
u_int32_t unk0; // unused
u_int32_t unk1; // unused
} dldata;
Parameters addr & size specify where in device memory data should be written. BootROM checks these parameters to ensure that uploaded data will fit into the download buffer [0xfffa0000-0xfffaffff] (0x10000 bytes maximum).
Then, the payload is uploaded by simply sending wIndex BULK transfers of size wValue each.
Unfortunate bugs conjunction
Although the overall logic of command AM_REQ_WR_LARGE_MEM looks simple and fine, there're still few peculiar details : size of uploaded data is defined in two different ways:
- dldata.size : exact size of uploaded data
- wValue * wIndex : size of each BULK transfer multiplied by the count of BULK transfers
While the first one dldata.size is checked, the second one isn't : size of each BULK transfer wValue is maxed out at 0x1000, but the count of BULK transfers wIndex is not checked.
As a consequence, an attacker can submit more BULK transfers than required to fill the download buffer. However, the BULK transfer handler tracks the total size of received data to ensure not exceeding the expected size dldata.size. So an invalid wIndex value is not sufficient to overflow the download buffer.
Nevertheless, a second bug makes exploitation trivial : after each BULK transfer, the download buffer pointer (initially set to 0xfffa0000) is incremented by the expected transfer size wValue, regardless of the actual size of data received.
So it's time to reuse that empty transfer trick we already used in the Exynos bootROM exploit. By sending empty BULK transfers, an attacker can manipulate the download buffer pointer to reach critical bootROM structures, like stack memory (below 0xfffe3800). For example, if the attacker sends 0x43 empty transfers, with wValue = 0x1000 : (0xfffa0000 + (0x1000 * 0x43) = 0xfffe3000, which is stack bottom address.
Once download buffer pointer has been manipulated, a last, non-empty BULK transfer is sent to overwrite memory.
Ultimately, these bugs lead to arbitrary code execution at bootROM level, even before the data signature check takes place.
Conclusion
USB download feature in Amlogic bootROM only runs signed bootloader images (when Secure Boot is enabled). However, few bugs in the USB stack allow an attacker with physical access to corrupt bootROM RAM. Ultimately, an attacker can execute arbitrary code in Secure World, at very early boot stage.
Despite these bugs, the overall code is quite robust for a BL1-kind software. It implements several hardening techniques :
- time-constant memory compare : to resist timing attacks ?
- complex return codes (i.e. success code is 0xa8df61e7 instead of one or zero) : to resist bit-flip attacks ?
- random execution delays : to resist glitching attacks ?
- mysterious recurrent hash check that immediately reboots if failed : control flow integrity ?
Amlogic bootROM also supports a password protection. This can be used to restrict access to the USB download feature, and thus mitigate this vulnerability.
Timeline
- 2020-10-19 Bug discovery
- 2020-10 Vulnerability disclosed to Google
2020-11-05 Google shared the report with Amlogic- 2020-12 Password mitigation enabled in factory for new Chromecast devices
- 2021-02-08 Password mitigation enabled by software update for Chromecast devices
- 2021-02-10 Public disclosure
- 2021-02-12 CVE-2021-0467 assigned to this issue
- 2021-02-15 Google retracts the date they communicated with Amlogic
- 2021-05-01 Vulnerability rated Critical in the Android Security Bulletin—May 2021