代码编译

使用的代码为非虫的Android软件安全权威指南,app6.c

代码如下:

#include <stdio.h>

int nums[5] = {1, 2, 3, 4, 5};
int for1(int n) {
    int i = 0;
    int s = 0;
    for (i = 0; i < n; i++) {
        s += i * 2;
    }
    return s;
}
int for2(int n) {
    int i = 0;
    int s = 0;
    for (i = 0; i < n; i++) {
        s += i * i + nums[n - 1];
    }
    return s;
}

int dowhile(int n) {
    int i = 1;
    int s = 0;
    do {
        s += i;
    } while (i++ < n);
    return s;
}
int whiledo(int n) {
    int i = 1;
    int s = 0;
    while (i <= n) {
        s += i++;
    }
    return s;
}

void if1(int n) {
    if (n < 10) {
        printf("the number less than 10\n");
    } else {
        printf("the number greater than or equal to 10\n");
    }
}

void if2(int n) {
    if (n < 16) {
        printf("he is a boy\n");
    } else if (n < 30) {
        printf("he is a young man\n");
    } else if (n < 45) {
        printf("he is a strong man\n");
    } else {
        printf("he is an old man\n");
    }
}


int main(int argc, char *argv[]) {
    printf("for1:%d\n", for1(5));
    printf("for2:%d\n", for2(5));

    printf("dowhile:%d\n", dowhile(100));
    printf("while:%d\n", whiledo(100));

    if1(5);
    if2(35);

    return 0;
}

删除了其中的switch函数,编译的时候函数一直报错,使用make编译,创建jni目录,把c文件和Makefile放入到目录中,修改文件为如下:

#设置目录
NDK_ROOT=D:\Androidstudio-sdk\android-ndk-r14b
TOOLCHAINS_ROOT=$(NDK_ROOT)\toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64
TOOLCHAINS_PREFIX=$(TOOLCHAINS_ROOT)\bin\arm-linux-androideabi
TOOLCHAINS_INCLUDE=$(TOOLCHAINS_ROOT)\lib\gcc\arm-linux-androideabi\4.9.x\include-fixed
PLATFORM_ROOT=$(NDK_ROOT)\platforms\android-14\arch-arm
PLATFORM_INCLUDE=$(PLATFORM_ROOT)\usr\include
PLATFORM_LIB=$(PLATFORM_ROOT)\usr\lib



MODULE_NAME=app6
BUILD_TYPE=c
PATH_ANDROID=/data/local/tmp/

RM=del

FLAGS=-I$(TOOLCHAINS_INCLUDE) \
    -I$(PLATFORM_INCLUDE) \
	-L$(PLATFORM_LIB) \
	-nostdlib \
	-lgcc \
	-Bdynamic \
	-lc	\
	-O0

OBJS=$(MODULE_NAME).o \
	$(PLATFORM_LIB)\crtbegin_dynamic.o \
	$(PLATFORM_LIB)\crtend_android.o

all:
	$(TOOLCHAINS_PREFIX)-gcc $(FLAGS) -c $(MODULE_NAME).$(BUILD_TYPE) -o $(MODULE_NAME).o -pie -fPIE
	$(TOOLCHAINS_PREFIX)-gcc $(FLAGS) -S $(MODULE_NAME).$(BUILD_TYPE) -o $(MODULE_NAME).S -pie -fPIE
	$(TOOLCHAINS_PREFIX)-gcc $(FLAGS) $(OBJS) -o $(MODULE_NAME) -pie -fPIE
clean:
	$(RM) *.o
install:
	adb push $(MODULE_NAME) $(PATH_ANDROID)
	adb shell chmod 755 $(PATH_ANDROID)$(MODULE_NAME)
	adb shell $(PATH_ANDROID)$(MODULE_NAME)

编译后如下,会生成一个无后缀的可执行文件。丢到IDA中。点击for1函数,空格切换图形视图。一般看到这种形式的箭头指向的时候基本就是for或者while语句。

1561960721772

1. for1函数

查看第一个汇编指令块,创建了三个函数,开头先做了保存子程序现场,开启堆栈空间。R0-R3,是用作传入变量寄存器,所以此处有三个变量,分别为,var_10, var_8, var_c。最后强制跳转到loc_3e8标记处。

1561962684226

loc_3e8处的汇编代码块,从存储器中加载两个变量值到R2,R3然后对比两个变量,如果R2小于R3,则跳转到loc_3c8标记处。其中对var_8做了一次逻辑左移处理,相当于R3 = R3 * 2^1。再获取变量var_c,执行操作R3 = R3 +R2,再存储到存储器中。下面就相当于for循环中的循环变量自加。最后循环跳出后,再把var_c的值返回。

1561964069808

按照以上逻辑写一段C代码,如下:

int for1(int var_10){
    int var_8 = 0;
    int var_c = 0;
    for(var_8 = 0; var_8 < var_10; var_8++){
        var_c = var_c + var_8 << 1;
    }
    return var_c;
}

2. for2函数

for2跟如上的for1基本类似,就是内部处理处有点不一样,其中nums,点击一下就可以看到是五位的数组,具体是,先var_8自乘,然后读取nums数组,R3为nums数组的地址值,也就是nums[var_10 -1],再加var_8的自乘和var_c。

1562135688840

C代码:

