ARM下的寄存器
函数的 1 - 4 个参数分别由 R0 - R3 寄存器来保存,剩下的参数则是从右往左依次压入栈中
R0 同时还被用于存储函数的返回值
注意:虽然arm为32位架构,但是它和x86不同,它不是单纯使用栈来传递参数
R0 <-> rdi 以及 存储函数返回值
R1 <-> rsi
R2 <-> rdx
R3 <-> rcx
R7 -> 用于存放系统调用号
R11 -> ebp(FP)
R13 -> esp(SP)
R14(LP) -> 存放函数返回地址
R15(PC) -> eip 存储着当前指令往后两个指令
不使用ret,而是直接使用pop指令对PC进行赋值
ARM下的指令(主要记录x86_64架构下没有的指令)
LDR指令
LDR{条件} <目标寄存器>, <addr>
作用:
加载指定地址上的数据放入到指定寄存器中
举例:
LDR R0,[R1] ;将存储器地址为R1的字数据读入寄存器R0
LDR R0,[R1,R2] ;将存储器地址为R1+R2的字数据读入寄存器R0
LDR R0,[R1,#8] ;将存储器地址为R1+8的字数据读入寄存器R0
LDR R0,[R1,R2] ! ;将存储器地址为R1+R2的字数据读入寄存器R0,并将新地址R1+R2写入R1
LDR R0,[R1,#8] ! ;将存储器地址为R1+8的字数据读入寄存器R0,并将新地址R1+8写入R1
LDR R0,[R1],R2 ;将存储器地址为R1的字数据读入寄存器R0,并将新地址R1+R2写入R1
LDR R0,[R1,R2,LSL#2]! ;将存储器地址为R1+R2×4的字数据读入寄存器R0,并将新地址R1+R2×4写入R1
LDR R0,[R1],R2,LSL#2 ;将存储器地址为R1的字数据读入寄存器R0,并将新地址R1+R2×4写入R1
TEQ指令
TEQ{条件} 操作数1,操作数2
作用:
将操作数1和操作数2进行异或,并将结果存储在标志位Z中。若操作数1和2相等则Z=1若果不相等Z=0
举例:
TEQ R0, R1
STR指令
STR{条件} 源寄存器,<存储器地址>
作用:
STR指令用于从源寄存器中将一个32位的字数据传送到存储器中。该指令在程序设计中比较常用,且寻址方式灵活多样,使用方式可参考指令LDR
举例:
STR R0,[R1],#8 ;将R0中的字数据写入以R1为地址的存储器中,并将新地址R1+8写入R1
STR R0,[R1,#8] ;将R0中的字数据写入以R1+8为地址的存储器中
LDM(Load Multiple)与STM(Store Multiple)指令
LDM和STM指令(可以用于替代PUSH和POP,但在aarch64中被舍弃)
功能概述
- LDM:从连续内存地址加载多个数据到寄存器组。
- STM:将寄存器组中的多个数据存储到连续内存地址。
- 核心作用:批量数据传输,适用于数组操作、栈管理、函数调用时的现场保护等场景
寻址模式
模式 | 全称 | 操作描述 | 典型用途 |
---|---|---|---|
IA | Increment After | 加载/存储后地址递增4字节 | 正向遍历内存块 |
IB | Increment Before | 加载/存储前地址递增4字节 | 较少使用(ARM模式特有) |
DA | Decrement After | 加载/存储后地址递减4字节 | 反向遍历内存块 |
DB | Decrement Before | 加载/存储前地址递减4字节 | 栈操作(满递减栈) |
指令条件
cond
:条件码(如EQ、NE),控制指令是否执行
mode
:寻址模式(如IA、IB、DA、DB)或堆栈模式(如FD、ED)
Rn
:基址寄存器,存储内存起始地址
!
:可选后缀,表示更新基址寄存器(Rn)的值
register_list
:寄存器列表,用逗号分隔或范围表示(如{r0-r3, r5}
)
^
:特权模式标志(仅在系统模式下使用)
LDM指令
LDM{cond}<mode> Rn{!}, <register_list>{^}
举例:
LDMIA R0!, {r1-r4} ;从R0指向的地址依次加载到r1→r4,完成后R0 += 16
STM指令
STM{cond}<mode> Rn{!}, <register_list>{^}
举例:
STMDB R1!, {r5-r8} ;将r5→r8逆序存储到R1-16地址,完成后R1 -= 16
ARM支持四种堆栈类型,通过LDM/STM的不同模式实现
类型 | 全称 | 栈指针指向 | 操作指令 |
---|---|---|---|
FD | Full Descending | 最后一个入栈项 | STMDB (压栈) LDMIA (弹栈) |
ED | Empty Descending | 下一个空位 | STMDA (压栈) LDMIB (弹栈) |
FA | Full Ascending | 最后一个入栈项 | 极少使用 |
EA | Empty Ascending | 下一个空位 | 极少使用 |
默认使用FD栈,对应:
- 压栈:
STMDB SP!, {reglist}
(地址递减前存储) - 弹栈:
LDMIA SP!, {reglist}
(地址递增后加载)
PUSH与POP指令
功能概述
- PUSH:将寄存器内容压入栈顶
- POP:从栈顶恢复寄存器内容
- 本质:PUSH和POP是STM和LDM的语法糖,专为栈操作优化
条件
reglist
:支持 低寄存器(r0-r7)或 高寄存器(r8-r12, lr, pc等),具体取决于指令集(ARM/Thumb)
PUSH指令
PUSH {reglist} ;等效于 STMDB SP!, {reglist}
举例:
PUSH {r0-r3, lr} 压栈保存r0-r3和返回地址
POP指令
POP {reglist} 等效于 LDMIA SP!, {reglist}
举例:
POP {r0-r3, pc} 恢复r0-r3并跳转返回(将lr弹入pc)
栈操作细节
- 地址更新:每次压栈后SP减4×N字节(N为寄存器数量),弹栈后SP增4×N字节。
- Thumb模式限制:Thumb指令集的PUSH/POP仅支持低寄存器和特定高寄存器 (比如
lr
,pc
)
其他指令
add r1, r2, #2
相当于 r1 = r2 + 2
sub r1, r2, r3
相当于 r1 = r2 - r3
还有跳转指令B
相关的一些指令,相当于jmp
B Label
:无条件跳转到Label
处
BL Label
:当程序跳转到标号Label
处执行时,同时将当前的PC
值保存到R14
中
还有一些带判断的跳转指令:
1.b.ne
是不等则跳转
2.b.eq
是等于则跳转
3.b.le
是大于则跳转
4.b.ge
是小于则跳转
5.b.lt
是大于等于则跳转
6.b.gt
是小于等于则跳转
7.cbz
为结果等于零则跳转
8.cbnz
为结果非零则跳转
MOV R0, R1
; 将R1的值复制到R0
MOV R0, R1, LSL#3
; R0 = R1左移3位后的值(等效乘以8)
MOV PC, LR
; 从子程序返回(跳转至LR保存的地址)
AARCH64下的寄存器
aarch64
实际上就是 arm
的64位版本
它的寄存器在64
位下都叫作Xn
寄存器,其对应的低32
位叫作Wn
寄存器
X0 ~ X7
用来依次传递参数,同时X0
存放着函数返回值
X8
常用来存放系统调用号或一些函数的返回结果,
X30
存放着函数的返回地址(aarch64
中的RET
指令返回X30
寄存器中存放的地址)
其中栈顶是X31(SP)
寄存器,栈帧是X29(FP)
寄存器
X32
是PC
寄存器
x0 <-> rdi 或 函数返回值
x1 <-> rsi
x2 <-> rdx
x3 <-> rcx
x4 <-> 第5个参数
x5 <-> 第6个参数
x6 <-> 第7个参数
x7 <-> 第8个参数
x29 <-> (FP) 栈帧寄存器
x30 <-> 存放返回地址(aarch64中的RET指令返回X30寄存器中存放的地址)
x31 <-> rsp 栈顶寄存器
x32 <-> rip(PC)
注意:arm架构中的ret只是返回,并不会改变sp指针
AARCH64下与ARM不同的指令
在aarch64
架构下指令的一大变化就是,不再使用push
和pop
指令压栈和弹栈了,也没有LDM
和STM
指令,而是使用STP
和LDP
指令
STP和LDP指令
STP指令
STP x4, x5, [sp, #0x20] 将sp+0x20处依次覆盖为x4,x5即x4入栈到sp+0x20,x5入栈到sp+0x28最后sp的位置不变
LDP指令
LDP x29, x30, [sp], #0x40 将sp弹栈到x29,sp+0x8弹栈到x30最后sp += 0x40
其中,STP
和LDP
中的P
是pair
(一对)的意思,也就是说,仅可以同时读/写两个寄存器
在aarch64架构下比arm架构新增了BLR指令,也是用于跳转的指令,下面是它对比BL指令
特征 | BL指令 | BLR指令 |
---|---|---|
跳转目标类型 | 静态地址(PC相对偏移) | 动态地址(寄存器存储的绝对地址) |
地址范围限制 | ±32MB(AArch64) | 无限制(支持全64位地址空间) |
返回地址保存 | 将PC+4存入X30(LR) | 同BL |
典型应用场景 | 直接调用固定位置的函数 | 函数指针调用、虚表跳转、动态链接库调用 |
安全性机制 | 目标地址固定,不易被篡改 | 需配合BTI/PAC防御ROP攻击 |
编译器优化 | 可内联展开或尾调用优化 | 通常无法优化,需保留完整调用链 |
流水线行为 | 目标地址可预测,分支预测效率高 | 目标地址动态变化,依赖BTB(Branch Target Buffer)缓存 |
使用方法对比: | ||
BL指令 |
BL target_label ;arget_label为符号地址,编码为PC + 偏移量
LR = PC + 4; // 保存返回地址
PC = PC + offset; // 跳转到目标标签地址
BLR指令
BLR Xn ;Xn寄存器存储目标函数地址
X30(LR) = PC + 4; // 保存返回地址
PC = Xn; // 跳转到寄存器指向的地址
ARM架构下的系统调用号
基于arm架构
的linux系统调用号:
unistd32.h - arch/arm64/include/asm/unistd32.h - Linux source code v5.13.19 - Bootlin Elixir Cross Referencer
基于aarch64架构
的linux系统调用号:
unistd.h - include/uapi/asm-generic/unistd.h - Linux source code v5.13.19 - Bootlin Elixir Cross Referencer
参考:
1.arm-pwn 学习 · De4dCr0w’s Blog
2.[原创] CTF 中 ARM & AArch64 架构下的 Pwn-Pwn-看雪-安全社区|安全招聘|kanxue.com