Xposed Hook基础

新建 Xposed 项目

在 Android Studio 中新建项目,依次修改以下配置文件

AndroidManifest.xml

插入以下内容,设置 xposed 模块的信息

1
2
3
<meta-data android:name="xposedmodule" android:value="true"/>
<meta-data android:name="xposeddescription" android:value="这是一个Xposed测试程序"/>
<meta-data android:name="xposedminversion" android:value="53"/>

image-20220224151440690

settings.gradle

插入依赖的仓库地址

1
maven { url "https://api.xposed.info/" }

image-20220224151644929

src/build.gradle

插入依赖的包

Android Studio 老版本中使用的是 provided,但该关键字已被弃用,新版本使用 compileOnly 替代。

1
2
compileOnly 'de.robv.android.xposed:api:82'
compileOnly 'de.robv.android.xposed:api:82:sources'

image-20220224151850996

编写代码

新建一个类,例如此处类名为 hook,功能为打印包名和进程名。此后我们的 hook 代码即在此编写。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package dev.svip.xp;

import android.util.Log;

import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

public class Hook implements IXposedHookLoadPackage {

    @Override
    public void-HandleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
        Log.i("blog.svip.dev", lpparam.packageName);
        Log.i("blog.svip.dev", lpparam.processName);
    }
}

添加入口点

右键点击 “main” 文件夹 , 选择 new --> Folder -->Assets Folder,新建 assets 文件夹,新建文件 xposed_init,类型为 Text

添加我们编写的类名。如有多个,每行加入一个类名即可。

image-20220224154146324

编译运行

手机上安装后,在 Xposed 中激活模块并重启系统,即可生效。

image-20220224155207675

手机重启后,我们可以看到 logcat 中有大量输出,将每个应用的包名和进程名都打印出来了。

image-20220224155319992

Hook 构造函数

首先,新建一个安卓项目,并创建 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
package dev.svip.teacher;

public class Teacher {
    int age = 0;
    String name = "";
    boolean hair = true;

    public Teacher() {

    }

    public Teacher(int age) {
        this.age = age;
    }

    public Teacher(int age, String name) {
        this.age = age;
        this.name = name;
    }

    public Teacher(int age, String name, boolean hair) {
        this.age = age;
        this.name = name;
        this.hair = hair;
    }

    public String getString() {
        return "\n " + "\nage:" + age + "\nname:" + name + "\nhair:" + hair;
    }
}

在主函数中使用创建的类

1
2
3
4
5
6
7
8
9
Teacher a = new Teacher();
Teacher b = new Teacher(18);
Teacher c = new Teacher(20, "James");
Teacher d = new Teacher(22, "Tom", false);

Log.e("blog.svip.dev", a.getString());
Log.e("blog.svip.dev", b.getString());
Log.e("blog.svip.dev", c.getString());
Log.e("blog.svip.dev", d.getString());

当运行此 app 时,打印日志如下

image-20220224210649128

我们在 xposed 模块中 hook 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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
public class hook implements IXposedHookLoadPackage {

    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
        //只hook目标应用
        if (lpparam.packageName.equals("dev.svip.teacher")) {
            //加载要hook的类
            Class<?> myclass = lpparam.classLoader.loadClass("dev.svip.teacher.Teacher");
            //hook构造函数,空参数
            XposedHelpers.findAndHookConstructor(myclass, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    Log.e("blog.svip.hook", "my teacher() is called");
                    //在xposed中打印日志
                    XposedBridge.log("my teacher() is called");
                }

                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    super.afterHookedMethod(param);
                    Log.e("blog.svip.hook", "my teacher() is over");
                    //在xposed中打印日志
                    XposedBridge.log("my teacher() is over");
                }
            });
            //hook构造函数,参数为int
            XposedHelpers.findAndHookConstructor(myclass, int.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    //修改参数的值
                    Object[] args = param.args;
                    args[0] = 99;
                    Log.e("blog.svip.hook", "my teacher(99) is called");
                }

                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    super.afterHookedMethod(param);
                    Log.e("blog.svip.hook", "my teacher(99) is over");
                }
            });
            //hook构造函数,参数为int,string
            XposedHelpers.findAndHookConstructor(myclass, int.class, String.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    //修改参数的值
                    Object[] args = param.args;
                    args[0] = 100;
                    args[1] = "xiaoming";
                    Log.e("blog.svip.hook", "my teacher(100, \"xiaoming\") is called");
                }

                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    super.afterHookedMethod(param);
                    Log.e("blog.svip.hook", "my teacher(100, \"xiaoming\") is over");
                }
            });
            //hook构造函数,参数为int,string,boolen
            XposedHelpers.findAndHookConstructor(myclass, int.class, String.class, boolean.class,
                    new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    //修改参数的值
                    Object[] args = param.args;
                    args[0] = 101;
                    args[1] = "xiaoli";
                    args[2] = true;
                    Log.e("blog.svip.hook", "my teacher(100, \"xiaoli\", true) is called");
                }

                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    super.afterHookedMethod(param);
                    Log.e("blog.svip.hook", "my teacher(100, \"xiaoli\", false) is over");
                }
            });
        }
    }
}

