30 分钟跑通你的第一个 Hook:LSPosed 模块开发实战
【免费下载链接】LSPosed_modMy changes to LSPosed项目地址: https://gitcode.com/GitHub_Trending/ls/LSPosed_mod
LSPosed 模块开发的核心,是在目标应用运行时拦截它的方法调用,不改一行 APK 代码就改变其行为。LSPosed 是基于 Riru/Zygisk 的 Xposed 框架现代实现,提供标准化的 Hook 注册与分发机制,你只需要写一个普通 Android 模块工程,就能在目标 App 的方法入口或出口插入自定义逻辑。
一个具体需求:不改源码隐藏按钮
假设你想让某个 App 首页上的一个按钮彻底消失,且不想重编译 APK。常规做法是反编译、改 smali、重新签名——流程长且容易被签名校验拦下。换一种思路:找到创建这个按钮的 Java 方法,在方法执行前修改传给它的参数,或者让"是否显示"的查询方法直接返回false。
这就是 LSPosed 模块能做的事:在目标应用加载时注册方法拦截,按你的规则改写参数、返回值,甚至直接替换整个方法。整个过程对目标 App 无感,模块本身就是一个带特殊元数据的普通 APK。
理解 Hook 机制:在方法入口装拦截器
把方法调用想象成一条公路,LSPosed 做的事情就是在公路入口装了一个检查站:所有"车辆"(调用请求)经过时,你可以检查并修改"货物"(方法参数),也可以在车辆驶出后改装"结果"(返回值)。技术上,这通过修改方法入口的字节码并插入回调来实现,注册逻辑集中在 XposedBridge。
LSPosed 整体分三层,你了解分工即可,不必深入源码:
- 注入层:基于 Magisk / Riru / Zygisk 把 Hook 框架注入 Zygote 进程。Zygote 是所有 App 进程的父进程,一次注入、所有 App 进程受益——这是模块无需修改 APK 即可生效的根本原因
- 核心层:
core模块,提供XposedBridge、XposedHelpers等模块开发用到的全部 API - 服务层:
daemon模块,负责配置管理、模块扫描、日志与跨进程服务通信
搭建环境,写最小可运行模块
先在命令行拉取仓库并构建,确认工具链可用:
git clone https://gitcode.com/GitHub_Trending/ls/LSPosed_mod cd LSPosed_mod ./gradlew build框架能正常构建后,新建一个模块工程(普通 Android App 即可),写一个入口类,只 Hook 目标包名,其余进程直接跳过。下面的示例用XposedHelpers.findAndHookMethod定位并挂上回调,它是 XposedHelpers 提供的工具方法,内部已处理类查找和异常包装:
public class MyModule implements IXposedHookLoadPackage { @Override public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable { if (!"com.example.target".equals(lpparam.packageName)) { return; } XposedBridge.log("目标进程已加载: " + lpparam.processName); XposedHelpers.findAndHookMethod("com.example.target.MainActivity", lpparam.classLoader, "initView", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) { // 你的 Hook 逻辑 } }); } }⚠️ 注意两点:handleLoadPackage在目标进程启动早期被调用,里面不要做耗时操作;日志用XposedBridge.log,它在 LSPosed 日志里默认归类为LSPosed标签,方便过滤。
然后给模块打三行元数据声明,LSPosed 靠它识别这是一个模块,并读取展示名称与最低框架版本:
<meta-data android:name="xposedmodule" android:value="true" /> <meta-data android:name="xposeddescription" android:value="我的第一个模块" /> <meta-data android:name="xposedminversion" android:value="93" />最后在assets/xposed_init写入入口类的全限定名,每行一个。这个文件是 LSPosed 扫描模块时的入口清单,路径错了模块就不会被加载:
com.example.MyModule把模块装进设备,在 LSPosed 管理器里为它勾选目标应用、重启目标进程,日志里看到"目标进程已加载"即代表注入链路全通。
两个最常用的拦截点
框架注册后,你能改的就是三样东西:入参、返回值、整个方法体。前两个覆盖 80% 的日常需求。
场景一:修改方法参数。想在setTitle生效前把标题改掉,就 Hook 它,在beforeHookedMethod里改写param.args[0]。参数是引用传递,你改完数组,原方法拿到的就是新值:
XposedHelpers.findAndHookMethod("com.example.target.MainActivity", lpparam.classLoader, "setTitle", CharSequence.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) { param.args[0] = "由 LSPosed 改写的标题"; } });场景二:替换返回值。想让某个"是否显示"的查询永远返回true,可以直接用XC_MethodReplacement整个替换原方法,原方法体不再执行。比afterHookedMethod里再调setResult更干净,也省掉一次原方法的开销:
XposedHelpers.findAndHookMethod("com.example.target.util.FeatureFlag", lpparam.classLoader, "isEnabled", XC_MethodReplacement.returnConstant(true));💡 如果原方法还要跑、只想覆盖结果,就用XC_MethodHook.afterHookedMethod里的param.setResult(...);两者选哪个,取决于你是否需要原方法的副作用。
处理跨版本兼容与调试
目标 App 升级后方法签名可能变。两个稳妥做法:
- 签名回退:先 Hook 新签名,
catch到异常后 Hook 旧签名,两条路径都保留 - API 级别分支:用
Build.VERSION.SDK_INT判断系统版本,在分支里挂不同的 Hook
方法找不到时别直接崩溃,先打日志确认类名和方法名在当前版本里是否还存在。调试时建议:用LSPosed日志标签过滤输出;在handleLoadPackage里尽早打印lpparam.processName,确认 Hook 落在预期进程;对拿不准的类用Class.forName试探是否存在,再决定是否挂载。
动手的下一步
- 方法查找与 Hook 工具:XposedHelpers
- 资源替换 API(改字符串、图片,不碰代码):XResources
- 日志系统实现:LogcatService
仓库自带的 manager 模块就是一个完整可参考的 Xposed 模块,clone 下来打开看它的AndroidManifest.xml元数据和xposed_init,再挑一个你天天用的 App,把上面的"隐藏按钮"做出来——跑通一次,Xposed 模块入门就正式完成了。
【免费下载链接】LSPosed_modMy changes to LSPosed项目地址: https://gitcode.com/GitHub_Trending/ls/LSPosed_mod
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考