跳转到主要内容
Linso
Linso
2025 年 11 月 29 日 星期六
本文仅供安全研究与学习使用,请勿用于非法用途
针对某多多使用的 Titan 长连接协议导致无法通过常规代理抓包的问题,本文提供了一种基于 Hook 的应用层拦截方案

概述

在对某多多进行协议分析时,发现其采用了名为 Titan 的私有长连接(LongLink)技术。这导致常规的 HTTP/HTTPS 抓包工具(如 Charles, Fiddler)无法捕获核心业务流量(如商品详情)。 本文主要探讨如何绕过网络层加密,直接在 Java 应用层截获数据:
  • Titan 协议拦截: Hook 网络库响应处理函数,获取解密后的原始响应
  • 商品详情抓取: Hook 业务层数据模型,将内存对象序列化为 JSON

技术架构与核心机制

关键注入点与探针部署

探针类型注入类/方法功能说明
FridaTitanApiResponse.getBodyBytes拦截 Titan 网络层原始响应数据
XposedGoodsResponse.setRenderResponse拦截商品详情页面渲染数据
Frida/XposedGson.toJson辅助将内存对象序列化为可读 JSON

数据流向与拦截逻辑


Titan 协议层拦截

针对 Titan 协议,直接在网络库的回调处进行拦截是最有效的方式。此时数据已经完成了协议层的解包,但尚未分发给业务逻辑。

Frida 注入脚本示例

以下脚本仅适配特定版本,生产环境需适配混淆
Java.perform(function () {
    // 1. 定位目标类 (需根据混淆调整)
    var TitanApiResponse = Java.use("xxx.xxxx.xxxxtitan.api.TitanApiResponse");
    var StringCls = Java.use("java.lang.String");

    // 2. Hook getBodyBytes 方法
    if (TitanApiResponse.getBodyBytes) {
        TitanApiResponse.getBodyBytes.implementation = function () {
            var hookPoint = "TitanApiResponse.getBodyBytes()";
            var data = this.getBodyBytes();

            // 3. 拦截并转码
            if (data) {
                try {
                    var resp = StringCls.$new(data, "UTF-8");
                    if (resp) {
                        console.log("\n=== 📥 " + hookPoint + " 响应捕获 ===");
                        // 截断输出避免日志爆炸
                        console.log(resp.length > 2000 ? resp.substring(0, 2000) + "..." : resp);
                        console.log("====================================\n");
                    }
                } catch (e) {
                    console.log("[!] " + hookPoint + " - 响应解析失败:", e);
                }
            }
            // 4. 必须返回原始数据,否则 App 会崩溃
            return data;
        };
    }
});
概述:
  • 定位类: 寻找实现 Titan 协议响应接口的关键类
  • 拦截点: getBodyBytes 通常是获取原始响应数据的必经之路
  • 无感注入: 仅读取数据进行打印或保存,不修改原始数据流,保证 App 正常运行

业务层数据抓取

对于结构复杂的业务数据(如商品详情),直接分析网络层 JSON 可能较为繁琐。直接 Hook 业务逻辑层的数据对象(Model)并进行序列化,可以获得结构化的清晰数据。

伪代码实现逻辑

伪代码,需配合 Xposed/YukiHookAPI 实现
// 目标:获取商品详情的结构化数据
// Hook 点:GoodsResponse.setRenderResponse(IntegrationRenderResponse response)

public void beforeHookedMethod(MethodHookParam param) {
    // 1. 获取入参对象 (IntegrationRenderResponse)
    Object renderResponse = param.args[0];

    if (renderResponse == null) return;

    // 2. 序列化为 JSON
    // 优先使用 App 内部混淆后的 Gson,避免类加载器冲突
    // 假设 App 内部 Gson 实例可通过静态方法获取
    String json = AppGson.toJson(renderResponse);

    // 3. 数据持久化或上报
    if (json != null && !json.isEmpty()) {
        Logger.i("GoodsDetail", json);
        FileUtil.write("/sdcard/pdd_goods_" + System.currentTimeMillis() + ".json", json);
    }
}
实现要点:
  • Gson 实例: 务必使用 App 自身的 Gson 实例进行序列化,否则可能会因为 ClassLoader 不同或缺少 TypeAdapter 导致序列化失败
  • 线程安全: IO 操作(如写文件、网络上报)应放入子线程执行,避免阻塞 UI 线程导致 ANR
  • 时机选择: setRenderResponse 通常在网络请求回调后、UI 渲染前调用,是截获数据的最佳时机

常见问题与排查

在实践过程中,可能会遇到以下问题:
  • Hook 不生效: 检查注入时机,建议在 Application.attachBaseContext 之后或 Activity 启动时进行注入
  • 混淆导致类找不到: 结合 Jadx 反编译对比版本差异,Titan 库的包名和类名经常变动
  • 数据乱码: 检查响应头是否为 Gzip 压缩,如果是,需先进行 Gzip 解压再转 String
  • Longlink 无法降级: 新版本 App 即使检测到系统代理也可能强制走 Longlink,因此直接 Hook 应用层比尝试降级协议更稳定

声明

本文所提供的脚本和代码逻辑仅用于安全研究技术交流目标应用的协议与混淆策略会频繁更新,文中涉及的类名与方法名可能已失效,请结合实际环境进行分析。