代码编译
使用的代码为非虫的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语句。
1. for1函数
查看第一个汇编指令块,创建了三个函数,开头先做了保存子程序现场,开启堆栈空间。R0-R3,是用作传入变量寄存器,所以此处有三个变量,分别为,var_10, var_8, var_c。最后强制跳转到loc_3e8标记处。
loc_3e8处的汇编代码块,从存储器中加载两个变量值到R2,R3然后对比两个变量,如果R2小于R3,则跳转到loc_3c8标记处。其中对var_8做了一次逻辑左移处理,相当于R3 = R3 * 2^1。再获取变量var_c,执行操作R3 = R3 +R2,再存储到存储器中。下面就相当于for循环中的循环变量自加。最后循环跳出后,再把var_c的值返回。
按照以上逻辑写一段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。
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循环体。汇编中并没有强制跳转,而且跳转标记在判断跳转处的上方。
根据汇编代码写出大致的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的判断指令。
根据汇编代码写出大致的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判断形式
按照汇编意思,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
其中可以看到有多个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中。
下部汇编类似,就不在一步步看啦。