第2章 环境搭建(上):QEMU用户态模拟器与交叉编译工具链

做异架构Pwn,第一道坎就是环境。x86跑ARM、MIPS的二进制文件,靠的就是QEMU用户态模拟。说白了,就是让一个程序以为自己在ARM Linux上跑,实际上它在你的x86笔记本里。

我刚开始搞ARM Pwn的时候,也踩过不少坑。有一次折腾了一整天,就为了一个qemu-arm-static的版本问题。嗯,今天我把这些经验都整理出来,你照着做就行。

2.1 QEMU用户态模拟器安装

QEMU有两种模式:系统模拟(跑整个操作系统)和用户态模拟(只跑单个程序)。我们做Fuzz,只需要用户态模式。

2.1.1 安装方式对比

安装方式 命令 适用场景 我个人的建议
apt包管理器 sudo apt install qemu-user 快速上手,Ubuntu 20.04+ 新手首选,省心
源码编译 ./configure --target-list=arm-linux-user 需要特定版本或补丁 老手才推荐,编译要20分钟
Docker镜像 docker pull multiarch/qemu-user-static CI/CD或快速验证 团队协作时好用
小提示:我个人习惯用apt安装,然后配合qemu-user-static包里的binfmt支持。这样你直接./arm-binary就能跑,不用每次手动敲qemu-arm。

2.1.2 具体安装步骤(Ubuntu/Debian系)

# 更新包索引
sudo apt update

# 安装QEMU用户态模拟器
sudo apt install qemu-user qemu-user-static

# 验证安装
qemu-arm --version
# 输出示例:qemu-arm version 6.2.0 (qemu-6.2.0)

# 安装binfmt支持(自动注册)
sudo apt install binfmt-support

装完之后,你可以试试跑一个ARM的hello world。如果没有现成的,用我下面这个命令生成一个最小的测试文件:

# 写一个最简单的ARM汇编
echo 'mov r0, #1
ldr r1, =msg
mov r2, #13
mov r7, #4
svc #0
mov r7, #1
svc #0
msg: .ascii "Hello, ARM!\n"' > test.s

# 用交叉编译器编译(后面会装)
arm-linux-gnueabihf-as test.s -o test.o
arm-linux-gnueabihf-ld test.o -o test.arm

# 直接运行(binfmt已注册)
./test.arm
# 输出:Hello, ARM!
注意:如果你用的是WSL或者某些容器环境,binfmt可能不生效。这时候老老实实敲qemu-arm ./test.arm。我曾经在WSL1上折腾了俩小时,最后发现直接指定模拟器最稳。

2.2 交叉编译工具链安装

交叉编译,就是在x86上编译出ARM能跑的程序。我们做Fuzz的时候,经常需要自己编译目标程序,或者编译一些辅助的so库。

2.2.1 工具链选择

ARM的交叉编译工具链有好几种命名方式,我列个表帮你分清:

工具链名称 目标架构 浮点支持 典型用途
arm-linux-gnueabihf ARM 32位 硬件浮点(hf) 大多数现代ARM Linux设备
arm-linux-gnueabi ARM 32位 软件浮点(soft) 老旧设备或特殊内核
aarch64-linux-gnu ARM 64位 硬件浮点 树莓派3b+、手机、服务器
核心建议:做Pwn题,90%的情况用arm-linux-gnueabihf就够了。CTF里ARM的题基本都是32位带硬件浮点的。我见过有人装错了abi版本,编译出来的程序在QEMU里跑不起来,排查了半天才发现是浮点问题。

2.2.2 安装gcc-arm-linux-gnueabihf

# 安装交叉编译器
sudo apt install gcc-arm-linux-gnueabihf

# 安装交叉编译的binutils(汇编器、链接器等)
sudo apt install binutils-arm-linux-gnueabihf

# 验证安装
arm-linux-gnueabihf-gcc --version
# 输出示例:arm-linux-gnueabihf-gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0

# 测试编译一个C程序
echo '#include <stdio.h>
int main() {
    printf("Pwn is fun!\n");
    return 0;
}' > test.c

arm-linux-gnueabihf-gcc -static test.c -o test.arm

# 用QEMU运行
qemu-arm ./test.arm
# 输出:Pwn is fun!

2.2.3 安装其他架构工具链(备用)

做异架构Pwn,你迟早会遇到MIPS、PowerPC、RISC-V的题。我建议一次性装好,省得以后临时抱佛脚。

# MIPS 大端
sudo apt install gcc-mips-linux-gnu

# MIPS 小端
sudo apt install gcc-mipsel-linux-gnu

# PowerPC
sudo apt install gcc-powerpc-linux-gnu

# RISC-V 64位
sudo apt install gcc-riscv64-linux-gnu
经验之谈:MIPS的题在CTF里经常出现,而且大端小端都有。我建议你至少把mipsel(小端)装上,因为家用路由器基本都是小端MIPS。PowerPC现在见得少了,但万一遇到呢?

2.3 环境验证与常见问题

2.3.1 快速验证脚本

我写了个小脚本,一键验证你的环境是否就绪:

#!/bin/bash
# check_env.sh - 验证异架构Pwn环境

echo "=== 检查QEMU ==="
qemu-arm --version || echo "❌ qemu-arm 未安装"

echo "=== 检查交叉编译器 ==="
arm-linux-gnueabihf-gcc --version || echo "❌ arm-gcc 未安装"

echo "=== 编译并运行测试 ==="
cat > /tmp/test_pwn.c << 'EOF'
#include <stdio.h>
int main() {
    printf("Environment OK!\n");
    return 0;
}
EOF

arm-linux-gnueabihf-gcc -static /tmp/test_pwn.c -o /tmp/test_pwn.arm
qemu-arm /tmp/test_pwn.arm && echo "✅ 环境正常" || echo "❌ 运行失败"

2.3.2 我踩过的坑

  • 动态链接问题:编译时没加-static,QEMU找不到ARM的ld-linux.so。解决方案:要么静态编译,要么把ARM的sysroot挂载到QEMU的-L参数里。
  • 浮点ABI不匹配:用gnueabi编译的程序在gnueabihf的QEMU里跑,直接段错误。检查readelf -h里的Flags字段。
  • QEMU版本太老:Ubuntu 18.04自带的QEMU 2.11不支持某些ARMv8指令。升级到20.04或源码编译最新版。
避坑指南:我曾经在编译一个IoT设备的fuzz target时,忘了加-march=armv7-a,结果生成的代码用了armv5te的指令,在目标设备上跑不起来。记住:编译时明确指定架构版本!

2.4 知识体系总览

下面这张图,帮你理清本章的核心脉络:

异架构Pwn环境搭建知识体系 异架构Pwn环境搭建核心流程 x86宿主机 (Ubuntu 20.04+) QEMU用户态模拟器 交叉编译工具链 apt安装 qemu-user binfmt自动注册 qemu-arm 直接运行 arm-linux-gnueabihf-gcc -static 静态编译 -march=armv7-a 指定架构 最终目标:在x86上编译并运行ARM二进制

这张图的核心逻辑很简单:宿主机上装QEMU负责运行,装交叉编译器负责编译。两条线汇合到一起,你就能在x86上调试ARM程序了。

2.5 本章小结

环境搭建是异架构Pwn的第一步,也是最容易出问题的一步。我见过太多人卡在「装好了但跑不起来」这个阶段。记住三个要点:

  • QEMU版本别太老,至少6.0以上
  • 交叉编译加-static,省去动态链接的麻烦
  • 验证环境用小脚本,别等到做题才发现工具链有问题