【AOSP专题】07. FART脱壳-02
抽取型:
加固:将app某些方法抽取出来,进行加密和保护。加载app时,默认不会还原到内存,只有执行此功能时才会临时还原并解密到内存(执行完毕后,可能会再次被抽取)。
脱壳:找到所有类主动调用执行类中的所有方法,实现主动加载至内存然后再导出。
1.前置必备
主动调用的本质是:
- 在APP中寻找所有的ClassLoader
- 在根据ClassLoader寻找所有的dex
- 在dex中找到的类
- 利用反射机制,找到类中所有的成员,在内存中dump导出(dex和CodeItem)
1.1 双亲委派
在安卓底层,类其实都是由各种ClassLoader来进行加载至内存的。例如:
Log.e("测试",String.class.getClassLoader().getClass().toString()); // BootClassLoader
Log.e("测试", Context.class.getClassLoader().getClass().toString()); // BootClassLoader
Log.e("测试",MainActivity.class.getClassLoader().getClass().toString()); // PathClassLoaderLog.e("测试",MainActivity.class.getClassLoader().getParent().getClass().toString()); // BootClassLoader
BootClassLoader,用于加载系统相关的类和方法。
PathClassLoader,用于加载当前APP和安卓相关的类和方法。
DexClassLoader、BaseDexClassLoader,一般用于用户自定义加载dex文件,然后调用其类和方法。注意:有些壳,就是将app自身的dex文件修改或者放在其他的目录,当程序启动时,先运行壳的ClassLoader加载壳的代码,然后再壳的代码中使用DexClassLoader再加载真正的业务先关的dex(代码)。
类在由ClassLoader加载时,遵循双亲委派机制。本质流程:
- 读取类对应的ClassLoader,然后根据继承关系找到其所有父级的ClassLoader。
- 优先由顶层的ClassLoader去加载,
- 加载成功,则直接返回。
- 加载失败,则再交给子ClassLoader去加载,依次类推,直到找到最后底层的ClassLoader。
注意: 父级ClassLoader不是指的父类,而是指的内部parent属性对应的值。
1.2 动态加载dex
Android中的ClassLoader于Java中的稍微有些不同,虽然两者都是满足双亲委派,但是直接findClass()会抛异常,所以我们不能直接继承classloader来自定义classLoader。要使用BaseDexClassLoader并重写了findClass(),要注意的是这里的classloader加载的是dex,不是class字节码。
ClassLoader比较常用的分为两种,PathClassLoader和DexClassLoader,虽然两者继承于BaseDexClassLoader,BaseDexClassLoader继承于ClassLoader,但是前者只能加载已安装的Apk里面的dex文件,后者则支持加载apk、dex以及jar,也可以从SD卡里面加载。
接下来:
- 编译生成一个jar或dex文件(也可以去其他app中拿一个编译好的dex)
- 将dex文件上传至手机,例如:
/data/local/tmp/demo.dex
- 自定义ClassLoader去加载并调用
demo.dex
并寻找其中的类进行调用
1.2.1 外部dex
例如:油联合伙人,解压apk,找到他的classes.dex文件(含所有代码)
上传至手机:
adb push demo.dex /data/local/tmp/demo.dex
注意:如果无法运行,可以给 demo.dex 赋一个可读的权限。
1.2.2 自己编译dex
package com.yltx.oil.partner.utils;import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;public class Md5 {public static String md5(String str) {try {byte[] digest = MessageDigest.getInstance("MD5").digest(str.getBytes("UTF-8"));StringBuilder sb = new StringBuilder(digest.length * 2);for (byte b : digest) {int i = b & 255;if (i < 16) {sb.append("0");}sb.append(Integer.toHexString(i));}return sb.toString();} catch (NoSuchAlgorithmException e) {throw new RuntimeException("MD5 should not be supported!", e);} catch (UnsupportedEncodingException e2) {throw new RuntimeException("UTF-8 should not be supported!", e2);}}
}
编译并生成jar包:
基于安卓SDK目录下的dx
,将jar包转换为dex文件:
D:\Service\AndroidStudio\build-tools\30.0.2\dx.bat --dex --output=demo.dex NetUtils.jar
再将生成的 demo.dex 上传至手机:
adb push demo.dex /data/local/tmp/demo.dex
注意:如果无法运行,可以给 demo.dex 赋一个可读的权限。
1.2.4 示例:动态加载dex
示例:直接使用DexClassLoader类去加载dex文件,并执行其中的功能。
详见LoaderDemo
package com.nb.loaderdemo;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;
import android.util.Log;import java.io.File;
import java.lang.reflect.Method;import dalvik.system.DexClassLoader;public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//String dexPath = this.getFilesDir().getPath() + File.separator + "y.dex";//String dexPath = this.getFilesDir().getPath() + File.separator + "demo.dex";String dexPath = "/data/local/tmp/demo.dex";Log.e("路径", dexPath);DexClassLoader classLoader = new DexClassLoader(dexPath, this.getFilesDir().getPath(), null, getClassLoader());try {/*Class clazz = classLoader.loadClass("com.yltx.oil.partner.utils.Md5");Object obj = clazz.newInstance();Method action = clazz.getMethod("md5", String.class);String result = (String) action.invoke(obj, "wupeiqi");Log.e("测试", result);*/Class clazz = classLoader.loadClass("com.yltx.oil.partner.utils.Md5");Method action = clazz.getDeclaredMethod("md5",String.class);String result = (String) action.invoke(clazz, "liuyifei");Log.e("测试", result);} catch (Exception e) {Log.e("测试", e.toString());}}
}
1.2.5 示例:动态加载dex
示例:直接使用自定义MyDexClassLoader类去加载dex文件,并执行其中的功能。