一个易上手的函数抽取样本还原(JAVA遍历类与加载SO的思路,还有LoadMethod阶段去获取完整Dex)

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这个函数,因为这个函数可以直接拿到basesize,就可以直接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次下载)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值