protobuf逆向:从抓包乱码到还原数据结构

一、为什么要做 Protobuf 逆向

Protobuf(Protocol Buffers)是 Google 推出的高性能二进制序列化协议,相比 JSON 体积可缩小 60%~80%,编解码速度快 3~5 倍,目前已广泛应用于微服务(gRPC)、移动端 APP、网络游戏、物联网设备等场景。但二进制序列化后,抓包工具中只会呈现一串无规律的 "乱码",无法像明文 JSON 一样直接读取字段含义,给接口调试、业务分析、安全测试带来了天然障碍。

本文从抓包拿到原始二进制数据开始,完整讲解从协议识别、手动解码推导到自动化还原完整 proto 定义的全流程,覆盖入门到高级的实操方法。

二、先搞懂:Protobuf 二进制的编码特征

做逆向的前提是先识别目标是不是 Protobuf,而识别的核心是理解其编码规则。

2.1 核心编码结构:Tag - Length - Value

Protobuf 消息本质是一系列字段的有序集合,每个字段遵循 Tag + [Length] + Value 的结构:

  • Tag:每个字段的开头,采用 varint 编码,由「字段号 << 3 | wire type」计算得到,同时标识了字段编号和值的编码类型。
  • Wire Type:共定义 6 种类型,常用的有 4 种:

表格

wire type 类型名称 对应 Protobuf 数据类型
0 VARINT int32、int64、uint32、uint64、sint32、sint64、bool、enum
1 I64(64 位固定长度) fixed64、sfixed64、double
2 LEN(长度分隔) string、bytes、嵌套 message、packed repeated 数组
5 I32(32 位固定长度) fixed32、sfixed32、float

2.2 快速识别 Protobuf 的典型特征

拿到一段二进制乱码,满足以下特征时,大概率是 Protobuf 编码:

  1. 字节分布有规律,高频出现0x08、0x10、0x18、0x20等数值,对应字段号 1、2、3、4 的 VARINT 类型 Tag;
  2. 字符串类数据采用「单字节 / 多字节长度前缀 + 原始内容」结构,没有引号、逗号、大括号等明文分隔符;
  3. 字段号通常从 1 开始连续递增,很少出现跳号过大的情况;
  4. 没有固定的文件头 Magic Number,消息整体长度不固定。

常见误区:很多业务接口会将 Protobuf 二进制先做 Base64 编码或 Gzip 压缩,抓包看到的可能是字符串或压缩包。遇到这种情况需要先解码、解压,得到纯二进制后再继续分析。

三、第一步:抓包提取原始二进制数据

不同业务场景的抓包方式和数据提取方法不同,核心目标是拿到纯净的 Protobuf 载荷。

3.1 HTTP/HTTPS 场景(Web / 移动端接口)

  • 常用工具:Charles、Fiddler、Burp Suite
  • 操作方法 :找到对应接口的请求或响应,选择「Save Body」「导出原始数据」功能,保存为.bin格式的二进制文件。
  • 判断依据 :如果响应头Content-Type为application/x-protobuf、application/grpc-web,基本可以确定是 Protobuf;如果是application/octet-stream,则需要结合编码特征进一步识别。

3.2 TCP/UDP 原生场景(游戏 / 私有协议)

  • 常用工具:Wireshark、tcpdump
  • 操作方法:通过端口、IP 筛选目标流量,右键选择「Follow → TCP Stream」,将展示格式切换为「Raw」,导出原始字节流。
  • 注意事项:私有协议通常会在 Protobuf 消息外加自定义包头(比如 2 字节消息长度、4 字节序列号、1 字节命令号),需要先根据协议格式剥离包头,取出真正的 Protobuf 载荷。

3.3 gRPC 协议特殊处理

gRPC 默认基于 HTTP/2 传输,单条消息的格式为:1 字节压缩标志 + 4 字节大端消息长度 + Protobuf 本体。 逆向时需要跳过前 5 字节的 gRPC 头部,取出后续的二进制数据,才是真正的 Protobuf 消息体。

四、入门实操:手动解码推导数据结构

手动解码是 Protobuf 逆向的核心能力,工具只是提升效率的辅助。我们用一段真实的二进制数据举例,完整走一遍解码流程: 示例二进制:08 96 01 12 07 74 65 73 74 69 6e 67

