re-arm について
re-armは、Cortex-M/Aの組み込み機器やファームウェアを分析するためのリバースエンジニアリングスキルです。アーキテクチャ判定、入口特定、呼び出し規約、MMIO解析を一連の手順にまとめています。
主な機能
- 01ARMアーキテクチャとエンディアンの確認
- 02ベクターテーブルとリセット入口の特定
- 03Thumb/ARM状態切り替えの解析
- 04AAPCS呼び出し規約の復元
- 05MMIOペリフェラルアクセスの追跡
活用シーン
- 01Cortex-Mベアメタルファームウェアの解析
- 02ARMファームウェア起動処理の確認
- 03関数の引数と戻り値の特定
- 04レジスターアクセスからペリフェラル動作を推定
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ dslsdzc/ rev-skills --skill re-armインストール後、Claude Code で/re-armを入力して読み込まれたか確認します。
詳細
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 反汇编;导入时核对处理器变体与端序
- Linux: 官方 release 包(需 JDK);部分发行版仓库有
- 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 及其衍生的官方仓库有);Fedoradnf install gdb(官方 gdb 支持多目标、可调裸机 ARM;arm-none-eabi-gdb 不在 Fedora 官方仓库,如需嵌入式专用工具链用官方预编译 ARM 工具链);Archpacman -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/Archgdb --version;载入裸机固件后set architecture armv7-m再file加载,info registers确认 - 真机 SWD/JTAG 调试走 OpenOCD + gdb([[re-hardware-io]]);仿真目标用
qemu-system-arm -s -S或qemu-arm -g <port>
操作手順
順番に実行し、各ステップの結果を保存します。動的実行はデフォルトでサンドボックス内で行います。
-
アーキテクチャとバイトオーダーの確認:
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 のバリアントを判定します。
- e_flags の上位ビットで浮動小数点 ABI を直接識別できます:
-
エントリーポイントの特定:
- Cortex-M ベクターテーブル(デフォルトは 0x0 から、1 項目 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 をカバー)で、Current EL / lower EL × SP_EL0 / SP_ELx × AArch64 / AArch32 の実行元 × Synchronous/IRQ/FIQ/SError に分類されます。VBAR_ELx はリセット後 undefined であり、起動コードで明示的に設定する必要があります——AArch64 の例外テーブルを探す際に、0x00000000 / 0xFFFF0000 の low/high vectors ルールを使わないでください。
- ベアメタル 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]]
- リセットベクターを特定したら、逆コンパイラーでエントリーポイントにラベルを付け、呼び出しチェーンをたどって主要なロジックを展開します。
- Cortex-M ベクターテーブル(デフォルトは 0x0 から、1 項目 4 バイト、アドレス = 例外番号×4):
-
AArch32 の Thumb/T32 関数境界(Thumb/ARM の切り替え):
- Thumb 関数のアドレスは LSB=1 です。ELF シンボル(
nm/readelf -sでは Thumb 関数は奇数アドレス)、BL のターゲット、ベクターテーブルの項目、仮想関数テーブルの関数ポインター項目はいずれも Thumb ビットを含みます。 bx rN/blx rN:ターゲットレジスターの bit 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 の即値プールと文字列参照を使って、関数境界を相互検証します。
- Thumb 関数のアドレスは LSB=1 です。ELF シンボル(
-
基底アドレス/再配置(ロードアドレスとリンクアドレス):
- ファームウェアのリンク基底アドレスと、抽出物の実際のロード位置が一致しないことはよくあります(例: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 で確認します。 - 逆コンパイルの前に修正してください。そうしないと、多くの参照がずれ、逆コンパイル結果が大きく崩れます。
-
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 も同時に復元)。葉関数(ネストした呼び出しがない関数)は LR を push せず、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 状態がないため、このルールは適用されません。
-
周辺機器レジスターとの相互参照(MMIO xref):
- Cortex-M の周辺機器領域は 0x40000000-0x5FFFFFFF(0x4000xxxx、0x48000000 などの具体的な周辺機器基底アドレスはデバイスによって異なります)、0xE0000000 以降は PPB(NVIC/SCB/SysTick)です。SoC のデータシートにあるメモリーマップと照合します。
- 典型的なアクセス形式:関数末尾の即値プールから
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 之外的编码才说明变体选错
コメント
Markdown 対応