当常规抓包工具在复杂的私有协议面前束手无策时,真正的技术挑战才刚刚开始。上周我遇到一个棘手的案例——某知名旅行App的酒店数据采集需求,Charles和Fiddler这些HTTP抓包利器完全失效。这促使我开启了一段从零逆向私有TCP协议的探索之旅,最终成功还原了完整的通讯流程。本文将分享这个过程中的关键突破点和实用技巧,适合中高级开发者在面对类似加密协议时参考。
当发现目标App完全绕过HTTP(S)协议时,第一反应往往是困惑。传统抓包工具的工作原理基于中间人攻击(MITM)技术,这在处理自定义TCP协议时几乎无效。我的解决路径分为三个关键步骤:
提示:在Android环境下,建议配合使用tcpdump获取更底层的网络数据
通过Wireshark的过滤器设置,我锁定了App与服务器之间的主要通信端口。观察到的典型数据流特征如下:
| 特征项 | 观察值 | 推测含义 |
|---|---|---|
| 起始字节 | 0x02 0x00 | 可能为协议版本标识 |
| 第3-4字节 | 变长数值 | 疑似数据包长度 |
| 固定间隔 | 30秒 | 心跳维持间隔 |
bash复制# 使用tcpdump捕获特定端口流量
adb shell tcpdump -i any -s 0 -w /sdcard/capture.pcap port 4433
当网络层分析遇到瓶颈时,逆向App本身成为突破口。现代Android应用的逆向需要多工具协同:
核心工具组合:
在目标App中,关键的通讯逻辑隐藏在名为SOTPConnection的类中。反编译后发现其核心方法:
java复制public boolean sendRequest(Request jVar) throws Exception {
byte[] rawData = jVar.getBytes();
OutputStream out = this.socket.getOutputStream();
out.write(rawData);
out.flush();
// ... 省略响应处理逻辑
}
更复杂的是数据加密部分,通过分析发现:
cd/ce声明为nativec复制// IDA反汇编看到的加密函数片段
void __fastcall AES_encrypt(uint32_t *state, const uint32_t *key) {
// 自定义的S-Box替换表
static const uint8_t SBOX[256] = {0x63, 0x7C...};
// ... 非标准AES实现逻辑
}
逆向私有协议最耗时的部分是理解其编解码规则。通过动态调试,我总结出该协议的三层结构:
关键发现是数据包使用TLV(Type-Length-Value)结构,通过Python可以构造测试请求:
python复制def build_request_packet(data):
import zlib, struct
compressed = zlib.compress(data)
encrypted = custom_aes_encrypt(compressed)
header = struct.pack('>HHLL', 0x0200, len(encrypted)+8, 0x01, 0x00)
return header + encrypted
注意:实际测试时需要处理字节序问题,该协议采用大端序
经过两周的逆向分析,最终实现的采集系统架构如下:
核心测试代码展示了酒店价格查询的实现:
java复制public class HotelDataFetcher {
private static final int HOTEL_ID = 12345;
private static final String CHECK_IN = "2023-11-01";
public static void main(String[] args) {
byte[] request = buildHotelRequest(HOTEL_ID, CHECK_IN);
byte[] response = sendRequest(request);
HotelList hotels = parseResponse(response);
System.out.println(hotels.getPrices());
}
// 其他工具方法省略...
}
遇到的典型响应数据结构示例:
json复制{
"roomTypes": [
{
"typeId": "DELUXE",
"price": 599,
"breakfast": true,
"cancelPolicy": "free"
}
],
"timestamp": 1698765432
}
现代App通常会部署多种防御措施,本案例中遇到的包括:
有效的应对方法包括:
javascript复制Interceptor.attach(Module.findExportByName("libc.so", "fopen"), {
onEnter: function(args) {
if(args[0].includes("frida")) {
this.blocked = true;
args[0] = NULL;
}
}
});
在大规模采集时,需要特别注意:
实现的连接管理器核心逻辑:
python复制class ConnectionPool:
def __init__(self, max_conn=5):
self.pool = []
self.max_conn = max_conn
def get_connection(self):
for conn in self.pool:
if conn.is_idle():
return conn
if len(self.pool) < self.max_conn:
new_conn = create_new_connection()
self.pool.append(new_conn)
return new_conn
raise PoolExhaustedError()
处理网络异常时的决策流程:
在进行任何形式的逆向工程前,必须明确:
实际操作中我遵循的原则:
整个项目最大的收获不是最终获取到的数据,而是破解过程中对网络协议、加密算法和系统设计的深入理解。记得在分析AES修改版算法时,为了验证一个猜想,我不得不重新翻阅《密码学基础》的教材——这种被迫深入底层原理的经历,比任何理论教程都来得深刻。