Android SO逆向1-ARM介绍

3xpl0it · 2015/11/02 10:33

 

0x00 概述


把之前学习SO逆向的笔记分享出来,内容比较简单,大牛就可以略过了。

0x01 ARM寄存器


1.1.通用寄存器

1.2.状态寄存器

img

N、Z、C、V均为条件码标志位,他们的内容可被运算的结果所改变。

1.3.地址空间

0x02 汇编语言


2.1.汇编指令格式

2.2.指令的可选后缀

2.3.指令的条件执行

条件码助记符后缀标志含义
0000EQZ置位相等
0001NEZ清零不相等
0010CSC指令无符号数大于或等于
0011CCC清零无符号数小于
0100MIN置位负数
0101PLN清零正数或零
0110VSV置位溢出
0111VCV清零未溢出
1000HIC置位Z清零无符号数大于
1001LSC清零Z置位无符号数小于或等于
1010GEN等于V带符号数大于或等于
1011LTN不等于V带符号数小于
1100GTZ清零且(N等于V)带符号数大于
1101LEZ置位或(N不等于V)带符号数小于或等于
1110AL忽略无条件执行

例子:ADDEQ R4,R3,#1 相等则相加,即CPSR中Z置位时该指令执行,否则不执行。

2.4.ARM指令分类

助记符指令功能描述助记符指令功能描述
ADC带进位加法指令MRC从协处理器寄存器到ARM寄存器的数据传输指令
ADD加法指令MRS传送CPSR或SPSR的内容到通用寄存器指令
AND逻辑与指令MSR传送通用寄存器到CPSR或SPSR的指令
B分支指令MUL32位乘法指令
BIC位清零指令MLA32位乘加指令
BL带返回的分支指令MVN数据取反传送指令
BLX带返回和状态切换的分支指令ORR逻辑或指令
BX带状态切换的分支指令RSB逆向减法指令
CDP协处理器数据操作指令RSC带错位的逆向减法指令
CMN比较反值指令SBC带错位减法指令
CMP比较指令STC协处理器寄存器写入存储器指令
EOR异或指令STM批量内存字写入指令
LDC存储器到协处理器的数据传输指令STR寄存器到存储器的数据存储指令
LDM加载多个寄存器指令SUB减法指令
LDR存储器到寄存器的数据加载指令SWI软件中断指令
MCR从ARM寄存器到协处理器寄存器的数据传输指令TEQ相等测试指令
MOV数据传送指令TST位测试指令

2.5.ARM寻址方式

2.5.1立即数寻址

2.5.2寄存器寻址

2.5.3寄存器间接寻址

2.5.4寄存器移位寻址

2.5.5基址变址寻址

2.5.6.多寄存器寻址

2.5.7.相对寻址

2.5.8.堆栈寻址

2.6.数据处理指令

2.6.1. MOV指令

2.6.2. MVN指令

2.6.3. 移位指令

2.6.4. ADD加法指令

2.6.5. ADC带进位加法指令

2.6.6. SUB减法指令

2.6.7. SBC带借位减法指令

2.6.8. RSC带借位的逆向减法指令

2.6.9. 逻辑运算指令

2.6.10. CMP比较指令

CMP{<cond>}{S} Rd,Rn,op2    将Rn的值和op2进行比较,同时更新CPSR中条件标志位的值(实际上是执行一次减法,但不存储结果),当操作数Rn大于op2时,则此后带有GT后缀的指令将可以执行(根据相应的指令判断是否执行,如GT,LT等)。
CMP R1,#10                  比较R1和10,并设置CPSR的标志位
ADDGT R0,R0,#5              如果R1>10,则执行ADDGT指令,将R0加5

2.6.11. CMN反值比较指令

2.6.12. MUL/MLA/SMULL/SMLAL/UMULL/UMLAL乘法指令

2.7.数据加载与存储指令

