当前位置: 首页 > news >正文

逆向解析携程App私有协议:从抓包困境到数据采集实战

1. 当抓包工具失效时如何破局

第一次尝试用Charles抓取携程App的数据包时,我遇到了一个令人沮丧的情况——所有的请求都显示为乱码或者直接无法捕获。这种情况在分析采用标准HTTP协议的App时很少见,但携程显然使用了某种私有协议。于是我把工具换成了Wireshark,这个决定成为了整个逆向工程的转折点。

Wireshark的原始数据包显示让我确认了一个重要事实:携程App使用的是基于TCP的自定义协议,他们内部称之为SOTP协议。这种协议不像HTTP那样有明文的请求头和响应体,所有数据都是二进制格式传输。更麻烦的是,数据在传输过程中还经过了压缩和加密处理,这解释了为什么Charles无法直接解析。

面对这种情况,我总结出三个关键突破口:

  • 协议识别:通过抓包确认协议类型和基本特征
  • 请求构造:分析如何构建合法的协议请求
  • 响应解析:理解服务器返回数据的格式和编码方式

在实际操作中,我发现携程的协议实现主要集中在SOTPConnection这个类中。通过反编译代码可以看到,所有的网络请求最终都会调用sendRequest方法。这个方法有几个值得注意的特点:首先它会检查一个神秘的ASMUtils接口,这可能是某种动态加载机制;其次它会处理请求的重试和超时逻辑;最重要的是它会通过socket.getOutputStream()直接发送二进制数据。

2. 逆向工程实战:从反编译到协议还原

使用jadx打开携程App的APK文件后,我首先搜索了与网络相关的关键词。很快,ProtocolHandle类引起了我的注意。这个类像是整个App的协议处理中枢,里面定义了几种不同的序列化方式:

public enum CommEncodingType { None, Normal, UTF8, PB, Json, SotpPB, SotpJson, PBSotp, PBJson, JsonSotp, JsonPB, GraphQL }

这个枚举类透露了几个重要信息:携程同时使用了Protobuf(PB)和Json两种序列化方式,而且还有它们的各种组合变体。更复杂的是,不同功能模块可能使用了不同的序列化组合,这意味着我们需要针对不同的API接口做差异化处理。

在分析酒店价格查询功能时,我发现请求和响应都采用了"SotpPB"格式——也就是先用Protobuf序列化,再套上SOTP协议的封装。这种设计既保持了Protobuf的高效,又增加了私有协议的安全层。为了验证这个发现,我写了一个简单的Python脚本来模拟这个流程:

import socket import gzip from google.protobuf import message def send_sotp_request(host, port, pb_message): # 1. Protobuf序列化 pb_data = pb_message.SerializeToString() # 2. Gzip压缩 compressed = gzip.compress(pb_data) # 3. 添加SOTP头 header = b'\x02\x00\x00\x00' # 示例头部 full_data = header + compressed # 4. 建立TCP连接并发送 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.connect((host, port)) s.sendall(full_data) return s.recv(10240)

这个脚本虽然简化了很多细节,但它展示了SOTP协议处理的基本流程。在实际操作中,每个步骤都需要更精确地还原App中的实现,特别是头部的构造和压缩算法参数。

3. 攻克加密难题:深入native层分析

当分析到数据加密环节时,遇到了更大的挑战。在EncodeUtil类中,关键的加密方法是以native方式实现的:

public native byte[] cd(byte[] bArr, int i); // 加密 public native byte[] ce(byte[] bArr, int i); // 解密

通过查看静态初始化块,可以确定这些方法实现在libctripenc.so中。这意味着我们需要分析ARM汇编代码,这对大多数Java开发者来说是个陌生的领域。我采取的方法是:

  1. 使用IDA Pro打开so文件,定位到cd和ce函数的实现
  2. 分析函数的控制流和关键算法特征
  3. 识别使用的标准加密算法(如AES、RSA等)
  4. 尝试用Java或Python重新实现相同的算法

经过反复调试,确认携程使用的是AES加密,但有几个自定义改动:

  • 密钥不是固定的,而是根据设备信息动态生成
  • 使用了非标准的CBC模式初始化向量(IV)处理方式
  • 在加密前后还进行了额外的字节变换操作

