19、协议逆向基础:Protocol Buffers解析、FlatBuffers、自定义二进制协议分析

协议逆向,说白了就是跟App的“黑话”打交道。你抓到了加密数据,但不知道它长什么样,怎么拆解?这时候就得靠协议逆向。

我个人习惯把协议分成三类:结构化序列化协议(比如Protobuf)、高效内存布局协议(比如FlatBuffers)、以及纯自定义二进制协议(各家自己定的规则)。今天咱们一个一个聊。

一、Protocol Buffers 解析

Protobuf 是 Google 搞的,现在移动端用得特别多。它的核心思路是:定义一份 .proto 文件,然后生成各语言的序列化代码。数据在传输时是紧凑的二进制,不是 JSON 那种明文。

1.1 怎么识别 Protobuf

你在抓包时看到一串二进制,怎么判断是不是 Protobuf?我总结几个特征:

  • 没有明显的分隔符:不像 JSON 有花括号、逗号,Protobuf 就是纯字节流。
  • 字段编号规律:每个字段前有一个 varint 编码的 tag,包含 field_number 和 wire_type。
  • 常见 wire_type:0(varint)、1(64位固定)、2(长度分隔)、5(32位固定)。

实战技巧:用 protoc 自带的 --decode_raw 选项,可以尝试解析未知的 Protobuf 数据。我在项目中遇到过好几次,服务端改了 proto 文件但没通知客户端,全靠这个命令临时救场。

1.2 手动解析示例

假设你抓到一段二进制:08 96 01 12 07 74 65 73 74 69 6e 67。我们来拆解:

08        → tag: field_number=1, wire_type=0 (varint)
96 01     → varint 值 = 150
12        → tag: field_number=2, wire_type=2 (length-delimited)
07        → 长度 = 7
74 65 73 74 69 6e 67 → UTF-8 字符串 "testing"

嗯,这里要注意:varint 编码用的是小端序,每个字节的最高位是 continuation bit。如果字节最高位是 1,说明后面还有字节。

1.3 逆向工具链

工具 用途 我的评价
protoc 官方编译器,支持 --decode_raw 必备,但需要自己写脚本
Protobuf Inspector Frida 插件,动态解析 调试时很好用
Blackbox Protobuf Burp Suite 插件 抓包时直接看字段

我的建议:别一上来就想着还原完整的 .proto 文件。先抓几个包,用 --decode_raw 看看字段结构,再结合业务逻辑猜字段含义。我曾经花了两天去猜一个 int64 字段,最后发现是时间戳——嗯,白费功夫。

二、FlatBuffers 分析

FlatBuffers 是 Google 的另一个作品,跟 Protobuf 思路完全不同。它的特点是:不需要解析,直接通过偏移量访问数据。说白了,数据在内存里怎么布局,传输时就是什么样。

2.1 为什么用 FlatBuffers

移动端游戏、实时性要求高的场景,FlatBuffers 很常见。原因很简单:

  • 零拷贝:收到数据后直接读取,不需要反序列化。
  • 内存效率高:没有额外的对象开销。
  • 访问速度快:O(1) 时间复杂度访问任意字段。

你想想看,一个游戏每帧要同步几百个对象的位置,如果用 JSON 或 Protobuf,光解析就能把 CPU 吃满。FlatBuffers 就没有这个问题。

2.2 识别 FlatBuffers

FlatBuffers 的二进制特征比较明显:

  • 文件头:通常以 4 字节的 magic number 开头,比如 "FLAT" 或自定义标识。
  • vtable 机制:每个对象前有一个 vtable,记录各字段的偏移量。
  • 对齐填充:字段按 4 字节或 8 字节对齐,中间可能有 padding。

避坑指南:我曾经在逆向一个手游时,发现它的 FlatBuffers 魔数被改成了 "GAME"。当时没注意,用标准解析器一直报错。后来才发现是魔数被魔改了。所以,别迷信标准魔数,多看看二进制头部的特征。

2.3 手动解析 FlatBuffers

