☰
Arkime 协议解析器架构指南:从 pcap 逆向分析到生产级 C 解析器落地
2026/9/28 2:55:32 网站建设 项目流程
  • 网络安全
  • 网络
  • 后端
  • 数据可视化

【免费下载链接】arkime

Arkime is an open source, large scale, full packet capturing, indexing, and database system.

项目地址:https://gitcode.com/gh_mirrors/ar/arkime
点击查看免费下载

本文是一份面向安全研究人员与 Arkime 开发者的协议解析器构建指南。它以 Arkime 仓库中协议解析器架构 Agent 的完整方法论为骨架,讲解如何从原始 pcap、十六进制转储等二进制数据出发,逆向推断协议结构,并在 Arkime capture 组件中落地为可运行、可测试、可上线的 C 解析器。读完本文,你将掌握协议分析五阶段流程、Arkime 解析器注册与分类机制、Byte Safe Buffers 安全解析范式,以及如何用仓库内现成的模板与测试语料验证自己的解析结果。

一、核心方法论总览:五阶段工作流

协议解析不是"看包猜格式"的玄学,而是一套可重复的系统性工程。无论你面对的是全新自定义协议、私有协议还是已知协议的变体,都建议按以下五个阶段推进:先分析、再假设、后设计、终实现、最后测试打磨。这五个阶段在 capture/DESIGN.md 描述的"解析器 vs 插件"架构中同样适用——解析器需要同时理解数据包的两个生命周期阶段(reader 线程的基础校验、packet 线程的深度解码与 SPI 字段生成)。

阶段目标关键产出
Phase 1 协议分析识别模式、字段边界与封装层次字段级结构清单
Phase 2 假设形成建立可验证的协议理论带注释的报文示例、待验证清单
Phase 3 解析器设计选择解析架构与错误处理策略架构决策与数据表示设计
Phase 4 实现落地为健壮的解析代码可运行解析器 + 使用示例
Phase 5 测试与打磨验证解析结果、覆盖边界验证报告与补充用例建议

二、Phase 1:协议分析——从报文样本中寻找结构

分析的起点永远是"尽可能多的报文样本",尤其是包含错误情形和边界条件的样本。随后按以下顺序系统化排查:

  1. 初步检查:通读所有报文,找出共同模式、共性与差异点。
  2. 结构发现:
    • 区分定长字段与变长字段;
    • 识别头部、载荷、尾部与分隔符;
    • 查找魔数(magic number)、版本号、长度指示字段;
    • 确定字节序(大端/小端);
    • 定位校验和、CRC 等完整性字段。
  3. 模式识别:
    • 横向对比多包,区分"协议常量"与"可变数据";
    • 通过字段值变化的位置推断字段边界;
    • 识别长度前缀字符串或数据块;
    • 识别常见编码(ASCII、UTF-8、BCD 二进制编码十进制)。
  4. 协议分层:判断是传输层、应用层还是自定义层协议,识别是否有隧道/封装。

在 Arkime 仓库中,这一步有现成的练兵素材:tests/pcap/目录存放了大量.pcap与配套.test断言文件(如dns-udp.pcap、dnp3_synthetic.pcap、s7comm相关样本等),tests/gen/目录下的gen_*_synthetic.py脚本(如 gen_s7comm_synthetic.py、gen_dnssec_synthetic.py)则展示了如何程序化生成合成流量来制造确定性的协议样本。分析未知协议时,可用 Wireshark、tcpdump、scapy 等工具先从 pcap 中抽取目标会话的原始字节,再做字段级拆解。

实战案例分析:S7comm 协议栈逆向

以 capture/parsers/s7comm.c 为例,它演示了典型的封装链逆向结果:TCP -> TPKT -> COTP -> S7comm/S7comm-plus。源码开头的常量定义就是分析阶段的直接产出:

/* TPKT header: version(1) + reserved(1) + length(2) = 4 bytes */ #define TPKT_HDR_LEN 4 /* Minimum S7comm header: protocol_id(1) + rosctr(1) + redundancy(2) + pdu_ref(2) + param_len(2) + data_len(2) = 10 */ #define S7COMM_HDR_LEN 10 /* S7comm protocol IDs */ #define S7COMM_PROTOCOL_ID 0x32 #define S7COMM_PLUS_PROTOCOL_ID 0x72

这正是"识别定长/变长字段、魔数、长度字段"三步的浓缩:TPKT 固定 4 字节头、COTP 1 字节长度指示、S7comm 以0x32/0x72为协议魔数、以param_len/data_len划分参数区与数据区。

三、Phase 2:假设形成——让每一个字节都有说法

分析之后必须把"直觉"固化成可检验的文档化假设:

  1. 记录协议结构的完整工作理论,给出字段逐一拆解;
  2. 对报文示例逐字节/逐字段标注你的假设;
  3. 列出需要测试验证的不确定项与歧义;
  4. 记录你做出且需要校验的假设前提。

这一步在实现中对应"分类器"里的防御性校验。仍以 s7comm 为例,其分类函数对假设做了三重验证后才确认协议归属:TPKT 首字节必须为0x03 0x00;COTP 连接类 PDU(0x0d/0x0e)必须携带 TSAP 参数(0xc1/0xc2)以区别于同样基于 COTP 的 RDP;数据类 PDU(0x0f)后续必须命中0x32或0x72协议 ID。任何一层不满足即放弃分类,避免误判。

四、Phase 3:解析器设计——架构、错误处理与数据表示

4.1 解析架构选型

根据协议形态选择最合适的解析范式:

  • 状态机(State machine):用于有状态的协议(握手、会话内多次交互);
  • 递归下降(Recursive descent):用于层次化结构(如 PANA 消息内嵌 EAP、再嵌 AVP);
  • 流式处理(Stream processing):用于持续数据流(TCP 流上分帧解析);
  • 分块处理(Chunk-based):用于带定界符的消息。

在 Arkime 中,TCP 流式协议的标准做法是使用ArkimeParserBuf_t双方向缓冲区做分帧重组。capture/parsers/s7comm.c的s7comm_tcp_parser展示了完整模式:arkime_parser_buf_add把新到达数据追加进对应方向的缓冲,然后循环按 TPKT 长度字段切帧,解析完一帧即arkime_parser_buf_del移除;若tpktLen > buf->len[which]则返回 0 等待更多数据。其底层实现在 capture/parsers.c 中:arkime_parser_buf_create()默认创建 1024 字节初始、8192 字节上限的双向缓冲(arkime_parser_buf_create2(1024, 8192)),arkime_parser_buf_add在数据超出容量时返回 -1,提示解析器终止注册。

4.2 错误处理策略

生产级解析器必须对以下情形给出明确行为:

  • 畸形报文(字段长度超界、非法取值);
  • 不完整数据(TCP 分片未到齐);
  • 版本不匹配;
  • 意外字段值。

Arkime 的统一约定是:解析函数返回ARKIME_PARSER_UNREGISTER表示"本会话不再需要此解析器";对临时性的"数据不够"则返回 0 继续等待。pana.c中pana_parse_avps对 AVP 长度的双重校验(avpLen > BSB_REMAINING即退出)就是畸形数据防护的典型写法。

4.3 数据表示与校验逻辑

  • 设计清晰的数据结构/类来承载解析出的协议元素;
  • 包含字段一致性检查、长度校验、校验和验证逻辑。

Arkime 的落地形态是"会话私有状态 + SPI 字段"。会话状态通过ARKIME_TYPE_ALLOC0(XXXInfo_t)分配并在分类时通过arkime_parsers_register(session, parser, uw, freeFunc)挂到会话上(见 capture/parsers/parser.template);SPI 字段则通过arkime_field_define声明、arkime_field_int_add/arkime_field_string_add写入,最终进入 Elasticsearch 等后端供查询。

五、Phase 4:实现——在 Arkime 中落地解析器

5.1 解析器生命周期与两个核心回调

Arkime 的 TCP/UDP 解析是两阶段流程(详见 capture/DESIGN.md):

  1. 分类(Classify):查看会话起始数据,用arkime_parsers_classifier_register_tcp/arkime_parsers_classifier_register_udp/arkime_parsers_classifier_register_port注册的分类器判定协议;
  2. 解析(Parsing):分类命中后,用arkime_parsers_register注册的解析函数处理会话数据并抽取 SPI 字段。

每个解析器必须导出一个统一的arkime_parser_init(),在加载时完成字段定义与分类器注册。parser.template 给出了最小骨架:

#include "arkime.h" extern ArkimeConfig_t config; typedef struct { int somePerSessionState; } CHANGEMEInfo_t; LOCAL int changeme_parser(ArkimeSession_t *session, void *uw, const uint8_t *data, int remaining, int which) { CHANGEMEInfo_t *changeme = uw; // Return ARKIME_PARSER_UNREGISTER when done parsing this session return 0; } LOCAL void changeme_free(ArkimeSession_t UNUSED(*session), void *uw) { CHANGEMEInfo_t *changeme = uw; ARKIME_TYPE_FREE(CHANGEMEInfo_t, changeme); } LOCAL void changeme_classify(ArkimeSession_t *session, const uint8_t *UNUSED(data), int UNUSED(len), int UNUSED(which), void *UNUSED(uw)) { if (arkime_session_has_protocol(session, "changeme")) return; arkime_session_add_protocol(session, "changeme"); CHANGEMEInfo_t *changeme = ARKIME_TYPE_ALLOC0(CHANGEMEInfo_t); arkime_parsers_register(session, changeme_parser, changeme, changeme_free); } void arkime_parser_init() { // Classify TCP sessions whose payload starts with the given bytes arkime_parsers_classifier_register_tcp("changeme", NULL, 0, (const uint8_t *)"\x12", 1, changeme_classify); }

