Reverse Engineering Samsung S6 SBOOT - Part II

In my previous article [1], I explained how to load Samsung's proprietary bootloader SBOOT into IDA Pro. The journey to the TEE OS continues in this second article which describes two techniques to locate Trustonic's TEE <t-base in the binary blob.


A few months back, I started digging into various TEE implementations and that led me to reverse engineer Samsung's proprietary bootloader SBOOT [1]. At that time, I suspected that the Trustonic's TEE <t-base was somehow embedded in the bootloader's image of Exynos-based smartphones, and it turned out that my assumptions were good. Back then, I used two techniques to locate <t-base in SBOOT but I did not find enough time to cleanup my notes and blog about it until now. This article describes the two techniques I used.

Searching an ARM Interrupt Vector Table: the Quick-and-Dirty Way

If you have ever opened sboot.bin in an hex editor, you have probably noticed that the bootloader image embeds trusted drivers. They are stored in the binary in the MobiCore Load Format, the format also used to store trusted applications on the filesystem. This file format is partially documented by Trustonic [2]. Blobs in the image can be located with the MobiCore Load Format's Magic MCLF:

$ hexdump -C sboot.bin | grep MCLF
000af980  4d 43 4c 46 03 00 02 00  01 00 00 00 02 00 00 00  |MCLF............|
0013fc40  05 00 02 00 08 10 d0 07  09 00 b0 40 4d 43 4c 46  |...........@MCLF|
00159000  4d 43 4c 46 05 00 02 00  0b 00 00 00 01 00 00 00  |MCLF............|
0016b000  4d 43 4c 46 05 00 02 00  08 00 00 00 02 00 00 00  |MCLF............|

I wrote an extraction script using binwalk [3]. You can download a copy of the plugin on GitHub [4].

$ mkdir -pv $HOME/.config/binwalk/{magic,plugins}
$ git clone
$ cp sboot-binwalk/magic/mclf $HOME/.config/binwalk/magic/
$ cp sboot-binwalk/plugins/ $HOME/.config/binwalk/plugins/
$ ls -sf $HOME/.config/binwalk/ $HOME/.binwalk # for old version of binwalk
$ binwalk -D 'mobicore mclf' ../sboot.bin

567696        0x8A990         SHA256 hash constants, little endian
674200        0xA4998         Android bootimg, kernel size: 0 bytes, [...]
677839        0xA57CF         POSIX tar archive, owner user name: [...]
1413120       0x159000        MobiCore Load Format, version 2.5
1478996       0x169154        SHA256 hash constants, little endian
1486848       0x16B000        MobiCore Load Format, version 2.5

MobiCore blobs in _sboot.bin.extracted/ can be loaded into IDA Pro with an appropriate loader. mclf-ida-loader [5] from Gassan Idriss is a good candidate.

MobiCore Trusted Driver loaded into IDA Pro

Without going too deep into the details, MobiCore blobs contain sections mapped in memory into different segments. The interesting fact about theses blobs is the .text section holding ARMv7 instructions while Samsung S6's SBOOT is entirely made of AArch64 instructions.

My intuition suggested that there were chances that <t-base also consisted of ARMv7 instructions and the whole TEE was running on a processor running in AArch32 state. Finding an ARMv7 Interrupt Vector Table within sboot.bin is a way to verify this hypothesis:

$ binwalk -m sboot-binwalk/magic/armel sboot.bin

1110016       0x10F000        ARMv7 Interrupt Vector Table, Little Endian
1110020       0x10F004        ARMv7 Interrupt Vector Table, Little Endian
1110024       0x10F008        ARMv7 Interrupt Vector Table, Little Endian
1110028       0x10F00C        ARMv7 Interrupt Vector Table, Little Endian
1110032       0x10F010        ARMv7 Interrupt Vector Table, Little Endian
1110080       0x10F040        ARMv7 Interrupt Vector Table, Little Endian
1110084       0x10F044        ARMv7 Interrupt Vector Table, Little Endian
1110088       0x10F048        ARMv7 Interrupt Vector Table, Little Endian
1110092       0x10F04C        ARMv7 Interrupt Vector Table, Little Endian
1110096       0x10F050        ARMv7 Interrupt Vector Table, Little Endian
1114080       0x10FFE0        ARMv7 Interrupt Vector Table, Little Endian

