认出 protobuf:先看特征再动手
抓包看到请求体是一堆二进制,先确认是不是 protobuf。特征很明显:
- 开头几个字节是
08 xx 12 xx 1a xx这种模式(field tag + wire type) - 能解出可读字符串,但字段之间没有分隔符
- Content-Type 常是
application/x-protobuf或application/octet-stream
protobuf 的 wire 格式核心规则:每个字段是 tag + value,tag = (field_number << 3) | wire_type。wire type 就五种:varint(0)、64位(1)、长度分隔(2)、32位(5)、嵌套消息走 2。
手工解码:protoc --decode_raw 是第一神器
不需要 proto 文件,protoc 自带裸解码:
# 把抓包的 body 存成 bin 文件
protoc --decode_raw < request.bin输出:
1: 1787968800
2: "keyword"
3 {
1: "device_id_xxx"
2: 20
}
4: 0x9e3779b9字段号、嵌套结构、值全出来了。缺的是字段名和类型语义——1 是 timestamp 还是 user_id?3.2 是 int32 还是 enum?这需要结合业务猜,但骨架已经立起来了。
从解码结果反推 proto 文件
syntax = "proto3";
message SearchRequest {
int64 timestamp = 1; // 1: 1787968800 看着像时间戳
string keyword = 2; // 2: "keyword"
DeviceInfo device = 3; // 3: 嵌套消息
fixed32 sign_seed = 4; // 4: 固定32位,像种子值
}
message DeviceInfo {
string device_id = 1;
int32 page_size = 2;
}反推的类型判断规则:
- wire type 0(varint):可能是 int32/int64/bool/enum,看值的范围猜
- wire type 2(长度分隔):能按 UTF-8 解出可读文本的是 string,解不出的是 bytes 或嵌套 message(递归 decode_raw 验证)
- wire type 5/1(32/64 位定长):fixed32/fixed64/float/double,看值像不像浮点
验证:encode 回去对拍
proto 文件写好后必须验证——用它能重新编码出和抓包字节级一致的数据:
protoc --encode=SearchRequest request.proto < decoded.txt > reencoded.bin
diff <(xxd request.bin) <(xxd reencoded.bin)字节一致 = 字段号和类型全对。不一致就看 diff 位置:tag 不对是字段号错,长度不对是类型错(比如把 int64 写成了 int32 导致 varint 截断)。
精髓:四个实战坑
- 字段号比字段名重要。protobuf 序列化只带字段号不带名,你 proto 里的字段名随便起(
field_1都行),号对了就能编解码。别纠结"猜不出字段含义",先保证结构对。 - repeated 字段的两种编码。packed(所有值连在一个 length-delimited 块里)和非 packed(每个值一个 tag)编码不同,decode_raw 看不出来——同一个字段号出现多次就是非 packed repeated;一个字段号后面跟一串紧密排列的 varint 就是 packed。
- map 是语法糖。
map<string, int32>实际是repeated MapEntry,每个 entry 是{1: key, 2: value}的嵌套消息。decode_raw 看到大量同构的两字段嵌套,就是 map。 - oneof 和 unknown fields。新版 protoc 的 decode_raw 对 unknown fields 也能解。遇到同一个字段号出现不同类型,可能是 oneof——proto 里用 oneof 声明。
进阶:从 App 里直接抠 proto 定义
手工猜 proto 是兜底方案。如果 App 没混淆 protobuf 代码,更快捷径:
- Java 层:jadx 搜
extends GeneratedMessageLite或com.google.protobuf,类名和字段直接给出完整 message 定义 - descriptors:很多 App 编译时把 FileDescriptor 嵌在代码里,
protobuf-inspector工具可以直接从 class 提取 .proto
总结
- protoc --decode_raw 无需 proto 文件裸解码,先拿结构
- 反推 proto 后必须 encode 对拍,字节一致才算对
- 字段号对就能用,字段名可以后补
- 优先从 App 的 protobuf 生成类里抠定义,手工猜测是兜底
交流微信:run1255