2. 开发环境搭建:搭建基于 Cortex-M33 的 QEMU 仿真环境、安装 ARM GCC 工具链

做嵌入式安全分析,第一件事不是买开发板,而是先把仿真环境搭好。我个人习惯在开始任何固件逆向项目之前,先确保能在 QEMU 里把目标跑起来。为什么?因为你手头可能没有物理板子,或者板子上的调试接口被锁了——我在项目中遇到过好几次这种情况。

这一章,我们就来搞定两件事:ARM GCC 工具链 和 基于 Cortex-M33 的 QEMU 仿真环境。说白了,就是让你的电脑能编译 TrustZone-M 固件,也能运行它。

2.1 ARM GCC 工具链安装

ARM GCC 是编译 ARM 裸机程序的核心工具。TrustZone-M 的固件通常用这套工具链编译,所以我们逆向分析时也得用它来编译测试样本。

2.1.1 下载与安装

我建议直接从 ARM 官方获取预编译的二进制包,省去自己编译的麻烦。你想想看,自己从源码编译 GCC 要花几个小时,何必呢?

  • 官方下载地址:developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain
  • 推荐版本:arm-none-eabi-gcc 10.3-2021.10 或更新版本
  • 平台支持:Windows、Linux、macOS

以 Ubuntu 20.04/22.04 为例,安装步骤很简单:

# 下载 x86_64 Linux 版本
wget https://developer.arm.com/-/media/Files/downloads/gnu-rm/10.3-2021.10/gcc-arm-none-eabi-10.3-2021.10-x86_64-linux.tar.bz2

# 解压到 /opt 目录
sudo tar xjf gcc-arm-none-eabi-10.3-2021.10-x86_64-linux.tar.bz2 -C /opt/

# 添加环境变量
echo 'export PATH=$PATH:/opt/gcc-arm-none-eabi-10.3-2021.10/bin' >> ~/.bashrc
source ~/.bashrc

# 验证安装
arm-none-eabi-gcc --version
小提示:如果你用的是 macOS,记得先安装 brew,然后 brew install arm-none-eabi-gcc 一步到位。Windows 用户直接下载 .exe 安装包,一路 Next 就行。

2.1.2 验证工具链

安装完成后,写一个最简单的 C 程序测试一下:

// test.c
int main(void) {
    volatile int a = 1;
    volatile int b = 2;
    volatile int c = a + b;
    return c;
}
arm-none-eabi-gcc -c -mcpu=cortex-m33 -mthumb -g -O0 test.c -o test.o
arm-none-eabi-objdump -d test.o

如果能正常反汇编出 Thumb 指令,说明工具链工作正常。嗯,这里要注意:-mcpu=cortex-m33 这个参数不能写错,否则编译器会生成不兼容的指令。

2.2 QEMU 仿真环境搭建

QEMU 是我们逆向分析 TrustZone-M 固件的主力仿真器。它支持 Cortex-M33 的完整指令集,包括 TrustZone 扩展。

2.2.1 为什么选 QEMU?

特性 QEMU 物理开发板
成本 免费 几百到几千元
调试能力 内置 GDB 服务器,断点/单步/内存查看 需要 JTAG/SWD 调试器
快照/回放 支持 -snapshot 模式,随时重置 不支持
TrustZone 支持 完整模拟 SAU/IDAU 和安全/非安全状态 取决于芯片型号
固件加载 直接加载 ELF/二进制文件 需要烧录工具

说白了,QEMU 就是我们逆向分析者的「沙盒」。你可以随意修改内存、打断点、甚至篡改固件逻辑,而不用担心把物理板子搞坏。

2.2.2 编译支持 Cortex-M33 的 QEMU

系统自带的 QEMU 版本可能比较老,不支持 Cortex-M33。我建议从源码编译最新版本:

# 安装依赖
sudo apt-get install git libglib2.0-dev libfdt-dev libpixman-1-dev zlib1g-dev ninja-build

# 克隆 QEMU 源码
git clone https://gitlab.com/qemu-project/qemu.git
cd qemu

# 切换到稳定分支(推荐 v7.2.0 或更新)
git checkout v7.2.0

# 配置编译选项,只编译 ARM 目标
./configure --target-list=arm-softmmu --enable-debug

# 编译(-j 后面的数字是你的 CPU 核心数)
make -j4

# 安装到 /usr/local
sudo make install
注意:编译 QEMU 需要至少 4GB 可用内存和 10GB 磁盘空间。我曾经在 2GB 内存的虚拟机上编译,结果卡了整整一下午。建议用物理机或者配置高一点的云服务器。

2.2.3 验证 QEMU 的 Cortex-M33 支持

编译完成后,检查 QEMU 是否支持 Cortex-M33 的机器模型:

qemu-system-arm -M help | grep mps3

如果输出中包含 mps3-an536 或 mps2-an505,说明你的 QEMU 支持 Cortex-M33。这两个模型都内置了 TrustZone 支持。

2.3 第一个 TrustZone-M 固件:Hello World

环境搭好了,我们来写一个最简单的 TrustZone-M 固件。这个固件会在安全世界和非安全世界之间切换,打印一条消息。

2.3.1 项目结构

trustzone_hello/
├── secure/
│   ├── secure_main.c
│   └── secure_entry.s
├── nonsecure/
│   ├── nonsecure_main.c
│   └── startup.s
├── linker/
│   ├── secure.ld
│   └── nonsecure.ld
└── Makefile

2.3.2 安全世界代码

// secure/secure_main.c
#include <stdint.h>

