随着 Apple Silicon 和 ARM 服务器的普及,ARM64 逆向分析变得越来越重要。本文整理 ARM64(AArch64)汇编的核心知识,以及在 IDA 中分析 ARM64 程序的要点。
一、寄存器
ARM64 有 31 个通用寄存器,加上几个特殊寄存器:
通用寄存器
X0-X30:64 位通用寄存器W0-W30:对应的低 32 位(写入 W 寄存器会自动零扩展到 X 寄存器)X0-X7:函数参数和返回值X8:间接返回值寄存器(结构体返回)X9-X15:临时寄存器(caller-saved)X16-X17:intra-procedure-call 临时寄存器(linker 使用)X18:平台保留(iOS 上用于 TLS)X19-X28:callee-saved 寄存器X29(FP):帧指针X30(LR):链接寄存器,存储返回地址
特殊寄存器
SP:栈指针(不在 X0-X30 中,但可以被某些指令引用)PC:程序计数器(不可直接读写,通过分支指令间接修改)XZR/WZR:零寄存器,读取永远返回 0,写入被丢弃
条件标志(NZCV,在 PSTATE 寄存器中)
- N(Negative):结果为负
- Z(Zero):结果为零
- C(Carry):产生进位/借位
- V(oVerflow):有符号溢出
二、常用指令
数据传输
| 指令 | 说明 | 示例 |
|---|---|---|
| MOV | 寄存器间传值 | MOV X0, X1 |
| MOVZ | 立即数移入(零扩展) | MOVZ X0, #0x1234 |
| MOVK | 立即数移入(保留其他位) | MOVK X0, #0x5678, LSL #16 |
| LDR | 从内存加载 | LDR X0, [X1] / LDR X0, [X1, #8] |
| STR | 写入内存 | STR X0, [SP, #-16]! |
| LDP | 加载一对寄存器 | LDP X29, X30, [SP], #16 |
| STP | 存储一对寄存器 | STP X29, X30, [SP, #-16]! |
LDR/STR 的寻址模式是 ARM64 的一个重点:
[X1]:基址寻址[X1, #8]:基址 + 偏移[X1, #8]!:前索引(先加偏移再访问,X1 更新)[X1], #8:后索引(先访问再加偏移,X1 更新)[X1, X2]:寄存器偏移[X1, X2, LSL #3]:带移位的寄存器偏移
IDA 中看到的函数入口通常是:
STP X29, X30, [SP, #-0x20]! ; 保存 FP 和 LR,栈减 0x20
MOV X29, SP ; 设置新的帧指针
函数出口:
LDP X29, X30, [SP], #0x20 ; 恢复 FP 和 LR,栈加 0x20
RET ; 返回(跳转到 LR)
算术与逻辑
| 指令 | 说明 | 示例 |
|---|---|---|
| ADD | 加法 | ADD X0, X1, X2 / ADD X0, X0, #1 |
| SUB | 减法 | SUB SP, SP, #0x40 |
| MUL | 乘法 | MUL X0, X1, X2 |
| SDIV | 有符号除法 | SDIV X0, X1, X2 |
| AND | 按位与 | AND X0, X1, #0xFF |
| ORR | 按位或 | ORR X0, X1, X2 |
| EOR | 按位异或 | EOR X0, X0, X0 (清零) |
| LSL | 逻辑左移 | LSL X0, X1, #3 |
| LSR | 逻辑右移 | LSR X0, X1, #4 |
| ASR | 算术右移 | ASR X0, X1, #1 (有符号除2) |
ADDS、SUBS 是带标志位更新的版本。CMP 实际上是 SUBS 的别名(结果被丢弃),TST 是 ANDS 的别名。
分支
| 指令 | 说明 |
|---|---|
| B target | 无条件跳转 |
| BL target | 跳转并链接(函数调用,返回地址存入 LR) |
| BR Xn | 寄存器间接跳转 |
| BLR Xn | 寄存器间接调用 |
| RET | 返回(等价于 BR X30) |
| B.cond target | 条件跳转(EQ/NE/GT/LT/GE/LE/CS/CC 等) |
| CBZ Xn, target | 比较并跳转:Xn == 0 则跳转 |
| CBNZ Xn, target | 比较并跳转:Xn != 0 则跳转 |
| TBZ Xn, #bit, target | 测试位并跳转:第 bit 位为 0 则跳转 |
| TBNZ Xn, #bit, target | 测试位并跳转:第 bit 位为 1 则跳转 |
CBZ/CBNZ 非常常见,它把比较和跳转合并为一条指令。在 IDA 中经常看到:
CBZ X0, loc_dead ; if (ptr == NULL) goto error
TBZ/TBNZ 用于测试特定位,常见于标志位检查:
TBZ W8, #0, loc_skip ; if ((flags & 1) == 0) goto skip
三、函数调用约定(AAPCS64)
ARM64 的标准调用约定是 AAPCS64(ARM Architecture Procedure Call Standard for 64-bit):
参数传递
- 前 8 个整型/指针参数:
X0-X7 - 前 8 个浮点参数:
D0-D7(SIMD/FP 寄存器) - 多出的参数通过栈传递
返回值
- 整型/指针返回值:
X0(128 位用X0+X1) - 浮点返回值:
D0 - 大结构体:通过
X8指向的内存返回
寄存器保存规则
- Caller-saved(调用者保存):
X0-X15,X16-X17——被调用函数可以随意修改 - Callee-saved(被调用者保存):
X19-X28,X29(FP),X30(LR)——如果函数使用了这些寄存器,必须在返回前恢复
IDA 中识别函数参数的关键就是追踪 X0-X7 的赋值:
MOV X0, #0x10 ; size = 16
BL _malloc ; ptr = malloc(16)
MOV X19, X0 ; 保存返回值到 callee-saved 寄存器
MOV X0, X19 ; arg0 = ptr
MOV W1, #0 ; arg1 = 0
MOV X2, #0x10 ; arg2 = 16
BL _memset ; memset(ptr, 0, 16)
四、在 IDA 中分析 ARM64
识别函数序言/尾声
IDA 通常能自动识别 ARM64 函数边界,但有时需要手动干预。标准序言模式:
; 典型的函数序言
STP X29, X30, [SP, #var_s0]!
MOV X29, SP
SUB SP, SP, #local_size ; 分配局部变量空间
STP X19, X20, [SP, #...] ; 保存需要的 callee-saved 寄存器
如果看到 PACIASP / AUTIASP 指令,说明启用了指针认证(PAC),这是 ARM64e(iOS/macOS)的安全特性:
PACIASP ; 对 LR 做签名
STP X29, X30, [SP, #-0x10]!
MOV X29, SP
; ... 函数体 ...
LDP X29, X30, [SP], #0x10
AUTIASP ; 验证 LR 签名
RET
switch-case 的识别
ARM64 的 switch 通常编译为跳转表。IDA 中的典型模式:
CMP W8, #5 ; 检查 case 数量
B.HI default_case ; 超出范围走 default
ADRP X9, #jump_table@PAGE
ADD X9, X9, #jump_table@PAGEOFF
LDRSW X10, [X9, X8, LSL #2] ; 从跳转表加载偏移
ADD X9, X9, X10
BR X9 ; 间接跳转
如果 IDA 没有自动识别跳转表,可以在跳转表地址处手动指定为 dword 数组,然后在 BR 指令上右键设置跳转目标。
Objective-C 消息发送
在分析 iOS/macOS 程序时,Objective-C 的方法调用通过 objc_msgSend 进行:
ADRP X8, #selRef_init@PAGE
LDR X1, [X8, #selRef_init@PAGEOFF] ; SEL = @selector(init)
MOV X0, X19 ; self
BL _objc_msgSend ; [self init]
X0 是 self(对象),X1 是 selector(方法名),X2 起是方法参数。IDA 的 Objective-C 分析插件通常能自动标注这些调用。
ADRP + ADD 模式
这是 ARM64 中加载全局地址最常见的模式。由于 ARM64 指令固定 4 字节,不能直接编码 64 位地址,所以用两条指令组合:
ADRP X8, #aHelloWorld@PAGE ; 加载页基址(4KB 对齐)
ADD X8, X8, #aHelloWorld@PAGEOFF ; 加上页内偏移
; X8 现在指向 "Hello World" 字符串
IDA 通常会自动将这对指令合并显示为对符号的引用。
总结
ARM64 汇编相比 x86-64 更规整——固定长度指令、清晰的寄存器分配约定、简洁的寻址模式。在 IDA 中分析时,重点关注函数调用约定(参数在 X0-X7 中传递)、STP/LDP 的序言/尾声模式、ADRP+ADD 的地址加载,以及 CBZ/CBNZ/TBZ/TBNZ 这些带比较的条件跳转。掌握这些模式后,阅读 ARM64 反汇编的效率会大幅提升。