助记符说明操作
LDR{}Rd,addr加载字数据Rd = [addr]
LDRB{}Rd,addr加载无符号字节数据Rd = [addr]
LDRT{}Rd,addr以用户模式加载字数据Rd = [addr]
LDRBT{}Rd,addr以用户模式加载无符号字节数据Rd = [addr]
LDRH{}Rd,addr加载无符号半字数据Rd = [addr]
LDRSB{}Rd,addr加载有符号字节数据Rd = [addr]
LDRSH{}Rd,addr加载有符号半字数据Rd = [addr]
STR{}Rd,addr存储字数据[addr] = Rd
STRB{}Rd,addr存储字节数据[addr] = Rd
STRT{}Rd,addr以用户模式存储字数据[addr] = Rd
STRBT{}Rd,addr以用户模式存储字节数据[addr] = Rd
STRH{}Rd,addr存储半字数据[addr] = Rd
LDM{}{type}Rn{!},regs多寄存器加载reglist = [Rn...]
STM{}{type}Rn{!},regs多寄存器存储[Rn...] = reglist
SWP{}Rd,Rm,[Rn]寄存器和存储器字数据交换Rd=[Rn],[Rn]=Rm(Rn!=Rd或Rm)
SWP{}B Rd,Rm,[Rn]寄存器和存储器字节数据交换Rd = [Rn],[Rn] = Rm(Rn!=Rd或Rm)

2.7.1. LDR/STR字数据加载/存储指令

2.7.2. LDRB/STRB字节数据加载/存储指令

2.7.3. LDRH/STRH半字数据加载/存储指令

2.7.4. LDM/STM批量数据加载/存储指令

2.7.5. SWP字数据交换指令

2.8.分支语句

助记符说明操作
B{cond}label分支指令PC<-label
BL{cond}label带返回的分支指令PC<-label,LR=BL后面的第一条指令地址
BX{cond}Rm带状态切换的分支指令PC = Rm & 0xffffffe,T=Rm[0] & 1
BLX{cond}labelRm带返回和状态切换的分支指令 | PC=label,T=1 PC; PC = Rm & 0xffffffe,T=Rm[0] & 1;LR = BLX后面的第一条指令地址

2.8.1. 分支指令B

2.8.2. 带返回的分支指令BL

BL{<cond>}label         在跳转之前,将PC的当前内容保存在R14(LR)中保存,因此,可以通过将R14的内容重新加载到PC中,返回到跳转指令之后的指令处执行。该指令用于实现子程序的调用,程序的返回可通过把LR寄存器的值复制到PC寄存器中来实现。
例子:
BL func             跳转到子程序
ADD R1,R2,#2        子程序调用完返回后执行的语句,返回地址
....
func                子程序
...
MOV R15,R14         复制返回地址到PC,实现子程序的返回

2.8.3. 带状态切换的分支指令BX

BX{<cond>} Rm       当执行BX指令时,如果条件cond满足,则处理器会判断Rm的位[0]是否为1,如果为1则跳转时自动将CPSR寄存器的标志T置位,并将目标地址的代码解释为Thumb代码来执行,则处理器会切换到Thumb状态,反之,若Rm的位[0]为0,则跳转时自动将CPSR寄存器的标志T复位,并将目标地址处的代码解释为ARM代码来执行,即处理器会切换到ARM状态。

注意:bx lr的作用等同于mov pc,lr。即跳转到lr中存放的地址处。 非零值存储在R0中返回。

那么lr存放的是什么地址呢?lr就是连接寄存器(Link Register, LR),在ARM体系结构中LR的特殊用途有两种:一是用来保存子程序返回地址;二是当异常发生时,LR中保存的值等于异常发生时PC的值减4(或者减2),因此在各种异常模式下可以根据LR的值返回到异常发生前的相应位置继续执行。  

当通过BL或BLX指令调用子程序时,硬件自动将子程序返回地址保存在R14寄存器中。在子程序返回时,把LR的值复制到程序计数器PC即可实现子程序返回。

2.9堆栈

2.9.1. 进栈出栈

出栈使用LDM指令,进栈使用STM指令。LDM和STM指令往往结合下面一些参数实现堆栈的操作。
FD:满递减堆栈。
ED:空递减堆栈。
FA:满递增堆栈。
EA:空递增堆栈。
满堆栈是指SP(R13)指向堆栈的最后一个已使用地址或满位置(也就是SP指向堆栈的最后一个数据项的位置);相反,空堆栈是指SP指向堆栈的第一个没有使用的地址或空位置。
LDMFD和STMFD分别指POP出栈和PUSH入栈

2.9.2. PUSH指令

PUSH{cond} reglist      PUSH将寄存器推入满递减堆栈
PUSH {r0,r4-r7}         将R0,R4-R7寄存器内容压入堆栈

2.9.3. POP指令

POP{cond} reglist       POP从满递减堆栈中弹出数据到寄存器
POP {r0,r4-r7}          将R0,R4-R7寄存器从堆栈中弹出

0x03 创建Android NDK程序


3.1. NDK程序创建过程

3.2. 编写程序

3.2.1. CLASS文件

