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
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
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 知识体系总览
下面这张图概括了本章搭建的开发环境整体架构:
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才能生效。
-d in_asm,cpu 参数,可以实时打印执行的指令和 CPU 状态。这对于分析 TrustZone 状态切换非常有用。
环境搭好了,工具链也验证通过了。接下来你就可以开始真正的逆向分析了——从固件中提取 TrustZone 的安全配置,分析安全监控调用(SMC)的处理逻辑,甚至尝试绕过安全边界。这些内容,我们后面几章会逐一展开。