ARM(32bit)简述
ARM指令的两种模式
ARM处理器存在两种运行模式:ARM与Thumb,这两种模块均与特权模式无关,例如,运行在SVC模式的代码可以是ARM与可以是Thumb,这两种模式的主要区别在于指令集:ARM模式的指令集是32bit,Thumb模式的指令集为16bit(但是也可以是32bit)。在编写ARM shellcode的时候,确定什么时候以及如何使用thumb指令集是尤其重要的,通常编写shellcode我们需要克服null字节,我们通常可以使用Thumb指令集来替代ARM指令集,以避免null字节。
Thumb指令集存在不同版本,如下为Thumb的不同版本,命名的不同仅仅是为了区分不同版本的指令,对于机器来说,运行的都是Thumb指令集。
- Thumb-1(16-bit):使用在ARMv6及之前的版本中
- Thumb-2(16-bit与32-bit):在Thumb-1的基础上增加了更多的指令,并且指令长度可以是16-bit或32-bit(ARMv6T2,ARMv7)
- ThumbEE:包括一些针对动态生成代码(dynamically generated code)的更改和添加(在执行之前或执行期间在设备上编译的代码)。
ARM与Thumb的区别
- 条件执行:ARM模式的所有指令都支持条件执行,存在一些ARM处理器的版本允许在Thumb模式下使用IT指令实现条件执行。
- 32-bit Thumb指令存在一个 .w 后缀
- barrel 移位器是ARM模式的一个特性,可以使乘法指令压缩到一个指令,例如,通常乘法指令需要两个指令(将寄存器乘以2,再使用MOV将结果存入另一个寄存器),在ARM模式下可以使用移位指令将乘法包括在一个MOV指令中:
Mov R1,R0,LSL #1 ;R1=R0*2
处理器要想切换两种状态,需要满足以下两个条件之一:
- 使用分支指令BX (branch and exchange)或BLX (branch, link, and exchange),并将目标寄存器的最低有效位设置为1。当使用bx跳转指令,跳到一个奇数地址时,默认跳到这个奇数地址-1的位置,然后标志位T位会置1,表示切换到Thumb指令集,所以我们引出下面这条指令,经常使用它来进行指令集的切换(r3随意,任意寄存器即可,别用特殊寄存器)
add r3, pc, #1
bx r3
- 如果CPSR的Tbit置为,则代表程序处于Thumb模式
寄存器规则
- 子程序间通过寄存器R0~R3来传递参数。这时,寄存器R0~R3可记作arg0~arg3。被调用的子程序在返回前无需恢复寄存器R0~R3的内容,R0被用来存储函数调用的返回值。
- 在子程序中,使用寄存器R4~R11来保存局部变量。这时,寄存器R4~R11可以记作var1~var8。如果在子程序中使用了寄存器v1~v8中的某些寄存器,则子程序进入时必须保存这些寄存器的值,在返回前必须恢复这些寄存器的值。R7经常被用作存储系统调用号,R11存放着帮助我们找到栈帧边界的指针,记作FP。在Thumb程序中,通常只能使用寄存器R4~R7来保存局部变量。
- 寄存器R12用作过程调用中间临时寄存器,记作IP。在子程序之间的连接代码段中常常有这种使用规则。
- 寄存器R13(Stack Pointer)用作堆栈指针,记作SP。在子程序中寄存器R13不能用作其他用途。寄存器SP在进入子程序时的值和退出子程序时的值必须相等。
- 寄存器R14(Link Registe)称为连接寄存器,记作LR。它用于保存子程序的返回地址。如果在子程序中保存了返回地址,寄存器R14则可以用作其他用途。
- 寄存器R15(Program Counter)是程序计数器,记作PC。每次执行指令,PC自动增加对应执行指令的长度,在ARM中总是4字节,Thumb总是2字节,在执行分支指令时,PC指向目的地址,在执行过程中,PC通常指向当前指令+2条指令长度的地址,即在ARM模式下,PC指向当前指令地址+8,在Thumb模式下,PC指向当前指令+4。这个和汇编指令的流水线有关吧
CPSR:Curren Program Status Register,当前状态寄存器,相当于x86里的EFLAG寄存器。上图为32-bit ARM中的CPSR(最右为低地址),对应字段如下:
ARM架构寄存器与Intel架构寄存器对比:
ARM架构 寄存器名 | 寄存器描述 | Intel架构 寄存器名 |
---|---|---|
R0 | 通用寄存器 | EAX |
R1~R5 | 通用寄存器 | EBX、ECX、EDX、EDI、ESI |
R6~R10 | 通用寄存器 | 无 |
R11(FP) | 栈帧指针 | EBP |
R12(IP) | 内部程序调用 | 无 |
R13(SP) | 堆栈指针 | ESP |
R14(LP) | 链接寄存器 | 无 |
R15(PC) | 程序计数器 | EIP |
CPSR | 程序状态寄存器 | EFLAGS |
传参规则
- 对于参数个数可变的子程序,当参数个数不超过4个时,可以使用寄存器R0~R3来传递参数;当参数超过4个时,还可以使用堆栈来传递参数。
- 在传递参数时,将所有参数看作是存放在连续的内存字单元的字数据。然后,依次将各字数据传递到寄存器R0,R1,R2和R3中。如果参数多于4个,则将剩余的字数据传递到堆栈中。入栈的顺序与参数传递顺序相反,即最后一个字数据先入栈。
返回值规则
- 结果为一个32位整数时,可以通过寄存器R0返回
- 结果为一个64位整数时,可以通过寄存器R0和R1返回
- 结果为一个浮点数时,可以通过浮点运算部件的寄存器f0、d0或s0来返回
- 结果为复合型浮点数(如复数)时,可以通过寄存器f0fn或d0dn来返回
- 对于位数更多的结果,需要通过内存来传递。
汇编
简略了解可以参考壁纸图片
详细可以参考arm汇编指令以及安全客-ARM架构下的 Pwn 的一般解决思路
知乎
AArch64
需要指出的是,AArch64架构并不是ARM-32架构的简单扩展,他是在ARMv8引入的一种全新架构。ARMv支持3种指令集,A64、A32和T32。AArch拥有31个通用寄存器(X0~X30或者W0~W30),系统运行在64位状态下的时候名字叫Xn,运行在32位的时候就叫Wn。,指令中设计的寄存器(X或W)决定了指令操作位数,如ADD W0,W1,W2 实现的是32bit运算;ADD X0,X1,X2实现的是64bit运算 。除此之外,还可以有其他bit运算,如Bx代表8bit,Hx代表16bit,Sx代表32bit,Dx代表64bit,Qx代表128bit
寄存器规则
X0~X7:用于函数传参(32bit用W,64bit用X),多余8位的用栈传递
X8:XR寄存器,当被调用函数返回一个结构时,X8为当前结构体的地址
X9~X15:用于函数内部调用
X16/X17 :Intra-Procedure-call寄存器,连接器使用这些寄存器在调用者和被调用者之间插入单板(veneer)。单板是小块代码。最常见的例子是分支范围扩展。A64中的分支指令范围有限。如果目标超出了这个范围,那么连接器需要生成一个单板来扩展分支的范围。
XZR/WZR :0寄存器,永远代表0
X29:frame pointer,帧寄存器
SP:stack pointer,ARMv8里有多个SP,每个SP与一个具体的异常等级(Exception level)相关联
X30:用于Link Register,等价于arm中的LR寄存器
PC:程序计数器,A64中PC不再是通用寄存器,并且不可以用于数据处理指令,PC的值可以通过如下指令读取
ADR Xd,.
其中ADR返回一个label的地址,"."符号表示当前位置,上述指令即加载当前指令位置,等价于读取PC指令值
系统调用
SVC :supervisor call:造成EL1层的异常,用于应用向OS发送请求
HVC :hypervisor call:造成EL2层的异常,用于OS调用hypervisor,EL0层不可使用该指令
SMC :Secure monitor call:造成EL3层的异常,用于OS或者hypervisor调用EL3的firmware,EL0层不可使用该指令
常见汇编指令
aarch64里用LDP(load pair)和STP(store pair)取代了LDM和STM,LDM与STM没有限制寄存器操作数的数量,但是LDP和STP规定一次LDP和STP指令只能操作2个寄存器,其基本含义如下
将X0寄存器的值所表示的内存处的值存入W3中,即w3=[X0];将X0寄存器的值+4所表示的内存处的值存入W7,即w7=[X0+4]
LDP W3, W7, [X0]
将DO值存入X4寄存器表示的内存中,将D1值存入X4寄存器的值8所表示的内存中,即[X4]=D0,[X4+8]=D1
STP D0, D1, [X4]
STP和LDP通常用来其push和pop作用,如下将X0和X1压入栈
STP X0, X1, [SP, #-16]!
如下指令从栈中弹出X0和X1
LDP X0, X1, [SP], #16
记住在AARCH64中stack poniter需要128bit对齐