首页
论坛
课程
招聘
[原创]360加固保免费版分析
2020-3-31 01:46 12911

[原创]360加固保免费版分析

2020-3-31 01:46
12911

吐槽一下

这玩意儿实在是有点恶心,各种骚操作来浪费你的时间。比如把jni接口的函数全部重新排列了一遍,你得在他赋值的时候手动去把两百多个函数复制过来。把if-else的分支跳转地址全放在数组里面,f5生成伪代码全是JUMPOUT,你得手动去修复所有地址。。。  


详细分析

自己随便新建个项目,用360加固保去加固。加固后apk包如下所示。原来的class都没了,assets下多了几个so。



通过AndroidManifest.xml可知入口在com.stub.StubApp。

先看attachBaseContext方法,可知是判断cpu,然后将对应的so文件释放到app的data目录下的.jiagu目录,然后用System.load方法加载该so,这里我手机对应的为libjiagu_a64.so。



用ida打开so,查找init_array,里面只有一个函数sub_17CC。 



函数sub_17CC调用sub_20B4,其内容如下,先通过mmap分配一段内存,然后通过sub_1F4C将一段数据解密后存在刚刚分配的内存中。再然后调用sub_1ECC对解密后的数据进行重定位。最后通过mprotect修改内存页属性。  



然后分析JNI_OnLoad,从刚刚解密的数据中获取函数地址,然后跳转,最终调用到__arm_a_1(地址0x89C0),  



__arm_a_1内容如下,sub_8950检查当前so名字是否为libjiagu开头的,不是则生成信号9结束进程。sub_837C读取/proc/net/tcp文件,检查端口0x5D8A,如果存在则kill进程,所以动态调试要把ida的默认端口改了。sub_83B4则是解密出一个so并通过自定义的加载器将它加载。然后找到新so的JNI_OnLoad调用。  



sub_83B4通过分析,最终调用到sub_5254的时候,所有数据都解密出来了,可以在此处dump新的so。

sub_5254函数有两个参数,其数据结构分别如下:

参数1中buf_1、buf_2、buf_3、buf_4分别为解密后的程序头表、JMPREL、RELA、DYNAMIC  



参数2中的decode_data为中间数据,参数1中的buf_1、buf_2、buf_3、buf_4就是从这里解密出来的,最后还包含一个so,该so被抹除了前面这几部分内容。  



在sub_5254入口处dump解密的so,脚本如下。  

auto buf_ptr;
buf_ptr=qword(x1+0x10);
msg("%x\n",buf_ptr);

auto buf_1_len_ptr,buf_1_len,buf_1_ptr;
buf_1_len_ptr=buf_ptr+1;
buf_1_len=dword(buf_1_len_ptr);
buf_1_ptr=buf_1_len_ptr+4;
msg("%x,%x\n",buf_1_ptr,buf_1_len);

auto buf_2_len_ptr,buf_2_len,buf_2_ptr;
buf_2_len_ptr=buf_1_ptr+buf_1_len;
buf_2_len=dword(buf_2_len_ptr);
buf_2_ptr=buf_2_len_ptr+4;
msg("%x,%x\n",buf_2_ptr,buf_2_len);

auto buf_3_len_ptr,buf_3_len,buf_3_ptr;
buf_3_len_ptr=buf_2_ptr+buf_2_len;
buf_3_len=dword(buf_3_len_ptr);
buf_3_ptr=buf_3_len_ptr+4;
msg("%x,%x\n",buf_3_ptr,buf_3_len);

auto buf_4_len_ptr,buf_4_len,buf_4_ptr;
buf_4_len_ptr=buf_3_ptr+buf_3_len;
buf_4_len=dword(buf_4_len_ptr);
buf_4_ptr=buf_4_len_ptr+4;
msg("%x,%x\n",buf_4_ptr,buf_4_len);

auto buf_5_len,buf_5_ptr;
buf_5_ptr=buf_4_ptr+buf_4_len;
buf_5_len=dword(buf_5_ptr+0x28)+word(buf_5_ptr+0x3a)*word(buf_5_ptr+0x3c);
msg("%x,%x\n",buf_5_ptr,buf_5_len);

auto new_buf_1_ptr,new_buf_2_ptr,new_buf_3_ptr,new_buf_4_ptr;
new_buf_1_ptr=qword(x0+0x50);
new_buf_2_ptr=qword(x0+0x80);
new_buf_3_ptr=qword(x0+0x88);
new_buf_4_ptr=qword(x0+0x90);
msg("%x,%x,%x,%x\n",new_buf_1_ptr,new_buf_2_ptr,new_buf_3_ptr,new_buf_4_ptr);


auto ph_off,jmprel_off,rela_off,dynamic_off;
ph_off=qword(buf_5_ptr+0x20);

auto ph_index,ph_size,ph_num,temp_ptr;
ph_index=0;
ph_size=word(buf_5_ptr+0x36);
ph_num=word(buf_5_ptr+0x38);
while(ph_index<ph_num)
{
    temp_ptr=new_buf_1_ptr+ph_size*ph_index;
    if(dword(temp_ptr)==2)
    {
        dynamic_off=qword(temp_ptr+8);
        break;
    }
    ph_index++;
}

auto dyn_index,dyn_size,dyn_num;
dyn_index=0;
dyn_size=0x10;
dyn_num=buf_4_len/dyn_size;
while(dyn_index<dyn_num)
{
    temp_ptr=new_buf_4_ptr+dyn_size*dyn_index;
    if(dword(temp_ptr)==0x17)
    {
        jmprel_off=qword(temp_ptr+8);
    }
    else if(dword(temp_ptr)==7)
    {
        rela_off=qword(temp_ptr+8);
    }
    dyn_index++;
}
msg("%x,%x,%x,%x\n",ph_off,jmprel_off,rela_off,dynamic_off);


msg("start\n");
auto dump_so ;
dump_so =fopen("D:\\ dump.so","wb");

savefile(dump_so, 0, buf_5_ptr, buf_5_len);
msg("save so(buf_5)\n");

savefile(dump_so, ph_off, new_buf_1_ptr, buf_1_len);
msg("save Phdrs((buf_1))\n");

savefile(dump_so, jmprel_off, new_buf_2_ptr, buf_2_len);
msg("save DT_JMPREL(buf_2)\n");

savefile(dump_so, rela_off, new_buf_3_ptr, buf_3_len);
msg("save DT_RELA(buf_3)\n");

savefile(dump_so, dynamic_off, new_buf_4_ptr, buf_4_len);
msg("save PT_DYNAMIC(buf_4)\n");


fclose(dump_so);

msg("end\n");


将dump出来的so用ida打开,该so因为没有Section Header Table,所以要自己到动态节中去找到init_array。

动态节内容如下。可以知道init_array在0x1277c0



init_array内容如下。这些函数都是在初始化一些变量。



然后分析JNI_OnLoad。

先是注册StubApp的各种native方法,内容如下:



然后再解析linker64查找符号rtld_db_dlactivireport保存起来。未被调试时返回0。后面解密vmp方法的时候会用到,如果不为0会导致解密失败。

然后将从dex中解析出附加数据,其包含加固的配置信息和所有原始dex。

解析dex的地方为sub_240C8,该函数主要就是对每个dex开启一个线程进行解密,然后join获取结果。  



所以在sub_240C8返回的时候,dump所有dex,返回值结构如下  



dump脚本如下。  

msg("start\n");
auto start,end,index,dex_ptr,file_size,dump_name,dump_dex;
start=qword(x0);
end=qword(x0+8);

index=0;
while(index*8+start!=end)
{
    dex_ptr=qword(qword(index*8+start));
    file_size=dword(dex_ptr+0x20);
    
    index=index+1;
    if (index==1)
    {
        dump_name="classes.dex";
    }
    else
    {
        dump_name="classes"+ltoa(index,10)+".dex";
    }
    
    msg("%s %08x %08x\n",dump_name,dex_ptr,file_size);
    dump_dex=fopen("D:\\"+dump_name,"wb");
    savefile(dump_dex, 0, dex_ptr, file_size);
    fclose(dump_dex);
    
}
msg("end\n\n");


然后通过DexFile::OpenCommon加载所有的dex文件。

然后通过循环调用sub_8B6E4,从dex中解析出附加数据,该附加数据包含所有被vmp方法的信息,如下所示,每个item包含5个字段,每个4字节。

第一个字段为方法在dex中的method_id,

第二个字段为方法类型,0为实例方法,1为静态方法,

第三个字段为方法对应的code_item在dex中的偏移。

第四个字段为360自定义的clas_id,注册vmp方法的时候用到。

第五个字段为后面紧跟着的指令数,如果为0表示直接解密原来code_item中的指令执行,不为0则执行的时候,把该处的指令复制到原来的code_item,执行完后又清除。  



然后到sub_1FF80,该函数将所有dex中的DexPathList.Element添加到原类加载器中

然后将所有dex前8字节清空。

然后解析dex,为vmp方法执行分配空间,并初始化一些数据结构。其中sub_8FD08会分配一个数组,每个vmp方法都会将sub_12D040函数的机器码复制过来。作为调用代理。之所以每个方法都复制,是因为到时调用的时候,会将函数的地址作为参数,这样通过偏移就能知道是调用哪一个方法。  



然后再根据配置决定是否dex2oat

再然后就是注册一些类的native方法,就先不管了。到此为止,所有加载工作就完成了。

回到StubApp类中attachBaseContext,在加载so后还调用了interface5,onCreate调用了interface21,通过前面注册是时候找到的方法,分析后,这两个方法只是根据配置做了一些操作,就先不管了。

现在来看之前dump的dex文件。对比下加固前后的内容,可以看到,onCreate方法被改为native方法了,而且类初始语句中多了一行代码StubApp.interface11(1344);



现在来分析interface11,通过前面可知,该方法绑定的函数为sub_327F4。找到该函数,f5生成伪代码,发现有JUMPOUT这玩意儿,这个还不能直接就跳过去按p新建函数,不然一块代码一个函数,流程根本就搞不清楚,而且变量也不对,,,,  



跳转地址是通过计算得到的,通过分析,sub_8F5A0返回1就跳到off_12A4D0[0],返回0就跳到off_12A4D0[1],所以可以判断出,这里其实是一个if-else,只是地址在数组里。因为所有dump出来的时候,没有Section Header Table,所以整个文件所有都是可读可写的,我以为是因为这个原因,导致ida不能识别出,于是手动将数组所在的地方建了 一个节,属性改为只读,结果还是识别不出来。于是我就放弃了,手动将跳转地址写入指令中。即将原来的ldr、br等指令改为beq、bne。

原始指令如下:



修改后指令如下:



然后就按照这个套路,遇到就修改,一个函数改了几十个地方,我特么人都要疯了,,,,

改完后就能看到流程了。  



该函数其实就是通过参数传进来的那个数字(自定义的class id),在附加数据找到对应的class,将class对应的所有vmp方法进行动态注册。

比如MainActivity中的StubApp.interface11(1344);    

1344的十六进制为0x540



而第一个字段为方法id,由此知道,该类的vmp只有id为0x3b8b这一个,通过解析dex,可知该方法为onCreate



现在来看绑定的本地函数是什么,可以看到,每个方法绑定的都是前面复制的那个函数sub_12D040的指令,



所以现在来分析sub_12D040,该函数就是调用了qword_12D0C0处保存的地址,该处是在之前复制指令的时候进行赋值的,为sub_3156C。  



现在来看sub_3156C,又是这玩意儿,又是几十处手动改好,人又疯了一遍,,,,  



改好后,分析流程如下,先通过第一个参数,判断出当前调用的是哪一个方法。

可以看出,当前调用的时候,第一个参数传了一个地址,  



通过汇编指令可以看到,该地址就是函数调用后的返回地址。

所以现在明白了,为什么每一个绑定的都是同一个函数,但是要复制到不同的位置。



然后,一个恶心的事又来了,特么把jni接口的函数顺序重新赋值了一遍,调用jni方法的时候,用的自定义的那个。于是,又只好把这玩意儿复制一遍,两百多个函数,人又疯了一遍,,,,,,

    


然后根据第五个字段,不为0则复制指令到code_item,然后直接用jni函数调用,调用完成后,再将其清空。  



如果是0,则申请寄存器空间,自己将参数解析出来,

然后调用sub_3E620,又是,,,,,人又疯一遍,,,,  



改好后,分析流程如下,解释执行的时候,逐条指令开始解密,并且解密后的指令不是加固前的,也是经过替换的。所以要想修复,需要每个指令都去分析一下才行



还原指令这个就先不搞了,以后有时间再弄了。。。  


总结

1.开始的时候,第一个so有部分代码是动态解密的。

2.然后会解密出第二个so,该so的程序头表、JMPREL、RELA、DYNAMIC是抽离的,通过自定义的linker加载。并且节表也被清空了。

3.所有dex都被加密隐藏在的classes.dex后面。

4. DexFile::OpenCommon加载所有dex,并设置类加载器的pathList.dexElements。将dex前8字节清空。

5.类初始化的时候注册当前类的vmp方法。

6.vmp化方法绑定的本地函数执行的时候,根据当前函数地址确定执行的是哪一个方法。

7.绑定的本地函数中if-else的分支地址被保存在变量中,通过br跳转,需要修复后才能看清流程。

8.jni接口中的函数被重新排列,需要创建一个对应的结构进行赋值操作。

9.vmp方法对应两种执行模式,一种是直接将指令复制到原地方,然后用jni函数调用,调用完成后再将其清空。另一种方式是自己实现的解释器,边解密边解释执行,且解密后的指令也是替换过的。要想修复,需要把每条指令的流程跟踪一遍,看它实际用途是什么。

10.疯了,疯了,,,,,


看雪侠者千人榜,看看你上榜了吗?

