1、ARM基础与逆向环境搭建

大家好,我是你们的嵌入式安全讲师。今天咱们聊聊ARM固件逆向的起点——基础知识和环境搭建。说实话,这章内容看着基础,但却是整个课程的根基。我见过太多人一上来就急着分析固件,结果连ARM和x86的区别都说不清楚,最后卡在反汇编代码里出不来。

嗯,咱们慢慢来。先搞清楚ARM是什么,再动手搭环境。

ARM架构概述

ARM,全称Advanced RISC Machines。它是一家英国公司,专门设计低功耗处理器架构。你想想看,现在手机、路由器、智能手表、汽车ECU,甚至一些服务器,里面都有ARM的身影。

ARM是RISC(精简指令集)架构。什么意思?说白了,就是指令数量少,每条指令干的事也简单。但简单不代表弱,ARM通过流水线、分支预测这些技术,把效率拉得很高。

我个人习惯把ARM架构分成三个系列:

  • Cortex-A系列:应用处理器。跑Linux、Android这些大系统。比如手机SoC里的CPU核心。
  • Cortex-R系列:实时处理器。用于汽车、工业控制,要求响应快、可靠性高。
  • Cortex-M系列:微控制器。资源少、功耗低,适合物联网设备。比如STM32、nRF52这些。

做固件逆向,我们打交道最多的其实是Cortex-M系列。为什么?因为大多数嵌入式设备,比如智能灯泡、传感器、门锁,用的都是M系列芯片。它们固件小、结构简单,适合入门。

重要提醒:Cortex-M系列不支持MMU(内存管理单元),所以不能跑完整的Linux。但可以跑FreeRTOS、RT-Thread这类实时操作系统。逆向时要注意,固件里可能没有虚拟地址的概念,所有地址都是物理地址。

ARM与x86的区别

这个问题我面试时经常问。很多人答不上来,或者只记得「ARM省电,x86性能强」。其实从逆向角度看,有几个关键区别:

对比项 ARM x86
指令长度 固定4字节(ARM模式)或2字节(Thumb模式) 可变长度(1-15字节)
寄存器数量 16个通用寄存器(R0-R15) 8个通用寄存器(EAX、EBX等)
寻址方式 相对简单,多用Load/Store 复杂,支持内存直接操作
字节序 可配置(大端/小端),默认小端 固定小端
条件执行 大部分指令可带条件码 只有少数指令支持

我记得第一次逆向ARM固件时,看到一堆带条件后缀的指令(比如MOVNEADDEQ),整个人都懵了。后来才明白,这是ARM的特色——条件执行。一条指令可以带条件,只有满足条件才执行。这能减少分支跳转,提高效率。

还有一个坑:ARM有两种指令集模式——ARM模式和Thumb模式。ARM模式每条指令4字节,Thumb模式2字节。逆向时一定要搞清楚当前代码是哪种模式,否则反汇编出来全是乱码。

小技巧:在GDB里用info registers查看CPSR寄存器,第5位(T位)如果是1,表示当前是Thumb模式。我习惯在分析固件前,先扫一遍中断向量表,看看复位向量指向的地址是奇数还是偶数——奇数表示Thumb模式。

Cortex-M/A/R系列介绍

咱们再深入一点。这三个系列虽然都叫ARM,但内部差异很大。

Cortex-M:我最熟悉的系列。M0、M3、M4、M7,性能依次递增。M0只有Thumb指令集,M3以上支持硬件除法、位带操作。逆向M系列固件时,要注意它没有MMU,也没有MMU相关的异常处理。另外,M系列的中断向量表固定在0x00000000或0x08000000(取决于芯片)。

Cortex-A:跑Linux的大家伙。有MMU,有虚拟地址。逆向A系列固件时,你看到的地址可能是虚拟地址,需要结合页表才能找到物理地址。我在分析某款路由器固件时,就遇到过这种情况——明明看到代码在0x40000000,但实际物理地址在0x80000000。

Cortex-R:介于M和A之间。有MMU但通常不用,或者用简化版。主要用于汽车、工业领域。逆向R系列固件时,要注意它可能有双核锁步(lockstep)机制,两个核心执行相同代码,互相校验。这种固件里经常有冗余代码。

开发板与仿真器选择

做逆向,硬件工具少不了。我建议初学者准备一块开发板和一个仿真器。

开发板推荐

  • STM32F103C8T6(蓝色药丸板):便宜、资料多、Cortex-M3核心。入门首选。
  • STM32F407VET6:性能更强,带FPU,适合分析浮点运算。
  • nRF52840 DK:蓝牙芯片,适合物联网逆向。

