android二代抽取壳的一些脱壳方法
修改rom
通过修改android源码自定义脱壳机,选择合适的时机通过主动调用加载apk中的类,然后再挑选合适的时机对dex文件进行dump。
主动调用
当一个app启动的时候会调用handleBindApplication函数,此函数会调用Application的attachBaseContext函数和Application的OnCreate函数。可以在handleBindApplication函数调用前或者此函数开头调用函数创建一个线程,因为加壳apk一般会在Application的attachBaseContext函数中通过DexClassLoader和PathClassLoader类自定义classloader并加载原始apk/dex文件,所以线程中可以轮询通过反射获取到应用程序默认classloader(mClassloader),需要轮询执行的原因是handleBindApplication还没有调用Application的attachBaseContext函数之前的mClassLoader还是壳自身的classloader,需要在壳代码修复mClassLoader后才能获取到原apk/dex的classloader。
//通过反射获取LoadedApk对象
Class ActivityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = ActivityThreadClass.getDeclaredMethod("currentActivityThread");
Object ActivityThreadObject = currentActivityThreadMethod.invoke(null);
Field mPackagesField = ActivityThreadClass.getDeclaredField("mPackages");
mPackagesField.setAccessible(true);
ArrayMap mPackages = (ArrayMap) mPackagesField.get(ActivityThreadObject);
WeakReference wr = (WeakReference) mPackages.get(this.getPackageName());
//通过反射获取LoadedApk对象的mClassLoader字段
Class LoadedApkClass = Class.forName("android.app.LoadedApk");
Field mClassLoaderField = LoadedApkClass.getDeclaredField("mClassLoader");
mClassLoaderField.setAccessible(true);
ClassLoader mClassLoader = mClassLoaderField.get(wr.get());
这里还有一种特殊情况就是原始apk/dex自己还会自定义classloader并加载其他dex文件,如果这个dex文件也进行了函数抽取那么需要获取他的classloader并进行主动调用使函数回填。这里可以通过第三方ART hook框架对DexClassLoader和PathClassLoader类的构造函数进行hook并获取到返回值就是自定义的classloader,然后再进行主动调用使其函数进行自动回填。
主动调用就是通过获取到classloader对应的DexPathList,进一步获取到所有的DexFile对象,通过调用DexFile的entries等方法枚举dex文件中所有的类名称,使用classloader调用loadclass进行主动加载。
dex Dump
通常修改一些native层的函数进行dexdump,只要native函数能够获取到native层DexFile对象就可以对dex文件进行dump。这里使用LoadMethod函数,此函数再dex文件中的函数被加载到虚拟机中时被调用,当然还有其他很多函数都可以。
不修改rom
不修改源码的情况下通常通过frida调试器进行主动调用和dex的dump,缺点是frida很容易被检测到。
主动调用
frida提供了enumerateClassLoaders这个API可以过去到apk中所有的classloader,这样的话就可以通过frida脚本枚举所有classloader,通过得到classloader对应的所有DexFile对象并调用entries枚举dex文件中的所有类名称进行主动加载。
function dealwithClassLoader(classloaderobj) {
if (Java.available) {
Java.perform(function () {
try {
var dexfileclass = Java.use("dalvik.system.DexFile");
var BaseDexClassLoaderclass = Java.use("dalvik.system.BaseDexClassLoader");
var DexPathListclass = Java.use("dalvik.system.DexPathList");
var Elementclass = Java.use("dalvik.system.DexPathList$Element");
var basedexclassloaderobj = Java.cast(classloaderobj, BaseDexClassLoaderclass);
var tmpobj = basedexclassloaderobj.pathList.value;
var pathlistobj = Java.cast(tmpobj, DexPathListclass);
var dexElementsobj = pathlistobj.dexElements.value;
if (dexElementsobj != null) {
for (var i in dexElementsobj) {
var obj = dexElementsobj[i];
var elementobj = Java.cast(obj, Elementclass);
tmpobj = elementobj.dexFile.value;
var dexfileobj = Java.cast(tmpobj, dexfileclass);
const enumeratorClassNames = dexfileobj.entries();
while (enumeratorClassNames.hasMoreElements()) {
var className = enumeratorClassNames.nextElement().toString();
if(-1 != className.indexOf("com.reverccqin")){
console.log("start loadclass->", className);
var loadclass = classloaderobj.loadClass(className);
console.log("after loadclass->", loadclass);
}
}
}
}
} catch (e) {
console.log(e)
}
});
}
}
dex Dump
dex Dump的话就通过frida hook LoadMethod等方法。
关于主动调用脱壳的对抗
检测主动调用的脱壳行为
因为主动调用主要是通过获取到对应的classloader,并调用loadclass将所有的类都主动加载一遍使其自动进行函数的回填。这就可以通过apk中编写一个永远不会使用的类,因为这个类永远都不会使用,所以apk可以通过调用entries枚举classloader中所有的类并判断此类是否被加载,如果被加载说明存在主动调用的脱壳行为,APK直接闪退
绕过检测
可以再进行主动调用的过程中对特定类进行过滤不进行主动加载,也可以通过hook entries欺骗apk的检测代码。