安卓 dex 通用脱壳技术研究(一)

http://my.oschina.net/cve2015/blog/508604


摘要  本文剖析安卓 Dalvik 解释器 Portable 的原理与实现,并通过修改 Dalvik 虚拟机实现 dex 文件的通用脱壳方案;本方法同样适应于 ART 虚拟机;

0x01 背景介绍

安卓 APP 的保护一般分为下列几个方面:

  1. JAVA/C代码混淆

  2. dex文件加壳

  3. .so文件加壳

  4. 反动态调试技术

其中混淆和加壳是为了防止对应用的静态分析;代码混淆会增加攻击者的时间成本, 但并不能从根本上解决应用被逆向的问题;而加壳技术一旦被破解,其优势更是荡然无存;反调试用来对抗对APP的动态分析;

7月份的乌云安全峰会上,来自上交的杨文博在"Android应用程序通用自动脱壳方法研究"中分享了他们开发的通用脱壳工具,通过定制Dalvik实现,并对目前国内6款主流加固产品的进行了测试,效果良好;

然而他并没有公开源码;昨天看雪zyqqyz同学发了一个自己的实现:DexHunter,详见Github;下面我们会讲解Dalvik解释器实现原理,并分析DexHunter代码实现;

目前来看,要对抗这种脱壳方法,最好的办法应该是APP启动时hook被修改的几处函数,并还原之;对开发者来说,应当将涉及资产、创新的关键代码放入Native层实现,并通过.so加壳和反调试进行保护;

上次与梆梆的交流,他们提到梆梆3.0正在研发类似PC上的VMP保护壳技术,实现APK保护;但个人认为要在兼容性方面做的工作实在太多,短时间内估计很难实现了;

下面开始,分3个方面介绍通过定制Dalvik的通用脱壳方案:1.Dalvik 解释器原理分析,2.DexHunter代码分析,3.测试

0x02 Dalvik 解释器原理分析

解释器是Dalvik虚拟机的执行引擎,它负责解释执行Dalvik字节码。在字节码加载完毕后,Dalvik虚拟机调用解释器开始取指解释字节码,解释器跳转到解释程序处执行。目前安卓解释器有两种,Portable和Fast解释器,分别使用C和汇编实现;优势分别是兼容性和性能,具体使用哪个可以自己来指定,因此本着简单的原则,我们分析并使用Portable解释器;

获取字节码并分析与解释执行是Dalvik虚拟机解释器的主要工作。Dalvik虚拟机的入口函数是vm/interp下的dvmInterpret函数;外部通过调用dvmInterpret函数进入解释器执行,其流程为dvmCallMethod->dvmCallMethodV->dvmInterpret。

在外部函数调用解释器以后,解释器执行的主要流程有以下几个步骤。

  1. 初始化解释器执行环境

  2. 根据系统参数,选择使用Portable或Fast解释器

  3. 跳转到相应解释器执行

  4. 取指及指令检查

  5. 执行字节码对应程序段

dvmInterpret函数作为解释器的入口函数,主要完成整个流程的前三部分,执行流程如下:

由于前三部分与我们定制Dalvik无关,这里不做详细介绍;

根据解释器的功能,可以想像的到,最简单的模型就是一个大的switch语句,对每条指令进行判断,然后case到相应的代码进行解释,解释完成后又回到switch顶部,如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
while  (insn) {
     switch  (insn) {
         case  NOP:
             break ;
         case  MOV:
             do  something;
             break ;
         ...
         case  OP:
             do  something;
             break ;
         default :
             break ;
     }
     取指;
}

然而当解释完成一条指令后,再重新判断指令类型是个昂贵的开销。因为对于每条指令,都将从switch顶部开始判断,也就是从NOP指令开始判断,直到找到相应的指令为止,这使得解释器的执行效率十分低下。

这类问题的解决方法就是空间换时间,Dalvik就采用了这个思路;它为每条指令分配一个对应的标签(Label),标签标示的是该指令解释程序的开始,每条指令的解释程序末尾,有取指动作,可以取下一条要执行指令;Dalvik具体使用GCC的Threaded Code技术来实现,它使用了一个静态的标签数组,用来存储各个字节码解释程序对应的标签地址,其具体以一个宏来定义:

dalvik/libdex/DexOpcodes.h

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
  * Macro used to generate a computed goto table for use in implementing
  * an interpreter in C.
  */
#define DEFINE_GOTO_TABLE(_name) \
     static  const  void * _name[kNumPackedOpcodes] = {                      \
         /* BEGIN(libdex-goto-table); GENERATED AUTOMATICALLY BY opcode-gen */  \
         H(OP_NOP),                                                            \
         H(OP_MOVE),                                                           \
         H(OP_MOVE_FROM16),                                                    \
         H(OP_MOVE_16),                                                        \
         H(OP_MOVE_WIDE),                                                      \
         ...
     }

下面看下H宏实现:

?
1
#define    H(_op)    &&op_##_op

