篇幅有限
完整内容及源码关注公众号:ReverseCode,发送 冲
历代加壳技术分类
动态加载是用到的时候再去加载,也叫懒加载,是dex加壳,插件化,热更新的基础,避免发布新版本需要用户全量更新app,快速迭代,提升用户体验。比如阿里的AndFix和HotFix,腾讯的tinker,美团的Robust等热修复框架的基础。动态加载的dex不具有生命周期特征,APP中的Activity,Service等组件无法正常工作,只能完成一般函数的调用。需要对ClassLoader进行修正,APP才能够正常运行,有反射替换和反射插入两种修正手段,这是加壳app必然要做的一步。
第一代壳:dex加密
Dex字符串加密
资源加密
对抗反编译
反调试
自定义 DexClass Loader
由于是dex整体保护,在内存中映射是整体连续的,通过定位起始地址将app完整脱下来
第二代壳:dex抽取与加固
对抗第一代壳常见的脱壳法
Dex method代码抽取到外部(通常企业版)
Dex动态加载
So加密
首先dex整体保护,其次对关键函数进行抽取(dump内存区域中dex关键类为空)进行so加密,对抗第一代壳常用脱壳法(从关键hook点dump dex)
第三代壳:dex动态解密与混淆
Dex method代码动态解密
So代码膨胀混淆
对抗之前出现的所有脱壳法
对抗第二代壳的一个通用脱壳工具DexHunter(通过遍历dex所有类进行加载初始化,将内存中dex完整恢复),dex中method动态解密,指定函数只有被调用时才执行,dex在内存中始终不是完整的状态
第四代壳:vmp壳
目前仍然是smali指令级别的vmp,未来将出现arm指令级别vmp
加壳技术分析
dex加固
- dex整体加固:文件加载(监控app访问文件的记录)和内存加载(对内存dex的解密直接动态加载)
- 函数抽取:在函数粒度完成代码的保护:dump下dex的关键函数体置空
- VMP和Dex2C:JAVA函数 Native化。vmp->逆向分析解释器找到取址译码
dex整体加固
dex整体加壳是基础防护,所有加壳的app必然都有,关键在于怎么区分函数抽取,vmp以及dex2c甚至多种技术混合的混合型壳。
文件加载:定位解密文件是关键
内存加载:加载时机和内存起始地址是关键
通用方案:dex打开和优化的流程以及产出的odex、dex2oat编译的流程和生成的oat文件等等
函数抽取
- 类加载和函数执行前的流程解密
- 函数执行中动态自解密
方案:关注被抽取的函数的执行流程是关键!定位被抽取的函数的恢复时机即可。
获取到保护的dex后,函数体的内容是无效的,注意这里说的是无效,而不是无意义,有的app壳后函数依然是有意义的,但不是我们想要的。f5查看函数体全部为空,则表现为函数抽取。
VMP
定位解释器是关键,找到映射关系便可恢复。
获取到保护的dex后,函数的属性由java属性变为 Native,典型的有数字的 onCreate函数 Native化JNI函数。vmp壳的核心就是dalvik虚拟机的解释器原理。
若所有vmp保护的函数都有自己的解释器则so必然很大,所以必然共享一个解释器,vmp化的函数注册地址一致或者函数逻辑相似。
MainActivity动态注册地址在d3dec3f1,TestActivity地址一致,说明vmp保护。
参考vmp加壳方案之ADVMP,vmp对每一种smali指令处理,保护了函数后函数属性发生改变,因为解释器一般是用JNI实现,java易被反编译,故没用java实现,基本由c实现,防止快速定位到解释器的取址译码和执行的流程会加ollvm混淆保护之类。
Dex2c
基础是编译原理,进行了等价语义转换,彻底还原难度巨大。
方案:关注JNI相关的api调用是关键,也是分析VMP和dex2c保护的函数的逻辑的关键。
获取到保护的dex后保护的函数属性有java变成native,dcc保护后所有java属性的函数变成jni函数,只能看到类名和参数。核心原理与编译原理相关,传统编译原理是编译器经过词法分析语法分析生成二进制代码,dex2c中生成结果是c、c++文件,经过ndk编译器最终编译为so,每一个函数基本编译成一个c/c++文件,ubuntu中编译最终生成一个so。
dex2c核心对指定java函数进行语义分析生成各个不同的c/c++代码编译为相应so,自然dex2c保护的函数逻辑不一致,自然注册地址不同。
MainActivity的地址在7ba61b17c0,TestActivity地址在7ba61ac604,说明dex2c保护。
dcc_out.apk在lib下编译生成了so文件中,jni函数静态注册都存放于so文件中,使用IDA v7打开libnc.so,搜索java就可以找到所有jni函数。
dcc_out编译于项目LoadDex,新增FirstActivity,SecondActivity
MainActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 static{
try {
System.loadLibrary("nc");
} catch (UnsatisfiedLinkError e) {
e.printStackTrace();
}
}
setContentView(R.layout.activity_main);
Button mybutton01=findViewById(R.id.button01);
mybutton01.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
context.startActivity(new Intent(context,FirstActivity.class));
}
});
Button mybutton02=findViewById(R.id.button02);
mybutton02.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
context.startActivity(new Intent(context,SecondActivity.class));
}
});AndroidManifest.xml
1
2 <activity android:name=".FirstActivity"></activity>
<activity android:name=".SecondActivity"></activity>activity_main.xml
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 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/LinearLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/button01"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="FirstActivity"
/>
<Button
android:id="@+id/button02"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="SecondActivity"
/>
</LinearLayout>
dcc_ollvm.apk 在dex2c过程中加入了ollvm,在ida中打开so,jni找不到。
参考dex2c加壳方案之dcc
1
2
3
4 git clone https://github.com/amimo/dcc.git
pyenv local 3.8.5 && cd dcc && pip install -r requirements.txt
vim filter.txt 添加.*onejane*. 编译函数名含有onejane的函数
python dcc.py dcc.apk -o dcc_out.apk 加固app
VMP和dex2c是高级防护
VMP:定位解释器是关键,找到映射关系便可恢复
dex2c:基础是编译原理,进行了等价语义转换,彻底还原难度巨大:
通用分类技术:关注JNI相关的ap调用是关键,也是分析VMP和dex2c保护的函数的逻辑的关键。
混合型壳
多种加固技术混合使用,比如先将原有smal指令流使用VMP或dex2c保护,构建object数组传递给jni函数进行调用,然后再经过函数抽取进一步保护
区分apk保护技术
是否Native化 | 函数体无效 | |
---|---|---|
函数抽取类壳 | 否 | 是 |
vmp壳 | 是 | native化 |
Dex2c壳 | 是 | native化 |
so加固
基于init、 init_array以及JNI_Onload函数的加壳
基于自定义linker的加壳
IDA快捷键
空格键 | 反汇编窗口切换文本跟图形 |
---|---|
ESC | 退到上一个操作地址 |
F5 | C伪代码 |
alt+t | 搜索文本 |
- 一二三代壳和加壳技术分类识别