首页
论坛
课程
招聘
[逆向分析] [脱壳反混淆] [原创]分析一下X加密
2021-5-9 14:54 6040

[逆向分析] [脱壳反混淆] [原创]分析一下X加密

2021-5-9 14:54
6040

前言

本地环境依然是6.0.1的系统,这应该是最后一个分析的抽取类型的壳,后面会正式进入VMP的分析,文章没有分析的太透彻,主要还是以脱壳为主。文中ida中出现的字段和函数名可能根据自己的理解被修改过,也可能出现错误,还请各位大佬多多担待,并且指正,由于某些原因最终的脱壳脚本没办法给大家提供,但是会有思路,还请大家多多包涵.

Java层入口

首先,这个壳有点不太一样
图片描述

 

大家通过看上面图片可以发现,这个应用的dex抽取过后和壳打到一起了。所以后面就不会有再去加载dex的操作。OK,下面正式开始分析

 

定位s.h.e.l.l.S

 

8083ea54f4569841b4cf223c614333cb.png

 

s.h.e.l.l.N->静态块

 

52ec6f14b60277f4f547ade1577a7037.png

 

之后进入so层的分析

So层入口

0a2129527928f9b85107fd8e24705287.png

so脱壳

调试的话,直接定位linker,调试一下.init_proc

 

08967748bf33ff0c80f40a2f5b2ff79d.png

 

642e42f56cd1da01ff1e2014463b2949.png

 

1bf9734883f257b83e52b74eba6aad1c.png

 

0b5f71799fe05c0271ab1d7cce0197b7.png

 

a1f58647bc4c47b92aa587f0310be08a.png

 

9d21960493a0029af14dce444fb4ed33.png

 

过掉UND之后,可以开始dump了。

 

7d5f359dba2f8a37b5c13a5cfcf24ca3.png

 

0c94068aebf921ea06b4cd2de7a9b4a2.png

 

1d5620b0e7a13da115b244fbabcb9e42.png

 

47d92a86955f749f0da598417382d3ba.png

 

发现并不是从ELF头开始的,而是0x10000起始的。所以直接修复了

 

我采用新建一个2进制文件,先将0x10000之前的数据copy进去,后面再拼接此dump出的数据,然后实际大小,是dump出的大小加上前面的数据得出总大小

 

24a0e23bd8c8ea5fb6ab220f7fd61c8c.png

 

2533331d0662840e0a5841e2af106860.png

 

然后把数据追加到新创建的so中,注意对齐

 

fbd2fc1bcddd46526a0c6ab96542f51e.png

 

由于我并不需要把so完美修复好,我本地修复主要是动态调试有个对照,所以这2个segment修复后,就可以看到大致的代码了。

 

e48f9df8b32844757a4f22c24925d5b3.png

分析INIT_ARRAY

init_array做了保护,被编译器拆分成很多函数,我分析的时候几乎是一个一个看的,总结出来了,init_array主要做2个事情,第一,字符串解密,并保存;第二,反调试,下面简单介绍一下主要流程.

 

f7b258035259409b0f8ff59405a24cdf.png

 

前面若干个函数主要做了一些字符串的解密,类似于下图

 

acfb51dbe1152b85cd64b460b7239613.png

 

定位sub_12BDC:

 

兼容性处理与函数地址初始化

 

e86d861968b7145a890a9de856a02207.png

 

定位sub_2CB80

 

这函数主要做反调试

 

89932a0756200c1124096af995766c2d.png

 

3e167cbc2e4afa02944dd17d22a25725.png

 

53c4e25320da4e4d7bc7100af5095b1e.png

JNI_ONLOAD

这个函数并不是很长,通过分析,核心做了2个事情。(这里说一下)

 

初始化一些数据:例如: 机型相关的数据:HARDWARE,MODEL,RELEASE,sdk_version等等(这个是为了兼容性考虑,后面逻辑会有一些判断),applicationInfo,processName,sourceDir, 待使用的文件路径,和一些Java层的class名字,最终都会保存在一个全局结构体中,和乐固类似。

 

Java层的函数动态注册: 其中主要涉及到如下函数

 

**N:l->sub_3C02C

 

N:r->sub_3EF5C

 

N:ra->sub_3F46C**

 

下面做简单分析,定位sub_3AB48(JNI_ONLOAD核心实现在这里)

 

 

 

 

 

 

JNI_ONLOAD走完了,下面继续回到Java层,看看调用了哪个native函数

 

sub_3C02C:N->l

这个函数有很多的兼容性的操作.

 

 

 

 

这里hook了非常多的art的函数,我们比较关注的,定位sub_26BAC, 壳的还原时机就是hook了这个loadMethod函数

 

 

后面还有一些逻辑,不过到这里,这个壳已经可以脱了。

opcode填充时机

定位sub_27034

 

 

 

 

然后进入sub_51CD8开始真正的填充

 

 

反调试与环境检测

反调试

init_array已经有3中反调试了

 

89932a0756200c1124096af995766c2d.png

 

3e167cbc2e4afa02944dd17d22a25725.png

 

53c4e25320da4e4d7bc7100af5095b1e.png

 

 

 

后面还有,但是我idb丢失了一次,这个忘记了,大家到时候自己调试一下在sub_2D6CC

环境检测

检测是否有/data/dexname

 

尝试env->loadClass("cn/youlor/Unpacker")

 

 

检测是否存在“/data/local/tmp/unpacker.config”

 

检测是否存在fart

 

 

 

检测/data/local/tmp/re.frida.server

 

 

然后有个专门的线程,检测maps中的内容,检测了如下字符串

 

com.android.reverse-

 

/data/local/tmp/libFupk3.so

 

xposed.Fdex2

 

/system/bin/app_process32_xposed

 

xposed.installer

 

app_process64_xposed

 

libxposed_art.so

 

io.va.exposed

 

io.virtualapp.sandvxposed

 

libriru_

 

com.saurik.substrate

 

re.frida.server

 

mapp.rm-

 

_frida-agent.so

 

com.example.FunDex-

nop反调试与环境检测

经过下面一段脚本的执行,可以直接F9让应用完美运行起来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
base =0x13FB4
 
 addr_patch1 = 0x2CB90
 addr_patch2 = 0x2CC5E
 addr_patch3 = 0x2CD48
 addr_patch4 = 0x4035C
 addr_patch5 = 0x3C336
 addr_pathc6 = 0x3C340
 addr_patch7 = 0x3C3B2
 addr_patch8 = 0x3C3FE
 
 addr_patch1_add = addr_patch1 - base
 addr_patch2_add = addr_patch2 - base
 addr_patch3_add = addr_patch3 - base
 addr_patch4_add = addr_patch4 - base
 addr_patch5_add = addr_patch5 - base
 addr_patch6_add = addr_pathc6 - base
 #去环境检测
 addr_patch7_add = addr_patch7 - base
 addr_patch8_add = addr_patch8 - base
 
 print(hex(addr_patch1_add))
 print(hex(addr_patch2_add))
 print(hex(addr_patch3_add))
 print(hex(addr_patch4_add))
 print(hex(addr_patch5_add))
 print(hex(addr_patch6_add))
 print(hex(addr_patch7_add))
 
 # only bypass
 if addr_patch1_add == 0x18bdc and addr_patch2_add == 0x18caa:
     addr_patch1_real = base_ea+addr_patch1_add
     addr_patch2_real = base_ea+addr_patch2_add
     addr_patch3_real = base_ea+addr_patch3_add
     addr_patch4_real = base_ea+addr_patch4_add
     addr_patch5_real = base_ea+addr_patch5_add
     addr_patch6_real = base_ea+addr_patch6_add
     addr_patch7_real = base_ea+addr_patch7_add
     addr_patch8_real = base_ea+addr_patch8_add
 
     idaapi.patch_dword(addr_patch1_real, 0x0000F04F)
     idaapi.patch_word(addr_patch2_real, 0xBF00)
     idaapi.patch_dword(addr_patch3_real, 0x0000F04F)
     idaapi.patch_dword(addr_patch4_real, 0xBF00BF00)
     idaapi.patch_word(addr_patch5_real, 0xBF00)
     idaapi.patch_dword(addr_patch6_real, 0xBF00BF00)
     idaapi.patch_dword(addr_patch7_real, 0x0000F04F)
     idaapi.patch_dword(addr_patch8_real, 0xBF00BF00)

脱壳

比较抱歉,这边由于某些原因,最终的脚本不能给到大家,下面说一下思路。

1.Dump壳已经准备好的数据

大家要先仔细调试一下sub_27034这个函数,就知道dump哪里了,很明显。

 

根据上面壳本身的还原逻辑,我们可以直接从内存中dump出3段数据,

 

第一段是真正的opcode相关信息存放的数据段,例如opcode的长度,以及真正的opcode。

 

后面2段是存放有关debuginfo相关的数据,例如debuginfo的值,以及当前debuginfo在第一段数据中对应的起始地址。

 

然后我们还需要保存当前3段数据的起始地址,因为这些数据dump出来的时候,存放的都是实际地址,所以我们需要减去起始地址,纯计算偏移去做。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
int getReallyAddr1(int addr) {
    int malloc_base = 0xAA640000;
    int r_addr = addr - malloc_base;
    return r_addr;
}
 
int getReallyAddr2(int addr) {
    int malloc_base = 0xAB140000;
    int r_addr = addr - malloc_base;
    return r_addr;
}
 
//0xAA640000:第二段数据的起始地址
//0xAB140000:第三段数据的起始地址
//这个函数计算真正的地址
int getReallyAddr(int addr) {
    if (addr > 0xAA640000 and addr < 0xAB140000) {
        return getReallyAddr1(addr);
    } else {
        return getReallyAddr2(addr);
    }
}
//这个函数计算用哪个mmap的内存去取数据
char *getReallyMp(int addr, char *mp1, char *mp2) {
 
    if (addr > 0xAA640000 and addr < 0xAB140000) {
        return mp1;
    } else {
        return mp2;
    }
}

2.根据原dex提取debuginfo

这里用py脚本解析DexFile就好。把每个code_item的debuginfo保存到文件,这里是否保存到文件取决于大家最终的修复脚本用py还是C,我是用C的,但是我的解析脚本是py,所以保存文件中给C解析.

1
2
3
4
5
6
7
8
9
10
11
12
13
ff = 'classes.dex'
p = DexParser(ff)
p.parse()
for classDef in p.class_def:
    dataOff = classDef['class_data_off']
    if dataOff != 0:
        dataItem = classDef['class_data_item']
        direct_method = dataItem['direct_methods']
        vir_methods = dataItem['virtual_methods']
        for dirMethod in direct_method:
            info_off = dirMethod['code_item']['debug_info_off']
        for vir_method in vir_methods:
            info_off = vir_method['code_item']['debug_info_off']

3.还原壳的修复逻辑

这里还原大家一定要直接看指令,不要F5,不要F5,不要F5!

 

1.还原sub_152D2

 

2.还原sub_51CD8

4.最终效果

脱壳前:

 

 

脱壳后:


[注意] 招人!base上海,课程运营、市场多个坑位等你投递!

最后于 2021-5-9 14:59 被GitRoy编辑 ,原因:
上传的附件:
收藏
点赞6
打赏
分享
最新回复 (10)
雪    币: 791
活跃值: 活跃值 (2559)
能力值: ( LV8,RANK:141 )
在线值:
发帖
回帖
粉丝
Simp1er 活跃值 2021-5-9 15:05
2
0
nnnb
雪    币: 458
活跃值: 活跃值 (554)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
ADR66 活跃值 2021-5-9 15:20
3
0
+1
雪    币: 983
活跃值: 活跃值 (696)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Youlor 活跃值 2021-5-9 15:28
4
0
牛逼,很详细了!
雪    币: 628
活跃值: 活跃值 (2218)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
珍惜Any 活跃值 2021-5-9 17:35
5
0
竟然检测Fundex
雪    币: 1004
活跃值: 活跃值 (311)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
疯子Tear 活跃值 2021-5-9 20:17
6
0
tql
雪    币: 8343
活跃值: 活跃值 (2402)
能力值: (RANK:200 )
在线值:
发帖
回帖
粉丝
LowRebSwrd 活跃值 4 2021-5-10 13:35
7
0
nb
雪    币: 210
活跃值: 活跃值 (238)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
dzahz 活跃值 2021-5-10 14:06
8
0
这是干嘛的
雪    币: 5917
活跃值: 活跃值 (2747)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
GitRoy 活跃值 3 2021-5-10 14:40
9
0
dzahz 这是干嘛的
记录一下分析加固的过程
雪    币: 7686
活跃值: 活跃值 (198)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
sqdebug 活跃值 2 2021-5-10 15:38
10
0

那个UND指令是IDA在ARM架构下的软件断点指令形式(UND指令是无效异常指令,执行时会触发CPU异常,IDA收到异常后就可以停下,即为软件断点作用),就跟windows的int3断点似的,出现这个问题,因为这段代码是自修改代码,SMC,你调试时不可以下多余一个的断点,建议直接f4或者单步调试,因为你f2下了断点,而且没有取消这个断点直接往下调试了,在它后面mmap时就把内存这个断点UND指令复制过去了,所造成的问题,注意,IDA下软件断点在UI表现上虽然看不到实际的内存修改,仅是断点处颜色改变,但是其内存处被修改成断点指令了。

最后于 2021-5-10 15:42 被sqdebug编辑 ,原因:
雪    币: 5917
活跃值: 活跃值 (2747)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
GitRoy 活跃值 3 2021-5-10 15:48
11
0
sqdebug 那个UND指令是IDA在ARM架构下的软件断点指令形式(UND指令是无效异常指令,执行时会触发CPU异常,IDA收到异常后就可以停下,即为软件断点作用),就跟windows的int3断点似的,出现这个 ...
原来如此!! 谢谢大佬指点!!
游客
登录 | 注册 方可回帖
返回