这些发现解释了为什么直接使用标准AES库无法正确解密数据。最终的解决方案是使用Frida框架挂钩这些native方法,直接获取输入输出,而不用完全逆向算法细节:

Interceptor.attach(Module.findExportByName("libctripenc.so", "Java_com_ctrip_EncodeUtil_cd"), { onEnter: function(args) { console.log("加密输入:"); console.log(hexdump(args[2], { length: parseInt(args[3]) })); }, onLeave: function(retval) { console.log("加密输出:"); console.log(hexdump(Memory.readPointer(retval), { length: Memory.readInt(retval.add(8)) })); } });

这种方法虽然取巧,但在面对复杂加密时能节省大量时间。不过要注意的是,过度依赖hook可能会影响性能,在生产环境中还是建议实现完整的算法。

4. 构建可靠的数据采集客户端

掌握了协议细节和加密方式后,就可以着手构建完整的数据采集客户端了。以酒店价格查询为例,完整的流程应该包含以下步骤:

  1. 构造Protobuf请求消息
  2. 序列化并压缩数据
  3. 添加SOTP协议头
  4. 加密请求体
  5. 发送TCP请求
  6. 接收响应并逆向处理

这里给出一个Java版的完整实现框架:

public class CtripClient { private static final String HOST = "api.ctrip.com"; private static final int PORT = 443; public HotelRoomListResponse queryHotelPrices(int hotelId, String checkInDate, String checkOutDate) throws Exception { // 1. 构造Protobuf请求 HotelRoomListRequest request = buildRequest(hotelId, checkInDate, checkOutDate); // 2. 序列化并压缩 byte[] pbData = request.toByteArray(); byte[] compressed = compress(pbData); // 3. 添加协议头 byte[] sotpData = addSotpHeader(compressed); // 4. 加密 byte[] encrypted = encrypt(sotpData); // 5. 发送请求 byte[] response = sendRequest(encrypted); // 6. 解密和解析 byte[] decrypted = decrypt(response); HotelRoomListResponse result = new HotelRoomListResponse(); ProtobufUtil.mergeFrom(decrypted, result); return result; } private byte[] sendRequest(byte[] data) throws IOException { try (Socket socket = new Socket(HOST, PORT); OutputStream out = socket.getOutputStream(); InputStream in = socket.getInputStream()) { out.write(data); out.flush(); ByteArrayOutputStream buffer = new ByteArrayOutputStream(); byte[] chunk = new byte[4096]; int bytesRead; while ((bytesRead = in.read(chunk)) > 0) { buffer.write(chunk, 0, bytesRead); } return buffer.toByteArray(); } } }

在实际部署时,还需要考虑几个工程化问题:

  • 连接池管理:避免频繁创建TCP连接
  • 异常处理:网络波动和协议变更的容错
  • 限流机制:防止请求过于频繁被服务器封禁
  • 数据缓存:减少重复请求

我建议使用类似Apache HttpClient的连接池实现,并设置合理的超时参数。对于大规模采集任务,可以考虑引入消息队列来控制请求速率。

5. 应对反爬机制的实用技巧

在长期运行数据采集客户端的过程中,我发现携程会不定期更新其反爬策略。以下是几个常见的反爬手段及应对方法:

设备指纹验证携程客户端会在请求中携带设备唯一标识,这个标识由多个设备参数组合生成。解决方案是在采集客户端中模拟真实的设备参数,包括:

  • 合理的Android ID和IMEI
  • 真实的屏幕分辨率和DPI
  • 常见机型型号

请求签名关键API请求会包含动态生成的签名,通常由请求参数+时间戳+密钥通过特定算法生成。通过反编译可以找到签名的生成位置,通常是某个Utils类中的sign方法。用Python实现的签名算法示例:

def generate_sign(params, timestamp, key): param_str = '&'.join(f'{k}={v}' for k,v in sorted(params.items())) raw = f'{param_str}&{timestamp}&{key}'.encode('utf-8') return hashlib.md5(raw).hexdigest()

行为检测服务器会分析请求模式,异常的访问频率和时间间隔会被识别为爬虫。建议:

