基于Android的ARM指令集学习笔记

"移动安全"

Posted by 1r0nz on January 16, 2017

前言

    传统android开发倾向于将所有代码部署在android应用层,这种方式在面对软件安全威胁的情况下时比较容易破解。将重要的代码使用android ndk原生层的方式实现,由于ndk原生层代码逆向原本难度较高,使得文件无法轻易被破解。因此,理解和掌握android原生层运行机制对逆向水平的提高有比较大的帮助。


原生程序生成

    原生程序的生成需要经过一下几步:预处理——>编译——>汇编——>链接(这几个文件后缀变化依次为a.c——>a.s——>a.o——>a ),其中经过第2步编译过后c代码就成了arm汇编代码。学习的重点也就是基于arm的汇编。


arm的一些基本概念

  1. arm微处理器共有37个32位寄存器,其中31个为通用寄存器,6个为状态寄存器。
  2. arm处理器的七种运行模式:
    • 用户模式usr:arm处理器正常的程序执行状态。
    • 快速中断模式fiq:用于高速数据传输或通道处理。
    • 外部中断模式irq:用于通用的中断处理。
    • 管理模式svc:操作系统使用的保护模式。
    • 数据访问终止模式:当数据或指令预取终止时进入该模式,可用于虚拟存储以及存储保护。
    • 系统模式sys:运行具有特权的操作系统任务。
    • 为定义指令中止模式und:当未定义的指令执行时进入该模式。
      需要注意的是,arm运行模式可以通过软件本省改变,也可以通过外部中断或者异常处理改变,不同模式所使用的寄存器也不一样,另外,除了用户模式其他六种模式都可以访问受保护的系统资源。
  3. 当运行在用户模式中,处理器可以访问的寄存器为不分组寄存器r0-r7、分组寄存器r8-r14、程序计数器r15(PC)还有状态寄存器cpsr。
  4. arm寄存器的两装工作状态:arm状态和thumb状态,其中arm状态执行32位字对齐。thumb状态执行16位字对齐。
  5. 寄存器只是临时存放处。

arm的程序结构

    组成:处理器架构定义、数据段、代码段和main函数。

  • 处理器架构定义
    .arch armv5te
    .fpu softvfp
    .eabi_attribute 20,1
    .eabi_attribute 21,1
    .eabi_attribute 23,3
    .eabi_attribute 24,1
    .eabi_attribute 25,1
    .eabi_attribute 26,2
    .eabi_attribute 30,6
    .eabi_attribute 18,4
    这些指令制定了程序使用的处理器架构、协处理器类型与接口的一些属性。
    .arch 制定了arm处理器架构
    .fpu 指定了协处理器的类型
    .eabi_attribute 指定了一些接口类型

  • 段定义
    c语言所定义的全局变量和常量都都会编译到.data下,常量数据放在.rodata的只读数据段中,代码放到.text数据段中,这里的代码才可以执行。arm指定段的格式为:
    .section name [,"flag"[,%type[,flag_specific_arguments]]]
    其中name为段名,flags为段的属性如读、写、可执行等,type指定了段的类型,如proghits和iaoshi段中包含有数据、note表示段中包含的数据非程序本身使用,flag_specific_arguments指定了一些平台相关的参数。
    例如:.section .note.GNU-stack,"",%progbits ,定义.note.GNU.stack段,作用是禁止生成可执行堆栈,保护代码安全,防止堆栈溢出漏洞带来的危害。

  • 注释与标号
    注释:/*…*/
    标号:<标号>:
    例如:
    LOOP: /*这里是标号*/
    .....
    sub r0,r0,#1
    cmp r0,#0
    bne loop

  • 汇编器指令
    所有以.开头的指令都是汇编器指令,汇编器指令是与汇编器相关的,它们不属于arm指令集。

  • 子程序与参数传递
    子程序与函数有相同的概念,声明方法如下:
    .global 函数名
    .type 函数名,%function 函数名: <…函数体…>
    arm规定r0-r3这四个寄存器用来传递函数调用的第1个和第4个参数,超出的参数用堆栈来传递。同时r0用来存放函数返回值,调用函数在返回前无需恢复这些寄存器的内容


arm处理器的寻址方式

简单的来讲寻址就是通过地址值得到真实操作数地址。

  • 立即寻址:通过立即数方式,简单的说通过具体的地址数寻找,如mov r0 ,#1234
  • 寄存器寻址:这个简单,寄存器与寄存器之间相互取值。
  • 寄存器移位寻址:LSL(逻辑左移), LSR(逻辑右移),ASR(算术右移),ROR(循环右移),RRX(带扩展的循环右移)
  • 寄存器间接寻址:将操作数的地址取值赋给寄存器LDR r0,[r1]
  • 基址寻址:基址寄存器与偏移量相加,形成操作数的有效地址。多用于查表、数组访问等操作。LDR r0,[r1,#-4]
  • 多寄存器寻址:LDMIA r0,{r1,r2,r3,r4}
  • 堆栈寻址:主要用LDM和STM指令前缀,表示多寄存器寻址,使用指令后缀FA、EA、FD、ED。STMFD SP!,{r1-r7,lr}将人r1-r7入栈,多用于保存子程序现场。
  • 块拷贝寻址:主要用LDM和STM指令前缀,表示多寄存器寻址,使用指令后缀IA、DA、IB、DB。STMIA r0!,{r1-r3}存储r1-r3寄存器的内容到r0寄存器指向的存储单元。
  • 相对寻址:以程序计数器pc的当前值i为基地址,指令中的地址标号为偏移量,将两者相加后得到的操作数的有效地址。如:
    BL NEXT
    ...
    NEXT:
    ...
    表示跳到next标号处执行。

arm指令集

arm指令集基本格式如下:<opcode>{<cond>}{S}{.W.N}<Rd>,<Rn>{,<operand2>}
opcode为指令助记符。如MOV、ADD等。
cond为执行条件。
S指定指令是否影响CPSR寄存器的值。如ADDS、SUBS等。
.W和.N为指令宽度说明符。可以是16或32位。默认是16位。
Rd为目的寄存器。
Rn为第一个操作数寄存器。
operand2为第二个操作数。


跳转指令

  • B跳转指令
  • BL跳转指令
  • BX带状态切换的跳转指令
  • BLX带链接和状态切换的跳转指令

存储器访问指令

LDR:从存储器中读数据到寄存器。
STR:用于存储数据到指定地址的存储单元。
LDM:同LDR读多个。
STM:同STR写多个。
PUSH:将寄存器推入满递减堆栈。
POP:从满递减堆栈中弹出数据到寄存器。
SWP:用于寄存器与存储器之间的数据交换。


数据处理指令

数据处理指令在arm指令集中占有大部分,在这里就不一一列出了。参考可以看这里或者这个


其他指令

这里还有一些比较重要常见的指令。
SWI:软中断指令。用于实现从用户模式到管理模式的切换。如系统功能调用。格式如下:
SWI {cond},immed_24
immed_24是软中断号,在android中系统功能调用为0号中断,使用R7寄存器存放系统调用号。使用R0-R3传递系统调用的前4个参数,大于4个参数用堆栈传递。
NOP:为空操作指令。用于空操作或者字节对齐。格式只有指令本身。
MRS:为读状态寄存器指令。格式为:
MRS RD,psr MSR:为写状态寄存器指令。格式为:
MSR Rd,psr_fields,operand2
psr的取值可以是cpsr或spsr。
fields指定传送的区域。