news 2026/9/15 15:29:06

抓包全是图片?私有长连接协议逆向与容灾破局指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
抓包全是图片?私有长连接协议逆向与容灾破局指南

你有没有遇到过这种情况:手机连着抓包代理,CA 证书也按教程装好了,代理配置完全正常,但打开一个头部 App 随手刷了几屏,抓包面板里满满当当全是图片请求,偶尔冒出几个 JSON 还是启动配置、埋点上报之类无关紧要的东西,最想看的业务数据一个都抓不到。

很多人这个时候会陷入自我怀疑:证书没装好?App 检测到代理了?要不要去网上找绕过证书校验的方案?我早年也在这个圈子里绕了很久,后来才意识到问题的方向从一开始就偏了——你盯着 HTTP 面板找数据,可这个 App 的核心业务流量根本没走过 HTTP。它走的是另一条完全独立的通道:私有长连接。这篇文章就把这件事背后的原理、协议特征、逆向路径,以及我实战中真正好用的“逆向容灾”破局方法一次讲清楚,给正在做安全研究和客户端开发的读者一个可落地的参考。

1. 抓包面板只剩图片?先搞清楚流量真正去了哪

1.1 不是没有数据,是数据走了你看不懂的通道

先说一个很多人忽略的事实:Charles、Fiddler 这类工具本质上是 HTTP/HTTPS 代理,它们只关心带有 HTTP 语义的流量——方法、路径、头、状态码。App 里的图片、JS、CSS 这些静态资源请求确实走 HTTP,所以你能在代理面板里看到一堆图片。

但一个成熟的头部 App,核心业务链路早就不是“每次请求都走一遍 HTTP”了。动态 Feed、搜索结果、评论、私信、IM 消息、系统通知,这些数据走的是一套自研的 TCP 长连接协议,通常是二进制格式,跟 HTTP 没有任何关系。代理工具连解析入口都找不到,自然不会在面板里展示给你。

如果你这时候把抓包工具换成 Wireshark,从网卡层面看流量,你会发现数据一直在跑。一条持续不断的长连接占据着某个固定端口,流量规律地进进出出,只是一旦尝试解码应用层内容,得到的全是乱码。数据一直都在,只是它用了你“看不懂”的语言。

1.2 长连接承载了几乎所有实时业务

要理解这个现象,得先从 App 的整体流量架构说起。

一套典型的大型应用网络架构,流量大致分两类。一类是“低频、大包、可缓存”的静态资源,比如图片、视频、安装包、样式文件,这些资源天然适合 HTTP + CDN,客户端发一次请求,CDN 边缘节点直接返回,响应快、不占源站带宽。你在抓包工具里看到的大量图片,就是这类流量。

另一类是“高频、小包、要实时”的业务数据,比如列表页的下拉刷新、IM 消息、点赞评论通知、App 内的统一推送。这类流量有几个共同特点:对延迟敏感、需要服务端主动推送、请求频率高但单包很小。如果每一条都走 HTTP,每次都要重新建 TCP 连接、走 TLS 握手,性能和功耗都是灾难。

所以头部应用的做法是:把一个应用里所有需要实时交互的能力,全部收敛到一条长连接上。客户端启动后建立一个 TCP 长连,登录之后保持着,后续的所有业务请求和响应都在这条连接上跑。一个服务端网关统一接入、统一调度、统一推送。你的所有“核心数据”,都在这条你看不见的连接里。

1.3 “全是图片”这个现象本身就是线索

我后来复盘时有个很重要的体会:“抓包面板全是图片”这个现象不能只当作文下一次性的结论,它本身就是一条高质量的线索。

它至少告诉你三件事。第一,这个 App 的业务流量通道和静态资源通道是分离的,HTTP 层面的代理对你没有意义,不用再纠结证书和绕过方案。第二,存在一条值得被分析的私有长连接,流量是二进制的,大概率有固定协议头。第三,如果你的 Wireshark 里出现了某个 TCP 流被启发式识别成媒体流的“异常”现象,那往往不是真的图片,而是协议解析器对未知二进制流的误判——这种现象本身就能帮你快速定位到那条关键流量。

所以我的习惯是:遇到“抓不到数据”的情况,第一反应不是换抓包工具,而是打开 Wireshark 看一眼完整的 TCP 连接列表,先把“哪条连接在干活”摸清楚。

2. 私有长连接协议的基础认知