  • 随机化请求间隔(1-5秒)
  • 模拟真实用户的浏览路径
  • 使用多个IP地址轮询

我在实际项目中维护了一个IP代理池,配合User-Agent轮换,有效降低了封禁率。同时建议实现自动检测机制,当发现请求失败率升高时自动切换代理或暂停采集。

6. 协议变更的监控与适配

私有协议最大的挑战在于随时可能发生变化。经过多次实战,我总结出一套协议变更的监测和应对流程:

首先建立基线测试用例,保存典型的请求响应样本。然后设置自动化脚本定期运行这些测试用例,检查以下几个方面:

  1. 协议头格式是否变化
  2. 加密解密是否仍然有效
  3. 关键API的响应结构是否改变

当检测到变更时,按以下步骤处理:

  1. 使用diff工具对比新旧版本APK
  2. 重点关注网络相关类的修改
  3. 必要时重新挂钩native方法获取最新算法
  4. 更新客户端实现并验证

为了快速定位变更点,我通常会为网络模块的关键类建立映射表,记录每个版本中的类名和方法签名变化。这个工作虽然繁琐,但能在协议变更时大大缩短调试时间。

http://www.jsqmd.com/news/656143/

相关文章:

  • 告别桌面混乱!用ShareMouse免费版搞定Mac和Windows双机键鼠共享(附权限设置避坑)
  • 转场视频素材网站推荐:5个适合短视频剪辑的常用平台 - Fzzf_23
  • 苏州B2B企业出海营销服务商汇总,涵盖海外社媒运营推广与海外展会营销推广,适配多场景需求(附带联系方式) - 品牌2026
  • 高通CamX HAL3源码解析:configure_streams如何分配硬件资源与创建Pipeline?
  • 议题征集|Community Over Code Asia 2026 期待你的声音!
  • 2026年中国GEO服务商实力测评:聚焦企业数字化商业价值 - 深度智识库
  • Matlab外部工具包集成指南:从路径设置到函数库的平滑融入
  • AI生成的设计模式真的能过Code Review吗?SITS2026现场压力测试:17个反模式拦截率100%
  • 4月揭晓:口碑好的自循环水冷系统生产厂家有哪些,管材加工卡盘配套/液压切管卡盘/电动切管卡盘,自循环水冷系统厂家哪家专业 - 品牌推荐师
  • J-Link RTT日志增强:用Python脚本实现时间戳与文件轮转
  • Ubuntu下VSCode配置C++开发环境全攻略
  • ESP8266 AT指令实战避坑指南:从连接WiFi到HTTP获取OneNET数据,这些细节别踩雷
  • Java企业级SMB/CIFS客户端革命:jcifs-ng如何解决传统库的三大架构痛点
  • 用ESP32和心知天气API做个桌面天气时钟(附完整MicroPython代码)
  • 2026年电池护板厂家推荐:理想、极氪、腾势等多品牌电池护板优质之选! - 速递信息
  • Topit:三步搞定macOS窗口置顶,让你的工作效率翻倍!
  • 2026年智能客服哪个更智能,牌子好及软件口碑升级推荐 - 品牌2026
  • 模型蒸馏(Distillation)与剪枝(Pruning)的区别及产品意义
  • k8s的job中restartPolicy限制
  • 实测分享:雯雯的后宫-造相Z-Image-瑜伽女孩生成瑜伽主题图片效果到底如何?
  • 海外项目实战:用Spring Boot + Google OAuth 2.0实现用户免密一键登录(附完整Demo)
  • 蓝牙协议栈实战:从HCI命令到GATT服务,手把手教你用Wireshark抓包分析BLE通信
  • 智能车竞赛技术报告 | 基础四轮组 - 电磁与视觉融合的循迹策略
  • Ozon定价指南:Ozon定价公式是什么?Ozon定价策略是什么? - 跨境小媛
  • 低成本金属3D打印机众筹金额翻倍,它会成为类似拓竹A1的“家用”产品?
  • GLM-OCR在AIGC内容审核中的应用:自动识别违规图文
  • 2026年3月评价高的分析仪厂家推荐,便携式光谱仪/合金分析仪/矿石分析仪/奥林巴斯光谱仪,分析仪直销厂家哪家好 - 品牌推荐师
  • [Spark] 图解Job、Stage、Task的生成逻辑与实战推演
  • intv_ai_mk11镜像免配置:开箱即用Web界面+独立venv环境部署详解
  • 2026年汽车音响改装店推荐:丰田、本田、特斯拉等多品牌音响改装优质之选! - 速递信息