纸上谈兵——有关 HyperCeiler Next 和 Remote Hook 的一些想法

2 小时前(已编辑)
/
50
2

纸上谈兵——有关 HyperCeiler Next 和 Remote Hook 的一些想法

前言

HyperCeiler,我的一个 Xposed 模块,风风雨雨已经走过了 3 年。

虽然说有一定前瞻的项目设计,以及后期的几次大大小小更改,为它续了好几次命。

但有个问题,是所有 Xposed 模块都逃不掉的——

在宿主更新后,Hook 极其容易失效。

对于一个 Xposed 模块而言,Hook 本身通常并不复杂(如果你在讲 HyperOS Runtime Hook 那当我没说),真正麻烦的是宿主在不断变化:类名、方法名、混淆结果,甚至项目重构,都能让之前所针对的 Hook 前功尽弃。

虽然已经出现了 DexKit 之类的反混淆与定位工具,但也是只能针对部分版本进行非常局限的匹配。

传统 Xposed 模块对此的处理方式通常是修改模块代码,重新构建 APK,然后发布一个新的模块版本。

这也就意味着一个很小的 Hook 修复,也可能需要经历:

发现问题
  ↓
逆向新宿主内容
  ↓
修改 Hook
  ↓
测试和编译
  ↓
发布新版本
  ↓
用户更新模块

这一漫长过程。

而 HyperCeiler 作为一个有着数百 Hook 特大项目,短时间内多次发包进行修补显然是不现实的。

HyperCeiler 大致的 Hook 数量

HyperCeiler 大致的 Hook 数量

于是我开始思考一个问题:

如果 Hook 本身不一定要跟着 APK 一起发布呢?

这是我最近游玩 Rizline 的时候突然冒出来的一个想法。

理论上,如果这套逻辑能够顺利落地,那 APK 本身将会成为一个 Hook 和配置工具,而 Hook 本身可以解耦出来。

而这,就是目前我正在考虑的 HyperCeiler Next——Remote Hook。

一、为什么要让 Hook 分家?

传统的 Xposed 模块基本是:

Module.apk
 ├── Hook A
 ├── Hook B
 ├── Hook C
 ├── Hook D
 └── ...

在编译的那一刻,所有 Hook 都永久固定在了 APK 当中。

如果某个宿主更新导致 Hook A 失效,那么通常只能等待开发者修复后安装新的模块版本解决。

但实际上,很多时候变化的只是某一个版本,甚至某一个点位的实现。

如果 Hook 主体逻辑没有发生变化,只是目标类发生移动,那么为了修改这几个类名而重新发布整个模块,并不是一个十分理想和划得来的做法。

至于 CI 构建版本,对于普通用户而言风险又实在太高——我可不能保证我一个错都不犯。

更何况最近越来越多的声音在说 HyperCeiler 看起来有些臃肿,大量用不到的宿主的 Hook 也存在于模块当中(虽然可以在管理器内取消应用作用域,但是用户心里的那个满足机制并没有被激活)。

因此,可以考虑把针对每个宿主的 Hook 都拿出来并拆分:

Module.apk
 ├── Hook Loader
 ├── Hook Tools
 └── ...

Remote Hook
 ├── com.example.a (version 1)
 ├── com.example.b (version 2)
 └── ...

这样主程序只需要提供运行环境,而具体的适配和 Hook 则可以独立且灵活的更新。

二、一个最初的设想

最简单的架构大概是

   Module.apk
       |
       | 请求当然宿主及其版本的 Hook 配置
       ↓
      API
       ↓
Hook Repository
       |
       ├── com.example.a
       |          ├── version 1
       |          ├── version 2
       |          └── ...
       ├── com.example.b
       |          ├── version 1
       |          ├── version 2
       |          └── ...
       └── ...

服务器根据客户端提供的信息返回所需的 Hook 功能列表。

例如客户端提供:

{
    "moduleVersion": 100,
    "androidVersion": 16,
    "systemVersion": 4,
    "device": "xxx",
    "targetPackage": "com.example.a",
    "targetPackageVersion": 1,
}

服务器返回:

{
    "hookVersion": 12,
    "minModuleVersion": 24,
    "targetPackage": "com.example.a",
    "targetPackageVersion": 1,
    "config": "https://api-hyperceiler.sevtinge.com/hook/com.example.a/1/12.json",
    "hook": "https://api-hyperceiler.sevtinge.com/hook/com.example.a/1/12.dex",
    "sha256": "...",
    "signature": "..."
}

然后模块验证完整性后执行即可。

这样,Hook 的更新就不一定需要等待整个 APK 的更新。

三、为什么选择 Dex?

Android 本身就提供 Dex 热加载机制。

传统 APK:

Module.apk
 ├── assets
 ├── lib
 ├── META-INF
 ├── res
 ├── classes.dex         ← 这里通常是 Hook 的所在位置
 ├── resources.arsc
 └── ...

而这种方案可以变成:

Module.apk
 ├── assets
 ├── lib
 ├── META-INF
 ├── res
 ├── classes.dex         ← 这里只留存 Runtime 和部分 Built-in Hook
 ├── resources.arsc
 └── ...

Remote Hook
 └── hook.dex

通过 ClassLoader 加载额外的 Dex 后,再由 Xposed 对目标进行 Hook。

从架构上来看,相当于:

         HyperCeiler
              │
        Hook Runtime
              │
    ┌─────────┴─────────┐
    │                   │
