69、Android逆向(五):Xposed框架使用、模块开发

Xposed框架,说白了就是Android世界里的“手术刀”。

它能在不修改APK的情况下,直接hook系统进程,改变应用的行为。我当年第一次接触它的时候,心里就一个想法:这玩意儿太狠了。你想想看,一个第三方框架,居然能让你在系统服务里插一脚,拦截任何Java方法的调用——这简直就是逆向工程师的终极武器。

Xposed的工作原理

Xposed的核心思路,是替换了Android系统的app_process进程。系统启动时,Xposed的替代版本会加载一个额外的JAR包,里面包含了hook框架。然后,它会扫描所有已安装的模块,把模块里定义的hook逻辑注入到目标进程里。

嗯,这里要注意:Xposed只对Java层有效。Native层的hook,它管不了。我在项目中遇到过好几次,以为hook了某个Java方法就能搞定,结果发现底层逻辑全在so文件里……那叫一个尴尬。

核心要点:Xposed通过修改Zygote进程,在应用启动前注入代码。所有hook操作都在Java虚拟机层面完成。

环境搭建

要玩Xposed,你得先有个root过的设备。模拟器也行,但真机更稳。我个人习惯用Android 5.0到7.0的旧设备,兼容性最好。高版本Android虽然也有EdXposed、LSPosed这些替代品,但底层原理其实没变。

  1. 安装Xposed Installer APK
  2. 在框架页面点击“安装/更新”,重启
  3. 确认框架激活状态

为什么强调要确认激活状态?因为有时候刷完框架,系统没报错,但实际没生效。我建议你装完以后,随便找个检测工具验证一下,省得后面debug半天才发现框架没跑起来。

模块开发入门

写一个Xposed模块,本质上就是实现一个IXposedHookLoadPackage接口。你需要在handleLoadPackage方法里,判断当前加载的是哪个应用包名,然后执行hook逻辑。

public class MainHook implements IXposedHookLoadPackage {
    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) {
        if (!lpparam.packageName.equals("com.example.target")) {
            return;
        }
        XposedBridge.log("已注入目标应用: " + lpparam.packageName);
    }
}

这段代码很简单,但有个坑:XposedBridge.log的输出,会写到Xposed Installer的日志里。如果你在真机上调试,记得打开日志窗口看输出。我曾经因为没看日志,以为hook没生效,折腾了半天才发现是包名写错了……

常用Hook API

Xposed提供了几个核心API,我列一下常用的:

API 作用
XposedHelpers.findAndHookMethod 按方法名和参数类型hook
XposedHelpers.findClass 动态加载类,避免ClassNotFoundException
XposedBridge.hookAllMethods hook某个类的所有同名方法
XposedHelpers.setObjectField 修改对象的字段值

我个人用得最多的是findAndHookMethod。它接受类名、方法名、参数类型列表,以及一个回调。回调里你可以修改参数、修改返回值,或者干脆替换整个方法逻辑。

XposedHelpers.findAndHookMethod(
    "com.example.target.MainActivity",
    lpparam.classLoader,
    "getSecretData",
    new XC_MethodHook() {
        @Override
        protected void beforeHookedMethod(MethodHookParam param) {
            XposedBridge.log("即将调用getSecretData");
        }
        @Override
        protected void afterHookedMethod(MethodHookParam param) {
            param.setResult("这是被hook后的数据");
        }
    }
);

你看,beforeHookedMethod在原始方法执行前触发,afterHookedMethod在之后触发。如果你想篡改返回值,就在afterHookedMethod里调用setResult。嗯,这里有个细节:如果你在beforeHookedMethod里调用了setResult,原始方法就不会执行了。这招可以用来跳过某些烦人的校验逻辑。

模块配置与打包

写完了hook代码,你还得配置一下模块信息。在AndroidManifest.xml里,你需要声明一个meta-data:

<meta-data
    android:name="xposedmodule"
    android:value="true" />
<meta-data
    android:name="xposeddescription"
    android:value="我的第一个Xposed模块" />
<meta-data
    android:name="xposedminversion"
    android:value="82" />

这三个meta-data缺一不可。尤其是xposedminversion,如果你填的版本号太高,旧版Xposed框架会直接忽略你的模块。我建议填82,兼容性最好。

打包的时候,记得把Xposed的API JAR包以provided方式引入,不要打包进APK里。否则模块体积会变大,而且可能和框架自带的API冲突。

调试技巧

调试Xposed模块,说实话挺痛苦的。你不能像普通App那样直接attach debugger,因为模块是注入到目标进程里的。我常用的方法有两种:

  • 日志输出:XposedBridge.log打日志,然后在Xposed Installer里查看。简单粗暴,但够用。
  • 文件写入:把调试信息写到/data/data/包名/下的文件里。适合记录大量数据。

我曾经试过用Logcat抓Xposed的日志,但发现有时候框架的日志级别不对,根本看不到。后来我就老老实实写文件了。你想想看,一个hook点如果没触发,你连日志都看不到,那才叫绝望。

小技巧:afterHookedMethod里打印调用栈,可以帮你快速定位是谁调用了目标方法。用new Throwable().printStackTrace()就行。

避坑指南

做Xposed开发,有几个坑我踩过,你注意一下:

  • 类加载器问题:hook系统服务时,要用ClassLoader.getSystemClassLoader(),而不是lpparam.classLoader。后者只对应用进程有效。
  • 方法签名错误:hook方法时,参数类型必须写对。比如int要写成int.class,不能写成Integer.class。否则框架找不到方法,直接报错。
  • 模块未激活:装完模块后,记得在Xposed Installer里勾选启用,然后重启。我见过有人装完模块没勾选,跑来问我为什么没效果……

警告:Xposed框架会修改系统分区。如果你用的是生产环境的设备,建议先备份数据。另外,某些银行类App会检测Xposed环境,一旦发现就直接闪退。这种情况,你得配合隐藏框架的工具一起用。

知识体系总览

下面这张图,是我梳理的Xposed模块开发核心流程。从环境搭建到模块打包,每一步都离不开这些环节:

Xposed模块开发核心流程 环境搭建 实现IXposedHookLoadPackage 编写Hook逻辑 配置并打包模块 关键API findAndHookMethod findClass hookAllMethods setObjectField 调试方式 XposedBridge.log 文件写入 常见坑 类加载器混淆 方法签名错误

这张图把整个流程串起来了。你从环境搭建开始,一步步走到模块打包,中间的核心就是编写hook逻辑。而右边的关键API和调试方式,是你必须掌握的工具。嗯,说实话,Xposed的入门门槛不高,但想玩得溜,得多练。

好了,这一章的内容就到这里。Xposed框架的使用和模块开发,说白了就是“找到目标方法,然后改它”。你只要掌握了API的用法,剩下的就是耐心和细心了。

公众号:蓝海资料掘金营,微信deep3321