界面已本地化;技能介绍与说明文档按所选语言由 AI 自动翻译,代码、命令、路径和专业术语保留原文,以原作者文档为准。
探索技能
效率与协作 · SKILL.md

re-arm

re-arm

【效率与协作】ARM 架构逆向(非 Android):Cortex-M/A 向量表、Thumb/ARM 切换、AAPCS 调用约定、位置相关代码重定位、MMIO 外设寄存器交叉。 触发词:ARM、arm32、Cortex-M、Cortex-A、Thumb、AAPCS、嵌入式逆向、裸机固件、stm32、向量表。

独立 GitHub 快照 · 自动分类 · 以原作者文档为准

Ddslsdzc / rev-skillsApache-2.0

技能说明

GitHub

name: re-arm description: > ARM 架构逆向(非 Android):Cortex-M/A 向量表、Thumb/ARM 切换、AAPCS 调用约定、位置相关代码重定位、MMIO 外设寄存器交叉。 触发词:ARM、arm32、Cortex-M、Cortex-A、Thumb、AAPCS、嵌入式逆向、裸机固件、stm32、向量表。 capabilities: [arch-analysis]

ARM 架构逆向(Cortex-M/A、Thumb、AAPCS)

何时使用 / 何时不用

  • 用:非 Android 场景的 ARM——嵌入式裸机/固件(Cortex-M0/M3/M4/M7、Cortex-A)
  • 用:Thumb/ARM 指令集切换、AAPCS 调用约定识别、M 系向量表入口定位、位置相关代码重定位
  • 用:MMIO 外设寄存器交叉分析(0x4000xxxx 外设区、LDR 立即数池)
  • 不用:Android .so(走 [[re-android-native]])
  • 不用:通用 AArch64 Linux 用户态程序(走 [[re-binary-core]] 通用底座,本技能只补 ARM 特有语义)
  • 不用:只需解包固件看内容([[re-fw-extract]])/ 分析文件系统配置([[re-fw-rootfs]])/ 整体启动固件([[re-fw-emulate]])
  • 不用:RISC-V / x86 等其他架构(走 [[re-binary-core]])

工具准备

所有工具先验证再使用。静态分析可免沙箱;qemu 动态执行默认沙箱 + 网络隔离([[re-analyze/platform-tips]] 最高原则)。

反编译器(Ghidra / IDA 任选其一)

  • Ghidra(内置 ARM 处理器模块,Cortex-M0/M0+/M3/M4/M7 选 Cortex 变体(v8-m 仅 M23/M33/M55)、Cortex-A 选 v7,均为小端 LE):
    • Linux: 官方 release 包(需 JDK);部分发行版仓库有 apt install ghidra / pacman -S ghidra
    • macOS: brew install --cask ghidra;Windows: 官方 zip
    • 验证: analyzeHeadless -help(headless 模式)或 GUI 导入 ARM ELF
    • Ghidra 按 Thumb 函数位 0(TMode)自动切换 Thumb/ARM 反汇编;导入时核对处理器变体与端序
  • IDA:商业版含 ARM32/ARM64 + Thumb 模块;Freeware 版架构支持范围以官方页面为准
  • 导入时确认架构(ARM vs AArch64)与变体——Cortex-M0/M0+ 实现 Armv6-M:以 16 位 Thumb 编码为主,但含少量合法 32 位编码(BL、DMB/DSB/ISB、MRS/MSR);必须选 v6-M 变体,只有出现 Armv6-M 之外的编码(MOVW/MOVT、CLZ、硬件除/取模)才说明变体选错

readelf / file —— 架构与字节序确认(binutils)

  • Linux: binutils 自带(apt install binutils 等);macOS: brew install binutils 或 LLVM 系 readelf;Windows: WSL 内
  • 验证: readelf --version

binwalk —— 固件与内嵌文件扫描

  • 安装渠道与版本分辨(v3 主线 / v2 legacy)见 [[re-fw-extract]] 的 binwalk/unblob 安装矩阵
  • 验证: binwalk --version、unblob --version

qemu-arm / qemu-aarch64 —— ARM 用户态仿真(动态验证)

  • Linux: apt install qemu-user / dnf install qemu-user / pacman -S qemu-user
  • macOS: brew install qemu(含用户态);Windows/WSL: WSL 内 Linux 版
  • 32 位为 qemu-arm、64 位为 qemu-aarch64;-L <rootfs> 指定动态库/链接器来源,-strace 跟踪系统调用
  • 验证: qemu-arm --version、qemu-aarch64 --version
  • 裸机 Cortex-M(无 OS 引导、向量表 0x0 起)qemu-user 不适用——用 qemu-system-arm -machine mps2-an385(Cortex-M3)等板级模型(-s -S 接 gdb),或 [[re-emulation]] 用 Unicorn 逐指令

