4、搭建第一个Avatar2+QEMU实验环境

好,咱们正式开始动手了。

前面聊了那么多概念,说实话,不亲手跑一遍,你很难真正理解Avatar2到底在干什么。我个人习惯是,学一个新框架,先让它在屏幕上亮起来,哪怕只是点个灯。这比看十篇文档都管用。

这一章,我们就来搭一个最简的实验环境。目标设备选STM32F4,QEMU里用它的机器模型,然后写一个Avatar2脚本把它们连起来。嗯,听起来步骤不少,但拆开看,其实就三件事。

4.1 为什么选STM32F4?

你可能会问,为什么是STM32F4,而不是别的?

原因很简单:QEMU对它的支持最成熟。我在项目中试过用Avatar2连一些冷门芯片,光是调QEMU的机器模型就花了两天。STM32F4不一样,QEMU里直接有stm32f4xx-soc模型,外设模拟得也比较全。说白了,就是拿来就能用,不用你自己去折腾底层。

另外,STM32F4的文档公开、资料多。你遇到问题,网上随便一搜就有答案。这对初学者来说,太重要了。

核心思路: Avatar2作为中间人,一端连QEMU模拟器,另一端连你的分析脚本。QEMU负责运行固件,Avatar2负责拦截和转发硬件访问。

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。

注意: 如果你在Windows上跑,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_memorywrite_memory要求地址对齐到4字节。如果你读一个奇数地址,会报错。解决办法是读4字节然后自己移位。
  • 权限问题:Linux下如果QEMU启动失败,检查一下/dev/kvm的权限。有时候需要sudo
调试技巧: 如果脚本跑不起来,先单独启动QEMU看看固件能不能正常运行。用 qemu-system-arm -M stm32f4xx -kernel blinky.bin -nographic -d in_asm 可以打印执行的指令,帮你定位问题。

4.6 本章知识体系

下面这张图,帮你理清整个流程:

Avatar2 + QEMU 实验环境架构 Avatar2 脚本 Python 控制层 read_memory() write_memory() 通信层 GDB stub / QMP TCP 连接 协议转换 QEMU 模拟器 stm32f4xx 模型 固件执行 外设模拟 工作流程: 1. Avatar2 脚本启动 QEMU 进程 2. QEMU 加载并执行固件 3. Avatar2 通过 GDB stub 读写内存/寄存器 4. 脚本分析硬件行为,实现逆向分析 关键: Avatar2 不修改固件 只做中间人拦截

看到这张图,你应该能理解:Avatar2其实是个中间人。它不修改你的固件,只是在固件访问硬件的时候,帮你把请求截下来,让你能看清楚每一步发生了什么。这就是硬件逆向的核心——观察和控制

好了,环境搭好了,脚本也能跑了。下一章,我们会在这个基础上,做点真正有意思的事——比如动态修改寄存器,或者Hook某个外设的访问。嗯,先别急,先把今天的内容消化掉。

总结一下:
  • 选STM32F4是因为QEMU支持好,文档多
  • QEMU启动要指定机器模型和固件路径
  • Avatar2脚本的核心是创建Avatar、添加QEMU后端、加载固件、启动
  • 验证环境是否通,读一个寄存器值看看
  • 注意QEMU版本、固件格式、内存对齐这些坑

公众号:蓝海资料掘金营,微信deep3321