root@v

Are You OK?

ARM汇编基础

vorblock / 2018-08-10


ARM汇编基础(简)

经常忘记,做个笔记,好作复习。。

内容主要来源于《Android软件安全与逆向分析》和《逆向工程权威指南》以及 ARM 汇编Azeria-labs

ARM架构

ARM属于RISC CPU,

数据类型

数据类型在汇编语言中的扩展后缀为-h或者-sh对应着半字,-b或者-sb对应着字节,但是对于字并没有对应的扩展

ldr = 加载字,宽度四字节
ldrh = 加载无符号的半字,宽度两字节
ldrsh = 加载有符号的半字,宽度两字节
ldrb = 加载无符号的字节
ldrsb = 加载有符号的字节
str = 存储字,宽度四字节
strh = 存储无符号的半字,宽度两字节
strsh = 存储有符号的半字,宽度两字节
strb = 存储无符号的字节
strsb = 存储有符号的字节

字节序

在内存中有两种字节排布顺序,大端序(BE)或者小端序(LE)。两者的主要不同是对象中的每个字节在内存中的存储顺序存在差异。一般X86中是小端序,最低的字节存储在最低的地址上。在大端机中最高的字节存储在最低的地址上。

数据访问时采取大端序还是小端序使用程序状态寄存器(CPSR)的第9比特位来决定的。

寄存器

37个32位寄存器,其中31个为基础寄存器,6个为状态寄存器。

用户模式下有

两种状态:

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还用来存放函数调用的返回值。

汇编器指令

寻址方式

MOV R0, #1234 ->R0=1234

MOV R1 = R2 ->R0=R1

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/STMFALDMEA/STMEALDMFD/STMFDLDMED/STMED

LDM和STM为指令前缀,表示多寄存器寻址,即一次传送多个寄存器的值。后面的后缀为指令后缀。

STMFD SP!, {R1-R7,LR} ->将R1~R7,LR入栈,多用于保存子程序的现场。

LDMFD SP!, {R1-R7,LR} ->出栈,恢复现场。

实现从连续地址数据从存储器的某一位置拷贝到另外一个位置,指令有:LDMIA/STMIALDMDA/STMDALDMIB/STMIBLDMDB/STMDB

LDMIA R0! {R0-R3} 从R0寄存器指向的存储单元中读取3个字到R1-R3寄存器

STMIA R0! {R0-R3} 存储从R1-R3寄存器的内容到R0寄存器指向的存储单元

以程序计数器PC的当前值为基地址,指令中的地址标号作为偏移量,将两者相加之后得到操作数的有效地址。

  BL NEXT
  	····
  NEXT:
  	········

ARM和Thumb指令集

基本指令简述

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

助记符{是否使用CPSR}{是否条件执行以及条件} {目的寄存器}, 操作符1, 操作符2

MNEMONIC     - 指令的助记符如ADD

{S}           - 可选的扩展位,如果指令后加了S,则需要依据计算结果更新CPSR寄存器中的条件跳转相关 的FLAG

{condition}   - 如果机器码要被条件执行,那它需要满足的条件标示

{Rd}          - 存储结果的目的寄存器

Operand1     - 第一个操作数,寄存器或者是一个立即数

Operand2     - 第二个(可变的)操作数,可以是一个立即数或者寄存器或者有偏移量的寄存器

第二操作数还有如下操作:

#123                    - 立即数
Rx                      - 寄存器比如R1
Rx, ASR n               - 对寄存器中的值进行算术右移n位后的值
Rx, LSL n               - 对寄存器中的值进行逻辑左移n位后的值
Rx, LSR n               - 对寄存器中的值进行逻辑右移n位后的值
Rx, ROR n               - 对寄存器中的值进行循环右移n位后的值
Rx, RRX                 - 对寄存器中的值进行带扩展的循环右移1位后的值
ADD   R0, R1, R2         - 将第一操作数R1的内容与第二操作数R2的内容相加,将结果存储到R0中
ADD   R0, R1, #2         - 将第一操作数R1的内容与第二操作数一个立即数相加,将结果存到R0中
MOVLE R0, #5             - 当满足条件LE(Less and Equal,小于等于0)将第二操作数立即数5移动到R0中,注意这条指令与MOVLE R0, R0, #5相同
MOV   R0, R1, LSL #1     - 将第二操作数R1寄存器中的值逻辑左移1位后存入R0

内存访问相关指令

通常,LDR被用来从内存中加载数据到寄存器,STR被用作将寄存器的值存放到内存中。

例子:

.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中的值0x3存放到R1做指向的地址 
    bkpt             
adr_var1: .word var1  /* var1的地址助记符 */
adr_var2: .word var2  /* var2的地址助记符 */

如何区分取址模式:

如果有一个叹号!,那就是索引前置取址模式,即使用计算后的地址,之后更新基址寄存器。

如果在[]外有一个寄存器,那就是索引后置取址模式,即使用原有基址寄存器重的地址,之后再更新基址寄存器

除此之外,就都是偏移取址模式了

有时候LDR并不仅仅被用来从内存中加载数据。还有如下这操作:

  .section .text
  .global _start
  _start:
     ldr r0, =jump        /* 加载jump标签所在的内存位置到R0 */
     ldr r1, =0x68DB00AD  /* 加载立即数0x68DB00ADR1 */
  jump:
     ldr r2, =511         /* 加载立即数511R2 */ 
     bkpt

这些指令学术上被称作伪指令。

在ARM中不能像X86那样直接将立即数加载到寄存器中。因为你使用的立即数是受限的。

立即数的值:v = n ror 2*r 有效的立即数都可以通过循环右移来得到

  #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位值。不过我们可以通过以下的两个选项来解决这个问题:

下面的部分指令用到在详细查,记的话脑壳痛

##### 跳转指令

  1. B
  2. BL
  3. BX
  4. BXL

##### 存储器操作指令

  1. LDM
  2. STM
  3. PUSH
  4. POP
  5. SWP

##### 数据处理

  1. MOV
  2. MVN
  3. ADD
  4. ADC
  5. SUB
  6. RSB
  7. SBC
  8. RSC
  9. MUL
  10. MLS
  11. MLA
  12. UMULL
  13. UMLAL
  14. SMUULL
  15. SMLAL
  16. SMLAD
  17. SMLSD
  18. SDIV
  19. UDIV
  20. ASR
  21. AND
  22. ORR
  23. EOR
  24. BIC
  25. LSL
  26. LSR
  27. RRX
  28. ROR
  29. CMP
  30. CMN
  31. TSL
  32. TEQ

##### 其他指令

  1. SWI

  2. NOP

  3. MRS

  4. MSR

###