Introduction to TrustZone and TEE Communication
ARM TrustZone technology partitions a system’s hardware and software resources into two distinct worlds: the Normal World (NW) and the Secure World (SW). The Secure World hosts a Trusted Execution Environment (TEE), which is designed to protect sensitive operations and data from potential compromise in the Normal World. This separation is crucial for security-critical applications like DRM, mobile payments, and biometric authentication.
Communication between applications running in the Normal World and Trusted Applications (TAs) residing in the Secure World is a fundamental aspect of TrustZone’s architecture. This communication is mediated by a TEE driver, typically a kernel module in the Normal World operating system (e.g., Linux on Android devices). Reverse engineering these TEE drivers is an advanced technique for understanding the underlying secure protocols, identifying vulnerabilities, or extending functionality.
The Role of the TEE Driver
The TEE driver acts as the crucial interface between Normal World user-space applications and the Secure World’s Trusted OS. Normal World applications interact with the TEE driver through standard Linux device interfaces, most commonly by opening a device file (e.g., /dev/tee, /dev/qcom_tz, /dev/optee) and issuing ioctl commands. These ioctl calls, along with their arguments, are then translated by the TEE driver into Secure Monitor Calls (SMCs) or other specific communication mechanisms understood by the Secure World firmware or Trusted OS.
Communication Flow Overview
- A Normal World application opens the TEE device file (e.g.,
open("/dev/tee", ...)). - The application issues an
ioctlcommand to the opened file descriptor, passing a command identifier and potentially input/output buffers. - The TEE driver (kernel module) receives this
ioctlcall. - The driver validates the command and prepares arguments for the Secure World.
- It initiates a Secure Monitor Call (SMC) to transition the CPU into the Secure World.
- The Secure World’s Trusted OS receives the SMC, dispatches it to the relevant Trusted Application.
- The TA processes the request, and results are returned via the Secure World, back through an SMC to the TEE driver, and finally to the Normal World application.
Static Analysis: Unveiling the Driver’s Secrets
Static analysis involves examining the TEE driver’s binary without executing it. The goal is to understand its internal structure, identify communication entry points, command identifiers, and the data structures used for communication.
Locating the TEE Driver
On Android devices, TEE drivers are typically found as kernel modules (`.ko` files) or embedded directly into the kernel image. Common names might include qcom_tz.ko, optee.ko, trusty.ko, or variations. You can search for them on a rooted device:
adb shell
find /lib/modules -name "*tee*.ko"
find /vendor/lib/modules -name "*tee*.ko"
# Or, look for device nodes:
ls -l /dev/tee*
ls -l /dev/tz*
ls -l /dev/qseecom
Decompilation and Disassembly
Once located, the driver binary can be loaded into reverse engineering tools like Ghidra, IDA Pro, or Binary Ninja. The primary focus should be the driver’s ioctl handler function, which is the gateway for Normal World communication. This function typically takes three arguments: the file inode, the command number, and a pointer to an argument structure.
Look for functions registered in the file_operations structure corresponding to the device node (e.g., .unlocked_ioctl = my_tee_ioctl). Inside the ioctl handler, you’ll often find a large switch-case statement or a series of `if/else if` blocks, where each case corresponds to a specific command ID.
long my_tee_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
// ... argument handling ...
switch (cmd) {
case TEE_CMD_OPEN_SESSION: // 0xXXXXXXXX
// Handle session opening logic
break;
case TEE_CMD_INVOKE_COMMAND: // 0xYYYYYYYY
// Process TA invocation
break;
// ... other commands ...
default:
// Unknown command
break;
}
// ... return value ...
}
Identifying Communication Primitives
Within each `ioctl` command handler, analyze how arguments are passed. Look for calls to kernel functions like `copy_from_user` or `copy_to_user`, which transfer data between user-space buffers (provided by `arg` in the `ioctl` call) and kernel-space buffers. These calls reveal the structure and size of the data being exchanged.
struct tee_invoke_arg {
__u32 uuid[4];
__u32 cmd_id;
__u32 num_params;
struct tee_param params[4];
// ... other fields ...
};
// Inside the TEE_CMD_INVOKE_COMMAND handler:
struct tee_invoke_arg *karg = kmalloc(sizeof(*karg), GFP_KERNEL);
if (copy_from_user(karg, (void __user *)arg, sizeof(*karg)))
{
// Handle error
}
// ... process karg ...
if (copy_to_user((void __user *)arg, karg, sizeof(*karg)))
{
// Handle error
}
By understanding these data structures and command IDs, you can start to reconstruct the ABI (Application Binary Interface) that Normal World applications use to communicate with Trusted Applications.
Dynamic Analysis: Observing Runtime Interactions
Dynamic analysis complements static analysis by allowing you to observe the TEE driver’s behavior during live execution. This is invaluable for validating static findings, understanding conditional logic, and capturing actual communication values.
Tracing User-Space Interactions
The `strace` utility is a powerful tool for monitoring system calls, including `ioctl`. By attaching `strace` to a Normal World application that interacts with TrustZone, you can observe the `ioctl` commands being sent to the TEE driver and the arguments passed.
adb shell
su # If needed for tracing system processes
strace -e ioctl -p 2>&1 | grep "tee"
# Example output:
# ioctl(4, _IOC(_IOC_READ|_IOC_WRITE, 'T', 0x01, 0x18), 0xbebfd000) = 0
The `_IOC` macro values reveal the command number (`cmd`) and argument size. You’ll need to correlate these with the command IDs identified during static analysis. The memory addresses (e.g., `0xbebfd000`) point to user-space buffers that contain the `ioctl` arguments. If possible, dump these memory regions to understand the actual data being exchanged.
Kernel-Level Tracing with ftrace or kprobes
For deeper insights, kernel tracing tools like `ftrace` or `kprobes` (if available on your kernel) can monitor specific functions within the TEE driver. This allows you to inspect arguments, return values, and execution flow directly within the kernel context.
adb shell
su
echo 'p:my_tee_ioctl my_tee_ioctl+0' > /sys/kernel/debug/tracing/kprobe_events
echo 1 > /sys/kernel/debug/tracing/events/kprobes/my_tee_ioctl/enable
echo 1 > /sys/kernel/debug/tracing/tracing_on
# Run your application
echo 0 > /sys/kernel/debug/tracing/tracing_on
cat /sys/kernel/debug/tracing/trace | grep "my_tee_ioctl"
This example would trace the entry point of `my_tee_ioctl`. You can also trace specific internal functions of the TEE driver to understand how it processes data or interacts with Secure Monitor Calls.
Advanced Debugging with JTAG/SWD
For the most in-depth analysis, especially when Secure World interactions are complex or require stepping through the Trusted OS, hardware debugging with JTAG or SWD can be invaluable. This provides low-level control over the CPU, allowing inspection of registers and memory across both Normal and Secure Worlds. However, this often requires specialized hardware and expertise, and is typically beyond pure software RE.
Reconstructing the Secure Protocol
The ultimate goal of TEE driver analysis is to reconstruct the communication protocol between the Normal World and the Trusted Applications. This involves merging insights from both static and dynamic analysis:
- Map Command IDs: Correlate the `ioctl` command IDs seen in `strace` (dynamic) with the handlers identified in the driver’s `ioctl` function (static).
- Decipher Data Structures: Based on `copy_from_user`/`copy_to_user` calls (static) and dumped user-space buffers (dynamic), precisely define the data structures exchanged for each command. Identify fields for input parameters, output results, and status codes.
- Identify Callbacks/Shared Memory: Some TEE implementations use shared memory (e.g., `mmap`) or asynchronous callbacks. Analyze the driver’s `mmap` handler and any asynchronous notification mechanisms to fully understand the communication channels.
- Infer TA Functionality: Once the NW-SW interface is understood, you can infer the capabilities and logic of the corresponding Trusted Applications. For instance, an `ioctl` command passing encrypted data and returning a hash likely indicates a cryptographic signing TA.
By carefully piecing together these details, a comprehensive understanding of the secure protocol can be achieved, enabling further security research, vulnerability discovery, or the development of custom Normal World clients for TAs.
Conclusion
Mastering TrustZone reverse engineering through TEE driver analysis is a sophisticated skill that bridges kernel-level understanding with secure environment interactions. By combining the precision of static analysis tools like Ghidra and IDA Pro with the runtime insights from dynamic techniques such as `strace` and `ftrace`, security researchers can effectively dissect the complex communication protocols that underpin the TrustZone ecosystem. This capability is essential for anyone looking to delve into the heart of device security, uncover hidden functionalities, or contribute to the robust defense of trusted execution environments.
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 →