Xposed Api详解到RPC的使用
移动安全
在日常移动安全工作中,一定会使用 hook ,那使用 hook 一定离不开一款安全工具,那就是 Xposed 。关于 Xposed 的使用,官网列举出上百条的 Api,这样很不利于查找和使用。本文对日常工作中常用的 Xposed Api 进行详解并演示使用方法。最后结合 NanoHttpd 弥补了 Xposed 不能 RPC 的功能。
XposedApi详解到RPC的使用 ------------------ ### XposedApi详解 要想学习 Xposed Api 一定要借助官方文档。 官方 Api 文档网址:<https://api.xposed.info/reference/packages.html> 下面介绍在开发 Xposed 插件中最常使用的几种方式 #### 在介绍之前,要先创建一个测试 App 测试 App 共两个类,一个是 MainActivity 类,一个是 Dog 类。 MainActivity 类代码: ```java package com.bmstd.hookdog; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.Toast; public class MainActivity extends AppCompatActivity implements View.OnClickListener { Button bt_hookNormalMethod; Button bt_hookConstructMethod; Button bt_hookEat; Button bt_hookOverloadEat; Button bt_hookInner; Dog dog; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); bt_hookNormalMethod = findViewById(R.id.bt_hookNormalMethod); bt_hookConstructMethod = findViewById(R.id.bt_hookConstructMethod); bt_hookEat = findViewById(R.id.bt_hookEat); bt_hookOverloadEat = findViewById(R.id.bt_hookOverloadEat); bt_hookInner = findViewById(R.id.bt_hookInner); bt_hookNormalMethod.setOnClickListener(this); bt_hookConstructMethod.setOnClickListener(this); bt_hookEat.setOnClickListener(this); bt_hookOverloadEat.setOnClickListener(this); bt_hookInner.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(MainActivity.this, "内部类被调用", Toast.LENGTH_LONG).show(); } }); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.bt_hookNormalMethod: dog = new Dog("辛巴", 14); int countSum = dog.count(100, 200); Toast.makeText(this, "得到的结果是:" + countSum, Toast.LENGTH_LONG).show(); break; case R.id.bt_hookConstructMethod: dog = new Dog("靓仔", 15); String dogString = dog.toString(); Toast.makeText(this, dogString, Toast.LENGTH_LONG).show(); break; case R.id.bt_hookEat: dog = new Dog("公爵", 13); String eatResult = dog.eat("猪肉"); Toast.makeText(this, eatResult, Toast.LENGTH_LONG).show(); break; case R.id.bt_hookOverloadEat: dog = new Dog("哈利", 11); String eatOverLoadResult = dog.eat("狗粮", 50); Toast.makeText(this, eatOverLoadResult, Toast.LENGTH_LONG).show(); break; } } } ``` Dog 类代码: ```java package com.bmstd.hookdog; class Dog { String name; private int age; static String type = "狗类"; public Dog(String name, int age) { this.name = name; this.age = age; } public static String work(String w) { return w; } public static String work(String w, int h) { String str = "狗正在" + w + "已工作" + h + "小时"; return str; } int count(int num1, int num2) { int sum = 0; sum = num1 + num2; return sum; } public String eat(String foodName) { return "我爱吃" + foodName; } public String eat(String foodName, int amount) { return "我爱吃" + foodName + ",一次吃" + amount + "克"; } private int sub(int num) { int subResult = num - 1; return subResult; } @Override public String toString() { return "Dog{" + "name='" + name + '\'' + ", age=" + age + '\'' + ", type=" + type + '\'' + '}'; } } ``` #### hook 构造方法 hook 构造方式使用 api,XposedHelpers.findAndHookConstructor。 hook 构造方法与 hook 普通方法类似,只是不用写方法名而已,因为构造方法的方法名就是类名。 这里 hook Dog 类的构造函数 ```java public Dog(String name, int age) { this.name = name; this.age = age; } ``` 编写代码: ```java package com.bmstd.xposed1; import de.robv.android.xposed.IXposedHookLoadPackage; import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.callbacks.XC_LoadPackage; public class HookTest implements IXposedHookLoadPackage { public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { if (loadPackageParam.packageName.equals("com.bmstd.hookdog")) { Class clazz = loadPackageParam.classLoader.loadClass("com.bmstd.hookdog.Dog"); XposedHelpers.findAndHookConstructor(clazz, String.class, int.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { param.args[0] = "逆刃"; param.args[1] = 8; } }); } } } ``` 正常情况下,点击 Hook 构造方法按钮,会走下面代码的分支 ```java case R.id.bt_hookConstructMethod: dog = new Dog("靓仔", 15); String dogString = dog.toString(); Toast.makeText(this, dogString, Toast.LENGTH_LONG).show(); break; ``` 然后由于 hook 住了构造方法,并修改了里面的参数,所以输出结果就不是 靓仔,15 ,而是 逆刃,8。  #### 获取和修改静态字段 获取静态字段,方法如下图  这里以获取 Dog 类的静态属性 type 为例 ```java static String type = "狗类"; ``` 编写代码: ```java package com.bmstd.xposed1; import de.robv.android.xposed.IXposedHookLoadPackage; import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.callbacks.XC_LoadPackage; public class HookTest implements IXposedHookLoadPackage { public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { if (loadPackageParam.packageName.equals("com.bmstd.hookdog")) { Class clazz = loadPackageParam.classLoader.loadClass("com.bmstd.hookdog.Dog"); Object type = XposedHelpers.getStaticObjectField(clazz, "type"); XposedBridge.log("获取类属性 type => " + type + ""); } } } ``` 成功获取到类属性  修改静态字段,与获取静态字段相似,只不过方法由 get 变为 set 修改静态字段,方法如下图  这里以修改 Dog 类的静态属性 type 为例 编写代码: ```java package com.bmstd.xposed1; import de.robv.android.xposed.IXposedHookLoadPackage; import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.callbacks.XC_LoadPackage; public class HookTest implements IXposedHookLoadPackage { public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { if (loadPackageParam.packageName.equals("com.bmstd.hookdog")) { Class clazz = loadPackageParam.classLoader.loadClass("com.bmstd.hookdog.Dog"); XposedHelpers.setStaticObjectField(clazz, "type", "猫类"); } } } ``` 此时点击 HOOK 构造方法,弹出的就不是狗类,而是猫类  #### 调用静态方法 调用静态方法,使用的方法如下图  这里举的例子,都调用 work 方法 可以看到 work 方法有重载,有两个 work 方法 ```java public static String work(String w) { return w; } public static String work(String w, int h) { String str = "狗正在" + w + "已工作" + h + "小时"; return str; } ``` 只需要在调用时,按参数的类型,严谨填入即可,无需考虑重载,想调用哪个就按哪个的参数类型进行填写。 分别调用上面两个 work 方法,代码如下: ```java package com.bmstd.xposed1; import de.robv.android.xposed.IXposedHookLoadPackage; import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.callbacks.XC_LoadPackage; public class HookTest implements IXposedHookLoadPackage { public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { if (loadPackageParam.packageName.equals("com.bmstd.hookdog")) { Class clazz = loadPackageParam.classLoader.loadClass("com.bmstd.hookdog.Dog"); Object work = XposedHelpers.callStaticMethod(clazz, "work", "狗正在看家"); XposedBridge.log("调用静态方法 work => " + work); Object workOverload = XposedHelpers.callStaticMethod(clazz, "work", "狗正在玩耍", 50); XposedBridge.log("调用静态方法 work => " + workOverload); } } } ``` 可以看到 work 的两个重载方法都可成功调用  #### 获取和修改动态字段 动态字段就是要有对象,而不能靠类直接进行调用。 所以首先要解决的第一个难点就是,获取对象! 获取对象的方式有两种 第一种方式就是使用 XposedHelpers.newInstance ,new 一个对象出来 第二种方式就是在 hook 方法时,使用 param.thisObject 获取对象 获取动态字段和获取静态字段方式的差异性只是少了 static 获取调用 eat 方法对象的 name,和 age 字段。编写代码: ```java package com.bmstd.xposed1; import de.robv.android.xposed.IXposedHookLoadPackage; import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.callbacks.XC_LoadPackage; public class HookTest implements IXposedHookLoadPackage { public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { if (loadPackageParam.packageName.equals("com.bmstd.hookdog")) { Class clazz = loadPackageParam.classLoader.loadClass("com.bmstd.hookdog.Dog"); XposedHelpers.findAndHookMethod(clazz, "eat", String.class, int.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { Object name = XposedHelpers.getObjectField(param.thisObject, "name"); int age = XposedHelpers.getIntField(param.thisObject, "age"); XposedBridge.log("获取实例属性 name => " + name); XposedBridge.log("获取实例属性 age => " + age); } }); } } } ``` 从上面的开发可知,调用的是下面的分支 ```java case R.id.bt_hookOverloadEat: dog = new Dog("哈利", 11); String eatOverLoadResult = dog.eat("狗粮", 50); Toast.makeText(this, eatOverLoadResult, Toast.LENGTH_LONG).show(); break; ``` 所以获取到的 name 是哈利,age 是 11  修改动态字段,就是将 set 变为 get 修改调用 eat 方法对象的 name,和 age 字段。 ```java package com.bmstd.xposed1; import de.robv.android.xposed.IXposedHookLoadPackage; import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.callbacks.XC_LoadPackage; public class HookTest implements IXposedHookLoadPackage { public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { if (loadPackageParam.packageName.equals("com.bmstd.hookdog")) { Class clazz = loadPackageParam.classLoader.loadClass("com.bmstd.hookdog.Dog"); XposedHelpers.findAndHookMethod(clazz, "eat", String.class, int.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { XposedHelpers.setObjectField(param.thisObject, "name", "多多"); XposedHelpers.setIntField(param.thisObject, "age", 6); } }); } } } ``` 通过 objection 搜索对象获取到 name 已经变成了多多,age 变成了 6。  #### 调用普通方法 调用普通方法,使用的方法如下图  首选运用是通过 new 对象,来调用 count 方法 ```java Object dog = XposedHelpers.newInstance(clazz, "哈里", 12); Object count = XposedHelpers.callMethod(dog, "count", 22, 11); XposedBridge.log("调用普通方法 work => " + count); ``` 调用普通方法成功,得到的结果是  运行第二种方法,通过 hook 然后使用 param.thisObject 获取对象,进行调用 ```java package com.bmstd.xposed1; import de.robv.android.xposed.IXposedHookLoadPackage; import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.callbacks.XC_LoadPackage; public class HookTest implements IXposedHookLoadPackage { public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { if (loadPackageParam.packageName.equals("com.bmstd.hookdog")) { Class clazz = loadPackageParam.classLoader.loadClass("com.bmstd.hookdog.Dog"); XposedHelpers.findAndHookMethod(clazz, "eat", String.class, int.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { Object count = XposedHelpers.callMethod(param.thisObject, "count", 66, 55); XposedBridge.log("调用普通方法 work => " + count); } }); } } } ``` 同样调用普通方法也可成功,得到的结果是  ### Xposed结合NanoHttpd使用RPC Xposed 本身是不支持 RPC 的,但 Xposed 的优点是,可以在代码中嵌入任意 Java 代码,与Android本身开发无差别。、 所以可以在 Xposed 中结合 NanoHttpd 完成 RPC。 首先在 Android Studio 工程中的 build.grade 中引入依赖 ```xml implementation 'org.nanohttpd:nanohttpd:2.3.1' ```  然后就可以使用 NanoHttpd 了。 Xposed 结合 NanoHttp 使用 RPC 原理如下图所示:  编写 RPC 代码,可以调用静态方法 work 和普通方法 eat ```java package com.bmstd.xposed1; import java.io.IOException; import java.util.Map; import de.robv.android.xposed.IXposedHookLoadPackage; import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.callbacks.XC_LoadPackage; import fi.iki.elonen.NanoHTTPD; public class HookTest implements IXposedHookLoadPackage { public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { // 判断包名 if (loadPackageParam.packageName.equals("com.bmstd.hookdog")) { // 寻找类 Class clazz = loadPackageParam.classLoader.loadClass("com.bmstd.hookdog.Dog"); class App extends NanoHTTPD { String msg = ""; // 这里的写法是不变的,变的只是端口 public App() throws IOException { super(8899); // 定义端口 start(NanoHTTPD.SOCKET_READ_TIMEOUT, true); // 启动 NanoHTTPD XposedBridge.log("\nRunning! Point your browsers to http://localhost:8899/ \n"); } // serve 就是处理请求和返回的地方,主要的逻辑就在 serve 里面写 @Override public Response serve(IHTTPSession session) { // 获取参数,但是这里只能获取 get 参数 Map<String, String> parameters = session.getParms(); // 如果参数的键有 work,就调用静态方法 work if (parameters.containsKey("work")) { String work = parameters.get("work"); msg = (String) XposedHelpers.callStaticMethod(clazz, "work", work); } // 如果参数的键有 foodName 和 amount,就调用普通方法 eat if (parameters.containsKey("foodName") && parameters.containsKey("amount")) { Object dog = XposedHelpers.newInstance(clazz, "哈里", 12); String foodName = parameters.get("foodName"); String amount = parameters.get("amount"); int i = Integer.parseInt(amount); msg = (String) XposedHelpers.callMethod(dog, "eat", foodName, i); } // 将结果返回页面 return newFixedLengthResponse(Response.Status.OK, NanoHTTPD.MIME_PLAINTEXT, msg); } } new App(); } } } ``` 由于 NanoHttpd 一定使用的是网络,所以被 hook 的 App 必须拥有网络权限,这点很重要,否则就会启动网络异常,报错误。 一定要在被 hook 的 App 中增加网络权限。这里就要在 com.bmstd.hookdog 设置网络权限。  此时通过网站就可以调用 Xposed 的主动调用了。 调用普通方法 eat 效果。  调用静态方法 work 效果。  这样就可以既隐藏代码细节,使用者又方便。 ### 总结语 本文共详解并举例了: hook构造方法;获取和修改静态字段;调用静态方法;获取和修改动态字段;调用普通方法。并最后让 Xposed 结合 NanoHttpd 弥补了 Xposed 不能 RPC 的功能。
发表于 2023-05-23 09:00:00
阅读 ( 9752 )
分类:
安全工具
0 推荐
收藏
0 条评论
bmstd
13 篇文章
×
温馨提示
您当前没有「奇安信攻防社区」的账号,注册后可获取更多的使用权限。
×
温馨提示
您当前没有「奇安信攻防社区」的账号,注册后可获取更多的使用权限。
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!