解析 FlatBuffers 的核心是理解 vtable 的结构。假设你有一个 table 对象:

// 二进制布局(简化)
[SOF]  [vtable偏移]  [数据区]  [vtable]
 4字节    4字节       变长      变长

// vtable 结构
[table大小]  [vtable大小]  [字段1偏移]  [字段2偏移] ...
  2字节        2字节         2字节        2字节

解析步骤:

  1. 读取 SOF(起始偏移),通常是 4 字节。
  2. 从 SOF 位置读取 vtable 偏移。
  3. 跳转到 vtable,读取字段偏移表。
  4. 根据字段偏移,从数据区读取实际值。

个人经验:我建议用 Python 写一个简单的 FlatBuffers 解析器,配合 Frida 动态调用。这样既能静态分析二进制,又能动态验证字段含义。我自己的工具链里,这个脚本已经迭代了三个版本了。

三、自定义二进制协议分析

这是最头疼的一类。没有标准,没有文档,全靠逆向工程师自己猜。我遇到过很多 App,自己定义了一套二进制协议,字段含义全靠服务端和客户端约定。

3.1 分析思路

面对一个陌生的二进制协议,我的套路是这样的:

  • 找固定模式:看有没有魔数、长度字段、校验和。
  • 对比多个包:同一个接口发两次,看哪些字节变了,哪些没变。
  • 边界测试:改一个输入参数,看哪些字节跟着变。
  • 找长度字段:通常变长字段前会有一个长度指示。

为什么会这样?因为自定义协议再奇葩,也得遵循基本的通信规则。服务端和客户端得能互相理解,所以一定有某种约定。

3.2 一个真实案例

我曾经逆向一个社交 App 的登录协议。抓到的包长这样:

AA BB 00 1E 01 00 00 00 0A 00 00 00 74 65 73 74
00 00 00 00 08 00 00 00 31 32 33 34 35 36 00 00

我的分析过程:

  1. AA BB:魔数,固定不变。
  2. 00 1E:总长度 30 字节(小端)。
  3. 01:命令号,登录请求。
  4. 0A 00 00 00:用户名长度 10。
  5. 74 65 73 74:用户名 "test"(但只有4字节,说明有 padding)。
  6. 08 00 00 00:密码长度 8。
  7. 31 32 33 34 35 36:密码 "123456"。

避坑指南:注意 padding!很多自定义协议为了对齐,会在字符串后面补 0x00。我刚开始分析时,经常把 padding 当成数据的一部分,导致解析出来的字符串多了一堆空字符。后来我养成了习惯:先看长度字段,再按长度截取,忽略后面的 padding。

3.3 工具推荐

工具 适用场景 备注
010 Editor 静态分析二进制 支持模板,可以写解析脚本
Binwalk 识别协议特征 适合找魔数、签名
Frida + 自定义脚本 动态 Hook 解析 我最常用的方式

四、知识体系总览

下面这张图,是我对协议逆向知识体系的总结。你可以把它当作一个思维导图来用:

协议逆向基础 Protocol Buffers 识别特征:varint tag 工具:protoc --decode_raw 实战:猜字段含义 FlatBuffers 核心:vtable 偏移机制 特征:魔数 + 对齐填充 场景:游戏、实时通信 自定义二进制协议 分析思路:找固定模式 技巧:边界测试 + 对比 注意:padding 和魔数 核心能力:从二进制中提取结构化信息 工具 + 经验 + 耐心 = 协议逆向

协议逆向这件事,说白了就是跟二进制数据较劲。Protobuf 有标准可循,FlatBuffers 有规律可抓,自定义协议就得靠经验积累。我个人觉得,最重要的不是记住每种协议的细节,而是培养一种“二进制直觉”——看到一串字节,能快速判断它大概是什么结构。

嗯,今天就聊到这里。记住:多动手,多对比,多总结。工具只是辅助,真正的功力在于你分析问题的思路。


公众号:蓝海资料掘金营,微信deep3321