HookwormForAndroid

Introduction: 一个基于 Magisk&Riru 的 Module,可以助你用超低成本开发各种 Hook 插件,无须 Xposed
More: Author   ReportBugs   
Tags:

此 Module 仅供大家学习研究,请勿用于商业用途。利用此 Module 进行非法行为造成的一切后果自负!

博客详情: https://blog.csdn.net/u011387817/article/details/113482330

背景:

前段时间在 WanAndroid 每日一问里有个问题:"应用进程中那 4 个 Binder 线程分别是跟谁通讯?"

一番简单分析无果后,就想着写个 Xposed 插件来 hook Thread 对象的创建,但是看到有同学提醒:"Xposed 插件的handleLoadPackage方法是在handleBindApplication时才回调的!"。 没办法,只能找其他的方案了。

忽然想到了 Magisk,但是我又不会做 Magisk 插件……

第二天看了下自己一直在用的那个【开启微信指纹支付】的 Magisk 模块源码(其实之前也看过好多遍了,一直没看懂,一头雾水),发现核心代码其实就是 libs 下面的那个 APK 的 dex!

反编译看了下,大概摸清了思路,但是想到用这种方式(先手动打包成 apk 放在插件项目 libs 下)开发起来太繁琐了,而且维护起来成本又高,还没有一个规范的模板,这样就很难抽出来为自己所用。

于是心有不甘的我又继续在 github 上面搜 Riru 相关的模块,看到一个叫【QQ Simplify】的项目,是用来阉割 QQ 一些 “花里胡哨” 的功能的,看了下代码,它是在进程 Fork 之后,用反射把 ServiceFetcher 里面的 LayoutInflater 对象换成自己的代理类,这样就可以在布局 inflate 时,选择性地把一些 View 的宽高 set 为 0,达到隐藏的效果。

结合这两个项目的部分代码以及思路,我封装出来一个入侵程度非常低的 Module,开发新的插件的话,只需要添加这个 Module 的依赖,然后在module.properties中配置一下模块属性就行了,非常简单!


原理:

有一个叫 Riru 的 Magisk 模块,它会把系统的 libmemtrack.so 替换掉(新版改为指定ro.dalvik.vm.native.bridge属性值为自己的 so),并对外公开 Zygote 初始化进程的一些 API,比如forkAndSpecializePost

在 Zygote Fork 进程的前后,都会对外 “发通知” ,如果趁这个时机向指定进程注入自己的代码,那么,当进程启动完成后,自己的代码就运行在指定进程内了,这样就可以为所欲为扩展一些功能,或者更改某些逻辑等等。


Q&A:

这个 Module 能做什么?

要知道,你的代码是运行在目标进程中的,这就相当于你参与了目标 app 的开发!

所以理论上目标进程中的所有数据以及行为,都能修改成你想要的结果,只要你能拿到对应的对象。

至于怎样拿到对象,这就看具体场景了。

比如你想修改某个 app 的某个页面按钮点击行为,那就可以先监听目标 Activity 的生命周期来获取到对应的 Activity 对象,进一步 find 到 View 实例然后给它重新 set 一个 OnClickListener。

再比如你想修改某个 app 的启动图,一个比较通用的方法是:监听对应 Activity 的 onCreate,在这里把Activity.mWindowmContentParent替换成你自定义的 FrameLayout,这样你就能在onLayout之前找到显示启动图的 View 实例,并对它做手脚。

它跟 Xposed 的关系/区别?

可以说是完全无关系的。

从能力上来看,很明显 Xposed 更强大,不过相对的,Xposed 插件开发起来难度也会高一些,因为它的优势主要体现在能监听任何一个方法调用,这就非常考验你对目标 app 代码的熟悉程度了,如果没掌握一定的逆向知识是搞不来的。

反观这个 Module,它的能力是不如 Xposed 的,比如它不能监听哪些 Class 被加载,不能直接感知到哪些对象被创建。只能用一些比较原始的方法来修改数据和行为,比如反射,动态代理等。优势是开发成本很低,甚至你不用反编译目标 app,没有逆向基础也可以,只需要一个UIAutomatorViewer工具来帮助获取到布局结构和资源 id 就能着手开发了。还有就是,目标 app 很难感知到这个插件的存在,它不像 Xposed 在异常堆栈中能看到相关字眼。无论安装和运行,都可以说是不留痕迹的。