2.1 头部 App 为什么要自建长连接:不止是省流量

很多人以为大厂自研长连接协议是为了防抓包、防逆向,这个理解其实本末倒置了。长连接的核心动机是工程上的性能诉求,防抓包只是附带红利。

首先是省电和资源开销。TCP 建连需要三次握手,TLS 还需要 1-2 个 RTT 的密钥协商。一个高频使用的 App,如果每次刷新都走一遍完整握手,电量和网络资源的消耗会明显上升。保持一条长连接,后续请求只需要在已有连接上发数据,开销小得多。

然后是实时性。HTTP 是“客户端主动拉”的模型,服务端没法主动往客户端推数据。IM 消息、订单状态变更、风控通知这类场景要求服务端能随时触达客户端,长连接天然支持双向通信。这也是为什么聊天类、直播类应用格外依赖长连接。

还有一点是弱网适应性和调度能力。移动网络下 IP 会频繁切换,长连接配合会话保持、连接迁移机制,能比短连接更平滑地应对网络变化。服务端网关还能利用一条连接做批量请求合并、按优先级调度,这些都是 HTTP 短连接模型做不到的。

2.2 一个典型私有协议包长什么样

既然要分析,就得知道目标长什么样。虽然各家协议细节不同,但二进制私有协议的设计思路上高度一致。我拆过几种不同应用的协议,包结构基本都长这样:

字段长度作用
魔数 Magic4 字节固定标识,用于快速识别协议,常见如 0xAA 0xBB 0xCC 0xDD
版本号1 字节协议版本,客户端升级后用来兼容新旧格式
命令字2 字节表示消息类型,比如 0x0001 是登录、0x0002 是拉取列表
序列号4 字节请求和响应对应的凭证,长连接上靠它区分是谁的响应
包体长度4 字节用于粘包拆包,接收方根据这个值读取完整包
加密标志1 字节标识包体是否加密、用的是哪套算法
包体N 字节真正数据,可能是 protobuf、msgpack,也可能是加密后的密文
校验值4 字节CRC32 或哈希,用于完整性校验

这个结构里,魔数和序列号对逆向分析尤其重要。魔数是你的“入场券”——只要在字节流里反复出现固定值,基本就能确定协议边界;序列号则帮你把请求和响应配对,哪怕整条连接上消息交织在一起,你也能理出逻辑。

2.3 抓包工具集体失效的根本原因

搞清楚了协议结构,再回头看“为什么抓包工具在这里集体失效”,原因就非常清晰了。

第一是协议不可识别。HTTP 有明文的方法名、路径、请求头,代理工具靠这些字段建立“请求-响应”模型。私有长连接没有这些语义,只有二进制头,代理工具不认,自然不显示。

第二是加密非标准化。TLS 还能靠代理重签证书来做中间人解密,但自研协议往往用的是自定义密钥协商,甚至直接在每次会话开始动态生成会话密钥。密钥不在握手阶段以标准方式交换,中间人就算拿到了流量,也没有解密入口。

第三是状态机割裂。HTTP 请求天然是一问一答,代理工具建立一个“请求→响应”配对很简单。长连接上同时跑着几十个未完成的请求,靠序列号在应用层做关联,抓包工具不知道序列号规则,只能看到一堆无头无尾的字节流。三个原因叠加,就是你“抓不到数据”的全部真相。

3. 逆向分析的第一层:摸清协议与定位加密点

3.1 静态分析先定位协议框架和加密库

面对一条私有长连接,我建议的第一步不是急着 hook,而是先做静态分析,把协议栈的大致构成摸清楚。

对 Android 应用来说,拿到 APK 后直接丢进 jadx 打开,搜索几个高频关键词:SocketOutputStreamProtocolEncoderDecoderCipherAESRSAMessagePacketBuffer。这些名字能帮你快速圈定协议处理代码的位置。

然后是看 so 库。现在的头部 App 几乎不会把核心协议逻辑放在 Java 层,太容易被 hook 了。你会在 APK 的lib/armeabi-v7alib/arm64-v8a目录下看到一堆.so文件,重点关注名字里带corenwprotocolcrypto的那几个。用 IDA 或 Ghidra 打开,先看导出的符号表,找sendrecvencryptdecrypt这类函数名。

