
知网主页:http://www.xyyh.xyz
知网源地址:http://apt.xyyh.xyz
注意:知网源地址是http没有s
公众号资源均来自网络,如有侵权请联系删除。
资源仅供学习交流,请于试用后24小时内删除。
不保证资源兼容与小编不同的机型,但亲测正常使用。
如果你觉得文章对你有帮助,请点横幅以支持小编更新。
先看实现效果
xp模块在文章末尾下载
拦截住的打印日志
抓包信息
Flutter 应用 Native 层Hook
⚠️ 免责声明:本目录下所有资料仅供学习交流使用,请勿用于商业用途。如有侵权,请联系管理员删除,谢谢!
一、项目背景
1.1 痛点
旧版大师兄影视App 在启动时会弹出强制更新弹窗,必须点击更新才能进入应用。新的版本广告实在太多去除难度也在不断加大,需要开发一个 Xposed 模块来拦截它强制更新弹窗。
1.2 技术选型
层面 | 技术 | 用途 |
Xposed 模块 | Kotlin + Jetpack Compose | 模块宿主 |
Native Hook | C++ + ShadowHook | 拦截系统调用 |
Hook 库 | 字节跳动 ShadowHook | PLT Hook |
二、第一轮探索:Java 层 Hook 为何全部失败?
按照传统的 Xposed 模块思路,首先尝试在 Java 层 Hook 常见的网络和数据处理 API:
2.1 尝试过的目标
Hook 目标 | 作用 | 结果 |
`javax.crypto.Cipher` | 捕获加解密操作 | ❌ 无调用 |
`JSONObject` | 捕获 JSON 解析 | ❌ 无调用 |
`String(byte[])` | 捕获字符串创建 | ❌ 无调用 |
`InputStream.read()` | 捕获网络数据 | ❌ 无调用 |
2.2 原因分析
全部失败的原因在于Flutter 的架构设计:
┌─────────────────────────────────┐ |
│ Dart VM │ |
│ (加密/解密/HTTP解析) │ ← 完全在 Dart 内部 |
├─────────────────────────────────┤ |
│ dart:io Socket │ ← Dart 直接调用系统调用 |
├─────────────────────────────────┤ |
│ Linux read/write/recv/send │ ← 唯一的突破口 |
└─────────────────────────────────┘ |
Flutter 的网络通信和加解密全部在 Dart VM 内部完成,不经过 Java 层的任何 API。这意味着:
**对于 Flutter App,Java 层 Hook 天然无效,必须下沉到 Native 层。**
三、Native Hook:在系统调用层面寻找突破口
既然 Java 层走不通,方向转向 Native 层——Hook 最底层的系统调用函数。
3.1 目标函数
所有 TCP 网络通信,无论是 HTTP、WebSocket 还是自定义协议,最终都会归结为几个系统函数:
函数 | 作用 |
`read()` | 从 socket 读取数据 |
`write()` | 向 socket 写入数据 |
`recv()` | socket 接收(专用) |
`send()` | socket 发送(专用) |
3.2 探索历程
版本 | Hook 目标 | 结果 |
v1 - v4 | recv/send | Hook 成功,但零调用 |
v5 | recv/send | 同上(验证确认) |
v6 | read/write + recv/send | ✅ 成功捕获请求,但响应无法匹配 |
经过多次迭代,最终发现 Flutter 的 dart:io 底层主要使用 read/write来操作 socket,而 recv/send则很少被调用。使用read + write组合可以完整捕获所有网络数据。
四、Gzip 压缩的陷阱
4.1 问题现象
当成功捕获到网络数据后,出现了新的问题:
●**请求方向**:成功捕获所有发往 `app.dsx.ac` 的 HTTP 请求,明文可见
●**响应方向**:无法在数据中匹配任何关键词
4.2 根因定位
经过分析日志发现,服务器在响应中使用了 Gzip 压缩:
响应数据是压缩后的二进制字节流,自然不可能在原始字节中匹配到明文关键词。
4.3 解决方案:Accept-Encoding 改写
在请求发出前,拦截并修改 HTTP 头中的 Accept-Encoding字段:
修改前:Accept-Encoding: gzip |
修改后:Accept-Encoding: identity |
这条简单的改动让服务器返回未压缩的明文响应。
五、加密响应:又一道坎
5.1 响应结构分析
即使获得了明文响应,API 返回的数据本身仍然是加密的。响应体结构如下:
[hex数组Base64]==[加密密文Base64][密钥标识Base64]==[明文长度Base64] |
具体字段:
●**hex数组**:68 个数字组成的十六进制数组
●**密钥标识**:`xgsecretcustompasswordsymbol`(28 字节固定字符串)
●**明文长度**:320 字节
5.2 解密尝试
尝试了多种标准解密方案,全部失败:
方案 | 结果 |
AES-CBC(使用已捕获的友盟密钥) | ❌ |
RC4 | ❌ |
XOR | ❌ |
各种组合变换 | ❌ |
原因:加密算法是 Dart 层的自定义实现,不依赖 Java 标准加密库(如 javax.crypto),也不在常见的开源加密库中。除非对 Dart 层进行逆向,否则无法还原解密逻辑。
六、最终方案:响应替换
6.1 核心思路
既然无法解密,干脆不解密——直接替换服务器响应。具体来说:
1. 检测到请求 URL 包含 /v6/version(版本检查接口)
2. 丢弃服务器的真实加密响应
3. 直接返回一个伪造的 JSON,告知 App "无需更新"
6.2 替换内容
{"code":0,"msg":"","data":[]} |
关键字段含义:
●`code: 0` — App 端判断为"无需更新"
●`data: []` — 空数据,表示没有更新信息
6.3 完整工作流程
┌─────────────────────────────────────────────┐ |
│ App 发送请求 │ |
│ write/send Hook 检测 URL │ |
│ ├─ /v6/version → 标记 fd 为 BLOCK │ |
│ │ + 将 Accept-Encoding 改为 identity │ |
│ └─ 其他请求 → 正常透传 │ |
├─────────────────────────────────────────────┤ |
│ App 读取响应 │ |
│ read/recv Hook 检查 fd 状态 │ |
│ ├─ BLOCK fd → 排空真实数据 │ |
│ │ → 返回 {"code":0,"msg":"","data":[]} │ |
│ └─ 其他 fd → 正常透传真实响应 │ |
├─────────────────────────────────────────────┤ |
│ 最终结果:弹窗消失 ✅,影视内容正常加载 ✅ │ |
└─────────────────────────────────────────────┘ |
核心是使用fd(文件描述符)状态管理:当一个 socket 被标记为 BLOCK,后续的 read 操作会排空真实数据并返回替换内容,对其他 socket 则完全透明。
七、关键代码解读
7.1 Native Hook 核心(精简版)
// write() Hook — 拦截请求 |
ssize_t (*orig_write)(int fd, const void *buf, size_t count); |
ssize_t hook_write(int fd, const void *buf, size_t count) { |
// 检测请求 URL |
if (contains((char*)buf, "/v6/version")) { |
fd_map[fd].block = 1; // 标记拦截 |
buf = replace_ae(buf); // 修改 Accept-Encoding |
} |
return orig_write(fd, buf, count); |
} |
|
// read() Hook — 拦截响应 |
ssize_t (*orig_read)(int fd, void *buf, size_t count); |
ssize_t hook_read(int fd, void *buf, size_t count) { |
ssize_t ret = orig_read(fd, buf, count); |
if (fd_map[fd].block == 1 && !fd_map[fd].sent) { |
drain_remaining(fd); // 排空剩余数据 |
const char *fake = "{\"code\":0,\"msg\":\"\",\"data\":[]}"; |
memcpy(buf, fake, min(count, strlen(fake))); // 返回替换内容 |
fd_map[fd].sent = 1; |
return min(count, strlen(fake)); |
} |
return ret; |
} |
Hook 库使用字节跳动开源的 ShadowHook,它通过修改 PLT(Procedure Linkage Table)来拦截函数调用,稳定且兼容性好。
7.2 Xposed 模块入口(精简版)
class XposedModule : IXposedHookLoadPackage { |
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) { |
if (lpparam.packageName != "com.zhiyin.qingdan.dashixiong") return |
System.loadLibrary("native-hook") // 加载 Native 库 |
} |
} |
Xposed 侧仅负责在目标 App 启动时加载 Native 动态库,所有 Hook 逻辑都在 C++ 层完成。
八、兼容性与潜在问题
8.1 支持范围
项目 | 配置 |
ABI | armeabi-v7a + arm64-v8a(覆盖 99%+ 真机) |
Android | 7.0+(minSdk 24) |
Xposed | api-82(LSPosed / EdXposed / 太极) |
九、经验总结
9.1 核心要点
1. Flutter App 的 Hook 必须在 Native 层— Java 层 API 全部失效,因为 Dart VM 绕过了它们
2. Socket 系统调用是最通用的切入点— 无论上层协议是什么,最终都走 read/write
3. Accept-Encoding → identity 解决 Gzip— 简单一行修改,省去解压逻辑
4. 响应替换比解密更实用— 当加密算法未知或自定义时,替换整个响应是最可靠的方案
写在最后
从 Java 层 Hook 的全面失败,到 Native 层系统调用的成功拦截,再到 Gzip 压缩和加密响应的层层阻碍——这一路走来,每一步都在验证一个道理:不存在某一种 Hook 技术、某一套工具、某一套代码,能通吃所有 APP / 所有环境的逆向抓包、拦截请求,但系统调用是永远绕不过的关口。
如果你也在处理类似 Flutter App 的 Hook 需求,希望这篇记录能帮你少走一些弯路。
关于学习资源请在知网云盘获取
安卓版和IOS去广告版已经放在知网云盘:pan.xyyh.xyz
本公众号所发布的一切破解补丁注册机和注册信息及软件的解密分析文章仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑和手机中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。如有侵权请邮件(zwqnyyds@126.com)与我们联系处理。不保证资源兼容所有手机,发布的资源小编亲测正常使用,如果您不能使用,请理性对待!
如果你觉得文章对你有帮助,请点横幅以支持小编更新