gdb-multiarch —— 交叉调试

  • Linux: Debian/Ubuntu apt install gdb-multiarch(该包在 Debian/Ubuntu 及其衍生的官方仓库有);Fedora dnf install gdb(官方 gdb 支持多目标、可调裸机 ARM;arm-none-eabi-gdb 不在 Fedora 官方仓库,如需嵌入式专用工具链用官方预编译 ARM 工具链);Arch pacman -S gdb(官方 gdb 已内置 multiarch)
  • macOS: brew install gdb(需 Developer Tools 授权,见 [[re-analyze/platform-tips]] macOS 分支)或 WSL 内 Linux 版;Windows/WSL: WSL 内 Linux 版
  • 验证: Debian/Ubuntu gdb-multiarch --version;Fedora/Arch gdb --version;载入裸机固件后 set architecture armv7-m 再 file 加载,info registers 确认
  • 真机 SWD/JTAG 调试走 OpenOCD + gdb([[re-hardware-io]]);仿真目标用 qemu-system-arm -s -S 或 qemu-arm -g <port>

操作步骤

按顺序执行,每步结果存档;动态执行默认沙箱。

  1. 架构与字节序确认:

    file target.bin                 # "ARM"=32 位(含 Thumb)、"AArch64"=64 位;MSB/LSB 端序提示
    file target.elf
    readelf -h target.elf           # Machine=ARM / AArch64;Data=little-endian / big-endian
    readelf -A target.elf | head    # Tag_ABI_VFP_args: 1=硬浮点(HFABI)、0=软浮点;EABI 版本
    
    • e_flags 高位直接标识浮点 ABI:EF_ARM_ABI_FLOAT_HARD(0x400) 硬浮点 / EF_ARM_ABI_FLOAT_SOFT(0x200) 软浮点(对应坑 5)
    • 裸机 .bin(无 ELF 头)由步骤 2 的向量表/复位向量判定架构、端序与 Cortex-M 变体
  2. 入口定位:

    • Cortex-M 向量表(默认 0x0 起,4 字节/项,地址 = 异常号×4):
      • 第 1 项(offset 0x0):初始 MSP 值——指向 RAM 区(0x20000000 附近),可反向佐证加载基址
      • 第 2 项(offset 0x4):Reset handler(复位后执行的第一条指令)
      • 其后 NMI(0x8)、HardFault(0xC)……每项地址 LSB 必须为 1(Thumb);LSB=0 触发 INVSTATE 进 HardFault
      • VTOR(0xE000ED08)可重定位向量表,固件可能把表放 RAM 或其他 flash 地址,先按 0x0 试,不成立再搜 MSP/Reset 组合
    • Cortex-A 启动代码——先分清 AArch32 / AArch64,两套异常模型不同:
      • AArch32(Armv7-A 经典模型):复位向量 0x00000000(或高端向量 0xFFFF0000,取决于架构版本的控制位),异常向量表 8 项(Reset/Undef/SWI/Prefetch Abort/Data Abort/Reserved/IRQ/FIQ)
      • AArch64(Armv8-A)没有这套固定 8 项表:异常向量基址来自各 EL 的 VBAR_EL1/EL2/EL3,每张表 16 个 slot、间隔 0x80(覆盖 +0x000..+0x780),按 当前 EL / 低 EL × SP_EL0 / SP_ELx × AArch64 / AArch32 来源 × Synchronous/IRQ/FIQ/SError 分类;VBAR_ELx 复位后为 undefined,须由启动代码显式配置——不要用 0x00000000 / 0xFFFF0000 这套 low/high vectors 规则去找 AArch64 异常表
      • 逆向裸机 Armv8-A 固件时,定位异常表的可靠线索是 msr vbar_el1/el2/el3, Xt 及其地址构造序列;reset entry 本身由实现/SoC 启动配置决定(平台可提供 core reset vector base 配置),不能假定固定在 0
      • 启动流程一般为 设栈指针 → 拷贝 .data → 清零 .bss → 配置 MMU/时钟 → 跳 main;带引导链的(boot ROM → BL1/BL2 → u-boot/EL3 固件)按链逐级衔接 [[re-fw-rootfs]]
    • 定位复位向量后在反编译器中标注入口,沿调用链展开主逻辑
  3. AArch32 Thumb/T32 函数边界(Thumb/ARM 切换):

    • Thumb 函数地址 LSB=1:ELF 符号(nm / readelf -s 中 Thumb 函数为奇地址)、BL 目标、向量表项、虚表函数指针项都带 Thumb 位
    • bx rN / blx rN:按目标寄存器位 0 切换状态(1=Thumb);blx 立即数形式直接切到 ARM 态目标
    • BL 立即数距离上限:ARM 态 ±32MB、Thumb 态 ±16MB(BL 为 32 位编码,Armv6-M 虽无完整 Thumb-2 亦支持 BL;实际可达范围按具体编码/重定位如 R_ARM_THM_CALL 判定,超距由链接器插 veneer);超限时链接器插入 veneer 跳板(形态如 ldr pc, =addr、movw+movt+bx),看到片状跳板序列不要当业务逻辑;veneer 也用于 ARM↔Thumb 状态切换
    • 剥离符号的二进制:binutils 靠映射符号 $a/$t/$d 决定反汇编状态,映射符号丢失后会按错误状态反汇编——用 arm-none-eabi-objdump -d -M force-thumb 强制 Thumb,或交给 Ghidra 按 TMode 启发式切换
    • 用步骤 6 的立即数池与字符串引用交叉验证函数边界
  4. 基址 / 重定位(加载地址 vs 链接地址):

    • 固件链接基址与提取物实际加载位置常不一致(例:链接 0x08000000 的 flash 固件被从外部存储地址提取;向量表第 1 项 MSP 指向的 RAM 范围可佐证)
    • 确定链接基址(复位向量推算 / 立即数池内字符串引用 / 跳转表交叉)→ 计算偏移 → Ghidra "Memory Map → Set Image Base" 或 IDA "Edit → Segments → Rebase program" 整体修正 → 修正后字符串、立即数池、跳转表全部对齐
    • 有 ELF 时对比 LMA/VMA(readelf -l):VMA 即链接地址;加载地址看 LMA / p_paddr
    • 先修正再反编译,否则大量引用错位、反编译面目全非
  5. AAPCS 调用约定识别:

    • 32 位(AAPCS32):r0-r3 传前 4 参(多余参数栈传)、返回值 r0、64 位参数占 r0:r1 等偶对;r4-r8/r10-r11 被调用者保存、r9 平台相关;r12(IP) 调用内临时、r13(SP)、r14(LR) 返回地址、r15(PC);公共接口 8 字节栈对齐
    • 函数序言特征:push {r4-r11, lr},尾部 pop {..., pc}(借 LR 同时恢复 PC);叶子函数(无嵌套调用)常不 push LR,直接 bx lr
    • HFABI(硬浮点):浮点参数走 VFP 寄存器 s0-s15 / d0-d7(双精度),结果 s0/d0;软浮点 ABI 浮点参数走 r0-r3;readelf -A 的 Tag_ABI_VFP_args 区分(混用时最易错,见坑 5)
    • 64 位(AAPCS64,AArch64):x0-x7 传参、x8 间接结果寄存器、x19-x28 被调用者保存、x29 帧指针、x30(LR)、SP 16 字节对齐;序言特征 stp x29, x30, [sp, #-16]!
    • ARM 上 C++ 产物(RTTI/虚表/异常)恢复交叉参考 [[re-cpp-abi]];AArch32 虚表中指向 Thumb/T32 函数的函数指针项同样带 Thumb 状态位(bit0=1,解析实际地址时清 bit0),AArch64 用 A64、无 Thumb 态,不适用此规则
  6. 外设寄存器交叉(MMIO xref):

    • Cortex-M 外设区 0x40000000-0x5FFFFFFF(具体外设基址如 0x4000xxxx、0x48000000 随器件而异),0xE0000000 起为 PPB(NVIC/SCB/SysTick);核对 SoC datasheet 的 memory map
    • 典型访问形态:ldr rN, [pc, #imm] 从函数尾部立即数池取地址 → str/ldr 访问外设;手工算池地址时注意 Thumb 态 PC=当前+4、ARM 态 +8,且按 4 字节对齐
    • 反编译器中把外设区标注为寄存器区(不是数据段)——防止误判(见坑 3);按 UART/GPIO/timer 等寄存器语义反推硬件行为,轮询 status/flag 位的循环就是与外设交互的握手/等待逻辑
    • 交叉验证:外设地址常量 + 初始化序列(时钟使能、GPIO 配置)对照厂商参考代码模式

跨域联合

  • [[re-binary-core]]:ARM ELF 通用初勘/反编译/调试底座([[re-ghidra]]、[[re-ida]]、[[re-gdb]]、[[re-radare2]] 照常使用)
  • [[re-firmware]]:固件类样本网关路径(re-firmware → re-fw-extract → re-fw-rootfs → 本技能)
  • [[re-fw-extract]] / [[re-fw-rootfs]]:固件解包与文件系统/配置分析前置
  • [[re-fw-emulate]]:整体启动固件或用户态运行 ARM 程序(qemu 系列)
  • [[re-rtos]]:MCU 固件内 RTOS(FreeRTOS 等)任务表/TCB 定位后按任务拆解反编译
  • [[re-hardware-io]]:JTAG/SWD 真机调试与 flash 读取(OpenOCD + gdb-multiarch)
  • [[re-emulation]]:无 qemu 场景用 Unicorn 模拟执行(裸机 Cortex-M 逐指令验证)
  • [[re-cpp-abi]]:ARM 上 C++ 产物 RTTI/异常/虚表恢复
  • [[re-variant]]:固件多版本对比与补丁 diff
  • [[re-sandbox]]:一切动态执行强制前置([[re-analyze/platform-tips]] 默认沙箱原则)
  • 配套:[[re-patching]](Thumb 字节补丁)、[[re-android-native]](Android .so 场景)

常见坑与陷阱

  • Thumb 奇地址误判(函数起点偏移 1 字节):现象——按符号或 BL 目标地址定位函数却从半条指令开始,反汇编全乱;原因——Thumb 函数地址 LSB=1 是模式位不是代码字节,直接当绝对地址用整体错位;对策——进函数/解跳转目标前把地址清位 0(&~1);手工算地址时以此为准(Ghidra/IDA 已自动处理)
  • BL/BLX 距离上限外的 veneer 混淆:现象——调用链里出现成片 movw/movt + bx、ldr pc 跳板,被当成业务逻辑分析;原因——BL 立即数只能覆盖 ARM ±32MB / Thumb ±16MB,超限链接器插 veneer;对策——识别短跳板形态(结尾 bx/ldr pc 且目标为远地址)后跳过,继续跟踪最终目标;跨状态(Thumb↔ARM)调用同样经 veneer
  • M 系外设映射区当数据段:现象——0x4000xxxx 区域被当"数据"且内容随机,外设读写被当普通内存访问分析不出语义;原因——MMIO 区是寄存器不是存储,读可改状态、写有副作用;对策——反编译器中标注为寄存器区,按 datasheet memory map + 寄存器表逐位还原语义,轮询 status 位识别握手/等待循环
  • R14(LR) 双用途反编译失真:现象——某些函数"没保存返回地址"却调用了子函数,调用图断裂;原因——LR 是寄存器不是专用返回栈,叶子函数可把它当普通临时寄存器,Cortex-M 异常入口的 LR 还可能是 EXC_RETURN 特殊值;对策——按 push {..., lr} 与 bl 前后 LR 赋值点重建调用关系;异常入口现场恢复序列(入栈 8 字 r0-r3/r12/LR/PC/xPSR,PC 在 [sp,#0x18])用于确认异常处理函数
  • HFABI 与软浮点混用时参数解读错误:现象——函数首参是 float,反编译却从 r0 取,值全不对;原因——ABI 判定错(把 softfp 当 hard,或反之);对策——readelf -A 的 Tag_ABI_VFP_args 与 e_flags 的 0x400/0x200 位定 ABI,再看参数实际落在哪组寄存器:首参从 s0-s15/d0-d7 收 = 硬浮点,从 r0-r3 收 = 软浮点。关键:-mfloat-abi=softfp 是"软浮点调用约定 + 允许 VFP 指令"——它照样发射 vmov/vadd/vcmp 等浮点指令,参数却仍走整数寄存器(实测:vmov s0, r1 把 r1 搬进 s0),所以浮点指令出现频率不能佐证 float ABI,只能佐证"代码用了 VFP"
  • Cortex-M0 变体选错(按指令宽度误判):现象——把 M0 固件按 v7/v8-M 反汇编,出现大量 Armv6-M 之外的 32 位指令;原因——ARMv6-M(M0/M0+)以 16 位 Thumb 编码为主,但并非「只有 16 位」:BL、DMB/DSB/ISB、MRS/MSR 就是合法的 32 位编码,仅按指令宽度判定会把合法编码当幻觉、也会漏掉真正的越界指令;对策——导入时确认变体(v6M vs v7/v8M),按具体 opcode 是否属于 Armv6-M 判定——出现 MOVW/MOVT、CLZ、硬件除/取模等 Armv6-M 之外的编码才说明变体选错
我的收藏