Android Software Reverse Engineering & Decompilation

Building Your Own TrustZone Tracer: Custom Tools for TEE Driver Communication Monitoring

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction to TrustZone and the Secure World

In modern Android devices, critical security functions like key management, DRM, and biometric authentication are often offloaded to a Trusted Execution Environment (TEE), typically implemented using ARM TrustZone technology. TrustZone provides a hardware-isolated “Secure World” alongside the “Normal World” (where Android runs). Communication between these two worlds is a highly privileged operation, mediated by TEE drivers in the Normal World kernel. Reverse engineering this communication channel is crucial for understanding how secure services operate, identifying potential vulnerabilities, or analyzing proprietary security features.

However, the Secure World operates as a black box. Its code is often proprietary and inaccessible to direct debugging from the Normal World. Our best vantage point for observing its behavior is by intercepting and analyzing the communication flow initiated by user-space applications through the TEE driver.

The TEE Driver: Gateway to Secure World Interactions

User-space applications communicate with the TEE via dedicated character device drivers, such as /dev/qseecom (Qualcomm Secure Execution Environment Communication) or /dev/mobicore (for Trustonic TEEs). These drivers expose an ioctl interface, acting as the sole entry point for sending commands and data to, and receiving responses from, Trusted Applications (TAs) running in the Secure World. Understanding the structure of these ioctl calls is paramount for reverse engineering TrustZone communication.

A typical interaction involves:

  1. Opening the TEE device (e.g., open("/dev/qseecom", O_RDWR)).
  2. Sending various ioctl commands to:
    • Load/start a specific Trusted Application (TA).
    • Allocate secure memory.
    • Send command buffers to the running TA.
    • Receive response buffers from the TA.
    • Close the TA.

Identifying Key IOCTLs

The first step in building a tracer is identifying the relevant ioctl command codes and their argument structures. This can be done through several methods:

  • Static Analysis of TEE Driver Binary: Using tools like Ghidra or IDA Pro to disassemble the TEE kernel module (e.g., qseecom.ko). Look for the qseecom_ioctl or similar functions that handle incoming ioctl calls.
  • Android Framework Sources: Sometimes, parts of the communication interface are defined in open-source components, such as Android’s Keymaster Hardware Abstraction Layer (HAL) or DRM modules.
  • Dynamic Analysis (Limited): Tools like strace can show which ioctl calls are made, but they often can’t fully peek into the content of large buffers passed as arguments. For example:
strace -e ioctl -p <PID_OF_APP>

This might show the ioctl command number and a pointer, but not the actual data. For deeper inspection, a kernel-level solution is necessary.

Building a Kernel-Level Tracer with Kprobes

To gain full visibility into the data passed to and from the TEE driver, we need to operate at the kernel level. A robust and relatively safe method is to use Linux Kprobes to hook the generic sys_ioctl syscall. This allows us to inspect the parameters before they are processed by the TEE driver. While modifying the sys_call_table is another option, Kprobes offers a safer and more maintainable approach for tracing.

Prerequisites:

  • Access to the Android device’s kernel source or pre-built kernel headers matching your device.
  • A cross-compilation toolchain for your device’s architecture (e.g., AArch64).

Step-by-Step: Writing the Kernel Module

We’ll create a simple kernel module that hooks sys_ioctl using a Kprobe and logs relevant information when an ioctl call to the TEE driver is detected.

Let’s consider hooking sys_ioctl and inspecting its arguments: fd (file descriptor), cmd (ioctl command number), and arg (pointer to argument buffer). We’ll specifically look for the file descriptor corresponding to our TEE device (e.g., /dev/qseecom).

