被动调用
以自实现的抽取壳为例,对Test01.proc1函数进行抽空,但这次不同的是此Test01类在apk加载的过程中并不会被加载。通过之前被动调用的方式dump dex发现此函数还是被抽空的状态。
总结:单单通过在LoadMethod调用后进行dump dex是一种被动调用的脱壳手段。因为APK在执行过程中只有加载此类时才会调用LoadMethod,然后壳进行抽取函数的回填,在调用LoadMethod函数后我们进行dump dex就可以得到回填后的dex文件。但是如果此类需要在执行过程中还没有加载此类的话就不会调用LoadMethod,壳也就暂时不会对抽取的函数进行回填,这样我们dump的dex中此函数就还是被抽空的状态。
主动调用的优点
由于只有一个类在被加载的时候才会调用LoadMethod进行函数的加载,这样壳代码才会对抽取的函数进行回填,我们的dump代码才有执行的时机。所以就可以通过遍历加载apk的所有类来实现主动调用,从而让我们的dump代码得到执行时机。这里使用寒冰大佬的frida_fart中的脚本,其是通过枚举所有的classloader并获取每一个classloader加载的dex文件,以及每一个dex文件中的类信息。这样得到类名后就可以利用classloader对其进行主动加载。
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)
}
});
}
}
通过执行此脚本可以发现被抽空的Test01.proc1函数对应的Test01类已经被主动加载了。
然后再次查看dump文件就可以发现Test01.proc1函数已经被成功回填。