ARM汇编基础(待补充)
ARM汇编基础(简)
经常忘记,做个笔记,好作复习。。
内容主要来源于《Android软件安全与逆向分析》和《逆向工程权威指南》以及 ARM 汇编 和Azeria-labs
ARM架构
ARM属于RISC CPU,
ARM模式 4个字节opcode 32位
Thumb模式 2个字节opcode 16位
Thumb-2模式 同上(只是有部分4个字节的opcode)
64位ARM 4个字节opcode
ARM机器码在版本3之前是小端。但是之后默认采用大端格式,但可以设置切换到小端。
数据类型
数据类型在汇编语言中的扩展后缀为**-h或者-sh对应着半字,-b或者-sb**对应着字节,但是对于字并没有对应的扩展
|
|
字节序
在内存中有两种字节排布顺序,大端序(BE)或者小端序(LE)。两者的主要不同是对象中的每个字节在内存中的存储顺序存在差异。一般X86中是小端序,最低的字节存储在最低的地址上。在大端机中最高的字节存储在最低的地址上。
数据访问时采取大端序还是小端序使用程序状态寄存器(CPSR)的第9比特位来决定的。
寄存器
37个32位寄存器,其中31个为基础寄存器,6个为状态寄存器。
用户模式下有
不分组寄存器(R0-R7) R7一般存放系统调用号
分组寄存器(R8-R14)
程序计数器(R15)
单前程序状态寄存器(CPSR)
两种状态:
ARM状态(32位对齐) | Thumb状态(16位对齐) |
---|---|
R0-R7 | R0-R7(相同) |
CPSR | CPSR(同) |
R11 | FP(栈帧指针) |
R12 | IP(内部程序调用) |
R13 | SP(栈指针) |
R14 | LR(链接寄存器)一般存放函数返回地址 |
R15 | PC(程序计数器) |
和x86对比:
CSPR:
32位的CPSR寄存器的比特位含义,左边是最大比特位,右边是最小比特位。每个单元代表一个比特。
条件码 | 助记符后缀 | 标志 | 含义 |
---|---|---|---|
0000 | EQ | Z置位 | 相等 |
0001 | NE | Z清零 | 不相等 |
0010 | CS | C置位 | 无符号数大于或等于 |
0011 | CC | C清零 | 无符号数小于 |
0100 | MI | N置位 | 负数 |
0101 | PL | N清零 | 正数或零 |
0110 | VS | V置位 | 溢出 |
0111 | VC | V清零 | 未溢出 |
1000 | HI | C置位Z清零 | 无符号数大于 |
1001 | LS | C清零Z置位 | 无符号数小于或等于 |
1010 | GE | N等于V | 带符号数大于或等于 |
1011 | LT | N不等于V | 带符号数小于 |
1100 | GT | Z清零且(N等于V) | 带符号数大于 |
1101 | LE | Z置位或(N不等于V) | 带符号数小于或等于 |
1110 | AL | 忽略 | 无条件执行 |
程序结构
Android平台采用的是GUN ARM汇编格式,汇编器为GAS
参数传递:R0-R3这4个寄存器用来传递函数调用的第1到4个参数,超出的参数通过堆栈来传递。R0还用来存放函数调用的返回值。
汇编器指令
.file
:源文件名.align
:代码对齐方式.ascii
:声明字符串.global
:声明全局符号.type
:指定符号的类型.word
:存放地址值.size
:设置指定符号的大小.ident
:编译器标识
寻址方式
立即寻址
MOV R0, #1234
->R0=1234寄存器寻址
MOV R1 = R2
->R0=R1寄存器移位寻址
LSL :逻辑左移,移位后寄存器空出的低位补0
LSR:逻辑右移,移位后寄存器空出的高位补0
ASR:算术右移,移位过程中符号位保持不变,若源操作数为正数,则移位后空出的高位补0,否则补1。
ROR:循环右移,移位后移出的低位填入移位空出的高位。
RRX:带扩展的循环右移,操作数右移一位,移出的空位用C标志的值填充。
MOV R0, R1, LSL #2
->R1左移两位(R1«2)赋值给R0,相当于R0 = R1*4寄存器间接寻址
LDR RO, [R1]
->将R1寄存器的数值作为地址,取出此地址中的值赋给R0寄存器基址寻址
多用于查表、数组访问操作。
LDR R0, [R1,#-4]
->将R1寄存器的数值减4作为地址,取出此地址的值赋给R0寄存器。多寄存器寻址
一条指令最多完成16个通用寄存器值的传送。
LDMIA R0,{R1,R2,R3,R4}
->LDM为数据加载指令,指令的后缀IA表示每次执行完加载操作后R0寄存器的值自增1个字,ARM指令集中,子表示的是一个32位数值。这条指令作用为:R1 = [R0],R2 = [R0+#4],R3 = [R0+#8],R4 = [R0+#12]。堆栈寻址
特定的指令来完成:
LDMFA/STMFA
、LDMEA/STMEA
、LDMFD/STMFD
、LDMED/STMED
。LDM和STM为指令前缀,表示多寄存器寻址,即一次传送多个寄存器的值。后面的后缀为指令后缀。
STMFD SP!, {R1-R7,LR}
->将R1~R7,LR入栈,多用于保存子程序的现场。LDMFD SP!, {R1-R7,LR}
->出栈,恢复现场。块拷贝寻址
实现从连续地址数据从存储器的某一位置拷贝到另外一个位置,指令有:
LDMIA/STMIA
、LDMDA/STMDA
、LDMIB/STMIB
、LDMDB/STMDB
。LDMIA R0! {R0-R3}
从R0寄存器指向的存储单元中读取3个字到R1-R3寄存器STMIA R0! {R0-R3}
存储从R1-R3寄存器的内容到R0寄存器指向的存储单元相对寻址
以程序计数器PC的当前值为基地址,指令中的地址标号作为偏移量,将两者相加之后得到操作数的有效地址。
1
BL NEXT ····NEXT: ········
ARM和Thumb指令集
基本指令简述
MNEMONIC{S}{condition} {Rd}, Operand1, Operand2
助记符{是否使用CPSR}{是否条件执行以及条件} {目的寄存器}, 操作符1, 操作符2
MNEMONIC - 指令的助记符如ADD
{S} - 可选的扩展位,如果指令后加了S,则需要依据计算结果更新CPSR寄存器中的条件跳转相关 的FLAG
{condition} - 如果机器码要被条件执行,那它需要满足的条件标示
{Rd} - 存储结果的目的寄存器
Operand1 - 第一个操作数,寄存器或者是一个立即数
Operand2 - 第二个(可变的)操作数,可以是一个立即数或者寄存器或者有偏移量的寄存器
第二操作数还有如下操作:
|
|
|
|
内存访问相关指令
通常,LDR被用来从内存中加载数据到寄存器,STR被用作将寄存器的值存放到内存中。
例子:
|
|
第一种偏移形式:立即数作偏移
1 2
STR Ra, [Rb, imm] LDR Ra, [Rc, imm]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
.data var1: .word 3 var2: .word 4 .text .global _start _start: ldr r0, adr_var1 @ 将存放var1值的地址adr_var1加载到寄存器R0中 ldr r1, adr_var2 @ 将存放var2值的地址adr_var2加载到寄存器R1中 ldr r2, [r0] @ 将R0所指向地址中存放的0x3加载到寄存器R2中 str r2, [r1, #2] @ 取址模式:基于偏移量。R2寄存器中的值0x3被存放到R1寄存器的值加2所指向地址处。 str r2, [r1, #4]! @ 取址模式:基于索引前置修改。R2寄存器中的值0x3被存放到R1寄存器的值加4所指向地址处,之后R1寄存器中存储的值加4,也就是R1=R1+4。 ldr r3, [r1], #4 @ 取址模式:基于索引后置修改。R3寄存器中的值是从R1寄存器的值所指向的地址中加载的,加载之后R1寄存器中存储的值加4,也就是R1=R1+4。 bkpt adr_var1: .word var1 adr_var2: .word var2
第二种偏移形式:寄存器作偏移
1 2
STR Ra, [Rb, Rc] LDR Ra, [Rb, Rc]
1 2 3 4 5 6 7 8 9 10 11 12 13 14
.data var1: .word 3 var2: .word 4 .text .global _start _start: ldr r0, adr_var1 @ 将存放var1值的地址adr_var1加载到寄存器R0中 ldr r1, adr_var2 @ 将存放var2值的地址adr_var2加载到寄存器R1中 ldr r2, [r0] @ 将R0所指向地址中存放的0x3加载到寄存器R2中 str r2, [r1, r2] @ 取址模式:基于偏移量。R2寄存器中的值0x3被存放到R1寄存器的值加R2寄存器的值所指向地址处。R1寄存器不会被修改。 str r2, [r1, r2]! @ 取址模式:基于索引前置修改。R2寄存器中的值0x3被存放到R1寄存器的值加R2寄存器的值所指向地址处,之后R1寄存器中的值被更新,也就是R1=R1+R2。 ldr r3, [r1], r2 @ 取址模式:基于索引后置修改。R3寄存器中的值是从R1寄存器的值所指向的地址中加载的,加载之后R1寄存器中的值被更新也就是R1=R1+R2。 bx lr adr_var1: .word var1
第三种偏移形式:寄存器缩放值作偏移
1 2
LDR Ra, [Rb, Rc, <shifter>] STR Ra, [Rb, Rc, <shifter>]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
.data var1: .word 3 var2: .word 4 .text .global _start _start: ldr r0, adr_var1 @ 将存放var1值的地址adr_var1加载到寄存器R0中 ldr r1, adr_var2 @ 将存放var2值的地址adr_var2加载到寄存器R1中 ldr r2, [r0] @ 将R0所指向地址中存放的0x3加载到寄存器R2中 str r2, [r1, r2, LSL#2] @ 取址模式:基于偏移量。R2寄存器中的值0x3被存放到R1寄存器的值加(左移两位后的R2寄存器的值)所指向地址处。R1寄存器不会被修改。 str r2, [r1, r2, LSL#2]! @ 取址模式:基于索引前置修改。R2寄存器中的值0x3被存放到R1寄存器的值加(左移两位后的R2寄存器的值)所指向地址处,之后R1寄存器中的值被更新,也就R1 = R1 + R2<<2。 ldr r3, [r1], r2, LSL#2 @ 取址模式:基于索引后置修改。R3寄存器中的值是从R1寄存器的值所指向的地址中加载的,加载之后R1寄存器中的值被更新也就是R1 = R1 + R2<<2。 bkpt adr_var1: .word var1 adr_var2: .word var2
如何区分取址模式:
如果有一个叹号!,那就是索引前置取址模式,即使用计算后的地址,之后更新基址寄存器。
如果在外有一个寄存器,那就是索引后置取址模式,即使用原有基址寄存器重的地址,之后再更新基址寄存器
除此之外,就都是偏移取址模式了
关于PC相对取址的LDR指令
有时候LDR并不仅仅被用来从内存中加载数据。还有如下这操作:
1 2 3 4 5 6 7 8
.section .text .global _start _start: ldr r0, =jump /* 加载jump标签所在的内存位置到R0 */ ldr r1, =0x68DB00AD /* 加载立即数0x68DB00AD到R1 */ jump: ldr r2, =511 /* 加载立即数511到R2 */ bkpt
这些指令学术上被称作伪指令。
在ARM中使用立即数的规律
在ARM中不能像X86那样直接将立即数加载到寄存器中。因为你使用的立即数是受限的。
立即数的值:v = n ror 2*r 有效的立即数都可以通过循环右移来得到
1 2 3 4 5 6 7 8 9 10
#256 // 1 循环右移 24位 --> 256 #384 // 6 循环右移 26位 --> 384 #484 // 121 循环右移 30位 --> 484 #16384 // 1 循环右移 18位 --> 16384 #2030043136 // 121 循环右移 8位 --> 2030043136 #0x06000000 // 6 循环右移 8位 --> 100663296 (十六进制值0x06000000) Invalid values: #370 // 185 循环右移 31位 --> 31不在范围内 (0 – 30) #511 // 1 1111 1111 --> 比特模型不符合 #0x06010000 // 1 1000 0001.. --> 比特模型不符合
这样并不能一次性加载所有的32位值。不过我们可以通过以下的两个选项来解决这个问题:
- 用小部分去组成更大的值。 MOV r0, #511 将511分成两部分:MOV r0, #256, and ADD r0, #255
1 2 3 4 5 6 7
.section .text .global _start _start: mov r0, #256 /* 1 ror 24 = 256, so it's valid */ add r0, #255 /* 255 ror 0 = 255, valid. r0 = 256 + 255 = 511 */ ldr r1, =511 /* load 511 from the literal pool using LDR */ bkpt
计算立即数的有效值脚本:https://raw.githubusercontent.com/azeria-labs/rotator/master/rotator.py
1 2 3 4 5 6 7
azeria@labs:~$ python rotator.py Enter the value you want to check: 511 Sorry, 511 cannot be used as an immediate number and has to be split. azeria@labs:~$ python rotator.py Enter the value you want to check: 256 The number 256 can be used as a valid immediate number. 1 ror 24 --> 256
下面的部分指令用到在详细查,记的话脑壳痛
跳转指令
B
BL
BX
BXL
存储器操作指令
LDM
STM
PUSH
POP
SWP
数据处理
MOV
MVN
ADD
ADC
SUB
RSB
SBC
RSC
MUL
MLS
MLA
UMULL
UMLAL
SMUULL
SMLAL
SMLAD
SMLSD
SDIV
UDIV
ASR
AND
ORR
EOR
BIC
LSL
LSR
RRX
ROR
CMP
CMN
TSL
TEQ
其他指令
SWI
NOP
MRS
MSR