Emulating Exynos 4210 BootROM in QEMU
Posted on Wed 07 March 2018 in Article
QEMU has support for the SMDKC210 machine, an ARM board based on Exynos 4210 SoC. Peripherals implemented in QEMU for this machine are UART, SDHCI, FIMD, I2C, Interrupt Combiner, GIC, Clock, PMU, RNG, MCT, PWM, RTC.
Samsung Galaxy S2 phone is also based on Exynos 4210, so it should be relatively easy to emulate its BootROM in QEMU.
This article describes how to extract BootROM (and associated fuses) from Galaxy S2 phone, implement additional required peripherals in QEMU (like the hardware cryptographic engine), and debug inevitable issues. Source code has been published on GitHub. We'll also take advantage of the dynamic debugging capability offered by QEMU to have a quick look at secure boot implementation.
Documentation
Samsung has released a (partial) datasheet. It includes the following memory map :
Another significant ressource is the U-Boot source code for development boards (like ODROID X) based on same processor. It contains interesting technical information on hardware peripherals (like addresses, registers, values).
Finally, hacker community has contributed a lot over the past years.
Dump all the things
First of all, we have to extract the BootROM we want to emulate from the Exynos 4210 SoC. We will also extract fuses data and first bootloader (from flash memory) as they are required to complete execution of BootROM.
We use a Galaxy S2 phone, with ADB access and root privileges thanks to CVE-2013-6282 exploit.
Dump bootloader
The bootloader stored on flash memory is loaded, authenticated (eventually), and executed by BootROM. Accessible through the mmcblk0boot0 device file, we dump it using builtin dd command :
# dd if=/dev/block/mmcblk0boot0 of=./mmc_boot.img seek=1 bs=512
1024+0 records in
1024+0 records out
524288 bytes transferred in 0.091 secs (5761406 bytes/sec)
We generate an entropy graph of dumped data using binwalk tool :
The first part with high entropy (almost E=1) is the encrypted FWBL1 image, the next stage to execute after BootROM.
Dump BootROM
Memory map indicates that BootROM, also called iROM, is mapped at address 0x0000_0000. On this device, stock Android kernel is compiled with /dev/mem support, so we can directly use the simple viewmem tool to dump BootROM (yeah, chipset from 2011).
# ./viewmem 0x00000000 0x10000 > ./bootrom.bin
[INFO] Reading 65536 bytes at 0x0...
Dump fuses
Fuses a.k.a. One-Time Programmable (OTP) memory usually contain important information for security, like boot settings, cryptographic keys or hashes. According to datasheet, fuses are in the SECKEY area at address 0x10100000.
However in this case, viewmem tool fails to read data directly. The cause of this issue is explained in another research for a similar processor. We learn that accessing SECKEY requires to enable the specific hardware clock CLK_SECKEY (bit 12 of CLK_GATE_IP_PERIR register).
The solution is to build a modified kernel to enable that clock at boot.
Samsung has released the kernel source code, however initramfs is missing to generate a fully fonctional kernel image. So we dump the original kernel partition (like we did for bootloader partition) and extract initramfs archive. We can then append extracted initramfs to our custom kernel by setting CONFIG_INITRAMFS_DIRECTORY option in kernel configuration.
We apply the following kernel patch to enable CLK_SECKEY clock at boot :
--- a/arch/arm/mach-exynos/clock-exynos4.c 2013-02-21 05:23:03.000000000 -0800
+++ b/arch/arm/mach-exynos/clock-exynos4.c 2018-02-25 00:11:03.817693249 -0800
@@ -1352,11 +1352,11 @@
static struct clk exynos4_init_clocks[] = {
{
-#ifndef CONFIG_CPU_EXYNOS4210
.name = "seckey",
.enable = exynos4_clk_ip_perir_ctrl,
.ctrlbit = (1 << 12),
}, {
+#ifndef CONFIG_CPU_EXYNOS4210
.name = "tzpc",
.devname = "exnos4-tzpc.5",
.enable = exynos4_clk_ip_perir_ctrl,
@@ -2386,6 +2386,17 @@
for (ptr = 0; ptr < ARRAY_SIZE(exynos4_clksrcs); ptr++)
s3c_set_clksrc(&exynos4_clksrcs[ptr], true);
+
+ printk(KERN_INFO "%s: Looking for seckey clock...\n", __func__);
+ for (ptr = 0; ptr < ARRAY_SIZE(exynos4_init_clocks); ptr++) {
+ if (exynos4_init_clocks[ptr].name == NULL)
+ break;
+
+ if (strcmp("seckey", exynos4_init_clocks[ptr].name) == 0) {
+ printk(KERN_INFO "%s: Enabling seckey clock\n", __func__);
+ clk_enable(&exynos4_init_clocks[ptr]);
+ }
+ }
}
static struct clk *exynos4_clks[] __initdata = {
After flashing and booting this custom kernel on Galasy S2 device, we can dump fuses data with viewmem tool, like we did for BootROM :
# ./viewmem 0x10100000 0x100 > ./fuses.bin
[INFO] Reading 256 bytes at 0x10100000...
Debugging
We have the BootROM, fuses data, and the first bootloader from flash memory. However, we should not expect it to run properly on the first try.
The following QEMU features were used to debug issues and develop new QEMU peripherals :
- Trace events : output debug log when specific instructions are hit.
- GDB stub : attach your favorite debugger i.e. GDB or IDA.
- Monitor console : interact with QEMU while guest is running, for example to enable specific trace events.
Most issues are related to peripherals and will cause QEMU to hang (e.g. infinite loop due to polling of non-implemented hardware register).
QEMU changes
Despite the number of peripherals implemented for this SoC, some are still missing to complete execution of BootROM with success. Following sections describe the main changes made in QEMU source tree for this project. New peripherals are heavily based on existing ones in upstream QEMU project.
Add BootROM loading support
For this machine, QEMU is supposed to run U-Boot bootloader or Linux kernel directly. By default, only a small & minimal bootloader is loaded in secondary CPUs, which is not relevant for this project.
In order to load BootROM in memory before starting the machine, we take advantage of the existing BIOS loading feature in QEMU.
With this change, BootROM image file can now be loaded in memory thanks to -bios parameter. It will be executed directly when emulated machine is reset.
One-Time Programmable (OTP) memory emulation
One-Time Programmable (OTP) memory is a MMIO peripheral used to store device-specific data. Read operations can be performed with simple load instructions, but write operations (or fusing) are usually more complex.
For this project, OTP peripheral is implemented as a simple read-only memory, initialized with fuses data dumped from Galaxy S2 device. Write operations are not supported.
When BootROM is executed, QEMU debug log provides detailed information on OTP data accessed.
- First OTP access is a read operation on area 0x18-0x2C. Based on static analysis, BootROM checks these fuses are provisioned (not null) :
[exynos4210_otp_read:66] Read OTP @0x18 (0x4)
[exynos4210_otp_read:66] Read OTP @0x1c (0x4)
[exynos4210_otp_read:66] Read OTP @0x20 (0x4)
[exynos4210_otp_read:66] Read OTP @0x24 (0x4)
[exynos4210_otp_read:66] Read OTP @0x28 (0x4)
- Same area 0x18-0x2C (20 bytes) is read a second time to derive HMAC-SHA1 key (to authenticate next bootloader) :
[exynos4210_otp_read:66] Read OTP @0x18 (0x1)
[exynos4210_otp_read:66] Read OTP @0x19 (0x1)
[...]
[exynos4210_otp_read:66] Read OTP @0x2a (0x1)
[exynos4210_otp_read:66] Read OTP @0x2b (0x1)
- The area is read a third time, but only first 16 bytes, to derive AES-CBC-128 key (to decrypt next bootloader) :
[exynos4210_otp_read:66] Read OTP @0x18 (0x1)
[exynos4210_otp_read:66] Read OTP @0x19 (0x1)
[...]
[exynos4210_otp_read:66] Read OTP @0x26 (0x1)
[exynos4210_otp_read:66] Read OTP @0x27 (0x1)
Advanced Crypto Engine (ACE) emulation
Advanced Crypto Engine (ACE) peripheral performs hardware-accelerated cryptographic operations. ACE interface, composed of many Special Function Registers (SFR), is way more complex than the OTP one. Fortunately, these SFR are documented in U-Boot source code.
Again, our implementation is limited to features actually used by BootROM : only AES-CBC-128 operations are supported.
When BootROM is executed, QEMU debug log details how ACE peripheral is used :
- Set AES key, IV, and other parameters (ACE_AES_CONTROL register) :
[exynos4210_ace_write:338] ACE_AES_CONTROL <0x0200> <- 0x0e8b
[exynos4210_ace_write:338] ACE_AES_KEY5 <0x0290> <- 0xebc4ad63
[exynos4210_ace_write:338] ACE_AES_KEY6 <0x0294> <- 0x27239f1a
[exynos4210_ace_write:338] ACE_AES_KEY7 <0x0298> <- 0x230ce305
[exynos4210_ace_write:338] ACE_AES_KEY8 <0x029c> <- 0xcaad75d2
[exynos4210_ace_write:338] ACE_AES_IV1 <0x0230> <- 0xee1c2939
[exynos4210_ace_write:338] ACE_AES_IV2 <0x0234> <- 0x6be93160
[exynos4210_ace_write:338] ACE_AES_IV3 <0x0238> <- 0xd8bbf993
[exynos4210_ace_write:338] ACE_AES_IV4 <0x023c> <- 0x29b98fe8
[exynos4210_ace_read:315] ACE_FC_INTPEND [0x000c] -> 0x0000
[exynos4210_ace_FCINTPEND:227] QEMU ACE: FCINTPEND triggered
- Set input & output buffer addresses (0x2021410), set buffer size (0x1bf0), and poll decryption process status (ACE_FC_INTPEND register):
[exynos4210_ace_write:338] ACE_FC_BRDMAS <0x0020> <- 0x2021410
[exynos4210_ace_write:338] ACE_FC_BTDMAS <0x0030> <- 0x2021410
[exynos4210_ace_write:338] ACE_FC_BTDMAL <0x0034> <- 0x1bf0
[exynos4210_ace_write:338] ACE_FC_BRDMAL <0x0024> <- 0x1bf0
[exynos4210_ace_read:315] ACE_FC_INTPEND [0x000c] -> 0x0000
[exynos4210_ace_FCINTPEND:227] QEMU ACE: FCINTPEND triggered
[exynos4210_ace_FCINTPEND:235] QEMU ACE: AES_control=0xe8b, FCBRDMAS=0x2021410, FCBRDMAS=0x1bf0, FCBRDMAS=0x2021410, FCBRDMAS=0x1bf0
[exynos4210_ace_write:338] ACE_FC_INTPEND <0x000c> <- 0x0004
The log shows that 0x1bf0 bytes of data at address 0x2021410 are decrypted (ACE_AES_CONTROL[0] bit) using AES-CBC mode (ACE_AES_CONTROL[2:1] bits).
Minor fixes in SD/MMC Host controller
SD controller emulation is already fully supported by QEMU, however few minor differences causes BootROM to hang.
First issue occurs when BootROM sends initialization command ACMD41 (SD_APP_OP_COND) multiple times. QEMU SD-card emulator only replies to the first attempt. Our patch ensures that SD-card always replies, even when SD-card state is already ready.
Second issue is the lack of support for SD Clock Enable register in Clock Control Register. The patch implements it like defined in the original contribution.
Thanks to these changes, BootROM can perform SD operations. QEMU debug log shows that 0x2000 bytes are read at offset 0x200 :
SD: sd_blk_read: addr = 0x00000200, len = 512
SD: sd_blk_read: addr = 0x00000400, len = 512
SD: sd_blk_read: addr = 0x00000600, len = 512
SD: sd_blk_read: addr = 0x00000800, len = 512
SD: sd_blk_read: addr = 0x00000a00, len = 512
SD: sd_blk_read: addr = 0x00000c00, len = 512
SD: sd_blk_read: addr = 0x00000e00, len = 512
SD: sd_blk_read: addr = 0x00001000, len = 512
SD: sd_blk_read: addr = 0x00001200, len = 512
SD: sd_blk_read: addr = 0x00001400, len = 512
SD: sd_blk_read: addr = 0x00001600, len = 512
SD: sd_blk_read: addr = 0x00001800, len = 512
SD: sd_blk_read: addr = 0x00001a00, len = 512
SD: sd_blk_read: addr = 0x00001c00, len = 512
SD: sd_blk_read: addr = 0x00001e00, len = 512
SD: sd_blk_read: addr = 0x00002000, len = 512
These read operations match with encrypted FWBL1 image in bootloader partition previously dumped.
Executing BootROM in QEMU
We compile our modified QEMU and run :
$ qemu-system-arm -machine smdkc210 -cpu cortex-a9 -s -S -bios ./bootrom.bin -sd ./mmc_boot.img
- -machine : emulated machine
- -s : Shorthand for -gdb tcp::1234
- -S : Do not start CPU at startup
- -bios : BootROM image file
- -sd : SD-card image file (equivalent to internal eMMC memory). We specify the path of bootloader dumped previously.
From static analysis, we know that main BootROM function ends by jumping to IRAM at address 0x02021410. We can also notice that AES decryption took place at the same address. We attach GDB and set a breakpoint to this address :
Breakpoint 2, 0x02021410 in ?? ()
(gdb) layout asm
B+> 0x2021410 b 0x2021434
0x2021414 b 0x2021414
0x2021418 b 0x2021418
0x202141c b 0x202141c
0x2021420 b 0x2021420
0x2021424 b 0x2021424
0x2021428 b 0x2021428
0x202142c b 0x202142c
0x2021430 b 0x2021430
0x2021434 mrc 15, 0, r0, cr1, cr0, {0}
[...]
GDB breaks at the exception vector table of decrypted FWBL1.
After resuming execution, QEMU debug log shows that FWBL1 reads 0x4000 bytes from flash memory at address 0x2200 :
SD: sd_blk_read: addr = 0x00002200, len = 512
SD: sd_blk_read: addr = 0x00002400, len = 512
[...]
SD: sd_blk_read: addr = 0x00005e00, len = 512
SD: sd_blk_read: addr = 0x00006000, len = 512
But that's another story since BootROM execution is already over.
Secure boot analysis
Secure boot process aims to assert the authenticity of all software components in boot chain. The first component in the chain is called root of trust, and in our case it's the BootROM. It loads, authenticates and decrypts the next component called FWBL1, stored in flash memory.
The footer of FWBL1 contains metadata structures that BootROM parses to perform authentication. Fortunately, these structures are defined in U-Boot source tree :
#define SB20_MAX_SIGN_LEN (2048/8)
typedef struct
{
SB20_RSAPubKey stage2PubKey;
int code_SignedDataLen;
unsigned char code_SignedData[SB20_MAX_SIGN_LEN];
SB20_PubKeyInfo pubKeyInfo;
unsigned char func_ptr_BaseAddr[128];
unsigned char reservedData[80];
} SB20_CONTEXT;
Field code_SignedData is the RSA-2048 signature of payload (FWBL1 code).
Then, pubKeyInfo structure contains information required to verify this signature :
#define SB20_HMAC_SHA1_LEN 20
typedef struct
{
SB20_RSAPubKey rsaPubKey;
unsigned char signedData[SB20_HMAC_SHA1_LEN];
} SB20_PubKeyInfo;
rsaPubKey structure is the RSA public key, composed of modulus N and public exponent E. signedData is a HMAC of this key to ensure it hasn't been modified.
So the authentication scenario of FWBL1 by BootROM is :
- verify HMAC of rsaPubKey : signedData == HMAC(Key, rsaPubKey)
- verify code_SignedData signature using rsaPubKey
Verifying HMAC value signedData requires the same secret Key that was used to generate it. However, static analysis reveals that Key is not directly stored in device : it's in fact derived at runtime with a XOR operation applied to OTP value OTP_key and the HMAC signedData itself :
- Key = signedData ⊕ OTP_key
Since OTP_key is in read-only OTP memory, attacker cannot replace rsaPubKey to forge a new signature, otherwise derivated Key would be different and HMAC verification would fail.
Finally, authenticated payload is decrypted using AES-CBC-128, with same Key as the HMAC one (first 16 bytes only). IV is the SHA1 of rsaPubKey (first 16 bytes).
Conclusion
QEMU is able to run Exynos 4210 BootROM with relatively small code changes (~600 LoC). This project allows to debug BootROM dynamically with GDB. It has been helpful for analyzing secure boot mechanism that loads and authenticates the next stage from flash memory.