int nums[5] = {1,2,3,4,5};
int for2(int var_10){
    int var_8 = 0;
    int var_c = 0;
    for(var_8 = 0; var_8 < var_10; var_8++){
        var_c =var_c + var_8* var_8  + nums[var_10 - 1];
    }
    return var_c;
}

3. dowhile函数

其中大部分都类似以上汇编代码,其中dowhile和whiledo存在有明显不同,dowhile是先执行内部的汇编代码,变量执行自加等操作,最后在判断是否一致,循环体上大致类似如下图,而whiledo更类似于如上的for循环体。汇编中并没有强制跳转,而且跳转标记在判断跳转处的上方。

1561972796048

根据汇编代码写出大致的C代码。

int dowhile(int var_10){
    int var_8 = 1;
    int var_c = 0;
    do{
        var_c = var_8 + var_c;
        var_8 = var_8 + 1;
    }while(var_8 < var_10)
    return var_c
}

4. whiledo函数

whiledo从结构体上和for1相同,本来这两种循环就及其类似,主要查看的时候能分清while循环的特点和逻辑来区分,哪个参数为判断参数,哪个参数为计算参数。在B指令执行的地方,就为for和while的判断指令。

1562122434389

根据汇编代码写出大致的C代码。

int whiledo(int var_10){
    int var_8 = 1;
    int var_c = 0;
    while(var_8 <= var_10){
        var_8 = var_8 + 1;
        var_c = var_c + var_8;
    }
    return var_c
}

5. if1 函数

如下,具有单分支的if结构体可以看出是if-else判断形式

1562123715096

按照汇编意思,C代码如下:

void if1(int var_8){
    if(var_8 > 9){
        puts("the number greater than or equal to 10");
    }else{
        puts("the number less than 10");
    }
}

在if判断中,判断体在汇编中和原代码是相反的,意思是var_8 > 9原文应该是var_8 <=9 。printf也由puts来改变输出,puts输出会自动添加换行符,也就不在需要原文中的\n。

void if1(int var_8){
    if(var_8 <= 9){
        puts("the number less than 10");
    }else{
        puts("the number greater than or equal to 10");
    }
}

6. if2函数

多判断结构体,也就是if-else-if

1562125307657

其中可以看到有多个LDR计算赋值指令,此处的LDR和MOV类似,只是有些时候MOV不适合使用,所以用LDR来赋值计算。

LDR     R3, =(aHeIsABoy - 0x5D0)

代表意思就是,aHeIsABoy的地址值减去0x5D0,赋值给R3。仔细看一下就可以明白,其中的参数都是原字符串。LDR就是aHeIsABoy减去0x5D0,此处是07EC减去0x5D0,为021C。而PC,程序计数器,指的是BL的地址值。详细参考:https://www.cnblogs.com/ichunqiu/p/9056630.html

也就是说,执行到ADD的时候,MOV在译码阶段,BL在取址阶段。PC寄存器总是指向随后的第三条指令。

用BL的地址值加上R3,05D0加上021C为7EC,也就是aHeIsABoy的地址值,当然IDA已经识别出来并注释到其后。再把字符串由puts输出。

LDR     R3, =(aHeIsABoy - 0x5D0)     //000007EC aHeIsABoy       DCB "he is a boy",0
ADD     R3, PC, R3      ; "he is a boy"
MOV     R0, R3          ; s
BL      puts
B       loc_628

按照汇编意思,C代码如下:

void if2(int var_8){
    if(var_8 > 15){
        if(var_8 > 29){
            if(var_8 > 44){
                puts("he is an old man");
            }
            else{
                puts("he is a strong man");
            }
        }
        else{
            puts("he is a young man");
        }
    }
    else{
        puts("he is a boy");
    }
}

由于汇编对判断的形式,用else if的形式可以这么写

void if2(int var_8){
    if(var_8 <= 15){
        puts("he is a boy");
    }
    else if(var_8 <= 29){
        puts("he is a young man");
    }
    else if(var_8 <= 44){
        puts("he is a strong man");
    }
    else{
        puts("he is an old man");
    }
}

看完以上的汇编分析后,可以得到循环体的特点,循环体一般为以下:

....    //汇编代码
B loc_xxx
....
CMP r1, r2
BXX loc_xxx
....
BX LR

判断形式一般为以下:

    ....
    CMP R1, R2
    BXX loc_xxx
    ....
loc_xxx
	....
	B   loc_zzz
loc_zzz
	....

当然,以上CMP后并不一定会跟随B指令,也会由其他条件执行指令,例如:

CMP R2, R3
ADDEQ R2, R3, #1

其中寄存器存值,仍然是需要注意点,不小心会看错赋值,如下:

LDR     R3, [R11,#var_8]
LDR     R2, [R11,#var_C] 
ADD     R3, R2, R3      

在执行一些类似ADD的指令操作后,其中的R3已不在代表var_8,其中的结果跟ADD后所获取到的R3赋值有关,这种情况可以看成:

a = 1;
b = 2;
a = a + b;  //3

所以,其中a的值已不在是1,同样如上中,R3也为此,在初期可能在不能连续查看汇编代码的情况下,会犯这种失误。

main 函数

main函数中的赋值和返回值寄存器对应之前函数中的返回和赋值,如MOV R0, #5。在for1中,R0赋值给var_10。

同时返回值在R0中。

1562137477746

下部汇编类似,就不在一步步看啦。





# Android逆向  

tocToc: