分析一下X加密

前言

本地环境依然是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'

= 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.最终效果

脱壳前:

脱壳后:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值