Built-in Hook       Remote Hook
    │                   │
    └─────────┬─────────┘
              │
          Xposed API
              ↓
             App

这样,Dex 就成为了一种相对独立的 "Hook 插件"。

四、本地缓存

光有云端下发逻辑可不太够。

如果每次启动宿主的时候都需要联网下载 Hook,显然是不现实的——这会导致宿主需要等待 Hook 下载完成才可以执行,本地配置也会随时更新,进程可能也会出现阻塞而被 Android 杀掉。

更何况按 HyperCeiler 现在的日活数据,我怕是掏空了钱包也支撑不起如此体量的下载。

sevtinge.com 在 2026.9.26-2026.9.27 平峰时期的请求总数

sevtinge.com 在 2026.9.26-2026.9.27 平峰时期的请求总数

所以我们应当设计本地缓存机制。

本地缓存路径应处在私有目录下,目录结构可参考服务器端。

启动流程则应变为

启动
  ↓
读取本地 Hook
  ↓
验证是否可用于当前宿主
  ↓是           ↓否
加载 Hook      更新 Hook
  ↓
后台检查可用更新

这也就意味着大幅降低云端依赖度和请求数量,满足了离线时也可 Hook 的需求。

五、安全问题

Hook 的权力特别大,可以随意更改宿主的任何可执行内容。

更何况 HyperCeiler 部分功能依赖 Root 权限,那么我们在面对安全问题时,应当更为谨慎。

动态加载远程 Dex,这意味着:

服务器可以改变模块实际执行的代码

这和普通的远程配置完全不是一个风险等级。

例如,服务器遭受远程攻击,dex 被替换,那么所有在线客户端都会被执行恶意代码。

因此,我初步构思应当在以下几个方面构建安全屏障。

服务器安全

这自然是不必多说的,服务器端即使不干这些活,暴露在公网也应当做好安全防护。

此处不展开讲述技术细节。

服务器权限管理

进一步考虑后,我认为 Remote Hook 的权限应当收到进一步限制。

服务器应当只能决定下发哪个配置,而不能直接执行 Hook、命令、配置。

也就是说,服务器只提供代码,而是否加载应由客户端自行决定。

本地下载前和下载时

本地应当使用 https 获取内容,甚至通过自有加密方式加密内容进行联络。

为了安全,我甚至计划不采用断点续传技术,一是本身文件就不大,二是可能存在一定风险。

本地下载后和加载时

本地通过 https 下载完成后,应当对下载内容进行鉴权。

SHA-256——用于确认下载文件没有发生意外变化。

数字签名——用于确认文件是由 HyperCeiler 发布,而并非第三方替换的恶意内容。攻击者完全可以把 SHA-256 也一并替换以绕过验证,所以数字签名显得格外重要。

视情况,也应加上其他验证。

确保以上内容都无误后,下发内容才允许存储和加载

APK 安全

之前就存在过通过修改 HyperCeiler APK 注入恶意内容的先例,在此之后 HyperCeiler 针对进行了 APK 安全改进。

但对于使用云端 Hook 方案的 HyperCeiler Next 而言,安全需要更进一步。

可能会采取 APK 加壳方式或把 Hook Runtime 全部迁移至 Native,并针对自身完整性进行云端校验。

六、HyperCeiler 的骄傲——主动安全模式

HyperCeiler 开创的主动安全模式也不应该在使用云端 Hook 时失效。也应当做出些许改进。

当检测到宿主发生 Crash 时,应当首先尝试回退上一个版本的 Hook,并可以自动标记此版本 Hook 存在异常。

若 Crash 持续出现,则触发主动安全模式,取消对其的 Hook。

七、踌躇不前

从技术角度来说,这套方案确实可行,也有 Xposed 模块小范围的进行了测试。

但是它会引入大量新的复杂度:

  • ClassLoader 和空加载保护
  • Dex 分包开发
  • 版本匹配和同版本不同 Dex 应对
  • 本地缓存管理
  • 签名
  • 网络安全和远程是否可用
  • Xposed API 限制
  • Hook 兼容性

所以,它并不是简单的:

"把 Hook 改成云端下发。"

真正需要解决的问题是:

如何让远程 Hook 的可靠性持平甚至超越内置 Hook。

若这个问题无法解决,远程 Hook 比本地 Hook 更为脆弱,上面所说的一切都是纸上谈兵。

八、0.9.24

若这个项目真的可以落地,一定是先做一个非常小的 PoC,而不是让它直接面向大众。

它的测试周期可能很长,HyperCeiler(Cemiuiler) 从 0.9 走向 1.0 正式公开用了 5 个月。

而 HyperCeiler Next 可能需要一年甚至更久。

在热情几乎消磨殆尽的背景下,我可能很难坚持把它做出来并公开给大众。

更何况 HyperOS 在逐渐转向 Rust+Flutter 的垃圾配置,Hook 甚至需要用到汇编,难度直线上升。

结语

对于 Xposed 模块开发而言,宿主更新并不是最麻烦的事。

真正麻烦的是:

宿主更新后,原本写死在模块里的假设可能全部失效。

传统模块把所有内容全部打包在了一起,而云端 Hook 的核心思路是把它们拆开。

这样一来,模块本身就不再需要知道所有未来宿主的实现细节。

APK 所需要的,只是一个完整健全稳定的 Hook Runtime。

这可以是 HyperCeiler 未来的一种架构方向,但目前也仅限于 "可以是"。

期待一下吧,万一这个系列有续作呢。

使用社交账号登录

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...