Xposed Hook插件Dex与壳Dex

插件 Dex 中类的 Hook

创建 Dex

新建项目,添加一个类

1
2
3
4
5
6
7
8
9
package dev.svip.dextest;

import android.util.Log;

public class test {
    public static void testFunc(){
        Log.e("blog.svip.dev", "I am from dex");
    }
}

编译项目,进入 app/build/intermediates/javac/debug/classes 目录,使用 dx 将 class 文件打包成 dex,然后将生成的 dex 文件上传至手机。

1
2
java -jar /root/Android/Sdk/build-tools/30.0.2/lib/dx.jar --dex --output=test.dex dev/svip/dextest/test.class
adb push test.dex /data/local/tmp/test.dex

使用 Dex 文件

新建项目 loadex,在 MainActivity 中添加以下代码

注意,如果 test.dex 被上传至手机的其他目录,需要为 app 设置权限才能正常读取。

1
2
3
4
5
6
7
8
DexClassLoader dexClassLoader = new DexClassLoader("/data/local/tmp/test.dex", this.getCacheDir().getAbsolutePath(), null, this.getClassLoader());
try {
    Class<?> myClass = dexClassLoader.loadClass("dev.svip.dextest.test");
    Method test = myClass.getDeclaredMethod("testFunc");
    test.invoke(null);
} catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
    e.printStackTrace();
}

Hook Dex 中的方法

新建 Xposed 项目,通过以下代码获取到当前 classloader 中的类。

 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
public void getClassByClassLoader(ClassLoader loader) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
    Class<?> baseDexClass = loader.loadClass("dalvik.system.BaseDexClassLoader");
    @SuppressLint("DiscouragedPrivateApi") Field pathList = baseDexClass.getDeclaredField("pathList");
    pathList.setAccessible(true);
    Object pathListObj = pathList.get(loader);

    Class<?> pathListClass = loader.loadClass("dalvik.system.DexPathList");
    @SuppressLint("DiscouragedPrivateApi") Field elements = pathListClass.getDeclaredField("dexElements");
    elements.setAccessible(true);
    Object[] elementsobj = (Object[]) elements.get(pathListObj);

    assert elementsobj != null;
    for (Object eleobj : elementsobj) {
        Class<?> elementclass = loader.loadClass("dalvik.system.DexPathList$Element");
        @SuppressLint("DiscouragedPrivateApi") Field dexfile = elementclass.getDeclaredField("dexFile");
        dexfile.setAccessible(true);
        DexFile dexfileobj = (DexFile) dexfile.get(eleobj);
        assert dexfileobj != null;
        Enumeration<String> entry = dexfileobj.entries();
        while (entry.hasMoreElements()) {
            Log.e("blog.svip.dev", entry.nextElement());
        }
    }
}
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
    if (lpparam.packageName.equals("dev.svip.loadex")) {
        getClassByClassLoader(lpparam.classLoader);
    }
}

image-20220306224229421◎ 类列表

我们知道,loadex 中加载了 dev.svip.dextest.test 类,而在此处我们并没有找到这个类,所以如果 hook 这个类中的方法,需要首先切换到对应的 classloader。在下面的代码中,我们首先 hook 类 DexClassLoader 的构造方法,从而切换到它的 classloader,然后去 hook 加载的类 dev.svip.dextest.test,后面流程就与普通给 hook 没有区别了。

 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
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
    if (lpparam.packageName.equals("dev.svip.loadex")) {
//            getClassByClassLoader(lpparam.classLoader);
        Class<?> dexclass = lpparam.classLoader.loadClass("dalvik.system.DexClassLoader");
        XposedHelpers.findAndHookConstructor(dexclass, String.class, String.class, String.class, ClassLoader.class, new XC_MethodHook() {
            @Override
            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                super.beforeHookedMethod(param);
            }

            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                super.afterHookedMethod(param);
                Class<?> myclass = ((DexClassLoader) param.thisObject).loadClass("dev.svip.dextest.test");
                XposedHelpers.findAndHookMethod(myclass, "testFunc", new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                        super.beforeHookedMethod(param);
                        Log.e("blog.svip.dev", "I am hooked in dex.");
                    }

                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        super.afterHookedMethod(param);
                        Log.e("blog.svip.dev", "hook method in dex over");
                    }
                });
            }
        });
    }
}