最后于 2020-6-8 23:13 被卧勒个槽编辑 ,原因: 附件
上传的附件:
收藏
点赞6
打赏
分享
最新回复 (46)
雪    币: 3259
活跃值: 活跃值 (179)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
yezheyu 活跃值 2020-3-31 03:30
2
0
学习了
雪    币: 7118
活跃值: 活跃值 (421)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
tDasm 活跃值 2020-3-31 08:13
3
0
学习
雪    币: 5784
活跃值: 活跃值 (425)
能力值: ( LV4,RANK:156 )
在线值:
发帖
回帖
粉丝
月落之汀 活跃值 1 2020-3-31 08:17
4
0
感谢分享!
之前也是到他自己的vmp解释器那里停下来了,太恶心了
顺便有几张图挂了
最后于 2020-3-31 08:20 被月落之汀编辑 ,原因:
雪    币: 15725
活跃值: 活跃值 (11832)
能力值: (RANK:75 )
在线值:
发帖
回帖
粉丝
Editor 活跃值 2020-3-31 11:04
5
0
感谢分享!
雪    币: 1120
活跃值: 活跃值 (217)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
上海刘一刀 活跃值 2 2020-3-31 11:06
6
0
感谢分享
雪    币: 656
活跃值: 活跃值 (37)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
Fireeye 活跃值 2020-3-31 12:12
7
0
感谢分享!
雪    币: 61
活跃值: 活跃值 (83)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
leezn 活跃值 2020-3-31 12:40
8
0
感谢分享  
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7GUNIANG 活跃值 2020-4-1 00:03
9
0
大佬 很想认识你
雪    币: 2377
活跃值: 活跃值 (275)
能力值: ( LV6,RANK:85 )
在线值:
发帖
回帖
粉丝
卧勒个槽 活跃值 2 2020-4-1 00:09
10
0
7GUNIANG 大佬 很想认识你[em_91]
雪    币: 195
活跃值: 活跃值 (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
peipei 活跃值 2020-4-1 00:30
11
0
那么是不是可以直接System.load解密出来的so    onCreate 不用修复了也就实现了脱壳
雪    币: 2377
活跃值: 活跃值 (275)
能力值: ( LV6,RANK:85 )
在线值:
发帖
回帖
粉丝
卧勒个槽 活跃值 2 2020-4-1 08:15
12
0
peipei 那么是不是可以直接System.load解密出来的so onCreate 不用修复了也就实现了脱壳
不能吧,它是从当前进程的apk安装包中解压出dex,然后从该dex解密出原来的dex,自己load的话解密不出东西来,而且解密的dex一直在内存中,没有释放到文件
雪    币: 3948
活跃值: 活跃值 (412)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
pghuanghui 活跃值 2020-4-1 09:50
13
0
感谢分享~
雪    币: 195
活跃值: 活跃值 (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
peipei 活跃值 2020-4-1 12:09
14
0
卧勒个槽 不能吧,它是从当前进程的apk安装包中解压出dex,然后从该dex解密出原来的dex,自己load的话解密不出东西来,而且解密的dex一直在内存中,没有释放到文件
可以dump出来的吧
雪    币: 2377
活跃值: 活跃值 (275)
能力值: ( LV6,RANK:85 )
在线值:
发帖
回帖
粉丝
卧勒个槽 活跃值 2 2020-4-1 12:16
15
0
peipei 可以dump出来的吧
没必要自己去加载吧,如果只是单纯想拿到dex,直接在OpenCommon下断点就行了,
最后于 2020-4-1 12:17 被卧勒个槽编辑 ,原因:
雪    币: 2339
活跃值: 活跃值 (9146)
能力值: ( LV9,RANK:166 )
在线值:
发帖
回帖
粉丝
0x指纹 活跃值 3 2020-4-1 13:15
16
0
膜膜膜膜膜膜tql
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7GUNIANG 活跃值 2020-4-1 16:14
17
0
卧勒个槽
认识一下 怎么样?
雪    币: 1035
活跃值: 活跃值 (986)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
爱吃菠菜 活跃值 1 2020-4-1 17:47
18
0
雪    币: 195
活跃值: 活跃值 (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
peipei 活跃值 2020-4-1 18:23
19
0
卧勒个槽 peipei 可以dump出来的吧 没必要自己去加载吧,如果只是单纯想拿到dex,直接在OpenCommon下断点就行了,
既然是逆向最起码带有点别的目的,去除广告,破解VIP等等 要保证脱壳后的app可以运行啊 只单单脱出dex只是一切的开始
雪    币: 2377
活跃值: 活跃值 (275)
能力值: ( LV6,RANK:85 )
在线值:
发帖
回帖
粉丝
卧勒个槽 活跃值 2 2020-4-1 18:34
20
0
peipei 既然是逆向最起码带有点别的目的,去除广告,破解VIP等等 要保证脱壳后的app可以运行啊 只单单脱出dex只是一切的开始
我就只是无聊,单纯分析下,
雪    币: 2377
活跃值: 活跃值 (275)
能力值: ( LV6,RANK:85 )
在线值:
发帖
回帖
粉丝
卧勒个槽 活跃值 2 2020-4-1 18:37
21
0
7GUNIANG 认识一下 怎么样?[em_86]
有啥事直说吧,私活啥的我不接,
雪    币: 2641
活跃值: 活跃值 (665)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
挤蹭菌衣 活跃值 2020-4-1 18:57
22
0
顶大佬  牛逼+10086
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7GUNIANG 活跃值 2020-4-2 17:13
23
0
卧勒个槽 有啥事直说吧,私活啥的我不接,[em_78]
我就是一个平凡的你需要的小猎头
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7GUNIANG 活跃值 2020-4-2 17:16
24
0
7GUNIANG 我就是一个平凡的你需要的小猎头
我是一个略懂技术的小猎头 请认识一下哈哈哈哈~
雪    币: 333
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_hzvbvwsa 活跃值 2020-4-3 08:05
25
0
感谢分享
游客
登录 | 注册 方可回帖
返回