exynos-usbdl : unsigned code loader for Exynos BootROM

Posted on Wed 17 June 2020 in Tool

In previous posts, we explained how to dump Exynos bootROM and reverse its USB stack.

These efforts led to the discovery of a bug in the USB stack that can be exploited to run arbitrary code.

The following chipsets are known to be affected by this bug :

  • Exynos 8890
  • Exynos 8895

exynos-usbdl, an open source tool available on Github, exploits this vulnerability to load and run unsigned code.

Good times with dental scraper

Vulnerability details

Exynos BootROM implements a very simple USB protocol to receive a bootloader binary from an USB host. That binary is encapsulated in a small structure dldata, and sent through USB bulk transfers.

typedef struct dldata_s {
    u_int32_t unknown0;
    u_int32_t size;// header(8) + data(n) + footer(2)
    u_int8_t  data[n];//example data of size 'n'
    u_int16_t unknown1;//footer
} dldata;

Integer overflow bug

BootROM writes received data into buffer dl_buf at address [0x02021800..0x02070000] (0x4E800 bytes). But first, a check ensures that transferred data won't overflow dl_buf buffer :

if (dl_buf + dldata.size >= 0x02070000) {
    usb_download_status = 0x02;//error
}

However, this check on 32 bits is vulnerable to an integer overflow if dldata.size is higher than 0xFDFDE7FF (dl_buf + 0xFDFDE800 ≡ 0x0 mod 2³²).

So the bootROM accepts a payload of regular size [0x10..0x4E800] bytes (don't go below if you want to avoid integer underflows...), but due to that bug, also accepts a payload of [0xFDFDE800..0xFFFFFFFF] bytes.

Empty transfer trick

The integer overflow bug allows us to bypass the size check to send a very large payload to the bootROM. Such contiguous memory overflow may corrupt memory after dl_buf buffer, but the memory layout will cause the target to crash before any interesting (from attacker's point of view) memory corruption happens.

First, because dl_buf is located at the end of the in-use memory (after stack area, for example), so there's no bootROM runtime data to corrupt. But also because accessing inexistent memory regions will cause a crash.

Nevertheless, there's a trick to overcome that limitation. A large download is expected to be splitted into multiple small transfers of equal size (0xfffe00 bytes), except the last transfer which can be smaller.

Each of these transfers triggers the USB transfer handler of the bootROM. This handler appends received data to the destination buffer dl_buf. We would expect that dl_buf pointer is then incremented by the size that has been appended. But instead, destination pointer dl_buf is incremented by the expected transfer size (0xfffe00 bytes). So, by sending empty transfers (transfer without data), we can increment the destination pointer dl_buf without actually writing any data, thus not risking any invalid memory access.

With this technique, we can easily increase dl_buf pointer to reach an address convenient for exploitation.

Finally, the last transfer (non-empty this time) is used to corrupt memory pointed by manipulated address dl_buf.

In summary, we can write 512 bytes (or less) of arbitrary data in memory range [0x0..0x020217FF], at an arbitrary offset. In this memory range, multiple data structures can be overwritten to achieve arbitrary code execution: function pointers, stack region, etc...

Conclusion

The USB boot feature in the Exynos bootROM only runs signed bootloader images (when Secure Boot is enabled). However a pre-auth integer overflow bug allows an attacker with physical access to corrupt bootROM RAM. So the attacker can execute arbitrary code in Secure World, at very early boot stage.

Timeline

  • 2020-02-12 Bug discovery
  • 2020-**-** Proof-of-concept development
  • 2020-05-06 Vulnerability disclosed to Samsung
  • 2020-06-10 Report dismissed as duplicate