要点拆解:

  • arkime_session_has_protocol防重复分类,arkime_session_add_protocol打上协议标签;
  • 分类回调里完成会话状态分配并arkime_parsers_register注册解析器;
  • 解析器返回ARKIME_PARSER_UNREGISTER即主动退出该会话的后续解析。

5.2 分类器注册:底层索引机制

从源码看,分类器注册并非线性扫描。capture/parsers.c中维护了一组分层索引表:按魔数首字节/前两字节建立classifiersTcp0、classifiersTcp1[256]、classifiersTcp2[256][256](UDP 同理),以及按端口分发的classifiersTcpPortSrc/Dst、classifiersUdpPortSrc/Dst表——这保证了大规模流量下分类的低开销。注册时的_internal变体还会校验sizeof(ArkimeSession_t)与ARKIME_API_VERSION,不匹配直接CONFIGEXIT,从编译期杜绝 ABI 错配。

分类器注册实参的完整语义(以 UDP 为例):

参数含义示例
name协议名"pana"
uw透传用户数据NULL
offset匹配起始偏移0
match魔数字节串"\x00\x00"
matchlen魔数长度2
func分类回调pana_udp_classify

pana.c的注册语句即为此格式:arkime_parsers_classifier_register_udp("pana", NULL, 0, (const uint8_t *)"\x00\x00", 2, pana_udp_classify);

5.3 字段定义:把解析结果变成可检索的 SPI

解析出的数据通过arkime_field_define声明为 Arkime 字段。s7comm.c中一个典型声明:

funcField = arkime_field_define("s7comm", "integer", "s7comm.func", "S7comm Function Code", "s7comm.func", "S7comm Function Codes", ARKIME_FIELD_TYPE_INT_GHASH, ARKIME_FIELD_FLAG_CNT, (char *)NULL);

字段类型支持ARKIME_FIELD_TYPE_INT_GHASH(整数,GHASH 索引)、ARKIME_FIELD_TYPE_STR_GHASH(字符串)等,标志位如ARKIME_FIELD_FLAG_CNT控制计数与聚合行为。写入侧则用arkime_field_int_add/arkime_field_string_add落到会话上。解析器内部还可维护常量表做语义映射——s7comm.c用s7comm_func_name()把功能码(0x04Read Var、0x05Write Var、0x28PLC Control、0x29PLC Stop 等)翻译成可读名称,pana.c用pana_msg_types[]数组映射 PANA 消息类型。

5.4 二进制安全解析:BSB 宏体系

capture/bsb.h的 Byte Safe Buffers 是 Arkime 所有解析器的二进制读取基座。核心设计是:每次导入前先做边界检查,越界即置错误标志(BSB_SET_ERROR将end置 NULL),后续所有操作经BSB_IS_ERROR感知失败,从机制上杜绝越界读。常用宏族:

宏用途
BSB_INIT(b, buf, len)初始化缓冲区
BSB_IMPORT_u08/u16/u24/u32/u64大端无符号整数导入
BSB_LIMPORT_u16/u32/u64小端变体(如某些协议字段)
BSB_IMPORT_skip(b, n)跳过保留/填充字段
BSB_IMPORT_ptr(b, x, size)取出指向载荷的指针(零拷贝)
BSB_IMPORT_bsb(b, x, size)切出子缓冲区(嵌套解析)
BSB_REMAINING(b)/BSB_WORK_PTR(b)剩余字节数 / 当前工作指针
BSB_SHRINK_REMAINING(b, rem)收紧边界防止越帧读取

pana.c的pana_parse_eap展示了嵌套解析范式:对 EAP 包先用BSB_IMPORT_u08/u16/skip剥头部,BSB_IS_ERROR判失败,再用BSB_REMAINING与BSB_WORK_PTR截取 Identity 载荷写入用户字段。

5.5 会话状态与资源回收

有状态协议需要在会话生命周期内保存解析上下文(如握手阶段、分帧缓冲)。模板中的CHANGEMEInfo_t即会话私有状态;TCP 流式解析则直接以ArkimeParserBuf_t *作为uw,用arkime_parser_buf_session_free作为析构函数(s7comm 即此用法)。会话释放时框架会调用注册的 free 回调,必须正确释放uw,避免内存泄漏。

六、Phase 5:测试与打磨——用仓库测试体系验证解析器

实现完成后进入验证循环:

  1. 解析全部样本,核对输出与预期一致;
  2. 测试边界与极端情况(截断包、超长帧、空载荷);
  3. 校验校验和/完整性字段;
  4. 与已知正确解析结果对比(如有);
  5. 提出能验证不确定点的补充测试用例。

