30
2023-6
今天距2024年184天
这是鸣谦安全第154次推文
点击上方蓝字“鸣谦安全“关注我,周二,周五,每晚 19:00准时推送。
微信公众号后台回复“资源”领取学习资料,回复“QQ群”一起进群学习闲聊。
本文4529字,阅读约需10分钟
文章首发于奇安信攻防社区:https://forum.butian.net/share/2276
在日常移动安全工作中,一定会使用 hook ,那使用 hook 一定离不开一款安全工具,那就是 Xposed 。
关于 Xposed 的使用,官网列举出上百条的 Api,这样很不利于查找和使用。本文对日常工作中常用的 Xposed Api 进行详解并演示使用方法。最后结合 NanoHttpd 弥补了 Xposed 不能 RPC 的功能。
XposedApi详解
要想学习 Xposed Api 一定要借助官方文档。
官方 Api 文档网址:https://api.xposed.info/reference/packages.html
下面介绍在开发 Xposed 插件中最常使用的几种方式
在介绍之前,要先创建一个测试 App
测试 App 共两个类,一个是 MainActivity 类,一个是 Dog 类。
MainActivity 类代码:
1package com.bmstd.hookdog;
2
3import androidx.appcompat.app.AppCompatActivity;
4
5import android.os.Bundle;
6import android.view.View;
7import android.widget.Button;
8import android.widget.Toast;
9
10public class MainActivity extends AppCompatActivity implements View.OnClickListener {
11 Button bt_hookNormalMethod;
12 Button bt_hookConstructMethod;
13 Button bt_hookEat;
14 Button bt_hookOverloadEat;
15 Button bt_hookInner;
16 Dog dog;
17
18 @Override
19 protected void onCreate(Bundle savedInstanceState) {
20 super.onCreate(savedInstanceState);
21 setContentView(R.layout.activity_main);
22 bt_hookNormalMethod = findViewById(R.id.bt_hookNormalMethod);
23 bt_hookConstructMethod = findViewById(R.id.bt_hookConstructMethod);
24 bt_hookEat = findViewById(R.id.bt_hookEat);
25 bt_hookOverloadEat = findViewById(R.id.bt_hookOverloadEat);
26 bt_hookInner = findViewById(R.id.bt_hookInner);
27
28 bt_hookNormalMethod.setOnClickListener(this);
29 bt_hookConstructMethod.setOnClickListener(this);
30 bt_hookEat.setOnClickListener(this);
31 bt_hookOverloadEat.setOnClickListener(this);
32
33 bt_hookInner.setOnClickListener(new View.OnClickListener() {
34 @Override
35 public void onClick(View v) {
36 Toast.makeText(MainActivity.this, "内部类被调用", Toast.LENGTH_LONG).show();
37 }
38 });
39
40 }
41
42 @Override
43 public void onClick(View v) {
44 switch (v.getId()) {
45 case R.id.bt_hookNormalMethod:
46 dog = new Dog("辛巴", 14);
47 int countSum = dog.count(100, 200);
48 Toast.makeText(this, "得到的结果是:" + countSum, Toast.LENGTH_LONG).show();
49 break;
50 case R.id.bt_hookConstructMethod:
51 dog = new Dog("靓仔", 15);
52 String dogString = dog.toString();
53 Toast.makeText(this, dogString, Toast.LENGTH_LONG).show();
54 break;
55 case R.id.bt_hookEat:
56 dog = new Dog("公爵", 13);
57 String eatResult = dog.eat("猪肉");
58 Toast.makeText(this, eatResult, Toast.LENGTH_LONG).show();
59 break;
60 case R.id.bt_hookOverloadEat:
61 dog = new Dog("哈利", 11);
62 String eatOverLoadResult = dog.eat("狗粮", 50);
63 Toast.makeText(this, eatOverLoadResult, Toast.LENGTH_LONG).show();
64 break;
65 }
66 }
67}
Dog 类代码:
1package com.bmstd.hookdog;
2
3class Dog {
4 String name;
5 private int age;
6 static String type = "狗类";
7
8 public Dog(String name, int age) {
9 this.name = name;
10 this.age = age;
11 }
12
13 public static String work(String w) {
14 return w;
15 }
16
17 public static String work(String w, int h) {
18 String str = "狗正在" + w + "已工作" + h + "小时";
19 return str;
20 }
21
22 int count(int num1, int num2) {
23 int sum = 0;
24 sum = num1 + num2;
25 return sum;
26 }
27
28 public String eat(String foodName) {
29 return "我爱吃" + foodName;
30 }
31
32 public String eat(String foodName, int amount) {
33 return "我爱吃" + foodName + ",一次吃" + amount + "克";
34 }
35
36 private int sub(int num) {
37 int subResult = num - 1;
38 return subResult;
39 }
40
41 @Override
42 public String toString() {
43 return "Dog{" +
44 "name='" + name + ''' +
45 ", age=" + age + ''' +
46 ", type=" + type + ''' +
47 '}';
48 }
49}
hook 构造方法
hook 构造方式使用 api,XposedHelpers.findAndHookConstructor。
hook 构造方法与 hook 普通方法类似,只是不用写方法名而已,因为构造方法的方法名就是类名。
这里 hook Dog 类的构造函数
1public Dog(String name, int age) {
2 this.name = name;
3 this.age = age;
4}
编写代码:
1package com.bmstd.xposed1;
2
3import de.robv.android.xposed.IXposedHookLoadPackage;
4import de.robv.android.xposed.XC_MethodHook;
5import de.robv.android.xposed.XposedBridge;
6import de.robv.android.xposed.XposedHelpers;
7import de.robv.android.xposed.callbacks.XC_LoadPackage;
8
9public class HookTest implements IXposedHookLoadPackage {
10 public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
11 if (loadPackageParam.packageName.equals("com.bmstd.hookdog")) {
12 Class clazz = loadPackageParam.classLoader.loadClass("com.bmstd.hookdog.Dog");
13
14 XposedHelpers.findAndHookConstructor(clazz, String.class, int.class, new XC_MethodHook() {
15 @Override
16 protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
17 param.args[0] = "逆刃";
18 param.args[1] = 8;
19 }
20 });
21 }
22 }
23}
正常情况下,点击 Hook 构造方法按钮,会走下面代码的分支
1case R.id.bt_hookConstructMethod:
2 dog = new Dog("靓仔", 15);
3 String dogString = dog.toString();
4 Toast.makeText(this, dogString, Toast.LENGTH_LONG).show();
5 break;
然后由于 hook 住了构造方法,并修改了里面的参数,所以输出结果就不是 靓仔,15 ,而是 逆刃,8。
获取和修改静态字段
获取静态字段,方法如下图
这里以获取 Dog 类的静态属性 type 为例
1static String type = "狗类";
编写代码:
1package com.bmstd.xposed1;
2
3import de.robv.android.xposed.IXposedHookLoadPackage;
4import de.robv.android.xposed.XC_MethodHook;
5import de.robv.android.xposed.XposedBridge;
6import de.robv.android.xposed.XposedHelpers;
7import de.robv.android.xposed.callbacks.XC_LoadPackage;
8
9public class HookTest implements IXposedHookLoadPackage {
10 public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
11 if (loadPackageParam.packageName.equals("com.bmstd.hookdog")) {
12 Class clazz = loadPackageParam.classLoader.loadClass("com.bmstd.hookdog.Dog");
13
14 Object type = XposedHelpers.getStaticObjectField(clazz, "type");
15 XposedBridge.log("获取类属性 type => " + type + "");
16 }
17 }
18}
成功获取到类属性
修改静态字段,与获取静态字段相似,只不过方法由 get 变为 set
修改静态字段,方法如下图
这里以修改 Dog 类的静态属性 type 为例
编写代码:
1package com.bmstd.xposed1;
2
3import de.robv.android.xposed.IXposedHookLoadPackage;
4import de.robv.android.xposed.XposedHelpers;
5import de.robv.android.xposed.callbacks.XC_LoadPackage;
6
7public class HookTest implements IXposedHookLoadPackage {
8 public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
9 if (loadPackageParam.packageName.equals("com.bmstd.hookdog")) {
10 Class clazz = loadPackageParam.classLoader.loadClass("com.bmstd.hookdog.Dog");
11
12 XposedHelpers.setStaticObjectField(clazz, "type", "猫类");
13 }
14 }
15}
此时点击 HOOK 构造方法,弹出的就不是狗类,而是猫类
调用静态方法
调用静态方法,使用的方法如下图
这里举的例子,都调用 work 方法
可以看到 work 方法有重载,有两个 work 方法
1public static String work(String w) {
2 return w;
3}
4
5public static String work(String w, int h) {
6 String str = "狗正在" + w + "已工作" + h + "小时";
7 return str;
8}
只需要在调用时,按参数的类型,严谨填入即可,无需考虑重载,想调用哪个就按哪个的参数类型进行填写。
分别调用上面两个 work 方法,代码如下:
1package com.bmstd.xposed1;
2
3import de.robv.android.xposed.IXposedHookLoadPackage;
4import de.robv.android.xposed.XposedBridge;
5import de.robv.android.xposed.XposedHelpers;
6import de.robv.android.xposed.callbacks.XC_LoadPackage;
7
8public class HookTest implements IXposedHookLoadPackage {
9 public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
10 if (loadPackageParam.packageName.equals("com.bmstd.hookdog")) {
11 Class clazz = loadPackageParam.classLoader.loadClass("com.bmstd.hookdog.Dog");
12
13 Object work = XposedHelpers.callStaticMethod(clazz, "work", "狗正在看家");
14 XposedBridge.log("调用静态方法 work => " + work);
15
16 Object workOverload = XposedHelpers.callStaticMethod(clazz, "work", "狗正在玩耍", 50);
17 XposedBridge.log("调用静态方法 work => " + workOverload);
18 }
19 }
20}
可以看到 work 的两个重载方法都可成功调用
获取和修改动态字段
动态字段就是要有对象,而不能靠类直接进行调用。
所以首先要解决的第一个难点就是,获取对象!
获取对象的方式有两种
第一种方式就是使用 XposedHelpers.newInstance ,new 一个对象出来
第二种方式就是在 hook 方法时,使用 param.thisObject 获取对象
获取动态字段和获取静态字段方式的差异性只是少了 static
获取调用 eat 方法对象的 name,和 age 字段。编写代码:
1package com.bmstd.xposed1;
2
3import de.robv.android.xposed.IXposedHookLoadPackage;
4import de.robv.android.xposed.XC_MethodHook;
5import de.robv.android.xposed.XposedBridge;
6import de.robv.android.xposed.XposedHelpers;
7import de.robv.android.xposed.callbacks.XC_LoadPackage;
8
9public class HookTest implements IXposedHookLoadPackage {
10 public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
11 if (loadPackageParam.packageName.equals("com.bmstd.hookdog")) {
12 Class clazz = loadPackageParam.classLoader.loadClass("com.bmstd.hookdog.Dog");
13
14 XposedHelpers.findAndHookMethod(clazz, "eat", String.class, int.class, new XC_MethodHook() {
15 @Override
16 protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
17 Object name = XposedHelpers.getObjectField(param.thisObject, "name");
18 int age = XposedHelpers.getIntField(param.thisObject, "age");
19
20 XposedBridge.log("获取实例属性 name => " + name);
21 XposedBridge.log("获取实例属性 age => " + age);
22 }
23 });
24 }
25 }
26}
从上面的开发可知,调用的是下面的分支
1case R.id.bt_hookOverloadEat:
2 dog = new Dog("哈利", 11);
3 String eatOverLoadResult = dog.eat("狗粮", 50);
4 Toast.makeText(this, eatOverLoadResult, Toast.LENGTH_LONG).show();
5 break;
所以获取到的 name 是哈利,age 是 11
修改动态字段,就是将 set 变为 get
修改调用 eat 方法对象的 name,和 age 字段。
1package com.bmstd.xposed1;
2
3import de.robv.android.xposed.IXposedHookLoadPackage;
4import de.robv.android.xposed.XC_MethodHook;
5import de.robv.android.xposed.XposedBridge;
6import de.robv.android.xposed.XposedHelpers;
7import de.robv.android.xposed.callbacks.XC_LoadPackage;
8
9public class HookTest implements IXposedHookLoadPackage {
10 public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
11 if (loadPackageParam.packageName.equals("com.bmstd.hookdog")) {
12 Class clazz = loadPackageParam.classLoader.loadClass("com.bmstd.hookdog.Dog");
13
14 XposedHelpers.findAndHookMethod(clazz, "eat", String.class, int.class, new XC_MethodHook() {
15 @Override
16 protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
17 XposedHelpers.setObjectField(param.thisObject, "name", "多多");
18 XposedHelpers.setIntField(param.thisObject, "age", 6);
19 }
20 });
21 }
22 }
23}
通过 objection 搜索对象获取到 name 已经变成了多多,age 变成了 6。
调用普通方法
调用普通方法,使用的方法如下图
首选运用是通过 new 对象,来调用 count 方法
1Object dog = XposedHelpers.newInstance(clazz, "哈里", 12);
2Object count = XposedHelpers.callMethod(dog, "count", 22, 11);
3XposedBridge.log("调用普通方法 work => " + count);
调用普通方法成功,得到的结果是
运行第二种方法,通过 hook 然后使用 param.thisObject 获取对象,进行调用
1package com.bmstd.xposed1;
2
3import de.robv.android.xposed.IXposedHookLoadPackage;
4import de.robv.android.xposed.XC_MethodHook;
5import de.robv.android.xposed.XposedBridge;
6import de.robv.android.xposed.XposedHelpers;
7import de.robv.android.xposed.callbacks.XC_LoadPackage;
8
9public class HookTest implements IXposedHookLoadPackage {
10 public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
11 if (loadPackageParam.packageName.equals("com.bmstd.hookdog")) {
12 Class clazz = loadPackageParam.classLoader.loadClass("com.bmstd.hookdog.Dog");
13
14 XposedHelpers.findAndHookMethod(clazz, "eat", String.class, int.class, new XC_MethodHook() {
15 @Override
16 protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
17 Object count = XposedHelpers.callMethod(param.thisObject, "count", 66, 55);
18 XposedBridge.log("调用普通方法 work => " + count);
19 }
20 });
21 }
22 }
23}
同样调用普通方法也可成功,得到的结果是
Xposed结合NanoHttpd使用RPC
Xposed 本身是不支持 RPC 的,但 Xposed 的优点是,可以在代码中嵌入任意 Java 代码,与Android本身开发无差别。
所以可以在 Xposed 中结合 NanoHttpd 完成 RPC。
首先在 Android Studio 工程中的 build.grade 中引入依赖
1implementation 'org.nanohttpd:nanohttpd:2.3.1'
然后就可以使用 NanoHttpd 了。
Xposed 结合 NanoHttp 使用 RPC 原理如下图所示:
编写 RPC 代码,可以调用静态方法 work 和普通方法 eat
1package com.bmstd.xposed1;
2
3import java.io.IOException;
4import java.util.Map;
5
6import de.robv.android.xposed.IXposedHookLoadPackage;
7import de.robv.android.xposed.XposedBridge;
8import de.robv.android.xposed.XposedHelpers;
9import de.robv.android.xposed.callbacks.XC_LoadPackage;
10import fi.iki.elonen.NanoHTTPD;
11
12public class HookTest implements IXposedHookLoadPackage {
13 public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
14 // 判断包名
15 if (loadPackageParam.packageName.equals("com.bmstd.hookdog")) {
16 // 寻找类
17 Class clazz = loadPackageParam.classLoader.loadClass("com.bmstd.hookdog.Dog");
18 class App extends NanoHTTPD {
19 String msg = "";
20
21 // 这里的写法是不变的,变的只是端口
22 public App() throws IOException {
23 super(8899); // 定义端口
24 start(NanoHTTPD.SOCKET_READ_TIMEOUT, true); // 启动 NanoHTTPD
25 XposedBridge.log("nRunning! Point your browsers to http://localhost:8899/ n");
26 }
27
28 // serve 就是处理请求和返回的地方,主要的逻辑就在 serve 里面写
29 @Override
30 public Response serve(IHTTPSession session) {
31 // 获取参数,但是这里只能获取 get 参数
32 Map<String, String> parameters = session.getParms();
33 // 如果参数的键有 work,就调用静态方法 work
34 if (parameters.containsKey("work")) {
35 String work = parameters.get("work");
36 msg = (String) XposedHelpers.callStaticMethod(clazz, "work", work);
37 }
38
39 // 如果参数的键有 foodName 和 amount,就调用普通方法 eat
40 if (parameters.containsKey("foodName") && parameters.containsKey("amount")) {
41 Object dog = XposedHelpers.newInstance(clazz, "哈里", 12);
42 String foodName = parameters.get("foodName");
43 String amount = parameters.get("amount");
44 int i = Integer.parseInt(amount);
45
46 msg = (String) XposedHelpers.callMethod(dog, "eat", foodName, i);
47 }
48
49 // 将结果返回页面
50 return newFixedLengthResponse(Response.Status.OK, NanoHTTPD.MIME_PLAINTEXT, msg);
51 }
52 }
53
54 new App();
55 }
56 }
57}
由于 NanoHttpd 一定使用的是网络,所以被 hook 的 App 必须拥有网络权限,这点很重要,否则就会启动网络异常,报错误。
一定要在被 hook 的 App 中增加网络权限。这里就要在 com.bmstd.hookdog 设置网络权限。
此时通过网站就可以调用 Xposed 的主动调用了。
调用普通方法 eat 效果。
调用静态方法 work 效果。
这样就可以既隐藏代码细节,使用者又方便。
敬请期待
<< 向右滑动查看下一张图片 >>
回复"QQ群"一起闲聊
个人微信:bmstd6
添加请注明来意
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论