还要留意它引用了哪些第三方序列化库。如果项目里用到了 protobuf,你会看到很多parseFromwriteTo的调用痕迹;如果是 msgpack,会有packunpack;flatbuffers 则是一堆GetRootXX开头的函数。这一步决定了你后面还原业务数据时要写哪种解析器。

3.2 动态插桩:从系统调用层开始挂

静态分析解决的是“代码在哪”的问题,接下来要解决“数据长什么样”的问题,这一步必须靠动态插桩。

我最常用的工具是 Frida。起步姿势很固定:先 hooksendrecv,看看协议包原始字节流到底什么样。

// frida -U -f com.example.app -l hook_send.js Java.perform(function () { var send = Module.findExportByName(null, "send"); if (send) { Interceptor.attach(send, { onEnter: function (args) { var len = args[2].toInt32(); if (len > 0) { var data = Memory.readByteArray(args[1], len); console.log("[send] fd=" + args[0].toInt32() + " len=" + len); console.log(hexdump(data, { offset: 0, length: len, header: true, ansi: true })); } } }); } });

脚本跑起来后,操作一遍 App 的关键功能,观察输出。这里有个经验:日志一定会很爆炸,所以要学会按条件过滤。先看 fd,找到那条长连接的 fd 是几,然后只打印这个 fd 的数据;同时按长度过滤,小于几十字节的心跳包直接跳过,重点关注中等长度以上的业务包。

如果 send 出来的字节流里带着明显的魔数开头,比如aa bb cc dd,说明协议是明文组包、后面再做加密或整体下发。如果满脸乱码、没有任何可读特征,说明可能在更上游就已经加密了。这时候就要顺着调用栈往上找,看数据在进入 send 之前经过了哪个函数——这个函数就是加密函数。

3.3 从 TCP 流特征反推协议格式

动态插桩能让你看到“活的”协议,但有些场景下没法长时间挂 Frida,那就回到流量侧,用 tcpdump 或 Wireshark 抓到原始字节流,再做离线分析。

我常用的一套流程是这样的:先在手机上用 tcpdump 抓一段流量,导出到 PC 上,用 Python 的 scapy 读取原始 TCP payload,然后扫描整个流里重复出现的头部字节模式。

from scapy.all import rdpcap packets = rdpcap("capture.pcap") payload_chunks = [] for pkt in packets: if pkt.haslayer("TCP") and pkt[2].payload: # 去掉 IP/TCP 头,取原始负载 raw = bytes(pkt[2].payload) if len(raw) > 16: payload_chunks.append(raw) # 寻找出现次数最多的头部模式,通常就是魔数 from collections import Counter prefix_counts = Counter() for chunk in payload_chunks: prefix_counts[chunk[:4]] += 1 print(prefix_counts.most_common(10))

跑完之后你会看到一个出现频率极高的前 4 字节,那基本就是魔数。有了魔数,再去做字段切割:比较多个包的长度与内容,长度字段、命令字段、序列号字段会逐渐浮出水面。

这一步的要点是“多取对比样本”。同一个接口多触发几次,对比两次请求之间哪些字段在变、哪些不变。变的字段里,序列号是递增的,时间戳是接近当前时间的,其他不定字段大概率是加密参数或随机量。

4. 逆向容灾:用工程冗余思维降维破局

4.1 为什么单一路径经常走不通

聊完常规路径,我想讲一个比具体招式更重要的方法论,也是标题里“逆向容灾”想表达的东西。

实际对抗中你会发现,单一分析路径特别容易断。你用代理抓包,它有证书校验;你去 hooksend,它有反 Frida 检测;你刚想静态分析 so,发现代码早就被混淆加 VMP 了;你好不容易定位到加密函数,发现密钥是每次会话动态协商的。每一步都对应着一道防御,单一路径走到底几乎必死。

“容灾”这个词来自后端架构,意思是系统出故障时能自动切换到备用节点,保证服务不中断。我把这套思路搬到了逆向分析里:不押注在单一方案上,而是设计一套多路径并行的分析管线,任何一条被对抗堵死,立刻切换到另一条,保证整个分析过程不中断。这就是“逆向容灾”的降维打击——你不是在和对方某一个防御点死磕,而是用体系对抗单点。

4.2 一套可切换的分析管线

我实际用的分析管线分四层,每层内部都有主备两条路径:

第一层是入口判断。主路径是 HTTP 代理抓包,用来判断“业务流到底走不走 HTTP”;备用路径是 tcpdump 底层抓包,用来确认长连接的位置、端口、频率。这一层的产出不是数据,而是“方向”。

