4、搭建第一个Avatar2+QEMU实验环境
好,咱们正式开始动手了。
前面聊了那么多概念,说实话,不亲手跑一遍,你很难真正理解Avatar2到底在干什么。我个人习惯是,学一个新框架,先让它在屏幕上亮起来,哪怕只是点个灯。这比看十篇文档都管用。
这一章,我们就来搭一个最简的实验环境。目标设备选STM32F4,QEMU里用它的机器模型,然后写一个Avatar2脚本把它们连起来。嗯,听起来步骤不少,但拆开看,其实就三件事。
4.1 为什么选STM32F4?
你可能会问,为什么是STM32F4,而不是别的?
原因很简单:QEMU对它的支持最成熟。我在项目中试过用Avatar2连一些冷门芯片,光是调QEMU的机器模型就花了两天。STM32F4不一样,QEMU里直接有stm32f4xx-soc模型,外设模拟得也比较全。说白了,就是拿来就能用,不用你自己去折腾底层。
另外,STM32F4的文档公开、资料多。你遇到问题,网上随便一搜就有答案。这对初学者来说,太重要了。
4.2 配置QEMU机器模型
先别急着写脚本。我们得先把QEMU这头搞定。
QEMU里模拟STM32F4,用的是stm32f4xx-soc这个机器。启动时,你需要指定几个关键参数:
- -M:指定机器类型,比如
stm32f4xx - -kernel:你要运行的固件二进制文件
- -nographic:不用图形界面,纯命令行
- -serial mon:stdio:把串口输出重定向到终端
举个例子,假设你有一个编译好的固件叫blinky.bin,启动命令大概是这样:
qemu-system-arm -M stm32f4xx \
-kernel blinky.bin \
-nographic \
-serial mon:stdio
这里有个坑,我提醒一下。QEMU的STM32F4模型默认只模拟了部分外设,比如GPIO、UART、TIMER这些是有的,但像DCMI(数字摄像头接口)这种就不一定了。我曾经在项目里想模拟一个摄像头采集的流程,结果发现QEMU根本不支持,白折腾了半天。所以,动手之前,先查一下QEMU的文档,确认你要用的外设在不在支持列表里。
qemu-system-arm -M stm32f4xx -M ? 查看支持的机器列表。用 qemu-system-arm -M stm32f4xx -device ? 查看支持的外设。
4.3 编写Avatar2脚本连接QEMU
好,QEMU能跑了。现在轮到Avatar2登场。
Avatar2的核心是Avatar对象。你创建一个Avatar实例,然后往里面添加架构、目标机、QEMU后端,最后启动。听起来抽象,看代码就明白了。
下面是一个最简的脚本,我加了详细注释:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from avatar2 import *
# 1. 创建Avatar实例
# 这里指定架构为ARM,因为STM32F4是Cortex-M4
avatar = Avatar(arch=ARM)
# 2. 添加QEMU后端
# qemu_executable 指向你的qemu-system-arm路径
# machine 指定机器模型
qemu = avatar.add_qemu_target(
qemu_executable='qemu-system-arm',
machine='stm32f4xx'
)
# 3. 添加目标机(Target)
# 这里我们创建一个名为"stm32"的目标
target = avatar.add_target(
name='stm32',
backend=qemu
)
# 4. 加载固件
# 注意:固件路径要写对,建议用绝对路径
target.load_binary('blinky.bin')
# 5. 启动模拟
avatar.init_targets()
# 6. 跑起来!
avatar.start()
# 7. 可选:读取某个内存地址的值
# 比如读取0x40020014(GPIOA的ODR寄存器)
val = target.read_memory(0x40020014, 4)
print(f"GPIOA ODR = 0x{val:08x}")
# 8. 清理
avatar.shutdown()
这段代码跑起来,QEMU就会加载你的固件,然后开始执行。Avatar2在背后做了什么事?它其实启动了一个QEMU进程,然后通过QEMU的GDB stub或者QMP协议跟它通信。你脚本里的read_memory,最终会变成一条GDB命令发给QEMU。
qemu_executable要写成qemu-system-arm.exe。另外,确保QEMU在PATH环境变量里,或者写完整路径。
4.4 验证环境是否搭好
脚本写完了,怎么知道它真的在工作?
我个人习惯是,先跑一个最简单的固件,比如点灯程序。STM32F4的GPIO寄存器地址是固定的,你可以用Avatar2去读那个寄存器的值。如果读出来的值跟你预期的一致,说明环境通了。
举个例子,STM32F4的GPIOA基地址是0x40020000,ODR寄存器偏移是0x14。如果固件里把GPIOA的第5脚拉高了,那ODR寄存器的bit5应该是1。你可以在脚本里加一行:
odr = target.read_memory(0x40020014, 4)
if odr & (1 << 5):
print("LED is ON!")
else:
print("LED is OFF")
嗯,看到输出,你就知道Avatar2和QEMU真的连上了。
4.5 避坑指南
这条路我走过,有些坑提前告诉你:
- QEMU版本问题:老版本的QEMU可能没有
stm32f4xx模型。我建议用QEMU 5.0以上。我曾经在QEMU 2.8上折腾了半天,最后发现模型根本不存在。 - 固件格式:
load_binary只接受纯二进制文件。如果你的是ELF文件,需要先用objcopy转一下:arm-none-eabi-objcopy -O binary firmware.elf firmware.bin - 内存对齐:Avatar2的
read_memory和write_memory要求地址对齐到4字节。如果你读一个奇数地址,会报错。解决办法是读4字节然后自己移位。 - 权限问题:Linux下如果QEMU启动失败,检查一下
/dev/kvm的权限。有时候需要sudo。
qemu-system-arm -M stm32f4xx -kernel blinky.bin -nographic -d in_asm 可以打印执行的指令,帮你定位问题。
4.6 本章知识体系
下面这张图,帮你理清整个流程:
看到这张图,你应该能理解:Avatar2其实是个中间人。它不修改你的固件,只是在固件访问硬件的时候,帮你把请求截下来,让你能看清楚每一步发生了什么。这就是硬件逆向的核心——观察和控制。
好了,环境搭好了,脚本也能跑了。下一章,我们会在这个基础上,做点真正有意思的事——比如动态修改寄存器,或者Hook某个外设的访问。嗯,先别急,先把今天的内容消化掉。
- 选STM32F4是因为QEMU支持好,文档多
- QEMU启动要指定机器模型和固件路径
- Avatar2脚本的核心是创建Avatar、添加QEMU后端、加载固件、启动
- 验证环境是否通,读一个寄存器值看看
- 注意QEMU版本、固件格式、内存对齐这些坑
公众号:蓝海资料掘金营,微信deep3321