首页
论坛
课程
招聘
一个自定义classloader的函数抽取壳样本
2020-11-9 19:18 4894

一个自定义classloader的函数抽取壳样本

2020-11-9 19:18
4894

题目出自2W班7月第三题

 

题目要求:基于frida实现的fart的一个版本是通过对ClassLink类中的LoadMethod函数进行hook实现对函数粒度的脱壳的。请编写基于xposed实现的fart版本插件,能够实现对函数粒度的脱壳

 

根据题目要求,可以知道,我们通过hook LoadMethod即可得到DexFile进而得到basesize,即可dump出dex。如果有函数抽取,即遍历所有的类得到codeitem后还原即可。

 

本次实践分为两个部分,一个即是原始思路,照葫芦画瓢参照FART的脱壳思路去编写代码,遇到困难暂时没法解决的时候,转变思路二,改用FART配合xposed去实现。

 

下面是解题过程:

 

首先我们可以分析frida-fart是如何实现demp dex的。
图片描述

 

图片描述

 

图片描述

 

粗略分析一下就是

  1. 拿到dexfile 就可以拿到base size
  2. 拿到artmethod 就可以拿到codeitem offset和method idx
  3. 计算codeitem的长度
  4. dump出来

那么我们在so层也可以画葫芦试试
测试安卓版本为8.1
部分代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void *pVoid = old_loadmethod3(thiz, thread, dex_file, it, klass, artmethod);
    __android_log_print(5, "hookso", "pVoid ptr:%p", pVoid);
 
    获取base和size
    const DexHeader *base = dex_file.pHeader;
    size_t size = dex_file.pHeader->fileSize;
    获取code item offset和method idx
    uint32_t codeItemOffset = artmethod->dex_code_item_offset_;
    uint32_t idx = artmethod->dex_method_index_;
    hook prettymethod方法 主动调用获得方法名
    const std::string &string = prettyMethodFunction(artmethod, artmethod,
                                                     true);
    通过偏移可以拿到codeitem
    long codeItemAddr = (long) base + codeItemOffset;
    CodeItem *codeItem = (CodeItem *) codeItemAddr;
    这部分代码可以直接dump dex出来
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);
        }
    }
    ...

上述看起来一步一步的确是可以拿得到codeitem,然后进一步拿得到ins的。
但是有一个问题我一直没法解决,就是某些方法会报access violation 异常。我通过搜索发现这个是底层发出来的异常,软件层貌似没法catch。

 

通过使用frida-fart我发现frida版本的也有这样的问题,但是frida这边可以通过try catch捕捉,让程序继续行走。

 

图片描述

 

因为暂时无法解决这个问题,所以我放弃这个方向。尝试用fart进行dump。

 

这里偷懒,就直接用寒冰大佬的xp+fart rom继续dump,就没有自己编译源码了。这里环境android6.0

 

那么这时候思路就转变了,通过hook loadMethod方法可以拿到artmethod,
而根据fart代码,dumpArtMethod这个方法,传入artmethod即可dump。

1
2
3
4
5
6
7
8
9
先执行原来的loadMethod逻辑
void *pVoid = old_loadmethod3(thiz, thread, dex_file, it, klass, artmethod);
    __android_log_print(5, "hookso", "pVoid ptr:%p", pVoid);
    然后通过so层hook dumpArtmethod函数,并将参数传入
    try {
        dumpArtMethodFunction(artmethod);
    }catch (...){
 
    }

上述通过so层调用FART的dumpArtMethodFunction方法,可以dump出dex,以及classlist。这是FART自带的功能。

 

而在java层,我们需要遍历所有类。
首先hook掉DexClassLoader和PathClassLoader的构造函数,并将classloader存起来。
因为是自定义的Xposed,而所以名字为XcustomBridge。其实这里就是Xposed,仅供参考,不可照抄

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
XcustomBridge.hookAllConstructors(DexClassLoader.class, new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    super.afterHookedMethod(param);
 
                    final ClassLoader classLoader = (ClassLoader) param.thisObject;
                    XcustomBridge.log("DexClassLoader:" + classLoader.toString());
 
 
 
                    mClassLoaders.add(classLoader);
 
                }
            });
 
            XcustomBridge.hookAllConstructors(PathClassLoader.class, new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    super.afterHookedMethod(param);
 
                    final ClassLoader classLoader = (ClassLoader) param.thisObject;
                    XcustomBridge.log("PathClassLoader:" + classLoader.toString());
 
 
 
                    mClassLoaders.add(classLoader);
                }
            });