Arkime 仓库为此提供了完整语料与工具链:tests/pcap/下每个.pcap都配有同名.test断言文件,tests/gen/下是生成合成流量的脚本,tests/ArkimeTest.pm提供了测试基建,tests/dns.t、tests/tls.t、tests/ssh.t等 Perl 测试则演示了端到端断言写法。为 S7comm 一类协议新增解析器时,可参照dnp3_synthetic的模式生成合成 pcap 并配套.test文件,把解析结果固化为回归测试。pana.c与s7comm.c正是模板注释中点名的"小而现代"参考实现(parser.template 明确建议对照学习)。

七、特定技术准则

7.1 二进制数据处理

  • 使用恰当工具:Pythonstruct、JavaByteBuffer、Rustbinarycrate 等;在 Arkime 内则是 BSB 宏族;
  • 始终显式指定字节序(BSB 默认大端,小端字段用BSB_LIMPORT_*);
  • 正确处理对齐与填充(如pana.c中 AVP 按 4 字节边界补齐:(avpLen + 3) & ~3);
  • 谨慎对待有符号 vs 无符号整数;
  • 位操作小心处理打包位域。

7.2 代码质量规范

  • 自文档化代码:变量名直接反映协议语义(msgLen、sessionId、rosctr等);
  • 为协议字段添加注释/docstring 说明用途(s7comm 头部的逐字段注释是范本);
  • 适当使用 ASCII 图示描绘报文结构;
  • 解析器保持对协议版本/变体的可扩展性(s7comm 同时处理0x32与0x72两个协议 ID);
  • 将解析逻辑与业务逻辑分离——Arkime 中即"解析器只产字段,不掺业务"。

八、沟通与协作:解析过程中的交流规范

解析工作高度依赖人与人的信息同步:

  • 做解析决策时总是解释推理过程;
  • 明确陈述假设与置信度;
  • 报文数据有歧义时主动提问澄清;
  • 用 ASCII 图、表格等可视化手段呈现报文结构;
  • 建议实验来验证不确定的协议方面。

当信息不足时,应主动索要:额外报文样本(尤其错误与边界情形)、协议文档(哪怕不完整或非官方)、协议使用场景上下文、特定字段的预期值/行为、已知协议版本或变体。

九、输出格式:一次完整交付应包含什么

每次分析交付建议包含六个部分,缺一不可:

  1. 协议结构分析:带字段描述的报文格式拆解;
  2. 带标注的示例:至少一个逐字节/逐字段标注的报文;
  3. 解析器实现:完整可运行代码,含全面错误处理;
  4. 使用示例:用提供的报文数据演示解析器用法;
  5. 验证结果:样本解析输出;
  6. 不确定项与下一步:列出歧义并给出解决建议。

十、特殊情形考量

  • 提供 pcap 文件时:先说明如何用 Wireshark、tcpdump、scapy 抽取相关报文;
  • 加密协议:明确声明必须先解密才能解析应用层数据(Arkime 仓库有独立的 capture/decryptPcap.js 与https-*系列测试样本可参考密钥交换与 TLS 解码链路);
  • 压缩协议:识别压缩算法并在解析前加入解压步骤;
  • 分片/重组:正确处理 IP 分片与 TCP 重组(ArkimeParserBuf_t正是为此设计的);
  • 高吞吐场景:考虑性能影响。Arkime capture 是多线程 glib2 应用(capture/DESIGN.md):packet 线程负责会话处理,会话按哈希固定到线程上,跨线程操作必须经arkime_session_add_cmd调度;分类器按首字节/前两字节建索引表正是为了把分类开销压到常数级;解析器应尽量只做必要的解码与字段生成,避免在热路径上做重量级操作。

结语

协议解析器架构的本质,是把"逆向推断"这个看似玄学的过程工程化为五阶段可交付流程。而 Arkime 仓库为这一方法论提供了完整落地环境:parser.template提供骨架,pana.c与s7comm.c提供现代范本,bsb.h提供安全二进制读取原语,parsers.c提供分类索引与分帧缓冲,tests/提供验证语料。以此为基础,你完全可以从一份原始 pcap 出发,产出一个能经受真实网络流量与边界用例考验的生产级解析器。

  • 网络安全
  • 网络
  • 后端
  • 数据可视化

【免费下载链接】arkime

Arkime is an open source, large scale, full packet capturing, indexing, and database system.

项目地址:https://gitcode.com/gh_mirrors/ar/arkime
点击查看免费下载
上一篇:AltoClef与Baritone集成:双剑合璧打造最强Minecraft自动化
下一篇:Learn Go with Tests数组和切片教程:高效数据处理指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询