/* 非安全世界的函数声明 */
extern void nonsecure_main(void);

/* 安全入口函数 */
void SecureMain(void) {
    volatile uint32_t *sau_ctrl = (uint32_t *)0xE000DD00;
    
    /* 配置 SAU,将 0x00200000 开始的 4KB 设为非安全区域 */
    *((volatile uint32_t *)0xE000DD04) = 0x00200000;  // SAU_RBAR
    *((volatile uint32_t *)0xE000DD08) = 0x00000FFF;  // SAU_RLAR
    *((volatile uint32_t *)0xE000DD0C) = 0x00000001;  // SAU_RLAR (NS=1)
    
    /* 启用 SAU */
    *saue_ctrl = 0x1;
    
    /* 跳转到非安全世界 */
    nonsecure_main();
    
    while(1);
}

2.3.3 非安全世界代码

// nonsecure/nonsecure_main.c
#include <stdint.h>

/* 简单的 UART 输出模拟 */
void uart_send_string(const char *str) {
    volatile char *uart = (char *)0x40004000;
    while(*str) {
        *uart = *str++;
    }
}

void nonsecure_main(void) {
    const char *msg = "Hello from Non-Secure World!\r\n";
    uart_send_string(msg);
    
    while(1);
}

2.3.4 链接脚本

安全世界的链接脚本需要把代码放在安全区域:

/* linker/secure.ld */
MEMORY
{
    FLASH (rx)  : ORIGIN = 0x10000000, LENGTH = 64K
    RAM   (rwx) : ORIGIN = 0x30000000, LENGTH = 32K
}

SECTIONS
{
    .text : {
        *(.text*)
    } > FLASH
    
    .data : {
        *(.data*)
    } > RAM
    
    .bss : {
        *(.bss*)
    } > RAM
}

2.3.5 编译与运行

# 编译安全世界
arm-none-eabi-gcc -mcpu=cortex-m33 -mthumb -mfloat-abi=soft \
    -ffreestanding -O0 -g -c secure/secure_main.c -o secure_main.o
arm-none-eabi-as -mcpu=cortex-m33 -mthumb -g secure/secure_entry.s -o secure_entry.o
arm-none-eabi-ld -T linker/secure.ld secure_main.o secure_entry.o -o secure.elf

# 编译非安全世界
arm-none-eabi-gcc -mcpu=cortex-m33 -mthumb -mfloat-abi=soft \
    -ffreestanding -O0 -g -c nonsecure/nonsecure_main.c -o nonsecure_main.o
arm-none-eabi-as -mcpu=cortex-m33 -mthumb -g nonsecure/startup.s -o startup.o
arm-none-eabi-ld -T linker/nonsecure.ld nonsecure_main.o startup.o -o nonsecure.elf

# 合并两个 ELF 文件
arm-none-eabi-objcopy -O binary secure.elf secure.bin
arm-none-eabi-objcopy -O binary nonsecure.elf nonsecure.bin
cat secure.bin nonsecure.bin > combined.bin

# 在 QEMU 中运行
qemu-system-arm -M mps3-an536 -kernel combined.bin -nographic -serial mon:stdio

关键点:QEMU 的 mps3-an536 模型默认启用了 TrustZone 支持。SAU(安全属性单元)的配置决定了哪些内存区域是安全的,哪些是非安全的。在逆向分析时,你可以通过修改 SAU 配置来绕过安全限制——这是 TrustZone 攻击的常见入口点。

2.4 知识体系总览

下面这张图概括了本章搭建的开发环境整体架构:

TrustZone-M 开发环境架构图 TrustZone-M 开发环境架构 主机开发环境 Ubuntu 20.04/22.04 | macOS | Windows + WSL ARM GCC 工具链 arm-none-eabi-gcc | arm-none-eabi-ld | arm-none-eabi-objdump 编译 TrustZone-M 安全/非安全固件 QEMU 仿真器 qemu-system-arm -M mps3-an536 支持 Cortex-M33 + TrustZone + SAU 完整模拟 TrustZone-M 目标固件 安全世界 (Secure World) SAU 配置 | 安全入口 | 安全外设 非安全世界 (Non-Secure World) 用户应用 | 非安全外设 | 系统调用 💡 逆向分析流程:固件 → 反汇编 → QEMU 动态调试 → 安全机制分析

2.5 常见问题与避坑指南

搭建环境时,有几个坑我踩过,分享给你:

  • QEMU 启动后黑屏:检查 -serial mon:stdio 参数是否添加。没有这个参数,UART 输出不会显示在终端上。
  • GCC 编译报错 "selected processor does not support TrustZone":确认 -mcpu=cortex-m33 参数正确。Cortex-M0/M3/M4 不支持 TrustZone。
  • 固件加载后 QEMU 崩溃:检查链接脚本中的内存地址是否与 QEMU 模型匹配。mps3-an536 的 Flash 起始地址是 0x10000000,不是 0x00000000。
  • SAU 配置不生效:我曾经犯过一个低级错误——忘记在安全入口函数中启用 SAU。SAU 默认是关闭的,必须写 SAU_CTRL = 1 才能生效。
调试技巧:在 QEMU 启动时加上 -d in_asm,cpu 参数,可以实时打印执行的指令和 CPU 状态。这对于分析 TrustZone 状态切换非常有用。

环境搭好了,工具链也验证通过了。接下来你就可以开始真正的逆向分析了——从固件中提取 TrustZone 的安全配置,分析安全监控调用(SMC)的处理逻辑,甚至尝试绕过安全边界。这些内容,我们后面几章会逐一展开。