紧接着加载我们的so,并遍历所有的classloader,执行loadClass操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
XcustomHelpers.findAndHookMethod(Application.class, "attach", Context.class, new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    super.afterHookedMethod(param);
 
                    XcustomBridge.log("attach after");
 
                    mContext = (Context) param.args[0];
 
 
                    XcustomHelpers.callMethod(Runtime.getRuntime(), "doLoad", "/system/lib/libnative-lib.so", mContext.getClassLoader());
 
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                Thread.sleep(30 * 1000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                                Log.e("hook1", Log.getStackTraceString(e));
                            }
 
 
                            for (int i = 0; i < mClassLoaders.size(); i++) {
                                ClassLoader classLoader = mClassLoaders.get(i);
                                TestClassloader(classLoader);
                            }
 
                            fart();
                        }
                    }).start();
 
 
 
                }
            });

将dump下来后bin文件批量恢复后,即可查看到,函数已经恢复了。
图片描述

 

但是如果点多几个函数查看
会发现部分函数并没有复原

 

图片描述

 

那么通过回想,我们可以得知,还原的代码其实都是app启动过程中调用过的方法,所以dump下来后,是包含代码的。因此这个壳也是函数抽取壳,而且恢复后不会复原。

 

而查看FART dump出来的ins文件,发现也缺了很多数据,那么这里可以猜测是某个环节出了问题导致没有dump出来。
然后通过回溯error log 可以发现是classloader的锅

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
 * 01-06 00:31:12.925 12000-12060/com.sup.android.superb W/System.err: java.lang.IllegalArgumentException: Expected receiver of type dalvik.system.BaseDexClassLoader, but got com.bytedance.frameworks.plugin.core.DelegateClassLoader
 * 01-06 00:31:12.925 12000-12060/com.sup.android.superb W/System.err:     at java.lang.reflect.Field.get(Native Method)
 * 01-06 00:31:12.925 12000-12060/com.sup.android.superb W/System.err:     at com.l.sevenclasshook.hook.Hook3.getFieldOjbect(Hook3.java:206)
 * 01-06 00:31:12.925 12000-12060/com.sup.android.superb W/System.err:     at com.l.sevenclasshook.hook.Hook3.TestClassloader(Hook3.java:259)
 * 01-06 00:31:12.925 12000-12060/com.sup.android.superb W/System.err:     at com.l.sevenclasshook.hook.Hook3.fart(Hook3.java:240)
 * 01-06 00:31:12.926 12000-12060/com.sup.android.superb W/System.err:     at com.l.sevenclasshook.hook.Hook3$3$1.run(Hook3.java:95)
 * 01-06 00:31:12.926 12000-12060/com.sup.android.superb W/System.err:     at java.lang.Thread.run(Thread.java:818)
 * 01-06 00:31:12.926 12000-12060/com.sup.android.superb W/System.err: java.lang.NullPointerException: null receiver
 * 01-06 00:31:12.926 12000-12060/com.sup.android.superb W/System.err:     at java.lang.reflect.Field.get(Native Method)
 * 01-06 00:31:12.926 12000-12060/com.sup.android.superb W/System.err:     at com.l.sevenclasshook.hook.Hook3.getFieldOjbect(Hook3.java:206)
 * 01-06 00:31:12.926 12000-12060/com.sup.android.superb W/System.err:     at com.l.sevenclasshook.hook.Hook3.TestClassloader(Hook3.java:260)
 * 01-06 00:31:12.926 12000-12060/com.sup.android.superb W/System.err:     at com.l.sevenclasshook.hook.Hook3.fart(Hook3.java:240)
 * 01-06 00:31:12.926 12000-12060/com.sup.android.superb W/System.err:     at com.l.sevenclasshook.hook.Hook3$3$1.run(Hook3.java:95)
 * 01-06 00:31:12.926 12000-12060/com.sup.android.superb W/System.err:     at java.lang.Thread.run(Thread.java:818)

那么这里我们知道了这个app有自定义的classloader。
图片描述

 

根据提示,我们可以发现这是一个直接继承于classloader的类。
那么来看看通常我们是如何获取classlist的。

1
2
3
4
5
6
7
8
9
10
11
12
Field pathList_Field = (Field) getClassField(appClassloader, "dalvik.system.BaseDexClassLoader", "pathList");
        Object pathList_object = getFieldOjbect("dalvik.system.BaseDexClassLoader", appClassloader, "pathList");
        Object[] ElementsArray = (Object[]) getFieldOjbect("dalvik.system.DexPathList", pathList_object, "dexElements");
        Field dexFile_fileField = null;
        try {
            dexFile_fileField = (Field) getClassField(appClassloader, "dalvik.system.DexPathList$Element", "dexFile");
        } catch (Exception e) {
            e.printStackTrace();
        } catch (Error e) {
            e.printStackTrace();
        }