image-20220306230722620

壳函数 hook

一代壳中使用了 dex 整体替换,所以如果直接 hook,会提示找不到对应的类。

我们对之前文章中的 apk 进行加固,加固后的 apk 可在此下载

通过反射的方式,我们可以获得原始的 ClassLoader。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public static ClassLoader getloader(ClassLoader loader) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException {
    @SuppressLint("PrivateApi") Class<?> activitythreadclass = loader.loadClass("android.app.ActivityThread");
    @SuppressLint("DiscouragedPrivateApi") Method currentactivity = activitythreadclass.getDeclaredMethod("currentActivityThread");
    currentactivity.setAccessible(true);
    Object activityThreadobj = currentactivity.invoke(null);

    @SuppressLint("DiscouragedPrivateApi") Field application = activitythreadclass.getDeclaredField("mInitialApplication");
    application.setAccessible(true);
    Object applicationobj = application.get(activityThreadobj);

    Class<?> applicationclass = loader.loadClass("android.app.Application");
    @SuppressLint("DiscouragedPrivateApi") Field loadedapk = applicationclass.getDeclaredField("mLoadedApk");
    loadedapk.setAccessible(true);
    Object loadedapkobj = loadedapk.get(applicationobj);

    @SuppressLint("PrivateApi") Class<?> loadedapkclass = loader.loadClass("android.app.LoadedApk");
    @SuppressLint("DiscouragedPrivateApi") Field mclassloader = loadedapkclass.getDeclaredField("mClassLoader");
    mclassloader.setAccessible(true);

    return (ClassLoader) mclassloader.get(loadedapkobj);
}

同样,我们也可以使用 Xposed API 实现相同的功能。

1
2
3
4
5
6
7
8
public ClassLoader getloaderByXposedApi(ClassLoader loader) throws ClassNotFoundException {
    @SuppressLint("PrivateApi") Class<?> activitythreadclass = loader.loadClass("android.app.ActivityThread");
    Object activityobj = XposedHelpers.callStaticMethod(activitythreadclass, "currentActivityThread");
    Object mInitialApplication = XposedHelpers.getObjectField(activityobj, "mInitialApplication");
    Object mLoadedApk = XposedHelpers.getObjectField(mInitialApplication, "mLoadedApk");

    return (ClassLoader) XposedHelpers.getObjectField(mLoadedApk, "mClassLoader");
}

加固后的 apk 的入口在 com.SecShell.SecShell.AW 中,所以我们 hook 这个类的 onCreate 方法。我们通过各种方式获得 ClassLoader 并打印出来,最终根据 ClassLoader 打印出类列表。

 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
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
    if (lpparam.packageName.equals("dev.svip.teacher")) {
        Log.e("blog.svip.dev", lpparam.classLoader.toString() + " start");
        XposedHelpers.findAndHookMethod("com.SecShell.SecShell.AW", lpparam.classLoader, "onCreate", new XC_MethodHook() {
            @Override
            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                super.beforeHookedMethod(param);
                Log.e("blog.svip.dev", lpparam.classLoader.toString() + " before");
            }

            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                super.afterHookedMethod(param);
                Log.e("blog.svip.dev", param.getClass().getClassLoader().toString() + " The classloader is from param");
                ClassLoader classLoader = getloader(param.getClass().getClassLoader());
                Log.e("blog.svip.dev", classLoader.toString() + " The classloader is from reflect");
                ClassLoader classLoaderFromApi = getloaderByXposedApi(param.getClass().getClassLoader());
                Log.e("blog.svip.dev", classLoaderFromApi.toString() + " The classloader is from api");
                Log.e("blog.svip.dev", lpparam.classLoader.toString() + " The classloader is from lpparam");
                getClassByClassLoader(classLoaderFromApi);
            }
        });
    }
}

结果如下,已忽略部分无关的类,我们可以看出,通过 param 获得的 ClassLoader 与 lpparam 不同,而且 lpparam 的值前后会发生变化,其值与我们自己实现的方法所获得的 ClassLoader 相同。最终能够顺利得到我们所需要的类(dev.svip.teacher.Teacher)

 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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