步骤 1:解析第一个 Tag

第一个字节为0x08,转换为二进制是00001000:

  • 低 3 位为 wire type:000 = 0 → VARINT 类型
  • 右移 3 位得到字段号:00001 = 1 → 字段编号为 1

步骤 2:解析 VARINT 值

Tag 后紧跟的0x96 0x01是一个 varint 数值:

  • varint 每个字节的最高位是延续位:0x96最高位为 1,表示后续还有字节;0x01最高位为 0,表示该数值结束。
  • 去掉每个字节的最高位,按倒序拼接有效位:0010110 0000001 → 十进制结果为150。
  • 结论:字段 1 的值为 150,类型属于 VARINT 族。

步骤 3:解析第二个 Tag

接下来的字节是0x12,二进制为00010010:

  • 低 3 位:010 = 2 → LEN(长度分隔)类型
  • 右移 3 位:00010 = 2 → 字段编号为 2

步骤 4:解析长度与内容

Tag 后紧跟的0x07表示内容长度为 7 字节。 后续 7 个字节74 65 73 74 69 6e 67为标准 ASCII 编码,对应字符串"testing"。

  • 结论:字段 2 的值为 "testing",类型为 string。

步骤 5:推导初步消息结构

根据以上分析,可以写出对应的 proto 骨架:

复制代码
message SampleMessage {
  optional int32 field_1 = 1; // 数值150,初步推测为int32
  optional string field_2 = 2; // 字符串类型
}

进阶判断技巧:如果同一个字段号在消息中重复出现,说明该字段是repeated数组;如果 LEN 类型的内容本身又符合 Protobuf 编码规则,则说明这是一个嵌套消息。

五、自动化逆向:工具链批量还原

手动解码适合短消息验证,面对多层嵌套、几十上百个字段的复杂消息,需要借助工具提升效率。

5.1 官方基准工具:protoc --decode_raw

Google 官方的 protoc 编译器自带原始解码功能,不需要任何 proto 文件,即可解析二进制并输出字段号与对应的值。

  • 使用命令:

    cat message.bin | protoc --decode_raw

  • 输出示例:

    1: 150
    2: "testing"
    3 {
    1: 12345
    2: "hello world"
    }

  • 优缺点:解析结果 100% 准确,是验证其他工具的基准;但只能输出匿名字段,无法区分具体子类型(比如 int32 还是 uint32),也没有语义化字段名。

5.2 结构化还原工具

  • PBTK(Protobuf Toolkit):专门用于 Protobuf 逆向的工具集,支持输入二进制自动生成 proto 骨架,可智能推断字段类型、识别嵌套结构与 repeated 数组,支持批量文件处理。
  • Protobuf Inspector :可视化桌面工具,导入二进制后以树形结构展开嵌套消息,支持直接编辑字段名和类型,最终导出完整的.proto文件。
  • Protoscope:命令行增强版解码工具,输出比 decode_raw 更易读的格式,支持高亮 Tag、长度、值,适合快速查看复杂消息。

5.3 客户端提取:从二进制文件中挖 Proto

如果目标有客户端程序(APP、PC 客户端),通常编译时会将 proto 描述符编译进二进制文件(so/dll/exe),提取的信息远多于纯黑盒逆向。

  • protodump:可从 ELF、PE、Mach-O 格式的二进制文件中扫描 Protobuf 描述符,直接提取完整的 proto 定义,包含消息名、字段名、嵌套结构、枚举值等全部信息。
  • Frida 运行时 Hook :Hook Protobuf 库的序列化 / 反序列化函数(如SerializeToString、ParseFromString),在运行时直接打印消息明文,甚至导出内存中的完整 proto 描述符。

5.4 gRPC 专项:利用反射直接拿定义

如果目标 gRPC 服务开启了服务器反射,可以无需逆向直接获取全部接口定义:

复制代码
# 列出所有服务
grpcurl -plaintext example.com:50051 list
# 查看指定服务的详细消息定义
grpcurl -plaintext example.com:50051 describe package.ServiceName

六、高级优化:从骨架到完整可用的 Proto

工具输出的 proto 骨架通常只有字段号和匿名类型,需要进一步优化补全,才能投入实际使用。