仿真器推荐

  • J-Link EDU Mini:便宜好用,支持大多数ARM芯片。我一直在用。
  • ST-Link/V2:STM32开发板自带,免费。
  • OpenOCD + FT2232:开源方案,适合进阶玩家。

避坑指南:我曾经买过一个山寨J-Link,结果调试时经常断连,浪费了我整整两天时间。仿真器这东西,尽量买正版或口碑好的开源方案。省下的钱不够买时间的。

GDB调试环境搭建

GDB是逆向的瑞士军刀。咱们用arm-none-eabi-gdb这个版本。

安装步骤(以Ubuntu为例):

sudo apt-get install gdb-multiarch
# 或者
sudo apt-get install arm-none-eabi-gdb

验证安装:

arm-none-eabi-gdb --version

启动GDB并连接目标板:

arm-none-eabi-gdb
(gdb) target remote localhost:3333
(gdb) monitor reset halt
(gdb) load firmware.elf
(gdb) continue

我个人习惯在GDB里设置几个常用断点:

(gdb) break *0x08000000  # 在复位向量处断住
(gdb) break HardFault_Handler  # 捕获硬件错误
(gdb) watch *0x40000000  # 监视某个外设寄存器

嗯,这里要注意:GDB默认是ARM模式反汇编。如果目标代码是Thumb模式,要手动切换:

(gdb) set arm disassembler-style thumb
(gdb) disassemble 0x08001000

OpenOCD配置

OpenOCD是开源调试器,负责把GDB的命令翻译成JTAG/SWD信号。配置起来有点麻烦,但配好了很爽。

一个典型的配置文件(stm32f1.cfg):

source [find interface/stlink-v2.cfg]
source [find target/stm32f1x.cfg]

adapter speed 1000
reset_config srst_only

$_TARGETNAME configure -event gdb-attach {
    echo "GDB attached, halting target"
    halt
}

启动OpenOCD:

openocd -f stm32f1.cfg

然后另开一个终端,启动GDB连接:

arm-none-eabi-gdb
(gdb) target remote localhost:3333

小技巧:如果OpenOCD连接不上,先检查接线。SWD只需要4根线:SWDIO、SWCLK、GND、VCC(可选)。我遇到过好几次,都是因为杜邦线接触不良。用万用表量一下,最靠谱。

固件获取方式

终于到了最核心的部分——怎么拿到固件?

常见方式有:

  1. 直接从芯片读取:用OpenOCD或J-Link连接芯片,执行dump_image命令。前提是芯片没有读保护。
  2. 从固件升级包提取:很多设备提供.bin或.hex升级文件。用binwalk或hexdump分析。
  3. 从Flash芯片拆焊读取:如果芯片有读保护,可以拆下Flash芯片,用编程器读取。比如W25Q64这类SPI Flash。
  4. 从OTA流量抓取:用Wireshark抓取设备升级时的网络流量,提取固件。

我记得有一次,客户拿来一个智能门锁,芯片有读保护。我试了各种方法都不行。最后拆开外壳,发现Flash芯片是独立的W25Q32。用热风枪吹下来,放到编程器上,几秒钟就读出来了。嗯,有时候暴力拆解比软件破解更有效。

用OpenOCD读取固件的命令:

# 先连接
openocd -f stm32f1.cfg
# 在telnet或GDB里执行
flash read_bank 0 firmware.bin 0x08000000 0x10000

或者用J-Link:

JLinkExe
> connect
> savebin firmware.bin 0x08000000 0x10000

重要提醒:读取固件前,一定要确认芯片的Flash起始地址和大小。STM32F103一般是0x08000000,大小64KB到512KB不等。读错了地址,出来的全是0xFF或0x00,白忙活。

知识体系图

下面这张图,是我自己整理的ARM固件逆向知识体系。你可以把它当作学习路线图:

ARM固件逆向知识体系 第1层:ARM基础与架构 第2层:逆向环境搭建 第3层:逆向工具链(IDA/Ghidra/Radare2) 第4层:固件分析与漏洞挖掘 第5层:实战项目与CTF挑战 ARM vs x86 Cortex-M/A/R 指令集模式 寄存器与寻址 GDB调试 OpenOCD配置 仿真器选择 固件获取

这张图展示了咱们课程的整体脉络。你现在站在第1层,别急,打好基础,后面几层会越来越有意思。


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