Introduction to TrustZone and TEE Drivers
Modern mobile devices, particularly Android smartphones, heavily rely on Trusted Execution Environments (TEEs) to protect sensitive operations like biometric authentication, DRM, and secure key storage. Qualcomm’s Secure Execution Environment (QSEE) or Arm’s TrustZone technology underpins many of these TEEs. For reverse engineers and security researchers, understanding the communication protocols between the Android Rich Execution Environment (REE) and the TEE is paramount. This guide provides a professional approach to mapping these communication channels by analyzing TEE drivers using Ghidra and IDA Pro.
TEE drivers, residing in the Linux kernel on the REE side, act as the gateway to the Trusted Applications (TAs) or Trustlets executing within the TEE. These drivers expose an interface, typically via device files (e.g., /dev/qseecom), through which user-space applications (like qseecomd or other system services) can send commands to and receive data from the TEE. Reverse engineering these drivers reveals the structure of these commands, the parameters they accept, and the expected responses, which is crucial for identifying potential vulnerabilities or understanding proprietary secure features.
Setting Up Your Analysis Environment
Effective TEE driver reverse engineering requires a powerful disassembler/decompiler. Ghidra and IDA Pro are the industry standards, each with unique strengths.
Ghidra: The Open-Source Powerhouse
Ghidra excels with its integrated decompiler, capable of producing highly readable pseudo-C code, which is invaluable for understanding complex driver logic. Its scripting capabilities (Java/Python) allow for automation of repetitive tasks and custom analysis.
To set up Ghidra for TEE driver analysis:
- Acquire the target device’s kernel image or standalone TEE driver binary (often a kernel module, e.g.,
qseecom.ko). - Load the binary into Ghidra, specifying the correct architecture (typically AArch64/ARM64) and base address if it’s a kernel module (often
0xFFFFFFC000000000or similar for kernel space). - Allow Ghidra to perform initial analysis, paying close attention to function identification and data cross-references.
IDA Pro: The Industry Standard
IDA Pro, with its robust graphing capabilities and a vast array of plugins, provides excellent insights into control flow and function call hierarchies. Its decompiler (Hex-Rays) is often considered top-tier for ARM architectures.
To set up IDA Pro:
- Load the kernel image or driver binary into IDA Pro, again specifying the architecture and base address.
- IDA’s auto-analysis will identify functions and data. Utilize the functions window and cross-references (
xkey) extensively. - The Hex-Rays decompiler is crucial for quickly understanding the C-like logic of complex functions.
Identifying TEE Driver Entry Points and IOCTLs
The primary interface for user-space interaction with TEE drivers in Linux is through ioctl() calls. Therefore, the first step is to locate the driver’s ioctl handler function.
Locating the File Operations Structure
Linux device drivers register a file_operations structure, which contains pointers to functions like open, release, read, write, and, critically, unlocked_ioctl (or compat_ioctl for 32-bit ioctls on a 64-bit kernel). You can find this structure by searching for references to register_chrdev or other device registration functions.
In Ghidra, search for strings like "qseecom" to find relevant data structures or use the symbol table to locate known driver entry points. In IDA Pro, use the strings window or search for cross-references to the driver’s device name.
A typical file_operations structure might look like this:
static const struct file_operations qseecom_fops = {
.owner = THIS_MODULE,
.open = qseecom_open,
.release = qseecom_release,
.unlocked_ioctl = qseecom_unlocked_ioctl,
.compat_ioctl = qseecom_compat_ioctl,
.llseek = no_llseek,
};
Focus on the unlocked_ioctl and compat_ioctl functions, as these are the main entry points for TEE communication.
Dissecting IOCTL Handlers and Command IDs
Once you’ve identified the ioctl handler (e.g., qseecom_unlocked_ioctl), the real work begins. This function typically uses a large switch statement or a series of if-else if blocks to dispatch different IOCTL commands based on the cmd argument passed to ioctl().
Example IOCTL Handler Structure (Pseudo-C)
long qseecom_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
long ret = -EINVAL;
switch (cmd) {
case QSEECOM_IOCTL_LOAD_APP:
// Handler for loading a Trustlet
ret = qseecom_load_app_handler(file, (void __user *)arg);
break;
case QSEECOM_IOCTL_SEND_CMD:
// Handler for sending commands to a loaded Trustlet
ret = qseecom_send_command_handler(file, (void __user *)arg);
break;
case QSEECOM_IOCTL_START_APP:
// Handler for starting a Trustlet session
ret = qseecom_start_app_handler(file, (void __user *)arg);
break;
// ... many more commands ...
default:
pr_warn("Unknown QSEECOM IOCTL cmd: 0x%x", cmd);
break;
}
return ret;
}
Your goal is to map each cmd value (e.g., QSEECOM_IOCTL_SEND_CMD) to its corresponding handler function and understand the structure of the arg parameter.
Analyzing Command Structures and Data Flow
The arg parameter typically points to a user-space structure that the driver copies into kernel space using functions like copy_from_user(). By analyzing the `ioctl` handler and its sub-functions, you can reconstruct these structures.
- Identify
copy_from_user()/copy_to_user()calls: These calls are critical for understanding the layout of input and output buffers. The size argument to these functions directly indicates the size of the structure being copied. - Reconstruct Structures: Based on the access patterns (offsets, sizes of reads/writes) after a
copy_from_user()call, you can infer the members of the structure. Ghidra’s decompiler excels here; examine the pseudo-C code to see how local variables are populated from the copied buffer. - Follow Function Calls: Often, the
ioctlhandler will call helper functions that prepare data, interact with the actual TEE interface (e.g., `qseecom_send_service_cmd`), or handle shared memory allocations (e.g., `qseecom_qmi_send_data`).
For instance, an IOCTL to send a command might take a structure defining the Trustlet ID, command ID within the Trustlet, and pointers to input/output buffers for the actual payload:
struct qseecom_send_cmd_req {
unsigned int app_id;
unsigned int cmd_id;
unsigned int i_buf_size;
unsigned int o_buf_size;
void __user *i_buf;
void __user *o_buf;
};
The `cmd_id` in this structure is distinct from the IOCTL `cmd`. This inner `cmd_id` is what the Trustlet itself uses to dispatch functions. Mapping these `cmd_id`s to specific Trusted Application functionalities is the ultimate goal.
Mapping TrustZone Communication Protocols
After dissecting the IOCTL handlers and the structures they consume, you can begin to build a comprehensive map of the REE-TEE communication:
- IOCTL Command IDs (Outer Layer): These control fundamental operations like loading TAs, opening/closing sessions, and sending generic commands.
- Trustlet-Specific Command IDs (Inner Layer): When an IOCTL like
QSEECOM_IOCTL_SEND_CMDis used, its payload often contains another command ID that directly corresponds to a function within a specific Trustlet. This is where you connect the driver’s logic to the TA’s expected behavior. - Input/Output Buffer Structures: Document the expected format for input and output buffers for each Trustlet command. This often involves nested structures or raw byte buffers.
- Shared Memory Handlers: Note how shared memory is allocated and managed for larger data transfers, as this bypasses direct `copy_from_user/to_user` for performance.
Example Workflow to Map a Specific Trustlet Command:
- Identify an IOCTL command related to sending generic commands to a Trustlet (e.g.,
QSEECOM_IOCTL_SEND_CMD). - Decompile its handler function (e.g.,
qseecom_send_command_handler) in Ghidra/IDA. - Reconstruct the input structure (e.g.,
qseecom_send_cmd_req) by analyzing `copy_from_user` calls and subsequent member accesses. - Observe where `req->cmd_id`, `req->i_buf`, and `req->o_buf` are used. They are typically passed to an internal kernel function responsible for communicating with the TEE hardware or a specific QMI (Qualcomm Message Interface) service.
- If the `cmd_id` is passed directly to the TEE, you’ve found a direct mapping. If it’s part of a larger, serialized payload, you’ll need to analyze the serialization/marshaling logic.
Advanced Tips and Challenges
- Symbol Stripping: Production kernel modules often have symbols stripped. Ghidra’s auto-analysis and function signature matching can help recover some names. IDA’s FLIRT signatures are also useful.
- Obfuscation: Some drivers might employ anti-reverse engineering techniques. Look for indirect calls, control flow flattening, or custom obfuscation layers.
- Dynamic Analysis: Combine static analysis with dynamic tracing (e.g., using `strace` on user-space components, or kernel tracing tools) to observe IOCTL arguments in real-time.
- Cross-Referencing: Constantly use cross-references to find where functions are called from and where data structures are used. This helps in understanding the broader context.
- Documentation: Keep meticulous notes. The sheer volume of commands and structures can be overwhelming.
Conclusion
Reverse engineering TEE drivers is a complex but rewarding endeavor, essential for understanding the bedrock of device security. By leveraging the advanced capabilities of Ghidra and IDA Pro, and adopting a systematic approach to dissecting IOCTL handlers and command structures, you can successfully map the intricate communication protocols between the Android REE and the TrustZone TEE. This knowledge not only deepens your understanding of mobile security but also empowers you to identify new attack surfaces and develop robust defenses for critical systems.
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 →