那如何根据指令得到相应的Label地址呢?Dalvik中使用了索引号:

?
1
2
3
4
5
6
7
8
9
10
11
12
enum  Opcode {
     // BEGIN(libdex-opcode-enum); GENERATED AUTOMATICALLY BY opcode-gen
     OP_NOP                          = 0x00,
     OP_MOVE                         = 0x01,
     OP_MOVE_FROM16                  = 0x02,
     OP_MOVE_16                      = 0x03,
     OP_MOVE_WIDE                    = 0x04,
     OP_MOVE_WIDE_FROM16             = 0x05,
     OP_MOVE_WIDE_16                 = 0x06,
     OP_MOVE_OBJECT                  = 0x07,
     ....
}

因此整个执行流程就是:取指令->取索引号->取Label得到解释程序地址->执行指令,并取下一条指令。

上面分析了解释器的基本模型,下面看Dalvik Portable的执行流程。其解析流程如图:

首先进行相关变量的声明,保存当前正在解释的方法curMethod、程序计数器pc、栈桢指针fp、当前指令inst、指令译码的相关部分包括保存寄存器值vsrc1,vsrc2,vdst、设置方法调用指针methodToCall等。

通过DEFINE_GOTO_TABLE(handlerTable)宏进行GOTO Label的绑定,获取并拷贝self->interpSave里保存的当前状态,包括方法method、程序计数器pc、堆栈帧curFrame、返回值retval、要分析的Dex文件的类对象信息curMethod->clazz->pDvmDex等已声明的变量。其代码如下:

dalvik/vm/mterp/out/InterpC-portable.cpp

?
1
2
3
4
5
6
     /* copy state in */
     curMethod = self->interpSave.method;
     pc = self->interpSave.pc;
     fp = self->interpSave.curFrame;
     retval = self->interpSave.retval;    /* only need for kInterpEntryReturn? */
     methodClassDex = curMethod->clazz->pDvmDex;

最后通过FINISH(0)来取得第一条指令开始执行字节码解析。

在Dalvik Portable中,解释程序是由一系列宏控制,以对应的Label来表示,以NOP操作为例,其定义如下:

dalvik/vm/mterp/out/InterpC-portable.cpp

?
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
/*--- start of opcodes ---*/
/* File: c/OP_NOP.cpp */
HANDLE_OPCODE(OP_NOP)
     FINISH(1);
OP_END
/* File: c/OP_MOVE.cpp */
HANDLE_OPCODE(OP_MOVE  /*vA, vB*/ )
     vdst = INST_A(inst);
     vsrc1 = INST_B(inst);
     ILOGV( "|move%s v%d,v%d %s(v%d=0x%08x)" ,
         (INST_INST(inst) == OP_MOVE) ?  ""  "-object" , vdst, vsrc1,
         kSpacing, vdst, GET_REGISTER(vsrc1));
     SET_REGISTER(vdst, GET_REGISTER(vsrc1));
     FINISH(1);
OP_END
/* File: c/OP_MOVE_FROM16.cpp */
HANDLE_OPCODE(OP_MOVE_FROM16  /*vAA, vBBBB*/ )
     vdst = INST_AA(inst);
     vsrc1 = FETCH(1);
     ILOGV( "|move%s/from16 v%d,v%d %s(v%d=0x%08x)" ,
         (INST_INST(inst) == OP_MOVE_FROM16) ?  ""  "-object" , vdst, vsrc1,
         kSpacing, vdst, GET_REGISTER(vsrc1));
     SET_REGISTER(vdst, GET_REGISTER(vsrc1));
     FINISH(2);
OP_END

HANDLE_OPCODE(OP_NOP)表示对应的是OP_NOP操作,紧接其后的是解释程序的具体实现。到OP_END结束。而在Portable中,所以有解释程序都由C语言编写。NOP操作中的HANDLE_OPCODE、FINISH和OP_END都是宏定义。其中HANDLE_OPCODE和OP_END是成对出现的,OP_END什么也不做:

?
1
#define    OP_END

所以

?
1
2
3
HANDLE_OPCODE(OP_NOP)
     FINISH(1);
OP_END

可以翻译为:

?
1
2
op_OP_NOP:
     FINISH(1);

对于NOP指令,其完成的工作就是什么也不做。因此,对应的解释程序就是直接取下一条将要执行的指令,也就是FINISH(1)所完成的工作。在FINISH()宏里,虚拟机获取下一条指令,并从指令中提取操作码号,根据该操作码号到指令解释程序查找表中得到相应的标签,然后跳转到该处理程序执行。其定义如下:

?
1
2
3
4
5
6
7
8
# define FINISH(_offset) {                                                  \
         ADJUST_PC(_offset);                                                 \
         inst = FETCH(0);                                                    \
         if  (self->interpBreak.ctl.subMode) {                                \
             dvmCheckBefore(pc, fp, self);                                   \
         }                                                                   \
         goto  *handlerTable[INST_INST(inst)];                                \
     }

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值