- 网络安全
- 网络
- 后端
- 数据可视化
【免费下载链接】arkime
Arkime is an open source, large scale, full packet capturing, indexing, and database system.
本文是一份面向安全研究人员与 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:协议分析——从报文样本中寻找结构
分析的起点永远是"尽可能多的报文样本",尤其是包含错误情形和边界条件的样本。随后按以下顺序系统化排查:
- 初步检查:通读所有报文,找出共同模式、共性与差异点。
- 结构发现:
- 区分定长字段与变长字段;
- 识别头部、载荷、尾部与分隔符;
- 查找魔数(magic number)、版本号、长度指示字段;
- 确定字节序(大端/小端);
- 定位校验和、CRC 等完整性字段。
- 模式识别:
- 横向对比多包,区分"协议常量"与"可变数据";
- 通过字段值变化的位置推断字段边界;
- 识别长度前缀字符串或数据块;
- 识别常见编码(ASCII、UTF-8、BCD 二进制编码十进制)。
- 协议分层:判断是传输层、应用层还是自定义层协议,识别是否有隧道/封装。
在 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:假设形成——让每一个字节都有说法
分析之后必须把"直觉"固化成可检验的文档化假设:
- 记录协议结构的完整工作理论,给出字段逐一拆解;
- 对报文示例逐字节/逐字段标注你的假设;
- 列出需要测试验证的不确定项与歧义;
- 记录你做出且需要校验的假设前提。
这一步在实现中对应"分类器"里的防御性校验。仍以 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):
- 分类(Classify):查看会话起始数据,用
arkime_parsers_classifier_register_tcp/arkime_parsers_classifier_register_udp/arkime_parsers_classifier_register_port注册的分类器判定协议; - 解析(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:测试与打磨——用仓库测试体系验证解析器
实现完成后进入验证循环:
- 解析全部样本,核对输出与预期一致;
- 测试边界与极端情况(截断包、超长帧、空载荷);
- 校验校验和/完整性字段;
- 与已知正确解析结果对比(如有);
- 提出能验证不确定点的补充测试用例。
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 二进制数据处理
- 使用恰当工具:Python
struct、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 图、表格等可视化手段呈现报文结构;
- 建议实验来验证不确定的协议方面。
当信息不足时,应主动索要:额外报文样本(尤其错误与边界情形)、协议文档(哪怕不完整或非官方)、协议使用场景上下文、特定字段的预期值/行为、已知协议版本或变体。
九、输出格式:一次完整交付应包含什么
每次分析交付建议包含六个部分,缺一不可:
- 协议结构分析:带字段描述的报文格式拆解;
- 带标注的示例:至少一个逐字节/逐字段标注的报文;
- 解析器实现:完整可运行代码,含全面错误处理;
- 使用示例:用提供的报文数据演示解析器用法;
- 验证结果:样本解析输出;
- 不确定项与下一步:列出歧义并给出解决建议。
十、特殊情形考量
- 提供 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.
相关推荐
3分钟在Linux桌面运行Android应用:Waydroid容器技术完全指南
3分钟在Linux桌面运行Android应用:Waydroid容器技术完全指南 想在Linux系统上无缝运行Android应用,却不想安装臃肿的虚拟机?Wayd
虚拟化操作系统PySparNN未来路线图:新特性预测与社区贡献终极指南
PySparNN未来路线图:新特性预测与社区贡献终极指南 PySparNN 是一个专为稀疏高维数据设计的Python近似最近邻搜索库,特别适合文本文档等场景。作
Keel版本策略详解:SemVer、Force、Glob和Regexp全面对比
Keel版本策略详解:SemVer、Force、Glob和Regexp全面对比 在Kubernetes容器编排环境中,Keel作为自动化Helm、DaemonS
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考