6.1 字段类型精准推断

  • VARINT 类型族 :
    • 数值均为正整数且范围不大 → int32 /uint32
    • 出现极大的无意义数值 → 可能是 sint32/sint64 的 ZigZag 编码负数
    • 数值为 10 位左右整数 → 大概率是 Unix 时间戳(int64)
    • 只有 0、1、2 等连续小整数 → enum 枚举类型
  • 固定长度类型族 :
    • 数值符合浮点数分布 → float(I32)/double(I64)
    • 整数且字节序固定 → fixed32 /fixed64
  • LEN 类型族 :
    • 内容为可读文本 → string
    • 内容符合 Protobuf 编码规则 → 嵌套 message
    • 内容是连续的同类型 VARINT → packed repeated 数组

6.2 字段名的语义还原

字段名不会编码进二进制,只能通过语义和旁证推断:

  1. 根据字段值反推语义:比如存手机号的字段可命名为phone,存用户 ID 的命名为user_id;
  2. 参考同业务其他明文接口、客户端日志、错误提示中出现的字段名;
  3. 从客户端二进制的调试符号、字符串常量中提取匹配的字段名称。

6.3 验证还原正确性

还原后的 proto 是否准确,可以通过编码回测验证:

  1. 使用还原的 proto 文件,将解码后的数据重新编码为二进制;
  2. 对比新生成的二进制与原始抓包二进制是否完全一致;
  3. 如果完全一致,说明结构 100% 正确;如果存在差异,重点检查字段类型判断是否错误、是否遗漏了可选字段。

七、常见坑与应对方案

  1. ZigZag 编码负数:普通 int32 编码负数会占 10 字节,sint32 采用 ZigZag 编码将负数映射为正整数,体积更小。如果 VARINT 值很大且无实际意义,优先考虑是 ZigZag 编码的负数。
  2. Packed Repeated 字段:repeated 基础类型字段默认采用 packed 模式,wire type 同样为 2,内容是连续的同类型值,很容易被误判为嵌套消息。
  3. 未知字段保留:Protobuf 解析时会自动保留未知字段,逆向时可能看到部分无法对应结构的字节,属于正常现象。
  4. 厂商自定义变种:部分厂商会修改 Protobuf 编码规则(比如调整 Tag 位宽、加密部分敏感字段),纯黑盒逆向难度大,建议结合客户端代码分析。

八、总结

Protobuf 逆向的完整流程可以总结为: 抓包提取原始流量 → 解码 / 解压 / 剥离包头得到纯 Protobuf → 工具初步解析出结构骨架 → 手动推断字段类型与语义命名 → 编码回测验证正确性

本质上 Protobuf 是一种结构化极强的二进制协议,只要掌握核心编码规则,配合合适的工具链,绝大多数场景下都可以还原出可用的数据结构。对于有客户端的场景,优先选择从二进制文件中提取 proto,效率和准确度都远高于纯黑盒逆向。

相关推荐
小静AI工程实验室11 小时前
Python 爬虫解析 JSON-LD:多块 script、@graph 与坏数据的 9 个边界
爬虫·python·json
要吃这碗饭11 小时前
YouTube 数据抓取出现 429/403 错误?2026 反爬机制拆解与爬虫选型指南
网络·爬虫·网络协议
袁袁袁袁满12 小时前
AI Agent 如何“看懂“互联网?
爬虫·python·自动化·爬虫实战·多线程爬虫
Helix25013 小时前
Python爬虫零基础实战:3步抓取网页数据并导出Excel
爬虫·python·beautifulsoup·excel·python教程·requests·网页数据
深蓝电商API1 天前
Frida入门到实战:hook so层与Java层的姿势总结
爬虫·frida
小静AI工程实验室2 天前
robots.txt 不是门禁:RFC 9309 规则、缓存与 Python 爬虫的 10 个边界
爬虫·python·缓存
远航计算机2 天前
AI 爬虫分三种,你 robots.txt 里挡的是哪一种?
人工智能·爬虫·aigc
深蓝电商API2 天前
大厂验证码对抗升级:从“破解”到“养环境”的思路转变
爬虫·验证码
Getgit2 天前
亮数据 | Browser API 实战:Booking.com 动态页面抓取对比
爬虫·亮数据·broowser api