在com.example.hellojni包中创建class文件,本文一次创建多个实例,共参考。

GetInt.java代码为

GetString.java代码为

GetFor.java代码为

GetIfElse.java代码为

GetWhile.java代码为

GetSwitch.java代码为

MainActivity.java代码为

build_headers.xml代码为

然后双击ANT中的HelloJNI,然后F5刷新工程项目,可以看到jni目录下,多出6个文件,com_example_hellojni_GetFor.h等,此文件里面就是函数.h接口文件,是没有具体代码的,需要把里面的函数复制到jni目录下的HelloJNI.cpp文件中,然后去实现函数的具体部分。

HelloJNI.cpp的代码为

以上就是一些实例的代码,下面就来分析逆向后的ARM代码。以下反汇编代码都是通过IDA得到的,至于IDA的使用方法,大家可以看看书。

3.2.2. getInt()方法

getInt()的方法代码如下:

反编译后的代码为:

3.2.3. getStr()方法

getStr()的方法代码如下:

反编译后的代码为:

img

3.2.3. getFor1()方法

getFor1()的方法代码如下:

JNIEXPORT jint JNICALL Java_com_example_hellojni_GetFor_getFor1
  (JNIEnv *, jclass, jint n){
    int i = 0;
    int s = 0;
        for (i = 0; i < n; i++){
            s += i * 2;
        }
        return s;
}

反编译后的代码为:

img

代码解释如下:

EXPORT Java_com_example_hellojni_GetFor_getFor1
Java_com_example_hellojni_GetFor_getFor1
               MOVS    R0, #0       ;R0 = 0
               MOVS    R3, R0       ;R3 = 0
               B       loc_FB0      ;跳转到loc_FB0
; ---------------------------------------------------------------------------
loc_FAA                                 ; CODE XREF: Java_com_example_hellojni_GetFor_getFor1+Ej
               LSLS    R1, R3, #1       ;R1=R3左移一位(即R1=R3*2)
               ADDS    R0, R0, R1       ;R0=R0+R1
               ADDS    R3, #1           ;R3=R3+1
loc_FB0                                 ; CODE XREF: Java_com_example_hellojni_GetFor_getFor1+4j
                CMP     R3, R2      ;比较R3和R2,R2是第一个参数,即n
                BLT     loc_FAA     ;如果R3<R2,跳到loc_FAA
                BX      LR          ;否则,子程序返回R0
;End of function Java_com_example_hellojni_GetFor_getFor1

3.2.4. getWhile()方法

getWhile()的函数代码如下:

JNIEXPORT jint JNICALL Java_com_example_hellojni_GetWhile_getWhile
  (JNIEnv *, jclass, jint n){
        int i = 1;
        int s = 0;
        while(i <= n){
            s += i++;
        }
        return s;
}

反编译后的结果为:

img

代码解释如下:

                 EXPORT Java_com_example_hellojni_GetWhile_getWhile
 Java_com_example_hellojni_GetWhile_getWhile
                 MOVS    R0, #0         ;R0 = 0
                 MOVS    R3, #1         ;R3 = 1
                 B       loc_FEA        ;跳转到loc_FEA
 ; 
-------------------------------------------------------------
 loc_FE6                                 ; CODE XREF: 
le_hellojni_GetWhile_getWhile+Cj
                 ADDS    R0, R0, R3   ;R0=R0+R3
                 ADDS    R3, #1           ;R3=R3+1
 loc_FEA                                 ; CODE XREF: 
le_hellojni_GetWhile_getWhile+4j
                 CMP     R3, R2         ;比较R3和R2,R2为第一个参数,即n
                 BLE     loc_FE6        ;如果R3<R2,跳转到loc_FE6
                 BX      LR             ;否则返回结果R0
 ; End of function Java_com_example_hellojni_GetWhile_getWhile

3.2.5. getIfElse()方法

getIfElse()的代码如下

JNIEXPORT jstring JNICALL Java_com_example_hellojni_GetIfElse_getIfElse
  (JNIEnv *env, jclass, jint n){
        if(n < 16){
            return env->NewStringUTF("he is a boy");
        } else if(n < 30){
            return env->NewStringUTF("he is a young man");
        } else if(n < 45){
            return env->NewStringUTF("he is a strong man");
        } else{
            return env->NewStringUTF("he is an old man");
        }
}

反编译后的结果为:

img

代码解释如下:

3.2.6. getSwitch()方法

getSwitch()的代码如下:

反编译后的结果为:

img

代码解释如下:

 

收藏

分享

来源: >