Amongst all the candidates, the first one at 0x10F000 is the best candidate as it is aligned. I loaded it into IDA Pro as an ARM Little-endian binary file to check if I picked a winning one:

$ dd if=sboot.bin of=_sboot.bin.extracted/10F000.bin bs=1 skip=$((0x10F000))
$ idaq _sboot.bin.extracted/10F000.bin

On the one hand, there are strings related to <t-base, on the other hand, references to strings and code in the binary are coherent... All these signs confirmed my assumptions. This approach is rather reliable and helpful to quickly find Trustonic's ARMv7 TEE OS without reverse engineering SBOOT.

Understanding Samsung S6's SBOOT: the Hard Way

Even if I managed to find what I was looking for, I was rather unsatisfied with the results as it left me with even more questions: how does the bootloader locates <t-base? How is it loaded? Which stage of the bootloader loads it? ... I needed to go deeper into SBOOT!

Where did I leave off in the previous episode?

SBOOT follows a proprietary format that is not documented. In my previous blogpost, I detailed how to load it into IDA Pro. Below, I recall the parameters to correctly load sboot.bin for the Samsung Galaxy S6:

  • Processor Type: ARM Little Endian [ARM]
  • Start ROM Address: 0x2102000
  • Loading Address: 0x2102000
  • Disassemble as 64-bit code: Yes

Looking for instructions that read or write to the register VBAR_EL3 led me to identify the Exception Vector Table used by the bootloader in AArch64 state and the runtime service descriptors rt_svc_descs array. They are respectively located at 0x210F000 and 0x210EB50:

Exception Vector Table and ``rt_service_descs`` array

I also recovered a few symbols along the path to identify the rt_service_descs. This is basically where I left off in the previous article. Let me detail how I enhanced IDA Pro's database on this binary. That way, it is going to be easier for interested readers to follow my explanations and repeat the steps.

Enhancing IDA Pro's Database

As mentioned in the previous article, SBOOT is partly based on ARM Trusted Firmware (ATF) [6], an open-source project that provides a reference implementation of secure world software for ARMv8-A., including a Secure Monitor executing at Exception Level 3 (EL3). For bootloaders running the AArch64 processor state, ATF defines 5 sequential stages:

  • Boot Loader stage 1 (BL1) AP Trusted ROM
  • Boot Loader stage 2 (BL2) Trusted Boot Firmware
  • Boot Loader stage 3-1 (BL31) EL3 Runtime Software
  • Boot Loader stage 3-2 (BL32) Secure-EL1 Payload (optional)
  • Boot Loader stage 3-3 (BL33) Non-trusted Firmware

The ATF documentation [7] states that it is possible to boot directly into BL31 and thus, to get rid of BL1 and BL2 which are redundant with manufacturer's proprietary boot ROM. This is the case on S6's SBOOT: Samsung only borrowed code from BL31 (EL3 Runtime Software). Since the source code is available, one can browse the code, recompile it, create function signatures, do binary diffing in order to recover as many symbols as possible and make the reverse engineering process easier.

To have a compiled binary as close as possible to SBOOT, I figured out which compiler was used. Fortunately, this piece of information is still available in SBOOT's strings:

$ strings sboot.bin | grep -i gcc
[...] hyunsung aarch64-linux-gnu-gcc (crosstool-NG linaro-1.13.1-4.8-2013.09 - Linaro GCC 201

Linaro releases binary cross-toolchain quarterly and the cross-toolchain I need has been released in 2013. Fortunately, it is still available on the website, in the release archive section [8].

$ wget
$ tar xJf gcc-linaro-aarch64-linux-gnu-4.8-2013.09_linux.tar.xz

Exynos is not an officially supported target platform in the open source version of ATF. That does not prevent us from compiling it, for another target platform for example:

$ git clone
$ cd arm-trusted-firmware
$ git checkout v0.4-rc2
$ export CROSS_COMPILE=$(pwd)/../gcc-linaro-aarch64-linux-gnu-4.8-2013.09_linux/bin/aarch64-linux-gnu-
$ make RESET_TO_BL31=1 PLAT=fvp bl31
LD      build/fvp/release/bl31/bl31.elf

It is possible to create FLIRT signatures using the ELF binary created during the firmware building process. Even if the target platform is different, FLIRT signatures should help retrieving most of the functions, at least the common ones between both platforms, as ATF is designed to be modular:

$ wget
$ idaq64 build/fvp/release/bl31/bl31.elf
[ ... load idb2pat plugin ... ]
$ sigmake build/fvp/release/bl31/bl31.pat bl31_v0.4_rc2.sig
$ mv bl31_v0.4_rc2.exc bl31_v0.4_rc2.exc.orig
$ tail +6 bl31_v0.4_rc2.exc.orig > bl31_v0.4_rc2.exc
$ sigmake -n"atf bl31 v0.4_rc2" -p13 arm-trusted-firmware/build/fvp/release/bl31/bl31.pat bl31_v0.4_rc2.sig
$ mv bl31_v0.4_rc2.sig /path/to/ida/sig/arm/

IDA Pro managed to identify a lot of functions using the database of signatures built. However, signatures in IDA are not 100% accurate. Thus, these results should be correlated with binary diffing results and manually verified while reversing.

SBOOT's BL33 stage reuses code from the Universal Boot Loader (U-Boot) [9]. Unfortunately, only fragments of code are used: creating function signatures is useless, and binary diffing using different tools (BinDiff [10], Diaphora [11], rizzo [12] for example) is preferred here.

$ git clone
$ cd u-boot
$ make espresso7420_config
$ make
$ idaq64 u-boot

Thanks to these steps, I was able to recover about 332 functions, by combining FLIRT signatures and importing only "safe" diffing results. At this point, the IDB is enhanced with symbols I can use as reference points to navigate between ARM Trusted Firmware's and U-Boot's source code, and SBOOT's assembly code.

BL31: EL3 Runtime Software

As previously mentioned, Samsung's SBOOT jumps directly into BL31, a.k.a the EL3 Runtime Software. This stage is responsible for architectural initialization [13] (e.g., replacing exception vectors to enable SMC handling), platform initialization [14] (e.g., enable the MMU) and runtime services initialization [15]. I am going to skip architectural and platform initializations and focus on the latter.

Runtime services initialization

BL31 is responsible for setting up the EL3 runtime services that will handle Secure Monitor Calls (SMC) instructions. ARM Trusted Firmware expects several runtime services to be implemented:

  • Standard service calls: implements the Power State Coordination Interface (PSCI) [16] specification, an ARM standard interface for power management (core idle management, dynamic addition and removal of cores, secondary core boot, system shutdown and reset, etc.),
  • Secure-EL1 Payload Dispatcher service: implements handlers for a wide range of SMC function identifiers and does world switches in response to SMCs,
  • And eventually other SMC handlers provided by OEMs for instance.

To make it easier to integrate services from different providers, a runtime service framework is available in ARM Trusted Firmware. Services are declared and automatically registered using the DECLARE_RT_SVC() macro: it instantiates a struct rt_svc_desc that is appended to a special ELF section rt_svc_descs, enabling the framework to easily find all service descriptors.

Initialization of service descriptors is done at runtime by runtime_svc_init() (in common/runtime_svc.c) and involves validating each of the declared runtime service descriptors, calling the service initialization function and populating the index rt_svc_descs_indices used for service handler lookup. The easiest way to find the actual location of rt_svc_descs is by reverse engineering runtime_svc_init() and by identifying RT_SVC_DESCS_START and RT_SVC_DESCS_END. These symbols delimit the bounds of the special ELF section (see bl31.ld.S):

``rt_service_descs`` array

On Samsung S6's SBOOT, rt_svc_descs has 6 descriptors and thus, as many registered runtime services. Amongst them, only 2 services, std_svc and tbase_fastcall, have initialization callbacks. These are executed when invoking runtime_svc_init().

mon_smc implements Samsung specific SMCs handlers. I'll detail one of them later in the article. std_svc is a standard Secure-EL1 Payload Dispatcher (SPD) service provided with ARM Trusted Firmware's source code (see services/std_svc/std_svc_setup.c). And, descriptors whose name is prefixed with tbase_ are <t-base related and these services are proprietary code.

tbase_fastcall's initialization function is rather simple. Basically, it retrieves the secure image descriptor at 0x20109EF0, initializes the secure_context structure at 0x2109F14, sets the world to be executed at the next el3_exit() to SECURE at 0x2109F3C, and registers a callback function tbase_init_entry at 0x2190F58.

``tbase_fastcall`` initialization routine

tbase_init_entry defines an opaque structure I arbitrarily called tbaseStruct_t which holds information on dRAM, the normal world's RAM, secDRam, the secure world's RAM which stores data, and secIRam, the secure world's RAM which stores instructions. The opaque structure starts with a magic number CMBT and is versioned (0x1) which means that it may change in the future. This callback is invoked by sub_2108114, in a SMC handler, while loading the secure payload (discussed later in the article).

``tbase_tbase_init_entry`` callback

Preparing EL3 to exit to normal world

The function runtime_svc_init() is called from bl31_main() which can be located easily in the assembly code using its strings references ("BL31 %s\r\n", "Built : ..."). On return from this function, bl31_main() prepares the next image (either BL32 or BL33) to be executed. At 0x210818C, the function bl31_plat_get_next_image_ep_info(NON_SECURE) is called to retrieve a reference to the entry_point_info_t structure describing a normal world image. Then, the Secure Configuration Register (SCR) is read into a register and the SCR_NS_BIT is set at 0x2108198. The function cm_set_el3_eret_context() ensures that SP_EL3 points to the right context and cm_set_next_eret_context() makes the firmware jump to NON_SECURE mode at the next el3_exit().

``bl31_main`` disassembled code

While comparing the disassembled code with bl31_main's source code, one may notice a few differences:

  1. Code responsible for calling the bl32_init (SPD runtime service initialization function set to tbase_init_entry) has been removed.
  2. Samsung seems to have changed a little bit the layout of the entry_point_info_t structure. As I reverse engineered it, the current structure is more likely as follows:
typedef struct entry_point_info {
    uintptr_t pc;
    uint32_t spsr;
    param_header_t type;
} entry_point_info_t;

The key to find the firmware image that is going to be executed and its location in memory is to determine which entry_point_info_t is returned by bl31_plat_get_next_image_ep_info() in the SECURE and NON_SECURE cases. We can retrieve this piece of information from bl31_plat_get_next_image_ep_info()'s assembly code:

``bl31_plat_get_next_image_ep_info`` disassembled code

The assembly code is easy to read and explicit. The bl31_param_ptr is a global variable that points to a structure bl31_params. According to the parameter - SECURE or NON_SECURE - passed to the function, it returns either the bl32_ep_info or the bl33_ep_info member. Fortunately, bl31_param_ptr is initialized only once at 0x2104200. It makes easier to retrieve the structure entry_point_info_t defined for the SECURE and NON_SECURE case:

``bl31_early_platform_setup`` disassembled code

According to the structure, the entrypoint for the secure world bl32_ep_reset is located at 0x2125000. This address only contains null bytes as the firmware is loaded at this address at a later time. The normal world entrypoint, bl33_ep_reset is located at 0x2134000 and contains valid AArch64 instructions. As the next piece of code to be executed is the normal world, let's see where the bl33_ep_reset can lead.

BL33: Non-trusted Firmware

Early instructions in bl33_ep_reset() are the same as u-boot's reset code for armv8 processors (see arch/arm/cpu/armv8/start.S). This is great because it will save us a lot of time and effort trying to understand it. reset's code starts by determining the current exception level and by setting up several system registers. As the processor currently runs in EL3, SCR_EL3 (EL0 and EL1 run as non secure code, FIQ and IRQ at any exception level are trapped), VBAR_EL3 and CPTR_EL3 (SIMD and FP instructions are not trapped) are modified as represented on the green code path.

``bl33_ep_reset`` disassembled code

lowlevel_init checks for the reset states (the code has been folded for readability). lowlevel_init continues then either on the warmboot (in black) or the coldboot (in green) path according to the determined state. It likely continues on the coldboot path as some code is relocated, by relocate_code() and copy_to_alive(), before loading the secondary boot image.

``load_second_boot`` disassembled code

Functions called from load_second_boot(), namely check_second_boot(), load_second_image() and coldboot(), are issuing secure monitor calls. As the function IDs used for these secure monitor calls are documented in u-boot (see arch/arm/include/asm/arch-exynos/smc.h), it was easy to determine what these functions do without reverse engineering any SMC handlers. The code of load_second_image() is more complex and closer to what I am looking for.

``load_second_boot`` disassembled code

The function load_uboot_image() in u-boot's source code (see board/samsung/smdk5410/smc.c is very similar to load_second_image() with the exception that some conditional code paths are missing. This function simply fills up an image_info structure (I renamed it to ld_image_info because of collisions) located at 0x43DFE000 with the necessary information about the boot device. This structure is then passed to the SMC handlers for SMC_CMD_SET_SIGNATURE_SIZE and SMC_CMD_LOAD_UBOOT. Only the case where the register X0 (representing bootdev) is set to UFS (0x20), represented in green on the following graph, is relevant in this case.

``load_second_image`` disassembled code

The ld_info_image structure is initialized and used as follows:

unsigned int load_second_image(unsigned int bootdev)
    ld_image_info *info_image = (ld_image_info *) 0x43DFE000;
    else if (bootdev == UFS) {
        info_image->bootdev.ufs.start_blk = 0x3e;
        info_image->bootdev.ufs.blkcnt =  0xd1;
        info_image->bootdev.ufs.dst_addr = 0x43e00000;
        info_image->size = 0xd1000;
    info_image->image_base_addr = 0x43E00000;
    info_image->secure_context_base = 0x2104C00;
    info_image->signature_size = 0;
    exynos_smc(SMC_CMD_SET_SIGNATURE_SIZE, 0, info_image, 0);
    return exynos_smc(SMC_CMD_LOAD_UBOOT, bootdev, info_image, boot_dev);

I am going to assume that the file SBOOT.bin is written as is on the Universal Flash Storage (UFS) device, and SMC_CMD_LOAD_UBOOT loads bytes into memory according to the structure ld_info_image passed as parameter. Let me check if these assumptions are right: if I manage to find valid opcodes at the offset 0x3e000 (0x3e * 0x1000 with 0x1000 being the size of an UFS device block), then I am on the right path.

$ dd if=sboot.bin of=secondboot.bin skip=$((0x3e * 0x1000)) bs=1 count=$((0xd1 * 0x1000))
$ aarch64-linux-gnu-objdump -b binary -m aarch64 --adjust-vma=0x43E00000 -D secondboot.bin | head -25
0000000043e00000 <.data>:
    43e00000:   14000001        b       0x43e00004
    43e00004:   10003fe0        adr     x0, 0x43e00800
    43e00008:   d5384241        mrs     x1, currentel
    43e0000c:   f100203f        cmp     x1, #0x8
    43e00010:   54000100        b.eq    0x43e00030  // b.none
    43e00014:   f100103f        cmp     x1, #0x4
    43e00018:   54000100        b.eq    0x43e00038  // b.none
    43e0001c:   d53e1100        mrs     x0, scr_el3
    43e00020:   b2400c00        orr     x0, x0, #0xf
    43e00024:   d51e1100        msr     scr_el3, x0
    43e00028:   d51ec000        msr     vbar_el3, x0
    43e0002c:   14000004        b       0x43e0003c
    43e00030:   d51cc000        msr     vbar_el2, x0
    43e00034:   14000002        b       0x43e0003c
    43e00038:   d518c000        msr     vbar_el1, x0
    43e0003c:   94000493        bl      0x43e01288
    43e00040:   94000007        bl      0x43e0005c
    43e00044:   9400000b        bl      0x43e00070

Nice! It seems that I found the next stage. The following IDAPython script mimics what I assumed the SMC_CMD_LOAD_UBOOT was doing:

import idaapi

def create_segment(name, start, end, data=False):
    seg = idaapi.segment_t()
    seg.startEA = start
    seg.endEA = end
    seg.bitness = 2 # 64 bits
    seg.perm = idaapi.SEGPERM_READ|idaapi.SEGPERM_WRITE|idaapi.SEGPERM_EXEC
    idaapi.add_segm_ex(seg, name, ("CODE", "DATA")[data], ADDSEG_OR_DIE)

def read_ufs(blkstart, blkcount):
    blksize = 4096
    size = blkcount * blksize

    offset = idaapi.get_fileregion_ea(blkstart * blksize)
    return idaapi.get_many_bytes(offset, size)

def load_ufs(blkstart, blkcount, address, segname):
    data = read_ufs(blkstart, blkcount)
    size = len(data)
    end = address + size

    create_segment(segname, address, end)
    idaapi.put_many_bytes(address, data)

# load secondary boot
load_ufs(0x3e, 0xd1, 0x43E00000, "secondary_boot")

Unfortunately, the secondary boot is not <t-base yet, but patience... Strings in the secondary boot suggest that I should be close now.

Secondary Boot

While reverse engineering the secondary boot, two particular strings caught my attention: "load Secure Payload done.\n" and "Fail to load Secure Payload!! [ret = %x]\n". Both strings are referenced in the function at 0x43E03774 I renamed to bl33_main(). Digging further, I noticed that this function is registered in an array located at 0x43E98FA8 and the function pointers are called from secondary_boot's startup code (see green-highlighted basic blocks):

``secondary_boot`` disassembled code

From bl33_main()'s graph, I can safely assume that the function at 0x43E01F58 (load_sp_image) is responsible for loading the secure payload, namely <t-base, in memory. For that purpose, it invokes a dedicated SMC handler SMC_CMD_LOAD_SECURE_PAYLOAD:

``bl33_main`` disassembled code

4 parameters are provided to the SMC handler:

  • X0: -300 or 0xFFFFFFFFFFFFFED4;
  • X1: 0x20, which is the constant to identify an UFS bootdev;
  • X2: 0x10F;
  • X3: 0x0, that I haven't identified.

Let me come back to X2 and how I identified that it was an integer corresponding to blkstart. For that purpose, I need to analyze the piece of code that handles the function ID -300.

I searched for this constant in each switch table for every runtime service handler. Unfortunately, it did not return any results. To understand why, one must follow the execution path from the moment an SMC instruction is executed to its SMC handler. They are likely instructions that modify the function ID before passing it to its corresponding handler.

All SMCs are caught by EL3 and the corresponding exception vector table entry gets executed. The following instructions in sync_exception_aarch64 at 0x210F400 are suspicious and obviously modify X0:

``smc_handler64`` disassembled code

This piece of code is not in ARM Trusted Firmware. It is not clear for me who made these modifications, either Samsung or Trustonic. Let me rip that piece of code and try to convert SMC_CMD_LOAD_SECURE_PAYLOAD:

$ cat calc_fid.c
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>

uint64_t calc_fid(uint64_t fid)
    uint64_t res;

    asm("mov x0, %0;"
        "ldr w9, =0xffff0000;"
        "and w8, w0, w9;"
        "cmp w8, w9;"
        "bne loc_210E324;"
        "mov w8, w0;"
        "mov x9, xzr;"
        "orn w9, w9, w8;"
        "add w9, w9, #1;"
        "ldr w8, =0x82000000;"
        "orr w0, w8, w9;"
        "mov %0, x0;"
        : "=r" (res) /* output operands */
        : "r"  (fid) /* input operands */
        :            /* clobbered registers */

    return res;

int main(int argc, char **argv)
    char *end;
    uint64_t fid;

    if (argc != 2) {
        printf("usage: %s <fid>\n", argv[0]);
        return 1;

    fid = strtoull(argv[1], NULL, 0);

    printf("0x%" PRIx64 "\n", calc_fid(fid));

    return 0;
$ aarch64-linux-gnu-gcc calc_fid.c -o calc_fid
$ qemu-aarch64-static ./calc_fid 0xFFFFFFFFFFFFFED4

Bingo! The handler for 0x8200012c is located at 0x210D8AC in mon_smc_handler (0x210BAF8). I am only interested in determining the semantics of the register X2, which holds the value 0x1DF. It seems that the SMC handler passes X2 to sub_2105388. The astute reader must have noticed that the logic of this function is very similar to load_second_image() that I already reversed. If I assume that X19 holds a pointer to a ld_image_info, it appears that X2 is ld_image_info.bootdev.ufs.startblk:

``sub_2105388`` disassembled code

Back to blkstart, if I multiply the constant 0x10F saved in the register X2 by the size of a block, I get 0x10F000 which is <t-base's offset. I've just found it with another method, after following a long and a tedious process.


In this article, I have presented two techniques to find <t-base's image in SBOOT. I also outlined SBOOT's logic: one may find it extremely complex without any documentation.


  • Quarkslab colleagues for proofreading this article and for their feedbacks.