Skip to content
NekoStashPublic

About

A LibXposed Developer Kit designed for Kotlin

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

RoxyHook

JitPack

RoxyHook 是面向 LibXposed API 102 的 Kotlin 封装与开发工具链。它提供平台无关的 Hook 核心、Android 生命周期与通信能力、LibXposed 平台适配、KSP 入口生成器,以及用于模块工程的 Gradle 插件。

本项目只面向 LibXposed API 102 及更高版本;不实现传统 XposedBridge、zygote 注入或 XResources 兼容层。

模块

模块 用途
roxy-annotations @RoxyEntry 模块入口注解。
roxy-core Hook DSL、运行时、作用域、偏好与平台抽象。
roxy-android Android 生命周期、模块资源与跨进程数据通道。
roxy-platforms:libxposed LibXposed API 102 平台适配、Service 与热重载接入。
roxy-ksp KSP 符号处理器;生成 Xposed 入口和元数据。
roxy-gradle-plugin 模块工程插件:自动依赖、元数据、R8 keep rules 和 APK 校验。
roxy-testing 平台无关 Hook 行为的契约与回归测试。
samples:demo-module 可编译的 LibXposed 模块 APK 示例。
samples:demo-target 被 Hook 的目标应用示例。

环境要求

  • JDK 21
  • Android SDK Platform 37
  • Android minSdk 26
  • 可访问 Google Maven、Maven Central 和 Gradle Plugin Portal 的网络

仓库携带标准 Gradle Wrapper(Gradle 9.5.0):

./gradlew --version
./gradlew projects

Windows 下可使用:

gradlew.bat --version
gradlew.bat projects

构建与验证

纯 JVM 模块与行为测试

./gradlew -PjvmOnly=true :roxy-testing:check :roxy-ksp:build :roxy-annotations:build :roxy-core:build

roxy-testing 会执行 Hook 契约与回归测试;它们是直接运行的测试套件,不依赖 JUnit 测试发现。

构建示例 APK

./gradlew \
  :samples:demo-target:assembleDebug \
  :samples:demo-module:assembleDebug \
  :samples:demo-module:assembleRelease

产物默认位于:

samples/demo-target/build/outputs/apk/debug/demo-target-debug.apk
samples/demo-module/build/outputs/apk/debug/demo-module-debug.apk
samples/demo-module/build/outputs/apk/release/demo-module-release-unsigned.apk

校验 RoxyHook 元数据与 DEX 入口

从仓库根目录运行:

./gradlew :samples:demo-module:roxyVerifyApk \
  --apk=samples/demo-module/build/outputs/apk/debug/demo-module-debug.apk

./gradlew :samples:demo-module:roxyVerifyApk \
  --apk=samples/demo-module/build/outputs/apk/release/demo-module-release-unsigned.apk

该任务验证 APK 中的 LibXposed 元数据和生成入口类是否存在于 DEX;它不替代 APK 签名检查、框架加载、设备注入或真实 Hook 行为验证。

完整源码审计

python3 tools/audit-source.py
./gradlew build

在模块工程中使用

源码仓库内的示例通过 includeBuild("roxy-gradle-plugin") 使用插件。对于独立工程,应在 settings.gradle.kts 同时配置插件与依赖仓库:

pluginManagement {
    repositories {
        maven("https://jitpack.io")
        google()
        mavenCentral()
        gradlePluginPortal()
    }
}

dependencyResolutionManagement {
    repositories {
        maven("https://jitpack.io")
        google()
        mavenCentral()
    }
}

然后在模块的 build.gradle.kts 中应用插件:

plugins {
    id("com.android.application")
    id("hk.uwu.roxyhook") version "<tag-or-commit>"
}

roxy {
    scope.add("com.example.target")
    staticScope.set(false)
}

插件会为 Android application 模块接入 RoxyHook 平台、KSP 处理器、LibXposed API 和每个变体的元数据。入口使用 @RoxyEntry 标注继承 RoxyModule 的顶层类型。

