ARM学习

ARM介绍

ARM 与 Intel

ARM处理器和Intel处理器有很多不同,其中最主要的区别在指令集。Intl为CISC(复杂指令集),具有更多,
功能更丰富的指令集,并且允许很多复杂指令访问内存。因此Intel有更多操作方法、寻址方式、但是寄存器比ARM少。CISC主要用于正常的PC、工作站以及服务器。
ARM为RISC(精简指令集),RISC有很多精简指令100个指令左右),并且RISC比CISC有更多的通用寄存器。
不同于Intel,ARM使用只对寄存器操作的指令,只通过LoadStore这种方式来访问内存。这代表在ARM中,
增加一个特定内存地址的值,需要三种指令(load,increment和store)。先通过load将特定内存地址
的值加载到寄存器,寄存器递增,然后使用store将寄存器中的值加载到内存中。
指令的减少有利有弊。一方面,指令执行的速度加快,系统可能有更快的执行速度(RISC系统通过减少每条指令的时钟
周期来缩短执行时间),另一方面,更少的指令意味着在更加强调中使用有限指令编写软件中注意效率。值得注意的是ARM
有两种指令集,ARM和Thumb指令集,Thumb指令可以是2或4个字节(详情看3.ARM指令集)
ARM 与 x86 更多的不同:
1.ARM中,大多数指令能被用为条件执行
2.Intel x86和 x86-64类的处理器使用小端格式
3.ARM架构在版本3之前采用小端格式,之后ARM处理器变为大端并且增加了可以改变大小端的设置
(数据访问可以是 little-endian 或 big-endian,由程序状态寄存器 (CPSR) 的第 9 位(E 位)控制。)
不仅ARM和Intel之间有很多区别,ARM之间的版本也有很多区别。只要了解基本原理,就很容易了解所选ARM
版本间的微小区别
avatar
相关命令

1
2
$ as program.s -o program.o
$ ld program.o -o program

大小端LSB与MSB
avatar

ARM数据类型和寄存器

ARM数据类型

和很多高级语言一样,ARM支持不同数据类型的操作,我们可以使用load(或store)操作的数据格式可以是有符号或
无符号的words,half words和bytes。这些数据类型可以扩展:-h或-sh 支持 half words,-b和-sb 支持bytes
有符号数和无符号数的区别:
1.有符号数包含正负,范围更小
2.无符号数可以储存大的整数(包括0),因此范围更大
Load 和 Store指令集如下
avatar

ARM寄存器

ARM寄存器取决与ARM的版本,根据ARM参考手册,大概30个32位通用寄存器,基于 ARMv6-M 和 ARMv7-M的除外
前16个寄存器可在用户级别下访问,其它寄存器可在高权限软件中
通用寄存器
avatar
专用寄存器
avatar
ARM寄存器与Intel寄存器之间的联系
avatar
R0-R12
在常见操作中用来临时储存值,指针(内存地址)。R0可以在算术运算中用为累加器或者用于存储之前调用函数的结果
R7存储系统调用编号,R11是帧指针,类似与RBP,此外ARM中r0 - r3 类似与rdi - rcx
R13
堆栈指针类似于RSP
R14
链接寄存器。用于存储调用下一函数时的上一函数地址
R15
类似RIP
CSRP
avatar
CSRP类似于EFLAGS
avatar

ARM Instruction set

ARM & Thumb

