JAVA遍历ClassLoader类的写法
public void GetClassLoaderClasslist(ClassLoader classLoader) {
//private final DexPathList pathList;
//public static java.lang.Object getObjectField(java.lang.Object obj, java.lang.String fieldName)
XposedBridge.log("start dealwith classloader:" + classLoader);
Object pathListObj = XposedHelpers.getObjectField(classLoader, "pathList");
//private final Element[] dexElements;
Object[] dexElementsObj = (Object[]) XposedHelpers.getObjectField(pathListObj, "dexElements");
for (Object i : dexElementsObj) {
//private final DexFile dexFile;
Object dexFileObj = XposedHelpers.getObjectField(i, "dexFile");
//private Object mCookie;
Object mCookieObj = XposedHelpers.getObjectField(dexFileObj, "mCookie");
//private static native String[] getClassNameList(Object cookie);
// public static java.lang.Object callStaticMethod(java.lang.Class<?> clazz, java.lang.String methodName, java.lang.Object... args) { /* compiled code */ }
Class DexFileClass = XposedHelpers.findClass("dalvik.system.DexFile", classLoader);
String[] classlist = (String[]) XposedHelpers.callStaticMethod(DexFileClass, "getClassNameList", mCookieObj);
for (String classname : classlist) {
// XposedBridge.log(dexFileObj + "---" + classname);
try {
classLoader.loadClass(classname);
Log.e("Hook1", "loadclass:" + classname);
} catch (ClassNotFoundException e) {
Log.e("Hook1", Log.getStackTraceString(e));
}
}
}
XposedBridge.log("end dealwith classloader:" + classLoader);
}
题目出自2W班7月第一题
题目要求:某些抽取壳在类被加载后便还原了被抽取的函数,请编写xposed插件完成对加壳app中所有类的加载,最终完成该类型抽取壳的修复
由题目其实可以很清楚的了解到,这个函数抽取壳,类被还原后就不会复原回去,那么解题思路就很明确了,只要遍历所有的类,再把Dex dump出来,即脱壳成功。
那么其实这道题考察的就是classloader的运用,无论壳如何变,为了能让上层应用能正常运用,必然逃不脱整个安卓框架层,那么既然框架层也是用的classloader去加载类的,那么就逃不脱classloader这个知识点。
下面开始解题过程。
使用环境:android8.1 ,edxp
首先看到app的dex其实已经是一个脱壳状态了,只是函数体被抽取了
那么思路其实已经很清晰了,就是遍历所有的类。然后再把代码dump下来即可。
那么如何dump Dex呢。在8.0以下,我们可以参考Fdex2的代码,在遍历完成后,使用dex类中的getBytes把dex文件dump出来。
public void initRefect() {
try {
Dex = Class.forName("com.android.dex.Dex");
Dex_getBytes = Dex.getDeclaredMethod("getBytes", new Class[0]);
getDex = Class.forName("java.lang.Class").getDeclaredMethod("getDex", new Class[0]);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
public void writeByte(byte[] bArr, String str) {
try {
OutputStream outputStream = new FileOutputStream(str);
outputStream.write(bArr);
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
XposedBridge.log("文件写出失败");
}
}
以上代码节选自Fdex2中。
但是因为我的环境是android 8.1,而在8.1上Dex类中已经没有这个函数了。
但是参考目前的一些frida脚本,可以发现大多数都是hook OpenCommon这个函数,因为这个函数可以直接拿到base和size,就可以直接dump了。但是我们这里使用LoadMethod这个函数,因为在题目三中也需要用到这个函数,因此这里我就用这个函数把。
以下代码是so中的代码。
extern "C" jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
LOGE("jni onload enter");
//1.首先在jni_onload中hook我们想要的函数。这里用的sandhook
hookLogic();
LOGE("jni onload stop");
return JNI_VERSION_1_6;
}
//2.然后我们分别在32位和64位中hook LoadMethod这个函数
void hookLogic() {
if (sizeof(void *) == 8) {
const char *libartPath = "/system/lib64/libart.so";
old_loadmethod = reinterpret_cast<void *(*)(void *, DexFile &,
art::ClassDataItemIterator &,
art::Handle *,
art::ArtMethod *)>( SandInlineHookSym(
libartPath,
"_ZN3art11ClassLinker10LoadMethodERKNS_7DexFileERKNS_21ClassDataItemIteratorENS_6HandleINS_6mirror5ClassEEEPNS_9ArtMethodE",
// "_ZN3art11ClassLinker10LoadMethodERKNS_7DexFileERKNS_21ClassDataItemIteratorENS_6HandleINS_6mirror5ClassEEEPNS_9ArtMethodE",
reinterpret_cast<void *>(new_loadmethod)));
} else {
const char *libartPath = "/system/lib/libart.so";
old_loadmethod = reinterpret_cast<void *(*)(void *, DexFile &,
art::ClassDataItemIterator &,
art::Handle *,
art::ArtMethod *)>( SandInlineHookSym(
libartPath,
"_ZN3art11ClassLinker10LoadMethodERKNS_7DexFileERKNS_21ClassDataItemIteratorENS_6HandleINS_6mirror5ClassEEEPNS_9ArtMethodE",
reinterpret_cast<void *>(new_loadmethod)));
}
}
void *(*old_loadmethod)(void *, DexFile &,
art::ClassDataItemIterator &,
art::Handle *,
art::ArtMethod *) = nullptr;
//3.紧接着在hook函数中拿到base与size。这里使用的dexFile去获取
void *new_loadmethod(void *thiz, DexFile &dex_file,
art::ClassDataItemIterator &it,
art::Handle *klass,
art::ArtMethod *dst) {
if (strcmp((char *) dex_file.pHeader->magic, "dex\n035") != 0) {
return old_loadmethod(thiz, dex_file, it, klass, dst);
}
// const u1 *base = dex_file.baseAddr;
const DexHeader *base = dex_file.pHeader;
int size = dex_file.pHeader->fileSize;
LOGE("new opheader::%p ", dex_file.pOptHeader);
LOGE("new header::%p ", dex_file.pHeader);
LOGE("new magic::%p ", dex_file.pHeader->magic);
LOGE("new loadmehtod::%p %i %s", base, size, dex_file.pHeader->magic);
int pid = getpid();
char dexFilePath[100] = {0};
sprintf(dexFilePath, "/sdcard/xxxxx/%p %d LoadMethod.dex", base, size);
mkdir("/sdcard/xxxxx", 0777);
int fd = open(dexFilePath, O_CREAT | O_RDWR, 666);
if (fd > 0) {
ssize_t i = write(fd, base, size);
if (i > 0) {
close(fd);
}
}
return old_loadmethod(thiz, dex_file, it, klass, dst);
}
首先在jni_onload中hook我们自己需要的函数,这里我们hook loadMethod方法。
这这个方法中,我们可以拿到DexFile,然后进而可以拿到base和size,然后拿到这两个后,我们就可以dump dex出来了。
那么在so层中的dump代码就已经写完了。java层需要做的就是遍历类即可。
因为可能环境的问题,我在xp代码中新建thread没有生效,有点问题,所以我直接在application中的attach函数中拿classloader。
这里可以直接在attach中拿是因为这个例子没有壳代码,如果先经过壳,那么这个classloader将会是壳的classloader,因此要注意。
所以遍历类的重点就是classloader,没有classloader就没法loadClass。
在拿到classloader后我就直接加载so了,因为在7.1以后的版本,dlopen会有一些限制,所以我干脆就放到了lib64目录下了,因为双亲委派的原因,最终会到这个地方找so
XposedHelpers.findAndHookMethod(Application.class, "attach", Context.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
mContext = (Context) param.args[0];
XposedHelpers.callMethod(Runtime.getRuntime(), "doLoad", "/system/lib64/libsandhook-native.so", mContext.getClassLoader());
GetClassLoaderClasslist(mContext.getClassLoader());
}
});
紧接着就是遍历class
这里拿到classList之后loadClass即可。
public void GetClassLoaderClasslist(ClassLoader classLoader) {
//private final DexPathList pathList;
//public static java.lang.Object getObjectField(java.lang.Object obj, java.lang.String fieldName)
XposedBridge.log("start dealwith classloader:" + classLoader);
Object pathListObj = XposedHelpers.getObjectField(classLoader, "pathList");
//private final Element[] dexElements;
Object[] dexElementsObj = (Object[]) XposedHelpers.getObjectField(pathListObj, "dexElements");
for (Object i : dexElementsObj) {
//private final DexFile dexFile;
Object dexFileObj = XposedHelpers.getObjectField(i, "dexFile");
//private Object mCookie;
Object mCookieObj = XposedHelpers.getObjectField(dexFileObj, "mCookie");
//private static native String[] getClassNameList(Object cookie);
// public static java.lang.Object callStaticMethod(java.lang.Class<?> clazz, java.lang.String methodName, java.lang.Object... args) { /* compiled code */ }
Class DexFileClass = XposedHelpers.findClass("dalvik.system.DexFile", classLoader);
String[] classlist = (String[]) XposedHelpers.callStaticMethod(DexFileClass, "getClassNameList", mCookieObj);
for (String classname : classlist) {
// XposedBridge.log(dexFileObj + "---" + classname);
try {
classLoader.loadClass(classname);
Log.e("Hook1", "loadclass:" + classname);
} catch (ClassNotFoundException e) {
Log.e("Hook1", Log.getStackTraceString(e));
}
}
}
XposedBridge.log("end dealwith classloader:" + classLoader);
}
之后就可以完整的dump出dex了。
但是这种方案的话会有点慢,因为每load一次,so层都会写文件覆盖一遍文件。
因此其实可以在load完之后设置一个flag,再去写文件,速度会提升很多。
上传的附件:
- 2.1测试app.7z.001 (6.68MB,31次下载)
- 2.1测试app.7z.002 (6.68MB,33次下载)
- 2.1测试app.7z.003 (6.68MB,33次下载)
- 2.1测试app.7z.004 (315.68kb,28次下载)