Hook 回调顺序

同一个 Hook 可以组合 before、replaceAny(含 replaceTo、replaceUnit)和 after:先执行 before ,再执行替换函数,最后执行 after。before 若设置 result 或 throwable 主动短路,则跳过替换函数,但 after 仍执行;替换失败而采用回退策略时,after 也会看到回退后的结果或异常。

可选的热重载作用域重放

LibXposed 不会在热重载后重新发送 package 事件。模块可实现 LibXposedHotReload,并使用 LibXposedPackageReplay 保存已到达的作用域,在新一代明确地重新安装 Hook:

private val replay = LibXposedPackageReplay()
override fun PackageScope.onLoad() { replay.record(this); install(this) }
override fun prepareHotReload(runtime: RoxyRuntime, param: HotReloadingParam) = replay.prepare(param)
override fun installAfterHotReload(runtime: RoxyRuntime, process: ProcessContext, param: HotReloadedParam) {
    replay.replay(runtime, process, param, ::install)
}

作用域快照只存 Android 框架值;目标 ClassLoader 从旧 Hook 的目标方法所属类恢复,不能唯一确定时跳过该作用域并记录警告,不会猜测或复用旧模块对象。仍需在 prepareHotReload 自行停止模块创建的非托管线程和 JNI 回调。 如需从已有模块的热重载实现迁移,可向 LibXposedPackageReplay 传入原有 Bundle 键前缀(例如 "my.module.reload.")以兼容进行中的旧代状态。

单 Hook / hookAll 保留旧回调(实验性 KEEP)

import hk.uwu.roxyhook.HotReloadPolicy

runtime.hook(method) {
    hotReloadPolicy = HotReloadPolicy.KEEP
    before { /* 不依赖会在热重载时释放的资源 */ }
}

runtime.hookAll(methods) {
    hotReloadPolicy = HotReloadPolicy.KEEP
    after { /* 每个选中方法独立保留 */ }
}

无需填写 id。同一模块、同一实际目标方法的匿名 KEEP 使用一个自动槽位;再次安装会复用已有 Hook, 不会替换它的回调。只有要在同一个方法上保留多个独立 Hook 时才需用不同的可选 id 区分。 类名相同但 ClassLoader 不同的目标不会混用;不依赖源码行号、lambda 类名或集合顺序。

  • 默认 REINSTALL 仍沿用现有流程,重装依赖模块的 installAfterHotReload / replay;KEEP 不会自动开启模块热重载。
  • KEEP 的代码、错误策略保持首次安装版本;重复声明的 priority 必须一致。新 DSL 配置块仍会执行,但新回调不会安装。
  • hookAll 支持重复成员去重、成员重排、交叠集合,以及从单 Hook 改为批量。新增成员首次安装,未再选中的旧 KEEP 不会自动卸载。
  • 批量失败仅回滚本次新装的成员,不卸载复用的旧 KEEP。成功后的 HookGroup.close() 则卸载组内全部成员;交叠组共享 handle,关闭一组会使另一组共享的成员失效。
  • 当前代 handle 的 remove() / close() 和 runtime 的普通 close() 可卸载 KEEP;旧代已移交的包装不再操作底层注册。KEEP 暂不支持 replace()、回调内 removeSelf() 或类初始化器 Hook。
  • 不支持 KEEP 的平台会显式拒绝。用户 id 不可使用 roxy.keep. 保留前缀。
  • 从匿名 KEEP 改为匿名 REINSTALL 不构成替换,会成为两条注册;需先显式移除旧 KEEP 或重启目标进程。删除声明也不等于卸载。

**生命周期代价:**保留的回调仍来自旧模块 ClassLoader,会阻止该代立即回收。不要捕获旧 runtime、scope、Activity, 或依赖 onDispose / quiesce 会关闭的订阅、线程、JNI、服务等资源;这些资源不会因 KEEP 而保留。 推荐只保留无外部生命周期依赖的参数/返回值处理。修改 KEEP 实现、首次升级到此功能或协议降级后应重启目标进程。 HookOptions 扩展可能影响旧二进制调用点,下游模块应重新编译,不承诺 data class 的完整二进制兼容。