......

我们是通过获得BaseDexClassLoader的pathList字段从而进一步往下获取classList的。但是由于我们现在是直接继承于classloader,所以我们要想办法获取classlist。

 

那么这里就有两种方法可以实现了。
第一个,fart使用过程中是dump classlist出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
if (appClassloader instanceof BaseDexClassLoader) {
 
        } else if (appClassloader instanceof ClassLoader) {
            List<String> nameList = new ArrayList<>();
 
            BufferedReader br = null;
            try {
                br = new BufferedReader(new InputStreamReader(new FileInputStream(new File(Environment.getExternalStorageDirectory() + "/8958236_classlist.txt")),
                        "UTF-8"));
                String lineTxt = null;
                while ((lineTxt = br.readLine()) != null) {
                    if (!TextUtils.isEmpty(lineTxt)) {
                        String name = lineTxt.replace("L", "").replaceAll("/", ".").replace(";", "");
                        Log.e("hook1", "after replace name:" + name);
                        nameList.add(name);
                    }
                }
                br.close();
            } catch (Exception e) {
                Log.e("hook1", Log.getStackTraceString(e));
            }
 
 
            for (String name : nameList) {
                try {
                    Log.e("Hook1", "===================>loadClass:" + name);
                    appClassloader.loadClass(name);
                } catch (Exception e) {
                    Log.e("Hook1", Log.getStackTraceString(e));
                }
            }
 
            return;
        }

因此这里我们直接遍历文件,然后loadClass即可。

 

接下来我们对dex方法体进行批量恢复后,可以看到,函数都恢复了。

 

图片描述

 

少部分没有恢复的函数,是因为这个classloader没有找到类,报class not found。接下来我们可以将所有classloader都跑一遍这个list即可

 

方法二
其实fart也是解析DexFile从而拿到classlist的。
既然有了整个dexFile文件,那么有啥不能解析的呢。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
if (size == 8958236) {
        u4 classDefSize = dex_file.pHeader->classDefsSize;
        u4 classDefsOff = dex_file.pHeader->classDefsOff;
 
        __android_log_print(5, "hookso", "base:%p", (void *) base);
 
        __android_log_print(5, "hookso", "classDefSize:%ld   classDefsOff:%p", classDefSize,
                            (void *) classDefsOff);
 
        u4 typeIdsOff = dex_file.pHeader->typeIdsOff;
        u4 stringIdsOff = dex_file.pHeader->stringIdsOff;
 
        __android_log_print(5, "hookso", "typeIdsOff:%p   stringIdsOff:%p", (void *) typeIdsOff,
                            (void *) stringIdsOff);
 
        int i;
        for (i = 0; i < classDefSize; i++) {
            long currClassAddr = (long) classDefsOff + i * 32 + (long) base;
            __android_log_print(5, "hookso", "currClass ptr:%p", (void *) currClassAddr);
            int *idx = (int *) (currClassAddr);
            __android_log_print(5, "hookso", "currClassIdx:%i", *idx);
 
            int tmpIdx = *idx;
            long currTypeIdAddr = ((long) typeIdsOff + 4 * tmpIdx + (long) base);
            int *currTypeIdx = (int *) currTypeIdAddr;
            __android_log_print(5, "hookso", "currTypeIdx:%ld", *currTypeIdx);
 
            tmpIdx = *currTypeIdx;
            long currStringOffAddr = ((long) stringIdsOff + 4 * tmpIdx + (long) base);
            int *currStringOff = (int *) currStringOffAddr;
            __android_log_print(5, "hookso", "currStringOff:%ld", *currStringOff);
 
            tmpIdx = *currStringOff;
            long off = (long) base + tmpIdx;
            __android_log_print(5, "hookso", "string off:%p", (void *) off);
 
            const uint8_t *strPtr = (uint8_t *) off;
            DecodeUnsignedLeb128(&strPtr);
            char *classname = (char *) strPtr;
 
            __android_log_print(5, "hookso", "classname:%s", classname);
 
        }
    }

在这里我们可以一层一层的不断获取不断遍历,就可以拿到我们想要的classname
图片描述

 

拿到classname后我们就可以用这个去遍历loadClass后再dump dex即可。

 

可以看到这个题目最后想要说的就是自定义classloader如何进行函数抽取还原,那么这里我们需要熟悉Dexfile的文件结构以及了解classloader如何使用


[2022夏季班]《安卓高级研修班(网课)》月薪两万班招生中~

上传的附件:
收藏
点赞2
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回