2、ARM架构基础:处理器模式、寄存器组、指令集概述、Thumb与Thumb-2模式
ARM架构,说实话,是异架构Pwn里绕不开的一座山。x86那套你玩得再溜,碰到ARM也得重新学起。我当年第一次接触ARM逆向时,看着那堆寄存器编号和奇怪的指令格式,头都是大的。但摸清楚之后你会发现,ARM的设计哲学其实很清晰——精简、规整、省电。
这一章我们只聊最核心的东西:处理器模式、寄存器组、指令集概览,以及Thumb/Thumb-2这两个你写shellcode时一定会碰到的模式。不扯虚的,全是实战用得上的。
2.1 ARM处理器模式
ARM处理器有好几种运行模式,但对我们写shellcode和做Pwn来说,真正需要关心的其实就两个:User模式和SVC模式(也叫Supervisor模式)。
| 模式 | 权限等级 | 典型用途 | Pwn场景 |
|---|---|---|---|
| User (USR) | PL0(最低) | 普通应用程序运行 | 你拿到的shellcode默认跑在这里 |
| Supervisor (SVC) | PL1(内核级) | 操作系统内核、异常处理 | 通过SVC指令陷入内核提权 |
| System (SYS) | PL1 | 运行特权任务,但使用User模式的寄存器组 | 偶尔在ROP链中见到 |
| 其他(FIQ、IRQ、Abort、Undef) | PL1 | 中断和异常处理 | shellcode一般不直接碰,但异常向量表是攻击面 |
2.2 寄存器组
ARM的寄存器比x86多得多,但别慌。我们只记那些写shellcode时真正会用到的。
ARM有16个通用寄存器(R0-R15),外加一个CPSR。其中R15就是PC(程序计数器),R14是LR(链接寄存器),R13是SP(栈指针)。
| 寄存器 | 别名 | 我的用法 | Pwn中的关键点 |
|---|---|---|---|
| R0-R3 | 参数寄存器 | 传参、返回值 | 函数调用时参数依次放入R0-R3,多余的压栈 |
| R4-R11 | 普通寄存器 | 保存局部变量 | 函数调用需保存/恢复,ROP里常用 |
| R12 | IP(Intra-Procedure-call) | 临时寄存器 | 链接器可能用它做跳板 |
| R13 | SP | 栈指针 | 栈溢出攻击的核心目标 |
| R14 | LR | 返回地址 | 函数返回时跳回LR指向的地址——ROP链的入口 |
| R15 | PC | 程序计数器 | 控制PC就等于控制执行流 |
| CPSR | 状态寄存器 | 标志位、模式位 | Thumb模式切换靠它(CPSR的T位) |
2.3 指令集概述
ARM指令集是固定长度的——ARM模式下每条指令4字节,Thumb模式下每条指令2字节(Thumb-2引入了混合长度,后面说)。这个特性对shellcode编写影响很大:你没法像x86那样用变长指令做混淆,但反过来,指令对齐更简单,不容易出现奇怪的解码错误。
我挑几个写shellcode时最常用的指令说一下:
- MOV:数据传送。注意ARM的立即数是有格式限制的(8位数值+4位循环移位),不是所有立即数都能直接写。
- LDR/STR:内存加载/存储。ARM是加载-存储架构,所有运算都在寄存器里做,不能直接操作内存。
- B/BL/BX:分支指令。B是跳转,BL是跳转并保存返回地址到LR,BX是跳转并切换指令集(根据目标地址的最低位判断是ARM还是Thumb)。
- SVC:软中断。这是shellcode调用系统调用的唯一途径。R7放系统调用号,R0-R6放参数。
- PUSH/POP:压栈出栈。ARM的PUSH/POP可以一次操作多个寄存器,非常方便。
; ARM模式下的shellcode片段:调用exit(0)
MOV R0, #0 ; 参数1:exit code = 0
MOV R7, #1 ; 系统调用号:exit (ARM Linux)
SVC 0 ; 触发软中断
关键区别:ARM Linux的系统调用号放在R7里,而x86 Linux放在EAX里。这个区别我刚开始总是记混,后来写了个小纸条贴在显示器上才改过来。
2.4 Thumb与Thumb-2模式
Thumb模式是ARM的一个“压缩”指令集。每条指令只有16位(2字节),代码密度更高,但功能也受限——比如不能直接访问所有寄存器,条件执行也基本被砍掉了。
Thumb-2是Thumb的增强版,引入了32位指令,让Thumb模式也能做ARM模式的大部分事情。现在的ARMv7以上芯片,基本都支持Thumb-2。
| 特性 | ARM模式 | Thumb模式 | Thumb-2模式 |
|---|---|---|---|
| 指令长度 | 固定4字节 | 固定2字节 | 混合(2或4字节) |
| 代码密度 | 低 | 高(约节省30%空间) | 高 |
| 寄存器访问 | 全部16个 | 仅R0-R7(低寄存器) | 全部16个 |
| 条件执行 | 几乎所有指令 | 仅分支指令 | 部分指令 |
| shellcode适用性 | 功能完整,但体积大 | 体积小,但写起来受限 | 推荐:兼顾体积和功能 |
切换Thumb模式的方法很简单:通过BX指令跳转到一个奇数地址。ARM的PC最低位(LSB)被用来指示指令集:0表示ARM模式,1表示Thumb模式。
; 从ARM模式切换到Thumb模式
ADR R0, thumb_code + 1 ; 目标地址+1,表示Thumb模式
BX R0
; 下面这段代码在Thumb模式下执行
.thumb
thumb_code:
MOV R0, #0
MOV R7, #1
SVC 0
2.5 知识结构总览
下面这张图把本章的核心知识点串起来了。你可以把它当作一个快速索引,以后写shellcode时拿不准了,回来扫一眼就行。
嗯,这一章的内容就这些。ARM架构的东西很多,但对我们写shellcode来说,上面这些已经够用了。处理器模式决定了你能做什么,寄存器组决定了你用什么干活,指令集决定了你怎么干活,Thumb/Thumb-2决定了你的shellcode能塞多小。