18654-18654/dev.svip.teacher E/blog.svip.dev: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/~~U7MK8bt0Qr4LFnECTnqVvw==/dev.svip.teacher-WLLv13iQtBINHly162LrwQ==/base.apk"],nativeLibraryDirectories=[/data/app/~~U7MK8bt0Qr4LFnECTnqVvw==/dev.svip.teacher-WLLv13iQtBINHly162LrwQ==/lib/arm, /data/app/~~U7MK8bt0Qr4LFnECTnqVvw==/dev.svip.teacher-WLLv13iQtBINHly162LrwQ==/base.apk!/lib/armeabi-v7a, /system/lib, /system/system_ext/lib, /system/product/lib, /system/vendor/lib]]] start
18654-18654/dev.svip.teacher E/blog.svip.dev: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/~~U7MK8bt0Qr4LFnECTnqVvw==/dev.svip.teacher-WLLv13iQtBINHly162LrwQ==/base.apk", dex file "InMemoryDexFile[cookie=[0, 4081994208]]"],nativeLibraryDirectories=[/data/app/~~U7MK8bt0Qr4LFnECTnqVvw==/dev.svip.teacher-WLLv13iQtBINHly162LrwQ==/lib/arm, /data/app/~~U7MK8bt0Qr4LFnECTnqVvw==/dev.svip.teacher-WLLv13iQtBINHly162LrwQ==/base.apk!/lib/armeabi-v7a, /system/lib, /system/system_ext/lib, /system/product/lib, /system/vendor/lib]]] before
18654-18654/dev.svip.teacher E/blog.svip.dev: dalvik.system.InMemoryDexClassLoader[DexPathList[[dex file "InMemoryDexFile[cookie=[0, 4081982224]]"],nativeLibraryDirectories=[/system/lib, /system/system_ext/lib, /system/product/lib, /system/vendor/lib]]] The classloader is from param
18654-18654/dev.svip.teacher E/blog.svip.dev: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/~~U7MK8bt0Qr4LFnECTnqVvw==/dev.svip.teacher-WLLv13iQtBINHly162LrwQ==/base.apk", dex file "InMemoryDexFile[cookie=[0, 4081994208]]"],nativeLibraryDirectories=[/data/app/~~U7MK8bt0Qr4LFnECTnqVvw==/dev.svip.teacher-WLLv13iQtBINHly162LrwQ==/lib/arm, /data/app/~~U7MK8bt0Qr4LFnECTnqVvw==/dev.svip.teacher-WLLv13iQtBINHly162LrwQ==/base.apk!/lib/armeabi-v7a, /system/lib, /system/system_ext/lib, /system/product/lib, /system/vendor/lib]]] The classloader is from reflect
18654-18654/dev.svip.teacher E/blog.svip.dev: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/~~U7MK8bt0Qr4LFnECTnqVvw==/dev.svip.teacher-WLLv13iQtBINHly162LrwQ==/base.apk", dex file "InMemoryDexFile[cookie=[0, 4081994208]]"],nativeLibraryDirectories=[/data/app/~~U7MK8bt0Qr4LFnECTnqVvw==/dev.svip.teacher-WLLv13iQtBINHly162LrwQ==/lib/arm, /data/app/~~U7MK8bt0Qr4LFnECTnqVvw==/dev.svip.teacher-WLLv13iQtBINHly162LrwQ==/base.apk!/lib/armeabi-v7a, /system/lib, /system/system_ext/lib, /system/product/lib, /system/vendor/lib]]] The classloader is from api
18654-18654/dev.svip.teacher E/blog.svip.dev: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/~~U7MK8bt0Qr4LFnECTnqVvw==/dev.svip.teacher-WLLv13iQtBINHly162LrwQ==/base.apk", dex file "InMemoryDexFile[cookie=[0, 4081994208]]"],nativeLibraryDirectories=[/data/app/~~U7MK8bt0Qr4LFnECTnqVvw==/dev.svip.teacher-WLLv13iQtBINHly162LrwQ==/lib/arm, /data/app/~~U7MK8bt0Qr4LFnECTnqVvw==/dev.svip.teacher-WLLv13iQtBINHly162LrwQ==/base.apk!/lib/armeabi-v7a, /system/lib, /system/system_ext/lib, /system/product/lib, /system/vendor/lib]]] The classloader is from lpparam
18654-18654/dev.svip.teacher E/blog.svip.dev: com.SecShell.SecShell.AP
18654-18654/dev.svip.teacher E/blog.svip.dev: com.SecShell.SecShell.AW
18654-18654/dev.svip.teacher E/blog.svip.dev: com.SecShell.SecShell.CP
18654-18654/dev.svip.teacher E/blog.svip.dev: com.SecShell.SecShell.H$a
18654-18654/dev.svip.teacher E/blog.svip.dev: com.SecShell.SecShell.H
18654-18654/dev.svip.teacher E/blog.svip.dev: com.SecShell.SecShell.a$a
18654-18654/dev.svip.teacher E/blog.svip.dev: com.SecShell.SecShell.a$b
18654-18654/dev.svip.teacher E/blog.svip.dev: com.SecShell.SecShell.a$c
18654-18654/dev.svip.teacher E/blog.svip.dev: com.SecShell.SecShell.a
18654-18654/dev.svip.teacher E/blog.svip.dev: com.SecShell.SecShell.b
18654-18654/dev.svip.teacher E/blog.svip.dev: com.SecShell.SecShell.c
18654-18654/dev.svip.teacher E/blog.svip.dev: dev.svip.teacher.BuildConfig
18654-18654/dev.svip.teacher E/blog.svip.dev: dev.svip.teacher.MainActivity
18654-18654/dev.svip.teacher E/blog.svip.dev: dev.svip.teacher.R$color
18654-18654/dev.svip.teacher E/blog.svip.dev: dev.svip.teacher.R$drawable
18654-18654/dev.svip.teacher E/blog.svip.dev: dev.svip.teacher.R$layout
18654-18654/dev.svip.teacher E/blog.svip.dev: dev.svip.teacher.R$mipmap
18654-18654/dev.svip.teacher E/blog.svip.dev: dev.svip.teacher.R$string
18654-18654/dev.svip.teacher E/blog.svip.dev: dev.svip.teacher.R$style
18654-18654/dev.svip.teacher E/blog.svip.dev: dev.svip.teacher.R
18654-18654/dev.svip.teacher E/blog.svip.dev: dev.svip.teacher.Teacher$student
18654-18654/dev.svip.teacher E/blog.svip.dev: dev.svip.teacher.Teacher
18654-18654/dev.svip.teacher E/blog.svip.dev:  
    age:0
    name:
    hair:true
    i am from student
    --age:10
    --name:mark
    --hair:true
    --city:beijing