**验证边界:**该功能尚需针对实际执行框架验证连续 A→B→C 重载:每一代的 oldHookHandles 必须包含更早代仍存活的 KEEP。 API 接口本身不保证这种跨多代枚举;JVM/模拟平台测试不能替代设备验证。未完成该验证前,不应将 KEEP 视为已支持的生产能力。 若框架漏报旧 handle,不能靠 Bundle 携带旧模块对象来绕过,应由执行框架补足契约或重启目标进程。

日志

roxy-core 提供 hk.uwu.roxyhook.RLog 门面,模块代码可在任意位置直接调用,无需持有运行时或平台引用:

RLog.info("module loaded")
RLog.error("hook failed", throwable)

路由规则:每次调用从当前打开的注入运行时(platform.info.isInjected)取得平台 logger,因此注入场景下消息进入框架日志;无活动注入运行时(模块自身进程、运行时已关闭、纯 JVM 测试)时回退到 RoxyLogger.STDERR,不会静默丢弃。PackageScope.log 走同一派发路径并自动附加 [包名/进程名] 前缀。RLog 仅弱引用运行时快照,不会产生全局强引用。

PackageScope 常用上下文

PackageScope 统一暴露当次注入事件的常用上下文,减少每个 Hook 里重复保管的样板代码:

API 语义 / 前置条件
mainProcessName / isMainProcess / processName 包声明的主进程名与当前进程名。
appInfo 当次 package 事件的 ApplicationInfo 防御性快照;system_server 或无平台数据时为 null。
application / appContext / appResources 宿主 Application attach 之后可用;attach 前为 null,system_server 中读取会失败。
systemContext 仅 system_server。经 SystemContextResolver 调用 ActivityThread 隐藏 API;失败抛出带原因的 IllegalStateException,无回退。
moduleAppFile 模块自身 APK File(LibXposed moduleApplicationInfo.sourceDir);平台未提供时失败。
moduleResources 模块资源;app 进程用 appContext、system_server 用 systemContext 作宿主 Context,未就绪即失败。
dataChannel 跨进程数据通道;自动取 appContext,校验包名一致且非 system_server,receiver 由 runtime 管理。使用 put 发送、receive 注册监听、waitFor 请求回复;通道按包路由且不提供认证,请勿发送敏感数据。
prefs / prefs() 默认远程偏好组 "default";prefs(group) 指定命名组。需要框架 REMOTE_PREFERENCES 能力。

systemContext 依赖 ActivityThread.currentActivityThread()/getSystemContext() 隐藏 API(与 YukiHookAPI 相同机制)。该路径受 Android 隐藏 API 限制影响,属于框架授予的受控例外,仅限 system_server;解析失败会显式抛出并携带底层原因,不会静默回退到其它 Context。

发布

本地 Maven 验证

./gradlew publishAllToMavenLocal
./gradlew -p roxy-gradle-plugin publishToMavenLocal

第一条命令发布所有 JVM JAR 与 Android AAR;第二条命令发布 Gradle 插件及其 plugin marker。

JitPack

仓库根目录的 jitpack.yml 使用 JDK 21,并会发布库与 Gradle 插件。推送到 GitHub 后,为提交或 tag 在 JitPack 页面触发构建:

https://jitpack.io/#NekoStash/RoxyHook

JitPack 产物坐标遵循:

com.github.NekoStash.RoxyHook:<artifact>:<tag-or-commit>

JitPack 构建的实际可用性应以该远程构建日志为准。

许可证

本项目采用 Apache License 2.0 许可证。第三方依赖、Gradle Wrapper 和工具组件受各自许可证约束;请在分发前审阅相关上游许可证与归属信息。

About

A LibXposed Developer Kit designed for Kotlin

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages