前言
传统android开发倾向于将所有代码部署在android应用层,这种方式在面对软件安全威胁的情况下时比较容易破解。将重要的代码使用android ndk原生层的方式实现,由于ndk原生层代码逆向原本难度较高,使得文件无法轻易被破解。因此,理解和掌握android原生层运行机制对逆向水平的提高有比较大的帮助。
原生程序生成
原生程序的生成需要经过一下几步:预处理——>编译——>汇编——>链接(这几个文件后缀变化依次为a.c——>a.s——>a.o——>a ),其中经过第2步编译过后c代码就成了arm汇编代码。学习的重点也就是基于arm的汇编。
arm的一些基本概念
- arm微处理器共有37个32位寄存器,其中31个为通用寄存器,6个为状态寄存器。
- arm处理器的七种运行模式:
- 用户模式usr:arm处理器正常的程序执行状态。
- 快速中断模式fiq:用于高速数据传输或通道处理。
- 外部中断模式irq:用于通用的中断处理。
- 管理模式svc:操作系统使用的保护模式。
- 数据访问终止模式:当数据或指令预取终止时进入该模式,可用于虚拟存储以及存储保护。
- 系统模式sys:运行具有特权的操作系统任务。
- 为定义指令中止模式und:当未定义的指令执行时进入该模式。
需要注意的是,arm运行模式可以通过软件本省改变,也可以通过外部中断或者异常处理改变,不同模式所使用的寄存器也不一样,另外,除了用户模式其他六种模式都可以访问受保护的系统资源。
- 当运行在用户模式中,处理器可以访问的寄存器为不分组寄存器r0-r7、分组寄存器r8-r14、程序计数器r15(PC)还有状态寄存器cpsr。
- arm寄存器的两装工作状态:arm状态和thumb状态,其中arm状态执行32位字对齐。thumb状态执行16位字对齐。
- 寄存器只是临时存放处。
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指定传送的区域。