#include <linux/kernel.h>#include <linux/module.h>#include <linux/kprobes.h>#include <linux/fs.h> // For struct file#include <linux/slab.h> // For kmalloc/kfree#include <linux/uaccess.h> // For copy_from_user#include <linux/fdtable.h> // For files_struct#include <linux/dcache.h> // For struct dentry#define TEE_DEVICE_NAME "qseecom" // Adjust for your TEE driver#define MAX_BUF_SIZE 128 // Max bytes to peek into argument buffer// Kprobe for sys_ioctlstatic asmlinkage long (*orig_sys_ioctl)(unsigned int fd, unsigned int cmd, unsigned long arg);static int entry_handler_ioctl(struct kprobe *p, struct pt_regs *regs){    unsigned int fd = regs->di; // Arg1: fd    unsigned int cmd = regs->si; // Arg2: cmd    unsigned long arg = regs->dx; // Arg3: arg    struct files_struct *files = current->files;    struct file *file;    char *buf = NULL;    // Get the file structure from the file descriptor    file = fget(fd);    if (file) {        const char *filename = NULL;        if (file->f_path.dentry && file->f_path.dentry->d_name.name) {            filename = file->f_path.dentry->d_name.name;        }        // Check if this is our target TEE device        if (filename && strstr(filename, TEE_DEVICE_NAME)) {            printk(KERN_INFO "TrustZoneTracer: PID %d (%s) - TEE IOCTL %s (0x%x), arg 0x%lxn",                   current->pid, current->comm, filename, cmd, arg);            // Attempt to peek into the argument buffer if it's a pointer            if (arg) {                // Heuristic: only try to copy if it looks like a valid user-space pointer                // and the command is likely to involve data transfer                if (access_ok(VERIFY_READ, (void __user *)arg, MAX_BUF_SIZE)) {                    buf = kmalloc(MAX_BUF_SIZE, GFP_KERNEL);                    if (buf) {                        if (copy_from_user(buf, (void __user *)arg, MAX_BUF_SIZE) == 0) {                            print_hex_dump(KERN_INFO, "  Arg Data: ", DUMP_PREFIX_OFFSET,                                           16, 1, buf, MAX_BUF_SIZE, true);                        } else {                            printk(KERN_INFO "  TrustZoneTracer: Failed to copy arg from user space.n");                        }                        kfree(buf);                    }                } else {                    printk(KERN_INFO "  TrustZoneTracer: Arg not accessible or too small.n");                }            }        }        fput(file);    }    return 0; // Don't modify the execution path}static struct kprobe kp = {    .symbol_name = "__arm64_sys_ioctl", // Adjust for your architecture and kernel version    .pre_handler = entry_handler_ioctl,};static int __init trustzone_tracer_init(void){    int ret;    ret = register_kprobe(&kp);    if (ret < 0) {        printk(KERN_ERR "TrustZoneTracer: register_kprobe failed, returned %dn", ret);        return ret;    }    printk(KERN_INFO "TrustZoneTracer: Kprobe on %s registeredn", kp.symbol_name);    return 0;}static void __exit trustzone_tracer_exit(void){    unregister_kprobe(&kp);    printk(KERN_INFO "TrustZoneTracer: Kprobe on %s unregisteredn", kp.symbol_name);}module_init(trustzone_tracer_init);module_exit(trustzone_tracer_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("Your Name");MODULE_DESCRIPTION("A kernel module to trace TrustZone TEE driver communication.");

Compiling and Deploying the Module

1. Create a Makefile:

obj-m := trustzone_tracer.oKERNELDIR := /path/to/your/kernel/sourcePWD := $(shell pwd)all: $(MAKE) -C $(KERNELDIR) M=$(PWD) modulesclean: $(MAKE) -C $(KERNELDIR) M=$(PWD) clean

2. Compile: Adjust KERNELDIR to point to your kernel source tree (or the directory containing build output if using pre-built headers). Run make.

3. Push to Device:

adb push trustzone_tracer.ko /data/local/tmp/

4. Load the Module: On the device (as root):

adb shellsuinsmod /data/local/tmp/trustzone_tracer.ko

5. Monitor Logs: Open another terminal to watch the kernel log output:

adb shell dmesg -w

Now, whenever an application makes an ioctl call to /dev/qseecom (or your specified TEE device), your tracer will log the process ID, the ioctl command number, and up to 128 bytes of the argument buffer (if accessible).

Analyzing Traced Data

The logged command numbers (e.g., 0x40087a02 for QSEECOM_IOCTL_START_APP) are often defined in the TEE SDK or within the driver itself. By correlating these with the `print_hex_dump` output, you can begin to reconstruct the data structures being exchanged. Look for patterns in the argument buffers: command IDs internal to the TA, sizes of subsequent buffers, and encrypted payloads. This raw data forms the basis for further reverse engineering of the TrustZone communication protocol.

Limitations and Advanced Considerations

  • Performance Overhead: Running a Kprobe on a frequently called syscall like sys_ioctl can introduce noticeable overhead.
  • Kernel Versioning: The exact symbol name for sys_ioctl can vary between kernel versions and architectures (e.g., sys_ioctl vs. __arm64_sys_ioctl). You might need to adjust the .symbol_name in the kprobe definition.
  • Secure Boot/Integrity: On devices with strong secure boot, loading unsigned kernel modules might be prevented or flagged.
  • eBPF: For more robust, safer, and dynamic tracing without recompiling and loading kernel modules, eBPF (extended Berkeley Packet Filter) offers a powerful alternative on newer kernels. It allows running custom programs in the kernel without modifying kernel source.
  • Obfuscation: The argument buffers often contain highly structured, proprietary, and potentially encrypted data. Further analysis will involve static analysis of the user-space client and the TA itself (if obtainable).

Conclusion

Building a custom kernel-level TrustZone tracer provides unparalleled insight into the secure world’s communication patterns. By leveraging Kprobes to intercept TEE driver interactions, reverse engineers can transform opaque secure operations into observable data flows, laying the groundwork for deeper security analysis and vulnerability research. This hands-on approach empowers researchers to peek behind the curtain of TrustZone, one ioctl call at a time.

Android Mobile Specs & Compare Directory

Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!

Compare Devices Specs →
Google AdSense Inline Placement - Content Footer banner