ARM32 汇编语言

0x00. 前言

最近在学习移动安全,为进行Android系统漏洞挖掘,需要学习ARM汇编语言。现在记录下自己学习ARM32汇编语言的要点与心得,以供参考。

0x01. ARM32汇编中寄存器

ARM32微处理器共有37个32位寄存器,其中31个为通用寄存器,6个为状态寄存器。ARM微处理器支持7种运行模式,分别是:用户模式(usr)、快速中断模式(fiq)、外部中断模式(irq)、管理模式(svc)、数据访问终止模式(abt)、系统模式(sys)、未定义指令中止模式(und)。由于ARM微处理器正常的程序执行状态为用户模式,因此先了解一下用户模式下ARM32。
在用户模式下,ARM32微处理器可以访问的寄存器有:不分组的寄存器R0-R7、分组寄存器R8-R14、程序计数器R15(PC)以及当前程序状态寄存器CPSR。ARM遵循ATPCS规则,ARM32汇编语言函数前4个参数使用R0-R3寄存器传递,多于4个的参数均通过堆栈传递,并且返回值通过R0寄存器返回。在使用软中断进行系统调时,系统调用号通过R7寄存器传递,用SWI指令产生软中断,实现从用户模式到管理模式的切换。例如,调用exit(0)的汇编代码如下:

1
2
3
MOV R0, #0  @参数0
MOv R7, #1 @系统功能号1为 exit
SWI #0 @执行 exit(0)

ARM32微处理器有两种工作状态:ARM32状态与Thumb状态。处理器可以在两种状态之间随意切换,当处理器处于ARM状态时,会执行32位对齐的ARM指令;当处于Thumb状态时,会执行16位对齐的Thumb指令。Thumb状态下对寄存器的命名与ARM32有部分差异,它们的关系如下表所示。

Thumb状态下寄存器 ARM32状态下寄存器 用途
R0-R7 R0-R7 通用寄存器
CPSR CPSR 程序状态寄存器
SL R10 栈限制寄存器
FP R11 桢指针寄存器
IP R12 内部过程调用寄存器
SP R13 栈顶指针寄存器
LR R14 子程序链接寄存器
PC R15 程序计数器

0x02. ARM32处理器寻址方式