ARM处理器有两种主要的运行状态(不包括jazelle),ARM 和 Thumb。这两种状态与级别无关,例如在SVC模式下运行的
代码可以是ARM和Thumb。这两种状态的主要区别在于指令集,其中ARM状态的指令总是32位的,Thumb状态下的指令是16
的(也可以是32位)。了解以及如何使用Thumb对于我们的ARM漏洞利用开发十分重要。在编写ARM shellcode时,我们需
要去除NULL字符,使用16位Thumb指令而不是32位ARM指令减少了拥有它们的机会。
ARM版本的调用约定非常令人疑惑,不是所有的ARM版本都支持相同的Thumb指令集。有时候,ARM引入了增强的Thumb指
令集(Thumbv2),它允许32位Thumb指令甚至条件执行,但是这在之前的版本是不能的。为了在Thumb状态下使用条件执
行,引入了”it”指令。通过了解目标设备的ARM版本以及特定的Thumb支持,可以帮助我们调整代码。
ARM信息中心((http://infocenter.arm.com/help/index.jsp).)
正如上文所说,有不同的Thumb版本。不同的命名可以更好的区分他们(处理器始终称为Thumb)。
1.Thumb-1(16位指令):用于ARMv6和更好的架构。
2.Thumb-2(16位指令和32位指令):通过添加更多指令允许它们为16位或32位宽(ARMv6T2、ARMv7)来扩展Thumb-1。
3.ThumbEE:包括一些针对动态生成代码(在执行前不久或执行期间在设备上编译的代码)的更改和添加。
ARM和Thumb的区别:
条件执行:ARM状态下的所有指令都支持条件执行。某些ARM处理器版本允许使用IT指令在Thumb中进行条件执行。条件执
导致更多的代码,因为它减少了要执行的代码数量并减少了昂贵的分支指令数量。
32位ARM和Thumb指令:32位Thumb指令有一个.w后缀。The barrel shifter是另一个独特的ARM模式功能。它可用与将
多条指令压缩位一条指令。如果当前程序状态寄存器中的T位被设置,即处于Thumb状态。

Instruction TO ARM Instructions

ARM指令通常后面有一两个操作数,一般使用如下模板:

1
MNEMONIC{S}{condition} {Rd}, Operand1, Operand2

由于ARM比较灵活,并非所有指令都使用模板中提供的所有字段。模板中字段的用途描述如下:

1
2
3
4
5
6
MNEMONIC - 指令的简称(助记符)
{S} - 一个可选的后缀。 如果指定了 S,则根据操作结果更新条件标志
{condition} - 执行指令需要满足的条件
{Rd} - 用于存储指令结果的寄存器(目标)
Operand1 - 第一个操作数。 寄存器或立即数
Operand2 - 第二个(灵活的)操作数。 可以是立即数(数字)或带有可选移位的寄存器

MNEMONIC、S、Rd和OPerand1字段比较简单,但是{condition}和CPSR寄存器的值密切相关(寄存器中特定位置的值)
。operand2被称为灵活操作数,因为我们可以以各种形式使用它–作为立即数(具有有限的值集)、寄存器或者带有移位
的寄存器。例子:

1
2
3
4
5
6
7
#123 - 立即值(具有有限的值集)。
Rx - 寄存器 x(如 R1、R2、R3 ...)
Rx, ASR n - 算术右移 n 位的寄存器 x (1 = n = 32)
Rx, LSL n - 寄存器 x 逻辑左移 n 位 (0 = n = 31)
Rx, LSR n - 寄存器 x 逻辑右移 n 位 (1 = n = 32)
Rx, ROR n - 寄存器 x 右循环 n 位 (1 = n = 31)
Rx, RRX - 寄存器 x 向右旋转一位,扩展

不同类型指令示例,如下

1
2
3
4
ADD R0, R1, R2 - 将 R1 (Operand1) 和 R2 (Operand2 以寄存器形式) 的内容相加,并将结果存入 R0 (Rd)
ADD R0, R1, #2 - 将 R1 (Operand1) 的内容和值 2 (Operand2 以立即值的形式) 相加,并将结果存入 R0 (Rd)
MOVLE R0, #5 - 仅当条件 LE(小于或等于)满足时,才将数字 5(操作数 2,因为编译器将其视为 MOVLE R0, R0, #5)到 R0 (Rd)
MOV R0, R1, LSL #1 - 将 R1(操作数 2 以逻辑左移寄存器形式)的内容左移一位到 R0 (Rd)。 因此,如果 R1 的值为 2,则它左移一位并变为 4。然后将 4 移至 R0。

常见指令如下
avatar

内存指令load 和 store

ARM使用load 和 store 访问内存,在x86中很多指令都能直接操作内存,而arm中,数据必须在操作前从内存移动
到寄存器。这意味着在ARM的特定地址出递增32位值将需要三种指令(加载,增加和存储),先将它加载到寄存器,然后
在寄存器中递增它,然后将它返回到内存。
比如现在LDR R2,[R0];的作用是将R0指向的内容复制到R2
STR R2,[R0];的作用即将R0指向的内容替换位R2
关于ARM的几种寻址方式
1.寄存器+立即数
avatar
2.寄存器+寄存器
avatar
3.寄存器+寄存器+立即数
avatar

多个加载与储存

有时需要加载或者储存多个数据,为此可以使用LDM (load multiple) 和 STM (store multiple)指令
用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
.data

array_buff:
.word 0x00000000 /* array_buff[0] */
.word 0x00000000 /* array_buff[1] */
.word 0x00000000 /* array_buff[2]. This element has a relative address of array_buff+8 */
.word 0x00000000 /* array_buff[3] */
.word 0x00000000 /* array_buff[4] */

.text
.global _start

_start:
adr r0, words+12 /* address of words[3] -> r0 */
ldr r1, array_buff_bridge /* address of array_buff[0] -> r1 */
ldr r2, array_buff_bridge+4 /* address of array_buff[2] -> r2 */
ldm r0, {r4,r5} /* words[3] -> r4 = 0x03; words[4] -> r5 = 0x04 */
stm r1, {r4,r5} /* r4 -> array_buff[0] = 0x03; r5 -> array_buff[1] = 0x04 */
ldmia r0, {r4-r6} /* words[3] -> r4 = 0x03, words[4] -> r5 = 0x04; words[5] -> r6 = 0x05; */
stmia r1, {r4-r6} /* r4 -> array_buff[0] = 0x03; r5 -> array_buff[1] = 0x04; r6 -> array_buff[2] = 0x05 */
ldmib r0, {r4-r6} /* words[4] -> r4 = 0x04; words[5] -> r5 = 0x05; words[6] -> r6 = 0x06 */
stmib r1, {r4-r6} /* r4 -> array_buff[1] = 0x04; r5 -> array_buff[2] = 0x05; r6 -> array_buff[3] = 0x06 */
ldmda r0, {r4-r6} /* words[3] -> r6 = 0x03; words[2] -> r5 = 0x02; words[1] -> r4 = 0x01 */
ldmdb r0, {r4-r6} /* words[2] -> r6 = 0x02; words[1] -> r5 = 0x01; words[0] -> r4 = 0x00 */
stmda r2, {r4-r6} /* r6 -> array_buff[2] = 0x02; r5 -> array_buff[1] = 0x01; r4 -> array_buff[0] = 0x00 */
stmdb r2, {r4-r5} /* r5 -> array_buff[1] = 0x01; r4 -> array_buff[0] = 0x00; */
bx lr

words:
.word 0x00000000 /* words[0] */
.word 0x00000001 /* words[1] */
.word 0x00000002 /* words[2] */
.word 0x00000003 /* words[3] */
.word 0x00000004 /* words[4] */
.word 0x00000005 /* words[5] */
.word 0x00000006 /* words[6] */

array_buff_bridge:
.word array_buff /* address of array_buff, or in other words - array_buff[0] */
.word array_buff+8 /* address of array_buff[2] */

根据上面的描述可以知道,adr指令是将地址加载到寄存器中,ldm指令则是将前面寄存器指针指向的内容依次存放到后面寄存器(
按四个字节递增,因此words段也是设计为四个字节的形式),stm正好与此相反,当ldm和stm后缀加上ia和ib时,表示三个及以上的
寄存器,有ib会将寄存器中的地址增加四个字节,还有da和db指令,作用显而易见,倒置操作寄存器的顺序

条件执行

条件指令可见下图
avatar
其中N表示负位,Z表示零位,V表示溢出值
一个简单的例子

1
2
3
4
5
6
7
8
9
.global main

main:
mov r0, #2 /* 设置初始值 */
cmp r0, #3 /* r0与3对比,不相等,lt被设置为1 */
addlt r0, r0, #1 /* 如果r0小于3,r0加1 */
cmp r0, #3 /* 再一次与3比较 0 */
addlt r0, r0, #1 /* 与3对比,小于3,加1 */
bx lr

IT指令如下
语法:IT{x{y{z}}} cond
cond 指定 IT 块中第一条指令的条件
x 指定 IT 块中第二条指令的条件切换
y 指定 IT 块中第三条指令的条件切换
z 指定 IT 块中第四条指令的条件切换
IT 指令的结构是“IF-Then-(Else)”,语法是两个字母 T 和 E 的结构:

IT 指 If-Then(下一条指令是有条件的)
ITT 指 If-Then-Then(接下来的 2 条指令是有条件的)
ITE 指 If-Then-Else(接下来的 2 条指令是有条件的)
ITTE 指 If-Then-Then-Else(接下来的 3 条指令是有条件的)
ITTEE 指 If-Then-Then-Else-Else(接下来的 4 条指令是有条件的)
IT 块内的每条指令都必须指定一个条件后缀,该条件后缀可以是相同的,也可以是逻辑逆的。使用 ITE,则第一条和第二条指令(
If-Then) 必须具有相同的条件后缀,而第三条 (Else) 必须具有前两条的逻辑逆。
例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ITTE   NE           ; //接下来3条指令有条件
ANDNE R0, R0, R1 ; //不改变条件位
ADDSNE R2, R2, #1 ; //ADDSNE更新条件位
MOVEQ R2, R3 ; //条件mov

ITE GT ; //接下来3条指令有条件
ADDGT R1, R0, #55 ; //GD为真加
ADDLE R1, R0, #48 ; //GD为假加

ITTEE EQ ; //接下来4条指令有条件
MOVEQ R0, R1 ; //条件MOV
ADDEQ R2, R2, #10 ; //条件ADD
ANDNE R3, R3, #1 ; //条件AND
BNE.W dloop ; //分支指令只能用在IT指令的最后一条指令中
//错误的情况
IT NE ; //下一条指令有条件
ADD R0, R0, R1 ; //语法错误,没有条件指令

部分条件指令及与其相反的指令
avatar
例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.syntax unified    @ this is important!
.text
.global _start

_start:
.code 32
add r3, pc, #1 @ increase value of PC by 1 and add it to R3
bx r3 @ branch + exchange to the address in R3 -> switch to Thumb state because LSB = 1

.code 16 @ Thumb state
cmp r0, #10
ite eq @ if R0 is equal 10...
addeq r1, #2 @ ... then R1 = R1 + 2
addne r1, #3 @ ... else R1 = R1 + 3
bkpt

跳转指令b
例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
.global main

main:
mov r1, #2 /* setting up initial variable a */
mov r2, #3 /* setting up initial variable b */
cmp r1, r2 /* comparing variables to determine which is bigger */
blt r1_lower /* jump to r1_lower in case r2 is bigger (N==1) */
mov r0, r1 /* if branching/jumping did not occur, r1 is bigger (or the same) so store r1 into r0 */
b end /* proceed to the end */
r1_lower:
mov r0, r2 /* We ended up here because r1 was smaller than r2, so move r2 into r0 */
b end /* proceed to the end */
end:
bx lr /* THE END */
//结合loop
.global main

main:
mov r0, #0 /* setting up initial variable a */
loop:
cmp r0, #4 /* checking if a==4 */
beq end /* proceeding to the end if a==4 */
add r0, r0, #1 /* increasing a by 1 if the jump to the end did not occur */
b loop /* repeating the loop */
end:
bx lr /* THE END */

需要注意的是blx指令会将arm模式切换到thumb模式,并将PC保存到R14中,方便返回,使用bx时,不用注意arm模式或者thumb模式。