18654-18654/dev.svip.teacher E/blog.svip.dev:  
    age:18
    name:
    hair:true
    i am from student
    --age:10
    --name:mark
    --hair:true
    --city:beijing
18654-18654/dev.svip.teacher E/blog.svip.dev:  
    age:20
    name:James
    hair:true
    i am from student
    --age:10
    --name:mark
    --hair:true
    --city:beijing
18654-18654/dev.svip.teacher E/blog.svip.dev:  
    age:22
    name:Tom
    hair:false
    i am from student
    --age:10
    --name:mark
    --hair:true
    --city:beijing
18654-18654/dev.svip.teacher E/blog.svip.dev: name:Tom age:22

此时我们使用 classLoader 就可以获取到需要的类,从而进行后续操作。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Class<?> teacherclazz = classLoader.loadClass("dev.svip.teacher.Teacher");
XposedHelpers.findAndHookMethod(teacherclazz, "getString", new XC_MethodHook() {
    @Override
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
        super.beforeHookedMethod(param);
    }

    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        super.afterHookedMethod(param);
        param.setResult("aaaaaaaaaaaaaaaaaaaaaaaa");
    }
});

参考链接

1、https://bbs.pediy.com/thread-252630.htm

2、https://developer.android.com/reference/dalvik/system/InMemoryDexClassLoader

3、https://developer.android.com/reference/dalvik/system/PathClassLoader

updatedupdated2022-03-092022-03-09