📍 【基础概念 1-5】 | 逆向工程系列课程
ARM架构是移动设备和嵌入式系统的绝对主流——全球超过95%的智能手机运行ARM处理器。从Android逆向到iOS越狱分析,从IoT固件提取到汽车电子破解,ARM汇编都是不可或缺的技能。ARM处理器以低功耗、高性能著称,广泛应用于智能手机、平板电脑、物联网设备、汽车电子和服务器领域。
| 架构 | 位数 | 模式 | 典型设备 |
|---|---|---|---|
| ARMv7-A | 32位 | ARM/Thumb | 老旧Android、iPhone 4 |
| ARMv8-A | 64位(AArch64) | AArch64/AArch32 | 现代手机、Apple Silicon |
| ARMv8-M | 32位 | Thumb-2 | 微控制器、IoT设备 |
| ARMv9-A | 64位 | AArch64 | 最新旗舰手机、服务器 |
┌──────────────────────────────────────────────────────┐
│ 64位通用寄存器 X0-X30 (64位) / W0-W30 (低32位) │
├──────────────────────────────────────────────────────┤
│ X0-X7 : 函数参数 / 返回值 (X0/W0) │
│ X8 : 间接结果位置 / 系统调用号 │
│ X9-X15 : 临时寄存器(caller-saved) │
│ X16-X17 : IP0/IP1,链接器内部使用 │
│ X18 : 平台寄存器(OS特定用途) │
│ X19-X28 : 被调用者保存寄存器(callee-saved) │
│ X29/FP : 帧指针(Frame Pointer) │
│ X30/LR : 链接寄存器(Link Register/返回地址) │
│ SP : 栈指针(Stack Pointer) │
│ PC : 程序计数器(Program Counter) │
├──────────────────────────────────────────────────────┤
│ 特殊寄存器 │
│ NZCV : 条件标志(Negative/Zero/Carry/oVerflow) │
│ FPCR : 浮点控制寄存器 │
│ FPSR : 浮点状态寄存器 │
└──────────────────────────────────────────────────────┘
AArch64调用约定(AAPCS64):前8个整数参数通过X0-X7传递,返回值在X0中。这与x86-64的6参数限制不同,ARM可以传递更多寄存器参数。
# MOV——数据传送
MOV X0, #42 # X0 = 42 (16位立即数)
MOV X1, X0 # X1 = X0 (寄存器间传送)
MOVZ X0, #0x1234, LSL#16 # X0 = 0x12340000 (零扩展移位)
MOVK X0, #0x5678, LSL#32 # X0[32:47] = 0x5678 (保留其他位)
# ADD/SUB——加减法
ADD X0, X1, X2 # X0 = X1 + X2
ADD X0, X1, #10 # X0 = X1 + 10 (12位立即数)
SUB X0, X1, #8 # X0 = X1 - 8
SUB X0, X1, X2, LSL#2 # X0 = X1 - (X2 << 2) 移位加算术
# MUL/DIV——乘除法
MUL X0, X1, X2 # X0 = X1 * X2 (低64位)
SMADDL X0, W1, W2, X3 # X0 = X3 + (signed)W1*W2 乘加
SDIV X0, X1, X2 # X0 = X1 / X2 (有符号除法)
UDIV X0, X1, X2 # X0 = X1 / X2 (无符号除法)
# 位操作
AND X0, X1, X2 # X0 = X1 & X2 (按位与)
ORR X0, X1, X2 # X0 = X1 | X2 (按位或)
EOR X0, X1, X2 # X0 = X1 ^ X2 (按位异或)
MVN X0, X1 # X0 = ~X1 (按位取反)
# 基本加载/存储
LDR X0, [X1] # X0 = *X1 (加载64位)
STR X0, [X1] # *X1 = X0 (存储64位)
LDR W0, [X1] # W0 = *(uint32*)X1 (加载32位)
LDRB W0, [X1] # W0 = *(uint8*)X1 (加载8位)
LDRH W0, [X1] # W0 = *(uint16*)X1 (加载16位)
# 带偏移的加载/存储
LDR X0, [X1, #16] # X0 = *(X1+16) 常量偏移
LDR X0, [X1, X2] # X0 = *(X1+X2) 寄存器偏移
LDR X0, [X1, X2, LSL#3] # X0 = *(X1 + X2*8) 数组访问
# 前索引/后索引(自动更新基地址)
LDR X0, [X1, #8]! # X1+=8; X0=*X1 (前索引,先更新后访问)
LDR X0, [X1], #8 # X0=*X1; X1+=8 (后索引,先访问后更新)
# 成对操作(常用在函数序言/结语)
STP X29, X30, [SP, #-16]! # SP-=16; 存FP和LR (等效push)
LDP X29, X30, [SP], #16 # 加载FP和LR; SP+=16 (等效pop)
# 条件比较与零比较
CMP X0, #10 # 设置标志: X0 - 10
CBZ X0, label # if X0==0 then jump (零则跳转)
CBNZ X0, label # if X0!=0 then jump (非零则跳转)
TBZ X0, #3, label # if X0的第3位为0则跳转 (测试位)
TBNZ X0, #3, label # if X0的第3位为1则跳转
# 条件分支(基于NZCV标志)
B.EQ label # 相等则跳转 (Z==1)
B.NE label # 不等则跳转 (Z==0)
B.GT label # 大于则跳转 (signed)
B.LT label # 小于则跳转 (signed)
B.GE label # 大于等于 (signed)
B.HI label # 高于则跳转 (unsigned)
B.LS label # 低于等于 (unsigned)
# 函数调用与返回
BL func # X30=PC+4; PC=func (带链接的分支)
BLR Xn # X30=PC+4; PC=Xn (寄存器间接调用)
RET # PC=X30 (返回,等价于BR X30)
# 条件选择(ARM独有!x86没有等效指令)
CSEL X0, X1, X2, EQ # if EQ then X0=X1 else X0=X2
CSINC X0, X1, X2, NE # if NE then X0=X1 else X0=X2+1
CSINV X0, X1, X2, EQ # if EQ then X0=X1 else X0=~X2
# 安装AArch64和ARMv7交叉编译器
$ apt-get install -y gcc-aarch64-linux-gnu gcc-arm-linux-gnueabihf
✅ 验证通过:安装AArch64(64位)和ARMv7(32位)交叉编译器
$ aarch64-linux-gnu-gcc --version | head -1
aarch64-linux-gnu-gcc (Ubuntu 11.4.0) 11.4.0
$ arm-linux-gnueabihf-gcc --version | head -1
arm-linux-gnueabihf-gcc (Ubuntu 11.4.0) 11.4.0
$ cat > /tmp/test_arm64.c <<'CEOF'
int add(int a, int b) { return a + b; }
int max(int a, int b) { return a > b ? a : b; }
int sum(int *arr, int n) {
int s = 0;
for (int i = 0; i < n; i++) s += arr[i];
return s;
}
CEOF
$ aarch64-linux-gnu-gcc -O0 -o /tmp/test_arm64 /tmp/test_arm64.c
$ aarch64-linux-gnu-objdump -d /tmp/test_arm64 | grep -A15 '<add>'
0000000000400654 <add>:
400654: a9bf7bfd stp x29, x30, [sp, #-16]!
400658: 910003fd mov x29, sp
40065c: b9000fe0 str w0, [sp, #12]
400660: b9000be1 str w1, [sp, #8]
400664: b9400fe1 ldr w1, [sp, #12]
400668: b9400be0 ldr w0, [sp, #8]
40066c: 0b000020 add w0, w1, w0
400670: a8c17bfd ldp x29, x30, [sp], #16
400674: d65f03c0 ret
✅ 验证通过:ARM64使用stp/ldp保存/恢复FP和LR寄存器
$ aarch64-linux-gnu-gcc -O2 -o /tmp/test_arm64_o2 /tmp/test_arm64.c
$ aarch64-linux-gnu-objdump -d /tmp/test_arm64_o2 | grep -A5 '<add>'
0000000000400584 <add>:
400584: 0b010000 add w0, w0, w1
400588: d65f03c0 ret
✅ 验证通过:O2优化后ARM64的add仅2条指令
$ aarch64-linux-gnu-objdump -d /tmp/test_arm64_o2 | grep -A5 '<max>'
000000000040058c <max>:
40058c: 6b01001f cmp w0, w1
400590: 1a81b000 csel w0, w0, w1, gt
400594: d65f03c0 ret
✅ 验证通过:ARM64使用csel(条件选择)指令,一条完成if-else
$ arm-linux-gnueabihf-gcc -O2 -mthumb -o /tmp/test_thumb /tmp/test_arm64.c
$ arm-linux-gnueabihf-objdump -d /tmp/test_thumb | grep -A5 '<add>'
00010554 <add>:
10554: 4408 add r0, r1
10556: 4770 bx lr
✅ 验证通过:Thumb指令仅2字节(16位),代码密度更高
| 特征 | x86-64 | AArch64 |
|---|---|---|
| 指令长度 | 变长(1-15字节) | 固定4字节 |
| 参数传递 | RDI/RSI/RDX/RCX/R8/R9(6个) | X0-X7(8个) |
| 返回值 | RAX | X0/W0 |
| 返回地址 | 栈上(call自动push) | LR寄存器(X30) |
| 帧指针 | RBP | X29(FP) |
| 条件执行 | 条件跳转(jcc) | 条件分支(B.cc)+CSEL |
| 调用方式 | call/ret | BL/RET |
| 函数序言 | push rbp; mov rsp,rbp | stp x29,x30,[sp,#-16]! |
| 寻址模式 | 复杂(基址+索引*比例+偏移) | 简化(基址+偏移/索引*比例) |
# ARM64函数调用完整流程
caller:
# 1. 准备参数
MOV X0, #10 # 第1个参数
MOV X1, #20 # 第2个参数
# 2. 保存caller-saved寄存器(如果需要)
# 3. 调用函数
BL callee # X30 = 返回地址; 跳转
# 4. 使用返回值(在X0中)
callee:
# 函数序言(prologue)
STP X29, X30, [SP, #-32]! # 保存FP和LR,分配栈空间
MOV X29, SP # 设置帧指针
STP X19, X20, [SP, #16] # 保存callee-saved寄存器
# 函数体...
# 函数结语(epilogue)
LDP X19, X20, [SP, #16] # 恢复callee-saved寄存器
LDP X29, X30, [SP], #32 # 恢复FP和LR,释放栈空间
RET # 返回(PC = X30)
# Linux AArch64系统调用约定
# 系统调用号放入X8
# 参数依次X0-X5
# 使用SVC #0触发
# write(1, "Hello", 5)
MOV X0, #1 # fd = stdout
ADR X1, msg # buf = "Hello"
MOV X2, #5 # count = 5
MOV X8, #64 # sys_write = 64
SVC #0 # 触发系统调用
# exit(0)
MOV X0, #0 # status = 0
MOV X8, #93 # sys_exit = 93
SVC #0
qemu-aarch64 -L /usr/aarch64-linux-gnu/ ./program