
先交代一个很实际的场景你在电脑上打开抖音网页版给某个用户发一条私信顺手把浏览器开发者工具切到 Network 面板结果看到的不再是平时熟悉的 JSON而是一坨几十KB的二进制数据字段名基本不可读点开 Response 甚至直接乱码。这就是抖音Web端私信接口的日常传输层用的是 Protobuf而不是传统的 JSON。这个项目就是我花了一整个周末从零开始把抖音Web端私信这条链路的 Protobuf 协议逆向出来的全过程。目标很明确搞清楚私信列表、会话记录、发送消息这些动作对应的接口长什么样消息体里每个字段到底是什么含义然后自己写一套脚本能够拉取会话、解析消息、构造发送内容实现对自己账号私信数据的本地化处理和自用场景下的自动化操作。适合看这篇内容的人对 Web 端协议逆向感兴趣的前端工程师、爬虫方向开发者、想把自己聊天记录导出备份的研究型用户。如果你只是想要一个现成的、能直接跑的工具那我劝你趁早关掉——因为抖音的协议字段经常调整签名参数也随时在变照抄代码大概率第二天就失效。但如果你把这套思路学会了以后遇到任何浏览器端二进制协议都能自己动手拆开看。1. 为什么是 Protobuf项目背景与整体破解思路1.1 Web端私信为什么不用 JSON 而是 Protobuf先说结论抖音Web端的私信接口核心数据交换格式已经从 JSON 切换成了 Protobuf。这不是抖音一家的问题主流大厂的高频业务接口都在做类似改造。Protobuf 全称 Protocol Buffers是 Google 开源的一种结构化数据序列化协议。直观理解就是JSON 是明文、每个字段都带 key 名可读性好但冗余多Protobuf 是二进制、字段只保留编号和值体积可以缩小到 JSON 的 1/3 到 1/5解析性能也更快。对抖音这种日活过亿、私信消息量巨大的平台来说用 Protobuf 能省下大量带宽和服务器解析成本。但这玩意儿对开发者来说就很不友好了。JSON 一眼能看出 nickname、content 在哪儿Protobuf 拿到手就是一堆字节流没有 .proto 描述文件的话你根本不知道 byte 0 到 byte 3 代表什么、byte 4 到 byte 10 又是什么。更麻烦的是抖音某些接口甚至有双重编码——外层是 Protobuf内层的 content 字段还塞了一段 base64 再套一层 JSON。1.2 逆向解析的整体路径我把整个破解过程拆成了五个环节抓包定位、二进制提取、字段探测、协议还原、消息构造与验证。这五步缺一不可而且顺序不能乱。第一步抓包定位要做的事是在海量的请求里找出私信相关的接口确认哪些请求走的确实是 Protobuf第二步是把 Response 里的二进制内容完整保存下来这一步看起来简单但很多人栽在复制不完整或者被浏览器自动转码上第三步是字段探测用工具把二进制流里的 field number、wire type、嵌套结构先摸出来相当于先画一张地图第四步是根据探测结果和业务语义反推出 .proto 文件第五步才是最难的——用还原出的协议去构造一条私信消息发出去验证整个链路是否真的通了。这条路径不是我拍脑袋想出来的是踩了好几个坑之后总结出来的。最初我直接跳到第四步想靠肉眼从十六进制里猜字段结果花了两个小时一无所获。后来老老实实回到工具探测才在几分钟内理清了外层结构。1.3 为什么从 Web 端下手而不是 App 端很多人一听说逆向抖音就想到 App 反编译我建议你如果不是有特殊需求千万别上来就碰 App。抖音 App 端不仅有加固壳几乎所有关键接口都走了 native 层抓包还要处理证书校验光绕过 SSL Pinning 就能劝退一批人。Web 端就友好太多。核心逻辑都在浏览器里跑的 JavaScript 里请求长什么样、参数怎么拼、Headers 带什么直接 DevTools 全部看得到。最狠的是Web 端没有 so 文件没有 dex 加固你想找签名逻辑直接在 Sources 面板里搜关键词就能定位到相关 JS 代码。当然 Web 端也有自己的硬骨头最典型的就是签名参数。抖音网页版很多接口必须带 a_bogus 或 X-Bogus 这类签名否则直接 401。这个后面我会详细讲。2. Protobuf 核心原理与字段还原方法2.1 十分钟看懂 Protobuf 二进制结构逆向 Protobuf第一个要过的坎就是理解它底层的 wire format。别被术语吓到它本质上只做三件事给每个字段一个编号、标记字段类型、写入字段值。任何字段在二进制里都会先有一个 key这个 key 是一个 varint变长整数它的计算方式是(字段编号 3) | 类型标识。也就是说key 的低 3 位表示字段类型高位表示字段编号。常见类型标识有四种0 表示 varint对应 int32、int64、bool、enum 这类整数值1 表示 64 位固定长度对应 fixed64、double2 表示长度前缀对应 string、bytes、嵌套消息、repeated 字段5 表示 32 位固定长度对应 fixed32、float。拿我们最常遇到的嵌套消息举例。抖音私信里的消息内容在 Protobuf 里往往不是一个扁平结构而是外层一个 Message 对象里面嵌着 Sender、Conversation、Content 这些子消息。子消息在二进制里就是一个 wire type 为 2 的字段它的值域里又递归地是一段完整的 Protobuf 编码。理解了这个再看二进制流就不会懵了。你会认出哪些是 key、哪些是长度、哪些是真正的数据就像看快递单一样先看编号再看打包方式再取货。2.2 没有 .proto 文件时怎么反推字段真正到了逆向现场你是没有现成的 .proto 文件的。这时候最关键的工作叫字段探测。我推荐三个工具按使用场景排序protobuf-inspector、blackboxprotobuf、Wireshark。protobuf-inspector 是个命令行工具能直接把一段二进制流解析成树状结构标出每个字段的编号、类型和猜测含义适合拿到数据后第一时间看全貌。blackboxprotobuf 是 Python 库更适合在脚本里动态解码和重新编码后面我要写的本地解析脚本主要靠它。Wireshark 则是在你怀疑某个接口的响应是 gzip 压缩或 chunked 编码时用来做底层抓包核对的浏览器 DevTools 对二进制的展示有时候会骗你。字段探测的核心方法论是控制变量法。举个例子我要确定哪个字段是消息内容就发一条内容为“hello”的私信和一条内容为“world”的私信分别抓包导出二进制用工具解析后对比值发生变化且与字符串长度匹配的那个字段大概率就是 content。同理发消息给不同人对比 Sender 字段改时间戳对比时间字段。这招看起来笨却最有效。2.3 还原 .proto 的实操细节和经典坑当你把每个字段的编号、类型、语义都摸清了就可以着手写 .proto 文件。这里有几个实操细节非常值得注意。第一枚举值不要瞎猜。Protobuf 里枚举在二进制中就是 varint但你并不知道 1 表示 TEXT 还是 2 表示 TEXT。我的做法是发送不同类型的消息文字、图片、表情抓包对比 message_type 字段的变化再用网页端实际展示效果倒推枚举含义。第二repeated 字段的特征是同一编号反复出现。解析器会把多个相同编号的字段组合成一个数组。但如果你看到同一个编号字段反复出现而你的 .proto 里还没定义它为 repeated那解析结果就会丢数据。这个坑我踩过现象是消息列表永远只能解析出最后一条。第三字段顺序可能会变。抖音某次版本更新后同一个消息类型里的字段顺序整体做了调整编号没变但排列变了。如果你的解析代码是硬编码按字段顺序读取的那就彻底废了。Protobuf 的设计本来就不保证字段顺序所以一定要基于 field number 来访问不要依赖顺序。3. 完整实操从 DevTools 定位到消息体构造3.1 第一步定位私信相关接口打开抖音网页版并登录自己的账号按 F12 进入开发者工具切到 Network 面板勾选 Fetch/XHR 过滤。然后在页面里随便找一个好友发一条私信比如发一串独特的字符“ping12345”。回到 Network 面板会看到一条新的请求接口路径通常是/im/v2/web/messages/或者/webcast/im/fetch/这类。点开这条请求看它的 Response Headers 里的 Content-Type大概率是application/x-protobuf或application/octet-stream这就说明响应体是二进制 Protobuf 而不是 JSON。再看请求 Payload。很多接口的请求体也是二进制 Protobuf只有极少数字段是明文的 query 参数。这时候别急着复制先把浏览器里这条请求完整保存下来后面要反复用来做控制变量对比。3.2 第二步把二进制消息拉下来做字段探测浏览器 DevTools 直接复制 Response 不靠谱它会把二进制转成字符串中间可能丢字节。我推荐用两种方式一种是在 Console 里用 fetch 重放请求然后把响应转成 ArrayBuffer 并通过saveAs保存成 .bin 文件另一种是复制这条请求为 cURL然后直接用 Python requests 在本地重发把response.content写成文件。我在实验里用的是 Python 方案。把 cURL 复制出来转成 Python requests 代码后最关键的是要带上完整的 Cookie、User-Agent、Referer 和签名参数否则接口很可能直接拒掉。如果你复制下来的 cURL 里有 a_bogus 参数直接用就行但要注意它有时效性过期了要回浏览器重新拿。拿到 .bin 文件后先用 protobuf-inspector 跑一遍pip install protobuf-inspector protobuf_inspector message.bin输出会以树状结构列出所有字段包括字段编号、wire type、猜测类型和值。看到形如1: varint (15)、2: message {...}的结构说明你已经成功撕开了一个口子。3.3 第三步构造自己的私信发送消息字段探测做完后你对发送消息请求的 Protobuf 结构基本心里有数了。这里我以最常见的文字私信为例梳理一下必须的字段conversation_id会话ID也就是你和对方会话的唯一标识通常由两个用户ID拼接或单独生成client_message_id客户端消息ID一般是一个 UUID 字符串用来做消息去重content消息正文有时候是纯文本有时候是 JSON 字符串或 base64 编码后的对象message_type消息类型文字、图片、视频各自有对应的枚举值send_time发送时间一般是毫秒级时间戳。构造的时候最麻烦的是 content 字段。我实验发文字消息时content 不是直接裸字符串而是先 JSON 序列化成{text:hello}这样的结构再整体塞进 Protobuf 的 string 字段。这个规律靠猜很难中我是通过控制变量法分别发纯文本、带表情文本、用户文本对比二进制里 content 区段的变化后才确定下来的。3.4 第四步签名参数与请求头补齐在抖音Web端光有正确的 Protobuf 请求体还远远不够请求头里必须带上签名参数否则接口会返回401或者弹参数校验失败。常见的几个参数是a_bogus、msToken、ttwid。这里我不建议自己去实现 a_bogus 的生成算法因为里面的混淆逻辑相当复杂而且平台会定期更新。更稳的做法是把浏览器当作你的签名服务器。具体来说脚本里用 Selenium 或 Playwright 启动一个带登录态的浏览器页面在页面上下文中执行一段 JS从 cookie 或接口响应里实时取签名参数再传给 requests 去发请求。如果你只是自己研究还有一种更轻量的方式直接在浏览器 Console 里用 fetch 发请求浏览器会自动帮你带好大部分签名和 cookie你只需要重写 fetch 的 body 为构造好的 Uint8Array就能验证消息是否发送成功。这也是我最推荐的快速验证方式后面写脚本时再考虑自动化签名。4. 核心环节实现Python 解析与本地化脚本4.1 环境准备与依赖安装既然要落地成脚本我这里用 Python 3.10 来做演示。需要装的库包括 blackboxprotobuf、requests、google.protobuf如果你选择用 protoc 生成代码的话另外建议装一个 jupyter 或 ipython边解析边调试体验会好很多。pip install blackboxprotobuf requests grpcio-toolsblackboxprotobuf 最牛的地方在于它不依赖 .proto 文件就能动态反序列化并且能保存推断出来的结构下次直接用同一套结构去解码新的二进制流。它内部会自动识别字段类型对嵌套消息、repeated 字段的支持也不错。4.2 解析会话列表与消息记录下面这段代码演示了如何加载从浏览器里抓下来的 .bin 文件并解析出私信会话列表和消息内容import json import blackboxprotobuf with open(conversation.bin, rb) as f: data f.read() message, typedef blackboxprotobuf.decode_message(data) print(json.dumps(message, ensure_asciiFalse, indent2))第一次跑的时候输出可能非常乱因为 blackboxprotobuf 会把每个二进制块都当成消息去猜。但没关系我们关注几个特征字段conversation_id、last_message、unread_count这类。找到之后把 typedef 保存下来后续解析新数据时可以复用能大幅提高解析准确率。如果你更想用正式的方式可以把第 2 节还原出来的 .proto 文件用 protoc 编译成 Python 模块然后像调本地类一样解析。这种方式代码更清晰缺点是抖音一改协议你就得重新编译。4.3 模拟发送一条私信并验证发送消息的核心是构造 Protobuf 请求体。假设你已经通过分析明确了各字段编号构造代码大概是这样的import uuid, time, blackboxprotobuf # 根据反推的字段编号构造消息结构 msg { 1: conversation_id_xxx, # 会话ID 2: str(uuid.uuid4()), # client_message_id 3: {text:hello from script}, # content 4: 1, # message_type1 代表文字 5: int(time.time() * 1000) # 发送时间戳(毫秒) } typedef { 1: {type: str}, 2: {type: str}, 3: {type: str}, 4: {type: int}, 5: {type: int} } payload, _ blackboxprotobuf.encode_message(msg, typedef)然后把这个 payload 作为请求体发给发送消息接口同时带上签名参数。发完后去网页端刷新会话如果能看到 “hello from script” 这条消息说明整条链路已经打通。这里提醒一句测试发送时一定用自己可控的小号或者不打扰他人的账号频率也控制在极低水平千万别拿这个去骚扰别人这是底线。4.4 从消息解析到本地备份与提醒协议通了之后能做的东西就多了。我第一件事是写了一个增量拉取脚本把每天新产生的会话和消息落到 SQLite 数据库里然后写一个简单的关键词提醒。这样即使网页版不常开也能在本地保留一份自己账号的私信备份。再进一步你还可以把解析出的消息导出成 HTML 或 JSON 文件做成类似微信聊天记录备份的效果。这个扩展方向很实用尤其适合有长期保存聊天记录需求的人。5. 常见问题与排查技巧实录5.1 接口返回 401 / 参数校验失败这是最常见的问题。现象是请求发出去后接口返回 401 或一串{status_code: 100}。原因几乎都是签名参数失效或者 Cookie 过期。排查思路是先对比浏览器当前请求和脚本请求的 Headers把缺失的 Referer、Origin、User-Agent 补上如果还不行重新回浏览器拿新的 a_bogus 参数。这里我踩过最深的坑是 a_bogus 有效期比 Cookie 还要短所以研究阶段宁可在浏览器里手动重放也不要在脚本里反复尝试。5.2 枚举和字段顺序对不上解析出来的 message_type 是 7 位数字但网页端显示明明是文字消息或者字段值错乱。这种情况一般是 .proto 里枚举定义不正确或者字段编号本身标错了。我的建议是回到控制变量法发送一次特殊内容在二进制里定位到变化的那一段再结合长度重新判断字段编号。不要相信别人博客里给死的字段编号抖音调整频率并不低。5.3 Content 字段里还有一层编码解析信息显示 content 字段是一串看起来很像 base64 的字符串直接 base64 解码后又是 JSON。这是因为业务方在 Protobuf 之上又做了一层自定义编码。遇到这种情况处理逻辑是先判断字符串是否包含{如果不包含就先 base64 解码再 JSON 解析如果解码出来还是一段二进制那就得继续用 Protobuf 解析。我在处理图片消息时遇到的就是这种三层嵌套。5.4 风控与频率限制明明参数都对、签名也对但发了几条消息后突然所有请求都异常甚至出现滑块验证。这是触发风控了。我的实操经验是Web端私信接口的频率阈值比想象中低普通研究场景以分钟为单位拉取一次就够了发送消息则尽量不要用脚本自动化只做理论验证。另外用小号测试不要拿主号冒险。我把上面这些问题整理成一个速查表方便你排查时对照现象可能原因处理建议401 参数校验失败a_bogus 过期 / Cookie 失效重放浏览器请求重新获取签名参数响应体解析为空响应是 gzip 压缩或 chunked 编码用 Wireshark 或 Python 自动解压消息列表缺失部分记录repeated 字段没标记修改 typedef标记为 repeated枚举值异常枚举编号定义错误控制变量法重测别用旧数据硬套content 乱码存在 base64 / 嵌套 Protobuf 二次编码逐层解码判断是否包含 JSON 结构请求被风控频率过高降低请求频率使用小号测试整个项目做下来我最深的体会是像 Protobuf 这种二进制协议只要理解了 wire format 的编码规则破解本身并不算难真正花时间的全在各种业务细节——字段含义的确认、枚举值的推断、多层嵌套的解码。再加上抖音这类平台会不定期调整字段结构任何写死的代码都活不过太久。所以如果你也想做类似的事我建议你把重点放在搭建一套“能快速探测字段变化”的流程上而不是某一个写死的脚本。比如把 blackboxprotobuf 的 typedef 保存成 JSON 文件每次发现数据解析不对就先重新探测字段结构再更新 typedef。这样才能在协议变动时用最小成本跟上节奏。另外技术研究一定要守住边界。解析自己账号的数据、做本地备份、写点自动化小工具这些都合理合法但不要去采集和骚扰他人更不要将这类技术用于批量私信推广或其他灰产场景。研究协议本身是很有趣的但对它的使用方式决定了这件事的价值。