前言
上一篇文章分析了Obfuscapk,这一篇文章继续分析另一个apk加固工具advmp。
作者说明:https://www.cnblogs.com/develop/p/4397397.html
github地址:https://github.com/chago/ADVMP
简单来说,advmp参考dalvik虚拟机的解释器对字节码进行解释执行。代码结构如下。
- AdvmpTest:测试用的项目。
- base:Java写的工具代码。
- control-centre:Java写的控制加固流程的代码。
- separator:Java写的抽离方法指令,然后将抽离的指令按照自定义格式输出,并同时输出C文件的代码。
- template/jni:C写的解释器的代码。
- ycformat:Java写的用于保存抽取出的指令等数据的自定义的文件格式代码。
我们来写一个demo,看看加固前后的效果:
代码中默认对separatorTest方法进行加固。可以看到加固后separatorTest方法已经变成一个native方法,并且加载了advmp.so。
dalvik虚拟机的解释器
首先参考infoQ上几篇文章回顾下dalvik虚拟机的解释器。我们知道可以通过RegisterNatives函数注册native方法,最后调用到dvmSetNativeFunc函数中将Method的nativeFunc指向dvmCallJNIMethod函数。
在dvmCallMethodV函数中将ins指向第一个参数,如果不是静态方法将this指针放入ins,根据后面参数的类型依次将后面的参数放入ins。
如果调用的是native方法就会进入Method的nativeFunc指向的dvmCallJNIMethod函数;如果调用的是java方法就会进入dvmInterpret函数。
下面我们来分析dvmInterpret函数。dvmInterpret函数选择解释器(这里以Portable解释器为例),调用dvmInterpretPortable函数。dvmInterpretPortable函数通过DEFINE_GOTO_TABLE定义了一个 handlerTable[kNumPackedOpcodes]数组。数组元素通过H宏定义,比如H(OP_RETURN_VOID)展开后得到&&op_OP_RETURN_VOID,表示op_OP_RETURN_VOID的位置。op_OP_RETURN_VOID通过HANDLE_OPCODE宏定义。
之后dvmInterpretPortable函数中调用了FINISH(0)取第一条指令并执行。移动PC,然后获取对应指令的操作码到inst。根据inst获取该指令的操作码(一条指令包含操作码和操作数),然后goto到该操作码对应的label处理处。在对应label处理处从指令中提取参数,比如INST_A或INST_B。处理后再次调整PC,使得能处理下一条指令。
ADVMP原理
了解了dalvik虚拟机的解释器原理之后我们就可以理解ADVMP加壳的过程了。
插入System.loadLibrary指令加载advmp.so:
抽取separatorTest方法的代码写到classes.yc文件中,生成新的separatorTest方法(native)。yc文件为自定义格式,保存了抽取出来的方法指令/访问标志/参数个数等等信息:
将separatorTest方法对应的C代码和注册该方法的C代码写到advmp_separator.cpp文件中,然后将该文件中的代码合并到定义了JNI_OnLoad方法的avmp.cpp文件中,如下所示为生成的advmp_separator.cpp文件:
JNI_OnLoad中首先调用registerFunctions函数然后释放并解析yc文件:
在separatorTest方法对应的C代码中调用了BwdvmInterpretPortable函数,第一个参数为从yc文件中得到的data,BwdvmInterpretPortable函数首先要做类似于dvmCallMethodV函数的工作,然后就可以像dvmInterpretPortable函数一样对字节码进行解释执行了。