加载模块,重启手机后,运行刚才的 app,打印日志如下,可以看出,构造函数已被成功 hook。

image-20220224210513364

修改对象的属性

通过反射

 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
public class SetFieldValue implements IXposedHookLoadPackage {
    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
        if (lpparam.packageName.equals("dev.svip.teacher")) {
            //修改参数为int的构造函数中的属性
            XposedHelpers.findAndHookConstructor("dev.svip.teacher.Teacher", lpparam.classLoader,
                    int.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<?> teachclas = lpparam.classLoader.loadClass("dev.svip.teacher.Teacher");
                    Field age = teachclas.getDeclaredField("age");
                    age.setAccessible(true);
                    age.set(param.thisObject, 999);

                    Field name = teachclas.getDeclaredField("name");
                    name.setAccessible(true);
                    name.set(param.thisObject, "zzzzzz");

                    Field hair = teachclas.getDeclaredField("hair");
                    hair.setAccessible(true);
                    hair.set(param.thisObject, true);
                }
            });
        }
    }
}

image-20220228161428107

通过 api

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class SetFieldValue implements IXposedHookLoadPackage {
    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
        if (lpparam.packageName.equals("dev.svip.teacher")) {
            XposedHelpers.findAndHookConstructor("dev.svip.teacher.Teacher", lpparam.classLoader,
                    int.class, String.class, boolean.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);
                            XposedHelpers.setIntField(param.thisObject, "age", 188);
                            XposedHelpers.setObjectField(param.thisObject, "name", "xxxxxxxx");
                            XposedHelpers.setBooleanField(param.thisObject, "hair", true);
                        }
                    });
        }
    }
}

image-20220228164151270

Hook 普通函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class HookFunc implements IXposedHookLoadPackage {
    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
        if (lpparam.packageName.equals("dev.svip.teacher")){
            Class<?> teachcls = lpparam.classLoader.loadClass("dev.svip.teacher.Teacher");
            XposedHelpers.findAndHookMethod(teachcls, "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);
                    //获取原来的返回值并设置新的返回值
                    Object result = param.getResult();
                    param.setResult(result + "\nI am hooked!");
                }
            });
        }
    }
}

image-20220228165518473

Hook 内部类中的函数

在 Teacher 类中添加一个内部类,并且修改 Teacher 类中的 getString 方法,使其调用 student 类。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public String getString() {
    student a = new student();
    a.age = 10;
    a.hair = true;
    a.name = "mark";
    return "\n " + "\nage:" + age + "\nname:" + name + "\nhair:" + hair + a.getString();
}
class student {
    int age = 19;
    String name = "mark";
    String city = "beijing";
    boolean hair = true;

    public int getAge() {
        return age;
    }

    public String getString() {
        return "\ni am from student\n--age:" + age + "\n--name:" + name + "\n--hair:" + hair +
                    "\n--city:" + city;
    }
}

运行效果如下

image-20220228173101945

hook 内部类时,使用 $ 符号连接类名。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class HookInnerClass implements IXposedHookLoadPackage {
    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
        if (lpparam.packageName.equals("dev.svip.teacher")) {
            XposedHelpers.findAndHookMethod("dev.svip.teacher.Teacher$student", lpparam.classLoader,
                    "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);
                            String str = "\nI am from hook student\nage:" + 999 + "\nname::" + 
                                    "blog.svip.dev" + "\nhair:" + "true" + "\ncity:" + "shanghai";
                            param.setResult(str);
                        }
                    });
        }
    }
}

hook 后的结果。

image-20220228214035812

Hook 匿名内部类

与 hook 内部类函数方式相同,函数名可以通过 GDA 逆向 apk 查看。

Hook JNI 函数

Java 层中的 JNI 函数与普通函数的 hook 方式相同。

主动调用方法

在 Teacher 类中添加以下三个方法