亮点:

  • 配置成本极低,添加这个 Module 依赖就行了,你只需关注你自己的代码逻辑;

  • 所有的配置都集中到了 module.properties 文件里,直接修改这个文件即可,比如主入口类,目标进程等;

  • 提供了一些基本的 API,方便进行 Hook 工作,一般情况下,只需要几行代码就能监听到按钮的点击,或者布局的加载了;

  • 自动刷入! 是的,从此解放双手,像开发普通应用那样,编译完就能自动刷入手机了,如果是手动安装的话,每次至少浪费 20 秒时间;

  • 免重启(最小化)安装! 提供快速调试的能力;


使用:

  1. 首先,clone 或直接下载这个 Module(注意!这只是一个 Module,需要被依赖到一个 APP Module 才能正常运作);

  2. 新建一个 Android 项目,Minimum SDK 至少为 23(即 6.0)。为了避免不必要的麻烦,Language 请选择 Kotlin 而不是默认的 Java,因为这个 Module 用到了 Kotlin;

  3. 新建好项目后,创建一个入口类,名字随便,比如就叫 ModuleMain。然后,在里面声明一个 public static void main(String packageName) 方法,这个方法是必须有的!当插件启动后会被调用;

  4. 导入刚刚下载 Module,并依赖到主模块中;

  5. 配置插件属性,先把模块根目录下的module.properties.sample复制一份,并改名为module.properties,然后编辑这个module.properties,根据里面的注释来完善插件信息,比如给 moduleMainClass 属性填上刚刚创建的 ModuleMain 完整类名(带包名);


module.properties 属性如下:

  • moduleId:模块唯一标识,只能使用字母 + 下划线组合,如:my_module_id;

  • moduleName:模块名称,自由填写;

  • moduleAuthor:模块作者,自由填写;

  • moduleDescription:模块描述,自由填写;

  • moduleVersionName:模块版本名,自由填写;

  • moduleVersionCode:模块版本号,自由填写;

  • moduleMainClass:主入口类名,例:com.demo.ModuleMain;

  • targetProcessName:目标进程名/包名,即要寄生的目标。不填写则寄生所有进程。同时寄生多个目标,用 ; 分隔,如: com.demo.application1;com.demo.application2;

  • automaticInstallation:编译完毕自动安装模块(需要手机已通过 adb 连接到电脑(只能连一台),并已安装 Magisk 和 Riru 模块!)。1 为开启,其他值为关闭;

  • debug:Debug 提供最小化安装(除首次安装外免重启)能力,可以快速测试模块(开启此选项需先开启 automaticInstallation)。

配置好这些属性之后,就可以编写代码,编译打包运行了! 注意,编译打包的话,需要运行project:assemble(Tasks/build/assemble)这个 Task,不是app:assemble也不是module:assemble


常用 API:

Name Description
Hookworm.setTransferClassLoader(true) 转接插件 Dex 的 ClassLoader
如果引用到了目标应用的一些自定义类或接口(或第三方库),则需要开启转接,否则会报 ClassNotFoundException
Hookworm.setHookGlobalLayoutInflater(true) 劫持全局的 LayoutInflater
Hookworm.setOnApplicationInitializedListener 监听 Application 初始化
Hookworm.registerActivityLifecycleCallbacks 监听 Activity 的生命周期
Hookworm.registerPostInflateListener 拦截 LayoutInflater 布局加载
Hookworm.getApplication 获取进程 Application 实例
Hookworm.getActivities 获取进程存活 Activity 实例集合
Hookworm.findActivityByClassName 根据完整类名查找 Activity 对象
HookwormExtensions.findViewByIDName 根据资源 id 名来查找 View 实例
HookwormExtensions.findAllViewsByIDName 根据资源 id 名来查找所有对应的 View 实例
HookwormExtensions.findViewByText 根据显示的文本来查找 View 实例
HookwormExtensions.findAllViewsByText 根据显示的文本来查找所有对应的 View 实例
HookwormExtensions.containsText 检测目标 View 是否包含某些文本


功能示例 Demo: https://github.com/wuyr/HookwormForWanAndroidTest


感谢:

首先感谢鸿神和WanAndroid 每日一问,如果没有那天的那个问题就没有这个库。

感谢Fingerprint pay for WeChatQQ Simplify,从这两个项目中学到很多思路以及代码。。。

感谢RiruMagisk,这两个东西是此 Module 的根基。

最后感谢大家的小星星🌟🌟,虽然可能屏幕前的你还没有点,但是先谢谢了!

Apps
About Me
GitHub: Trinea
Facebook: Dev Tools