ARM微处理器采用的是精简指令集,指令间的组合灵活。ARM微处理器支持九种寻址方式,分别是:立即寻址、寄存器寻址、寄存器移位寻址、寄存器间接寻址、基址寻址、多寄存器寻址、堆栈寻址、块拷贝寻址、相对寻址。先介绍其中几种寻址方式。

  1. 寄存器移位寻址
    寄存器移位寻址是ARM指令集特有的寻址方式,寄存器移位寻址方式:在操作前对源寄存器操作数进行移位操作。支持以下5种移位操作:
    LSL: 逻辑左移,移位后寄存器空出的低位补0。
    LSR: 逻辑右移,移位后寄存器空出的高位补0。
    ASR: 算术右移,移位过程中符号位保持不变,如果源操作数为正数,则移位后寄存器空出的高位补0;否则补1。
    ROR: 循环右移,移位后移出的低位填入移位空出的高位。
    RRX: 带扩展的循环右移,操作数右移一位,移位后寄存器空出的高位用C标志的值填充。
    例如:

    1
    MOV R0, R1, LSL #2   @R0=R1*4
  2. 基址寻址
    基址寻址是将基址寄存器与偏移量相加,形成操作数的有效地址,所需的操作数保存在有效地址所指向的存储单元中。基址寻址多用于查表、数据访问等操作。例如:

    1
    LDR R0, [R1, #-4]   @R0=[R1-4]
  3. 多寄存器寻址
    多寄存器寻址一条指令最多可以完成16个通用寄存器值的传送。比如LDMIA和LDMIB指令,LDM是数据加载指令,指令的后缀IA表示每次执行完加载操作后寄存器的值自增1个字;指令的后缀IB表示每次执行加载操作前寄存器的值自增1个字;还有两条指令后缀DA和DB,分别表示在指令操作后和操作前寄存器的值自减1个字。ARM32指令集中,字表示一个32位的数字,注意:该条指令的源寄存器与目的寄存器位置。例如:

    1
    LDMIA R0, {R1, R2, R3, R4}   @R1=[R0], R2=[R0+4], R3=[R0+8], R4=[R0+12]
  4. 堆栈寻址
    堆栈寻址是ARM指令集特有的一种寻址方式,堆栈寻址需要使用特定的指令来完成。堆栈寻址的指令有LDMFA/STMFA、LDMEA/STMEA、LDMFD/STMFD、LDMED/STMED。LDM和STM为指令前缀,表示多寄存器寻址。FA(Full Ascending stack)、FD(Full Descending stack)、EA、ED为指令后缀,其中:FA表示满递增堆栈,堆栈向高地址生长,堆栈指针指向下一个要放入的空地址;FD表示满递减堆栈,堆栈向低地址生长,堆栈指针指向最后一个入栈的有效数据数据项; EA表示空递增堆栈,堆栈向高地址生长;ED空递减堆栈,堆栈向低地址生长。例如:

    1
    2
    STMFD  SP!, {R1-R7, LR}   @将R1-R7, LR入栈,多用于保护子程序现场
    LDMFD SP!, {R1-R7, LR} @将数据出栈,放入R1-R7, LR寄存器。多用于恢复子程序现场
  5. 块拷贝寻址
    块拷贝寻址可实现连续地址数据从存储器的某一位置拷贝到另一位置。块拷贝寻址的指令有LDMIA/STMIA、LDMDA/STMDA、LDMIB/STMIB、LDMDB/STMDB。指令前缀和指令后缀前面已经介绍了。例如:

    1
    2
    STMIA  R0!, {R1-R3}   @从R0寄存器指向的存储单元中读取3个字到R1-R3寄存器
    LDMIA R0!, {R1-R3} @存储R1-R3寄存器的内容到R0寄存器指向的存储单元
  6. 相对寻址
    相对寻址以程序计数器PC的当前值为基地址,指令中的地址标作为偏移量,将两者相加之后得到操作数的有效地址。例如:

    1
    2
    3
    4
    BL NEXT
    ....
    NEXT
    ....

0x03. ARM32指令集与Thumb指令集

前面讲过ARM32微处理器有ARM32与Thumb两种工作状态,因此,有ARM32与Thumbe指令集。一般地,ARM32指令集每条指令占4个字节码,Thumb指令集每条指令占2个字节码,两者不能混用。但是可以通过BX、BLX等指令在跳转的时候实现切换。同时,在使用IDA进行逆向时,IDA对此识别也有问题,可能会把Thumb的代码识别为ARM,或者反过来。一旦调试起来,运行到相应位置,便会报出异常,导致程序退出,我们可以使用Alt+G可以修改相应的识别。

  1. 跳转指令
    ARM中有两种方式可以实现程序挑战:一种是使用挑战指令直接跳转;另一种是给PC寄存器直接赋值实现挑战。跳转指令有4种:B跳转指令、BL带链接的跳转指令、LX带状态切换的跳转指令、BLX带链接和状态切换的跳转指令。

现在介绍下ARM32指令集与Thumb指令集切换方法,在BX和BLX指令跳转时,判断目标地址最低位是否为1。
如果为1,跳转时将CPSR寄存器标志T置位,并将目标地址处的代码解释位Thumb代码,处理器切换到Thumb状态;
如果为0,跳转时将CPSR寄存器标志T复位,并将目标地址处的代码解释位ARM32代码,处理器切换到ARM32状态。

  1. ARM32与Thumb跳转偏移计算
    ARM32: 低27位是偏移位置,下跳: 偏移=(目标地址 - 当前PC地址)/指令长度; 正数下跳,负数上跳。
    Thumb: 目标地址 = 偏移 * 指令长度 + 当前PC地址

  2. ARM指令执行(多流水线)
    ARM指令执行分为3步:取地址 ->分析 ->运行。在涉及程序计数器相加时需要注意。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    @ 1  取地址 ->分析  ->运行
    @ 2 取地址 ->分析 ->运行
    @ 3 取地址 ->分析 ->运行
    @ 因此,执行一条涉及PC的指令时,PC一般指向下两条指令的地址;
    @ 例如: Thumb指令集, PC = PC + 2*2; ARM32指令集,PC = PC + 4*2
    @ R2=0x30c2, PC = PC + 2*2, R2 = 0x756A8F12 + 4 + 0x30c2 = 0x756ABFD8
    0x756A8F12 ADD R2, PC

0x04. 参考文献

1. Wiki ARM Architecture
2. Android软件安全与逆向分析
3. ARM汇编之寄存器