第二层是数据获取。主路径是 hooksend/recv,拿到原始字节流;备用路径是透明网关注入,把流量重定向到分析机上做镜像。如果 App 检测到代理特征导致连接被重置,就走备用路径。

第三层是解密定位。主路径是 hook 已知系统加密库(AES_encryptRSA_private_decrypt这类导出符号);备用路径是 hook 应用自定义加解密方法,直接在上层拿解密后的数据。

第四层是算法还原。主路径是用unidbg这类 JNI 模拟框架把核心算法摘出来跑,不依赖真机环境;备用路径是直接内存 dump,把密钥和中间结果一次性捞出来。

这四层每一层都是独立的,上一层的输出传给下一层,但任意一层的“主”挂了,都能用“备”顶上。整个管线像一个容灾系统,从数据获取到算法复现始终保持链条畅通。

4.3 容灾切换决策表与优先级规则

为了让这套思路落地,我习惯把不同场景下的卡点和切换策略整理成一张表,分析卡壳时直接对着表做判断:

阶段卡点典型信号切换策略
代理抓包无业务流量面板只有图片和静态资源放弃 HTTP 层,转 tcpdump 找长连接
连接建立后立刻断开疑似检测到代理特征换透明网关方案,不在客户端本地挂代理
hook send/recv 输出乱码字节流无魔数、无可读结构说明上游已加密,向上查找加解密函数
SSL 握手失败或证书错误连接被重置放弃重签证书,直接 hook 加解密函数在内存中取明文
加解密函数被混淆静态分析看半天没头绪转 unidbg 模拟执行,不分析算法只观察输入输出
模拟执行环境被检测进程运行崩溃或超时转内存 dump,直接捞密钥和明文缓冲

优先级规则也很简单:能先走流量侧解决的,不急着挂钩子;能挂钩子解决的,不急着抠算法;能抠算法的,不急着碰加固。每一步都在上一个方案失败后才升级,避免在一棵树上吊死。

5. 实战复盘:从“全是图片”到协议复原

5.1 案例信息收集与初步假设

理论讲完,我用一个虚拟案例把整套流程串一遍。假设你面对的是一个社交类 App,目标是想搞清楚列表页数据是怎么下发的、协议长什么样。

第一步,按容灾管线的入口判断层操作。Charles 挂代理打开 App,刷列表,面板里除了图片就是埋点 JSON。业务数据一条都没有。到这里,基本上可以判定它走的是私有长连接,接下来切换到 tcpdump 从底层抓包。

抓了几分钟,拿到一份 pcap。过滤出 TCP 流后,发现客户端和某个 IP 的某个固定端口之间有一条长连接,维持了全程,流量密集。导出 payload 后跑一遍前文说的前缀扫描,很快就看到一个出现频率极高的四字节魔数:0x12 0x34 0xAB 0xCD。方向确认。

5.2 关键突破:Hook 到加密入口拿到明文

有了魔数,下一步要确定包体是明文还是密文。直接在 Frida 里 hooksend,看到你发出去的每一个包都是整齐的协议头,但包体部分全是高熵乱码,没有可读字段,基本判定包体是加密的。

顺着调用栈往上追。这种场景下,frida 的Thread.backtrace能打出调用栈,把栈顶附近的函数一个个看过去,最后定位到一个 Native 函数,名字大概叫xxx_encrypt_packet。这个函数接收一个明文 buffer、输出一个密文 buffer。在这个函数的返回处挂一个读内存的操作,明文就直接出现了。

跑通一次之后,再把recv对应链路也 hook 上。响应侧同样有一层解密逻辑,输入是密文、输出是明文。把两边的明文都打出来,整个协议的业务层就完全暴露在你面前了。

5.3 协议还原与验证的完整链路

拿到明文的下一步,是写一个离线解析脚本把消息体解出来。假设包体是 protobuf,直接用protoc--decode_raw看一眼原始字段编号,再配合已知的明文内容,很快能还原出业务字段。

