简介:这份资料面向对网络协议逆向工程与即时通讯协议分析感兴趣的安全研究人员、逆向工程师及高校学生,聚焦协议抓包、字段解析与通信流程还原等实际问题。内容涉及法国学者Georges Bossert与Frédéric Guihéry在协议逆向方面的研究成果,并收录香港中文大学关于微信协议分析的PDF论文,可帮助读者理解从流量捕获到协议结构推断的完整思路,适合具备一定网络与逆向基础的中高级学习者。资源以zip压缩包形式提供,整体约3.11MB,体积轻便,便于离线查阅与归档。目前已有1388人学习下载,说明其在协议分析方向具有一定参考价值。读者可从中获取协议逆向的方法论框架、微信通信协议的学术分析视角,以及对照论文梳理字段含义与交互流程的线索,为后续抓包实验与协议研究工作提供理论支撑与思路启发。
1. 从一次抓包翻车说起:协议逆向到底在逆向什么
去年帮一个做游戏后端的朋友排查联机延迟问题,他信誓旦旦说“肯定是服务器带宽不够”,结果我在他机器上挂了个抓包工具跑了十分钟,发现客户端每隔 200ms 就发一个几十字节的小包,里面塞着一堆看起来像随机数的字段。带宽根本不是瓶颈,真正的问题是这套私有协议在弱网下没有做任何重传和合并,丢一个包就得等下一个周期。这件事让我重新审视“网络协议分析逆向”这件事——它从来不是把二进制流翻译成人类可读文本那么简单,而是要先搞清楚这套协议在解决什么问题、它的状态机长什么样、哪些字段是真正驱动行为的。微信这类国民级应用的协议分析更是如此,网上流传的所谓“微信协议”资料九成是过时的或者干脆是臆想的,真正有价值的是那套分析方法本身。这篇内容面向三类人:想系统入门协议逆向的新手、手里有抓包数据但看不出门道的中级选手,以及想把分析方法迁移到游戏协议、IoT 协议上的老手。我会把抓包、字段定位、状态还原、加密层剥离这条链路拆开讲,中间穿插我自己踩过的坑,能抄作业的地方直接给步骤和判断依据。
2. 抓包之前先想清楚:三层环境与四个观测点
2.1 为什么直接开 Wireshark 往往抓了个寂寞
新手最常见的动作是装个抓包工具,选上网卡,点开始,然后对着一屏 TCP 重传和 TLS 握手发呆。问题出在观测层次没选对。协议分析至少要区分三层环境:本机回环层(客户端与本地代理/模拟器之间的流量)、宿主网络层(真实网卡出口)、应用层载荷(解密后的业务数据)。绝大多数人卡在第二层,因为现代应用默认走 TLS,你抓到的是密文,除了看包长和时序什么都得不到。
我一般会先确认目标进程走的是哪条链路。以桌面端为例,用系统自带的资源监视器看目标进程的远程地址和端口,再决定抓哪块网卡。如果是移动端模拟器,流量往往先经过宿主的一个虚拟网卡,这时候抓宿主网卡比抓模拟器内部更靠谱。判断依据很简单:抓到的包如果全是 443 端口且长度规整,说明你只拿到了 TLS 记录层,需要往下走解密或中间人方案;如果能看到明文 HTTP 或自定义二进制头,说明这一层已经够用了。
四个观测点我习惯按顺序过一遍:连接建立阶段(看 TLS 版本、SNI、证书链特征)、请求响应节奏(看心跳周期、并发数)、载荷长度分布(看是否有固定头部)、异常路径(断网重连、切后台再回来时的包序列)。这四个点过完,基本能判断这套协议是“短连接轮询”还是“长连接推送”,是“文本 JSON”还是“二进制 TLV”。别小看这个判断,它决定了后面字段分析的工作量差一个数量级。
2.2 中间人方案的取舍与证书信任的坑
要看到应用层明文,绕不开中间人。这里不展开具体工具品牌,只说判断逻辑:能改客户端信任链的方案优先于全局劫持,因为前者影响面小、可复现,后者容易把系统其他流量搅乱。常见做法是给目标环境单独装一张自签根证书,让客户端信任它,然后代理进程用它动态签发站点证书。难点在于很多应用做了证书固定(pinning),你换了证书它直接拒绝连接。
遇到 pinning 不要急着上 hook 框架,先确认固定的是哪一层:有的应用只固定叶子证书的公钥哈希,有的固定中间 CA,有的干脆在 native 层做校验。判断方法是逐步替换证书看在哪一步断掉。如果替换叶子证书就断,说明固定的是叶子;如果换中间 CA 才断,说明固定的是链。这个判断直接决定你后面是改配置还是上动态插桩。我见过有人一上来就上重型框架,结果目标应用根本没做 pinning,白折腾两天。
提示:中间人环境只在自己可控的测试设备和测试账号上做,别拿生产账号和真实业务数据练手,这是底线。
2.3 一个最小可用的抓包脚本骨架
下面这段 Python 用 scapy 做最基础的包捕获和按五元组聚合,目的是让你在正式上重型工具前先确认链路通不通。它不依赖任何商业工具,跑通说明你的网卡选择和权限没问题。
from scapy.all import sniff, IP, TCP from collections import defaultdict # 按 (源IP,源端口,目的IP,目的端口) 聚合,统计包数和字节数 flows = defaultdict(lambda: {"pkts": 0, "bytes": 0}) def handle(pkt): if IP in pkt and TCP in pkt: key = (pkt[IP].src, pkt[TCP].sport, pkt[IP].dst, pkt[TCP].dport) flows[key]["pkts"] += 1 flows[key]["bytes"] += len(pkt[TCP].payload) # iface 换成你实际要抓的网卡名,count 先跑 200 个包看分布 sniff(iface="eth0", prn=handle, count=200, store=False) for k, v in sorted(flows.items(), key=lambda x: -x[1]["bytes"])[:10]: print(k, v)这段代码的关键在iface参数,Linux 下用ip link看网卡名,Windows 下用Get-NetAdapter。count=200是防止无限跑,先看前 200 个包的流向分布,找出流量最大的那条五元组,那就是你的主战场。store=False是为了不把包全存内存里,长时间抓包必须加。跑完如果发现某条流字节数远大于其他,基本可以锁定它是业务主通道,后面重点分析它。
3. 字段定位:从字节流里把“有意义的那几个字节”揪出来
3.1 固定头部与长度字段的识别套路
拿到一条业务流的原始字节后,第一步不是猜每个字段含义,而是找结构。绝大多数二进制协议都有固定头部,头部里至少有一个长度字段和一个类型/命令字段。识别长度字段有个笨但有效的办法:把连续多个包的同一偏移位置的字节取出来,看哪个位置的值和包总长呈线性关系。比如偏移 0x04 处的两字节大端值如果总是等于包长减 8,那它大概率就是长度字段。
我一般会写个小脚本把同一条流的前 50 个包按偏移对齐打印成十六进制矩阵,肉眼扫一遍找规律。下面这段是核心逻辑,把每个包的 payload 按字节展开成一行,方便对齐比较。
# 假设 pkts 是已按时间排序的 payload 字节列表 for i, p in enumerate(pkts[:50]): # 每字节两位十六进制,空格分隔,方便按列对齐 print(f"{i:03d} len={len(p):4d} " + " ".join(f"{b:02x}" for b in p[:32]))跑出来你会看到类似“第 4、5 字节在所有包里都是 00 1a”这种规律,那就是长度或版本号。p[:32]只打印前 32 字节,因为头部信息基本都在前面,打太多反而看不清。如果发现某几列的值随包长变化,重点盯那几列。这个方法的局限是遇到变长头部或压缩载荷会失效,那时候就得上状态机还原了。
3.2 用差分法定位命令字与业务字段
固定头部找到后,剩下的就是区分“命令字”和“业务参数”。差分法的思路是:控制变量,只改一个业务动作,看哪个字节变了。比如在客户端里依次做“登录”“拉取列表”“发一条消息”三个动作,各抓几个包,对比同一偏移位置的值。登录包的某字节是 0x01,拉列表是 0x02,发消息是 0x03,那这个字节就是命令字。命令字后面紧跟的往往是序列号或会话标识,再往后才是业务载荷。
这里有个血泪经验:别假设字段一定按顺序排列。我遇到过一套协议把长度字段放在包尾的,也见过命令字藏在载荷中间的。差分的时候如果发现某个字节在同类动作里稳定、跨类动作里变化,先记下来,别急着下结论,多抓几组再验证。验证方法是构造一个只改这个字节的包重放,看服务端行为是否按预期变化——这一步在测试环境做,别碰线上。
3.3 变长字段与嵌套结构的拆解
当协议里出现“列表”“数组”这类结构时,通常会有一个计数字段加若干变长元素。拆解顺序是:先找计数(一个较小的整数,值等于后面元素个数),再按元素内部的长度前缀逐个切。如果元素本身还有嵌套,就递归套用同样的方法。判断依据是切完之后剩余字节应该刚好为 0,如果切完还剩一堆,说明你的长度理解错了,回去重新对齐。
我习惯在脚本里加一个断言:解析完整个包后偏移量必须等于包长,不等就打印剩余字节。这个断言帮我抓出过好几次“长度字段其实是包含头部自身”的误解。很多协议文档不写清楚长度是否含头,只能靠这个断言反推。
4. 状态还原:把一堆孤立的包串成一条会话
4.1 会话标识与请求响应的配对
单看一个包永远看不出协议全貌,必须还原会话。第一步是找会话标识:登录响应里通常会下发一个 token 或 session id,后续请求都带着它。找到之后按这个标识把包分组,组内再按序列号或时间排序。请求和响应的配对靠序列号最稳,没有序列号就靠“请求后第一个同命令字的反向包”来配,但这种方法在并发高的时候会错配。
我一般会先统计每个会话的包数和持续时间,找出“长会话”和“短会话”。长会话往往是心跳加推送,短会话是请求响应。两类分开分析,别混在一起看,混在一起你会觉得协议毫无规律。判断依据是长会话的包间隔是否稳定,稳定就是心跳,不稳定就是推送或流式传输。
4.2 心跳、重连与超时参数的提取
心跳包的特征是周期固定、载荷极小、响应也极小。提取心跳周期的方法是把长会话里相邻包的时间差排序,取出现频率最高的那个值。重连逻辑则要看断网后的包序列:客户端是先重发未确认的请求,还是先重新登录拿新 token,这决定了这套协议有没有做幂等。超时参数看的是“发出请求后多久没响应会触发重试”,这个值在弱网优化里非常关键。
这些参数不是拿来炫技的,它们直接决定你能不能复现一个稳定的客户端行为。比如你要做一个协议兼容的测试工具,心跳周期设错会导致服务端判定你离线,重连逻辑设错会导致重复提交。我见过有人把心跳周期照抄成 30 秒,结果目标服务端实际要求 15 秒,工具跑一会儿就被踢。
4.3 用状态机图(文字版)固化理解
还原到一定程度,我会用文字把状态迁移写下来,不画图,就写成“状态A --事件X--> 状态B”这种列表。比如“未登录 --发送登录请求--> 登录中 --收到成功响应--> 已登录 --心跳超时--> 未登录”。写的过程就是查漏的过程,你会发现有些迁移你根本没抓到过包,那就得回去补抓。这个文字状态机后面写测试用例、写模拟客户端都直接能用,比一堆散落的笔记强得多。
5. 加密层剥离:什么时候该停手,什么时候该继续
5.1 判断加密发生在哪一层
不是所有加密都值得剥。先判断加密发生在传输层(TLS)还是应用层(自定义加密)。传输层加密靠中间人就能解决,应用层加密则要看密钥来源:如果密钥是硬编码在客户端里的,那属于“防君子不防小人”,分析价值有限但成本也低;如果密钥是服务端动态下发的,且和会话绑定,那剥离成本会高很多,要评估投入产出比。
判断方法:解密 TLS 后如果载荷仍是高熵字节(看起来像随机数),说明还有应用层加密;如果载荷是规整的 JSON 或 TLV,说明只有传输层加密。高熵判断可以简单看字节分布,随机字节的十六进制里 0-9a-f 出现频率接近均匀,文本则集中在少数几个值上。
5.2 常见自定义加密的识别特征
自定义加密常见几类:固定密钥异或、滚动异或、AES 加固定 IV、以及基于时间戳的动态密钥。识别特征是看相同明文在不同时间抓包后密文是否一致:一致说明密钥和 IV 都固定;不一致但长度一致,可能是 CBC 模式加随机 IV 或滚动异或;长度都不一致,可能有压缩或变长编码。我一般先抓两次相同操作的包对比,这一步能排除掉一半的可能性。
注意:分析加密的目的是理解协议行为、做兼容性测试或安全评估,不要用于绕过任何访问控制或获取未授权数据。
5.3 剥离后的验证:用重放确认理解正确
剥离加密、改完字段后,一定要做重放验证。重放不是简单把包原样发回去,而是只改你要验证的那个字段,其他保持原样,看服务端响应是否符合预期。如果改了字段服务端没反应,可能是你改的字段根本不参与业务逻辑,也可能是校验和没重算。校验和的位置通常在头部末尾或包尾,改完载荷要同步更新。这一步做扎实,你对协议的理解才算真正落地,否则全是纸上谈兵。
6. 避坑清单:五个让我返工过的真实问题
6.1 抓包抓到的是代理自己的流量
现象:抓了半天发现所有包都指向本机某个端口,业务包一个没有。原因:系统代理或抓包工具自身把流量劫持了,你抓的是工具和上游之间的流量。解决:先关掉所有代理,确认目标进程直连,再决定在哪一层抓;如果必须用代理,就抓代理的出口网卡而不是回环网卡。
6.2 长度字段理解错导致解析全乱
现象:按长度切包,切到一半偏移对不上,后面全是乱码。原因:长度字段可能包含头部自身,也可能不含,还可能单位是“字”不是“字节”。解决:用第 3 章那个“解析完偏移必须等于包长”的断言反推,多试几种假设,哪种能让所有包都对齐就用哪种。
6.3 把心跳包当成业务包分析
现象:某个包每 15 秒出现一次,载荷固定,怎么改服务端都没反应。原因:那是心跳,不承载业务。解决:先按包间隔频率把心跳筛掉,再分析剩下的;判断依据是改它不影响任何业务状态。
6.4 证书固定没识别就上中间人
现象:代理配好了,目标应用就是连不上,日志里一堆握手失败。原因:应用做了证书固定,不信任你的自签证书。解决:按 2.2 的方法逐步替换证书定位固定层级,再决定是改配置还是上插桩,别一上来就堆工具。
6.5 在并发场景下错配请求响应
现象:请求和响应配对错乱,分析出的字段含义自相矛盾。原因:高并发下多个请求同时在途,靠“下一个反向包”配对不可靠。解决:优先找序列号字段做配对;没有序列号就按命令字加时间窗口配对,窗口别开太大。
7. 落到一套可复用的分析流程:从抓包到结论的六步法
把前面几章串起来,我实际干活时走的是固定六步,每一步都有明确的产出物和停止条件,避免陷进细节出不来。第一步定观测层,产出是一张“网卡-进程-端口”对照表,停止条件是确认能抓到目标业务流。第二步抓基线,产出是正常操作下的包序列和五元组统计,停止条件是主通道锁定。第三步找结构,产出是头部偏移表和长度字段假设,停止条件是用断言验证所有包能对齐。第四步差分定字段,产出是命令字表和关键业务字段表,停止条件是重放验证通过。第五步还原会话,产出是文字版状态机和心跳/重连参数,停止条件是能解释断网重连的完整包序列。第六步评估加密,产出是“加密层级 + 剥离成本”的判断,停止条件是明确该继续还是该停手。
这套流程的价值在于每一步都有可验证的产出,而不是抓了一堆包存在硬盘里。我见过太多人抓包抓了几个 G,问他协议长什么样说不出来,就是缺了“产出物”这个约束。下面这张表是我用来记录每一步结论的模板,字段不多但够用。
| 步骤 | 产出物 | 验证方式 | 常见返工点 |
|---|---|---|---|
| 定观测层 | 网卡-进程-端口对照表 | 抓到目标业务流 | 抓到代理自身流量 |
| 抓基线 | 五元组流量统计 | 主通道字节数占比最高 | 心跳混入业务流 |
| 找结构 | 头部偏移表 | 解析偏移等于包长 | 长度含头与否判断错 |
| 差分定字段 | 命令字与业务字段表 | 单字段重放响应符合预期 | 并发下配对错乱 |
| 还原会话 | 文字状态机与参数表 | 能解释重连全序列 | 会话标识找错 |
| 评估加密 | 加密层级与成本判断 | 明确继续或停手 | 把应用层加密当传输层 |
具体到操作上,第三步的断言我一般写成函数,每次解析新包都跑一遍,不通过就打印剩余字节的十六进制,肉眼扫一眼往往能看出是不是多切或少切了几个字节。第四步的重放我会先在测试环境构造最小用例,只改一个字段,观察响应码或返回数据的变化,变化符合预期才认为字段定位正确。第五步的状态机我会拿真实断网场景去对,比如手动切飞行模式再切回来,看抓到的包序列是否和文字状态机一致,不一致就说明漏了迁移。
有一个细节值得单独说:别在分析初期就追求“完整还原所有字段”。协议里大量字段是预留位、填充位或者对业务无影响的元数据,全搞清楚性价比极低。我的经验是先覆盖“驱动状态迁移的字段”和“影响数据正确性的字段”,这两类搞清楚,协议就通了八成,剩下的边用边补。这个取舍判断是新手和老手最大的差别之一,新手容易陷进逐字节考据,老手知道哪些字节可以先放着。
回过头看,我最早做协议分析时最大的毛病是“抓到什么分析什么”,没有先想清楚要回答什么问题。后来我强迫自己每次动手前先写一句话,比如“我要确认这套协议在弱网下有没有重传机制”,然后所有抓包和脚本都围绕这句话服务,分析效率完全不一样。从那以后我每次开新协议分析任务,都强制先写这句目标句再动手,没写清楚就不碰抓包工具。希望这套流程能帮到你,少走我当年那些弯路。
本文还有配套的精品资源,点击获取