1
2
3
4
5
6
7
8
9
public String publicmethod(){
    return "I am from public method.";
}
private String privatemethod(){
    return "I am from private method.";
}
public static String publicstatic(){
    return "I am from public static method.";
}

在 Xposed 项目中,我们可以通过反射主动调用目标应用中的方法,也可以通过 Xposed 提供的 api 进行调用。

static 方法

使用反射或者 Xposed api 直接调用即可

1
2
3
4
5
6
7
//反射
Method publicstatic = teacherclass.getDeclaredMethod("publicstatic");
String str = (String) publicstatic.invoke(null);
Log.e("blog.svip.dev", str);
//Xposed api
str = (String) XposedHelpers.callStaticMethod(teacherclass, "publicstatic");
Log.e("blog.svip.dev", str + " Called by xposed api.");

非静态方法

同样可以使用反射或者 Xposed api 调用,但是需要首先获得实例。以下提供四种获取实例的方式。

  • 使用反射获取实例并调用

    因为构造方法有重载,所以可以通过有无参数两种方式来创建实例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Object teacherobj = teacherclass.newInstance();
Method publicmethod = teacherclass.getDeclaredMethod("publicmethod");
str = (String) publicmethod.invoke(teacherobj);
Log.e("blog.svip.dev", str + " 默认无参构造方法");

Object teacherobj1 = teacherclass.getConstructor(int.class, String.class).newInstance(18, "小明");
Method privatemethod = teacherclass.getDeclaredMethod("privatemethod");
privatemethod.setAccessible(true);
str = (String) privatemethod.invoke(teacherobj1);
Log.e("blog.svip.dev", str + " 有参构造方法");
  • 使用 Xposed api 获取实例并调用
1
2
3
4
5
6
7
Object teacherobject = XposedHelpers.newInstance(teacherclass, 18);
str = (String) XposedHelpers.callMethod(teacherobject, "publicmethod");
Log.e("blog.svip.dev", str + " Called by xposed api. 有参构造方法");

Object teacherobject2 = XposedHelpers.newInstance(teacherclass);
str = (String) XposedHelpers.callMethod(teacherobject2, "privatemethod");
Log.e("blog.svip.dev", str + " Called by xposed api. 无参构造方法");
  • 通过 hook 构造方法获取实例并调用

前面两种方法是创建一个新实例,而此种方法是 hook 已有的实例。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
Class<?> teacherclazz = lpparam.classLoader.loadClass("dev.svip.teacher.Teacher");
XposedHelpers.findAndHookConstructor(teacherclazz, 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);
        Method publicmethod = teacherclazz.getDeclaredMethod("publicmethod");
        Method privatemethod = teacherclazz.getDeclaredMethod("privatemethod");
        privatemethod.setAccessible(true);
        String publicstr = (String) publicmethod.invoke(param.thisObject);
        String privatestr = (String) privatemethod.invoke(param.thisObject);
        Log.e("blog.svip.dev", publicstr + " Get instance by hook init.");
        Log.e("blog.svip.dev", privatestr + " Get instance by hook init.");
    }
});
  • 通过 hook 参数获取实例并调用

当某些方法的参数是类的实例时,可以通过 hook 该方法,得到其参数,即可获得实例,从而主动调用类的方法。

我们在 app 的主函数中创建一个方法,并在主函数中调用它。

image-20220304143002045

在 Xposed 中 hook getInfo 方法,其参数即为我们需要的实例。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
Class<?> mainActivity = lpparam.classLoader.loadClass("dev.svip.teacher.MainActivity");
Class<?> teacherClazz = lpparam.classLoader.loadClass("dev.svip.teacher.Teacher");
XposedHelpers.findAndHookMethod(mainActivity, "getInfo", teacherClazz, new XC_MethodHook() {
    @Override
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
        super.beforeHookedMethod(param);
        Method publicmethod = teacherClazz.getDeclaredMethod("publicmethod");
        Method privatemethod = teacherClazz.getDeclaredMethod("privatemethod");
        privatemethod.setAccessible(true);
        String publicstr = (String) publicmethod.invoke(param.args[0]);
        String privatestr = (String) privatemethod.invoke(param.args[0]);
        Log.e("blog.svip.dev", publicstr + " Get instance by hook method's param.");
        Log.e("blog.svip.dev", privatestr + " Get instance by hook method's param.");
    }

    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        super.afterHookedMethod(param);

    }
});

参考资料

1、https://www.freebuf.com/articles/terminal/189021.html

updatedupdated2022-04-062022-04-06