import protobuf_pb2 as pb data_bytes = bytes.fromhex( "0a 11 1a 0f ..." # 解密后的 protobuf 报文 ) msg = pb.FeedResponse() msg.ParseFromString(data_bytes) print(msg)

还原出消息结构之后,还有一个必须做的步骤:验证。把解析脚本跑一遍,拿解析出的条数与 App 页面上实际展示的条数对照,再对比一条数据的 ID 是否和页面显示一致。如果都对上了,恭喜你,这个协议就算被完整破译了。

验证完之后还要做回归测试——升级版本后重测一遍,看看协议头里有没有出现新字段、加密逻辑有没有变化。我遇到很多情况是算法没变、只是密钥协商方式变了,这种情况下把密钥协商的部分单独拎出来分析即可,不用从零重新来一遍。

6. 几个我在实战中踩过的坑和最后的边界提醒

最后聊几个实际的体会,都是常规文章里不太会讲但很影响效率的细节。

第一,别只盯 Java 层。很多人在 jadx 里看到一堆眼花缭乱的 Java 代码,以为协议逻辑都在这一层,结果折腾半天发现真正的加解密核心在 so 的 Native 函数里,Java 层只是做了层薄封装。我现在的习惯是一开始就做好双线排查,Java 层和 Native 层同时铺开,谁先有结果就顺着谁走。

第二,注意半包和粘包。长连接上数据是流式的,你 hookrecv拿到的一次数据不一定正好是一个完整协议包,可能是半个包,也可能包含两个包。遇到这种问题别慌,把字节先拼起来,再依照长度字段拆包。这个坑在数据量大的时候尤其容易出现,Frida 日志里如果全是长度对不上的乱包,先想想是不是没做粘包处理。

第三,版本升级后协议一定会变。私有协议不是一次性定死的,客户端发版通常伴随协议调整。所以逆向分析的结果要写成脚本、留下文档,而不是记在脑子里。下次协议变更时,对比新旧差异会省很多事。

第四,也是最重要的:这套方法只应该用在你拥有或者已经获得授权的应用上。安全研究、漏洞挖掘、防御方案的制定,都是正当用途。想通过逆向去爬取用户数据、绕过付费验证、破解商业产品拿去做灰黑产,这既不合法,也违背了技术研究的初衷。我在分析过程中还会刻意控制数据采集范围,只抓必要的协议样本,分析完就清理,不做无意义的留存。

回到最初的问题——抓包面板全是图片没有数据,从来不是一个“抓不到”的问题,而是一个“方向没找对”的问题。那些你看不见的数据,永远都在那条你看不懂的长连接里流动着。理解了私有长连接的设计逻辑,再配合一套“逆向容灾”的多路径分析思路,主动权就能回到你手里。下次再碰上一个抓包抓了个寂寞的 App,不妨先放下代理,打开 Wireshark,问自己一句:它跟哪个 IP 的哪条连接,一直在说话。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 15:28:55

两阶段鲁棒优化在微网多电源容量配置中的MATLAB实现与CCG求解

简介:这是一份面向微电网规划与电力系统优化方向的两阶段鲁棒优化容量配置Matlab代码包,适合电气工程、自动化及数学相关专业学生用于课程设计、毕业设计或算法复现。代码基于参数化编程,提供2014/2019a/2021a版本兼容的M文件与运行结果&…

作者头像 李华
网站建设 2026/9/15 15:28:24

GESP C++七级80+高分备考指南

这份指南适配四年级零基础孩子的学习节奏,每天仅需30-40分钟,兼顾校内文化课进度,6周即可稳稳冲刺80高分,拿到CSP-J初赛免试资格。 一、核心考点权重拆解 80得分的核心是优先抓分值占比最高的模块,把精力集中在性价比…

作者头像 李华
网站建设 2026/9/15 15:25:43

深圳全网站建设公司安全避坑指南,一文搞懂

深圳全网站建设公司安全避坑指南,一文搞懂 别再盯着那些花里胡哨的模板网站发呆了,真的,模板网站太丑不够用,更可怕的是它背后隐藏的安全黑洞。很多老板觉得只要页面好看就行,结果上线没两周,后台被黑、数据被拖、页面被挂黄码,哭都来不及。…

作者头像 李华
网站建设 2026/9/15 15:24:46

STM32F407驱动TB6612双路电机的硬件-软件协同调试指南

简介:这是一套面向嵌入式初学者与电机控制项目开发者的STM32F407硬件驱动开发完整方案,聚焦于多路电机协同控制场景,适用于智能小车、自动化设备原型验证等实践需求。资源包含244个文件,以68个C源码和73个头文件构成核心固件框架&…

作者头像 李华