资讯详情

Zeek Modbus 协议分析指南:base/protocols/modbus 脚本包、事件体系与 modbus.log 日志实战

📅 2026/10/9 5:14:13 | 华诺云谱 👁 阅读
Zeek Modbus 协议分析指南:base/protocols/modbus 脚本包、事件体系与 modbus.log 日志实战
网络安全网络IDS【免费下载链接】zeekZeek is a powerful network analysis framework that is much different from the typical IDS you may know.项目地址https://gitcode.com/gh_mirrors/ze/zeek点击查看免费下载Modbus 是工业控制系统ICS/SCADA中最常见的应用层协议之一对它的深度解析是网络流量安全监控的关键环节。本指南以 Zeek 仓库中 doc/scripts/base/protocols/modbus/index.rst 所描述的base/protocols/modbus脚本包为主线完整讲解 Zeek 如何分析 Modbus/TCP 流量、定义 Modbus 函数码与异常码常量、产出modbus.log日志以及如何利用脚本层事件实现自定义监控。读完本文你将掌握 Zeek 中 Modbus 分析的完整数据流从底层 binpac 协议解析器 → 中间层 BIF 事件 → 脚本层日志记录并能独立编写针对 Modbus 主站/从站、寄存器读写、异常响应的检测脚本。包结构与加载方式base/protocols/modbus脚本包位于 scripts/base/protocols/modbus/由三个脚本文件组成包入口load.zeek 按顺序加载两个模块load ./consts load ./mainconsts.zeek定义 Modbus 标准函数码与异常码的常量映射表main.zeek核心分析脚本负责创建modbus日志流、注册默认端口、填充日志记录并输出日志。在 Zeek 脚本中只需一行即可启用完整的 Modbus 分析能力load base/protocols/modbus该脚本包是基础base脚本默认随 Zeek 的init-default.zeek加载也就是说在标准安装下无需任何额外配置Modbus 流量一旦出现就会被解析并记录。底层协议解析器从原始字节到 Modbus 结构脚本层之上Zeek 使用基于 binpac 生成的协议解析器完成二进制报文解析源码位于 src/analyzer/protocol/modbus/其中 modbus.pac 是解析器的总装入口它声明了ModbusTCP分析器并为双向流量各建立一个ModbusTCP_Flowupflow/downflow分别对应请求与响应方向。Modbus/TCP 传输头MBAPmodbus-protocol.pac 定义了协议报文结构。每一个 Modbus/TCP PDU 由 7 字节的传输头加函数体组成传输头字段如下字段宽度含义tid2 字节事务标识符Transaction identifier用于请求/响应配对pid2 字节协议标识符Modbus 协议规定必须为 0解析器通过enforce(pid 0)强制校验len2 字节后续字节长度解析器要求len 2uid1 字节单元标识符Unit identifier即传统意义上的从站地址fc1 字节功能码Function code整体 PDU 的定义体现了请求/响应方向决定结构的建模思路ModbusTCP_PDU(is_orig)依据is_orig选择解析为ModbusTCP_Request还是ModbusTCP_Response。值得注意的两点设计响应区分正常与异常ModbusTCP_Response通过header.fc 0x80判断——功能码最高位0x80被置位即为异常响应ModbusTCP_ExceptResponse仅含 1 字节异常码否则走ModbusTCP_NormalResponse按功能码分发到具体响应结构。未识别功能码兜底请求与响应的 case 分发均有default - unknown: bytestring restofdata对暂未建模的功能码余下数据以原始字节保留保证解析器不会因未知功能码而崩溃。功能码覆盖范围解析器在 function_codes 枚举中建模了以下功能码的请求/响应结构READ_COILS (0x01)、READ_DISCRETE_INPUTS (0x02)、READ_HOLDING_REGISTERS (0x03)、READ_INPUT_REGISTERS (0x04)、WRITE_SINGLE_COIL (0x05)、WRITE_SINGLE_REGISTER (0x06)、DIAGNOSTICS (0x08)、WRITE_MULTIPLE_COILS (0x0F)、WRITE_MULTIPLE_REGISTERS (0x10)、READ_FILE_RECORD (0x14)、WRITE_FILE_RECORD (0x15)、MASK_WRITE_REGISTER (0x16)、READ_WRITE_MULTIPLE_REGISTERS (0x17)、READ_FIFO_QUEUE (0x18)、ENCAP_INTERFACE_TRANSPORT (0x2B)、OBJECT_MESSAGING (0x5B)以及厂商/机型相关的PROGRAM_*、POLL_*等专有功能码。READ_EXCEPTION_STATUS (0x07)、GET_COMM_EVENT_COUNTER (0x0B)、GET_COMM_EVENT_LOG (0x0C)、REPORT_SLAVE_ID (0x11)在枚举中被注释掉未建立专门的解析结构。解析结果的校验与确认机制modbus-analyzer.pac 承担两层职责协议确认连接对象维护confirmed/orig_pdu/resp_pdu三个布尔状态。只有当双向各成功解析出至少一个完整 PDUSetPDU置位对应方向标志后IsConfirmed()才返回真此时才调用AnalyzerConfirmation()确认该连接为 Modbus。若后续报文解析失败则会触发AnalyzerViolation。数据合法性校验例如读保持寄存器/读输入寄存器响应的byte_count必须为偶数寄存器 2 字节对齐写单线圈的value只能是0x0000或0xFF00多寄存器写入的byte_count必须与 quantity 匹配否则报告AnalyzerViolation并拒绝该 PDU。诊断请求FC0x08则依据 diagnostic_subfunctions 枚举进一步校验子功能码数据长度必须是 2 的倍数除 Return Query Data (0x00) 外其余子功能数据必须恰好 2 字节各子功能的取值还有专门约束如RESTART_COMMUNICATIONS_OPTION只允许0x0000/0xFF00违规时通过 reporter 产生modbus_diag_invalid_request_data等 weird 日志。事件体系脚本层与解析器的接口解析器解析出的每一个结构都会通过 events.bif 定义的 BIF 事件派发到脚本层。这些事件分为三类通用事件每条消息都会触发modbus_message(c, headers, is_orig)任何 Modbus 消息无论该功能码是否被进一步支持modbus_exception(c, headers, code)任何 Modbus 异常响应。按功能码细分的请求/响应事件modbus_read_coils_request/response、modbus_read_discrete_inputs_request/response、modbus_read_holding_registers_request/response、modbus_read_input_registers_request/response、modbus_write_single_coil_request/response、modbus_write_single_register_request/response、modbus_write_multiple_coils_request/response、modbus_write_multiple_registers_request/response、modbus_read_file_record_request/response、modbus_write_file_record_request/response、modbus_mask_write_register_request/response、modbus_read_write_multiple_registers_request/response、modbus_read_fifo_queue_request/response、modbus_diagnostics_request/response、modbus_encap_interface_transport_request/response。所有事件均携带headers: ModbusHeaders参数该记录由解析器在 modbus-analyzer.pac 的 HeaderToVal 中构造包含tid、pid、uid、fc、len五个字段。线圈与寄存器值被转换为ModbusCoils布尔向量与ModbusRegisterscount 向量类型的 Zeek 值线圈数据按位解包——bytestring_to_coils中逐位取出(coils[i/8] (i % 8)) % 2转为布尔值寄存器数据则按大端序拆为 16 位无符号整数。脚本层核心main.zeek 详解main.zeek 是脚本包的主体其核心逻辑分为四部分。默认端口const ports { 502/tcp } redef;Modbus/TCP 的 IANA 标准端口为 502/TCP。zeek_init事件中通过Analyzer::register_for_ports(Analyzer::ANALYZER_MODBUS, ports)完成端口到分析器的注册。由于是redef常量你可以在本地脚本中扩展监听端口redef Modbus::ports { 1502/tcp };日志流与 Info 记录脚本包redef enum Log::ID { LOG }扩展了日志 ID 枚举并在zeek_init中创建日志流Log::create_stream(Modbus::LOG, Log::Stream($columnsInfo, $evlog_modbus, $pathmodbus, $policylog_policy));日志路径为modbus即输出modbus.log。Info记录的字段就是modbus.log的列其完整定义如下字段类型说明tstime请求发生时间uidstring连接的唯一标识符idconn_id连接四元组orig_h/orig_p/resp_h/resp_ptidcount(可选)Modbus 事务标识符unitcount(可选)单元从站标识符funcstring(可选)发送的功能消息名称如READ_HOLDING_REGISTERSpdu_typestring(可选)REQ请求或RESP响应exceptionstring(可选)响应失败时的异常名称此外脚本包还通过redef record connection { modbus: Info optional; }在连接记录上挂载modbus字段用于在连接生命周期内暂存待输出的记录注意track_address字段是在加载策略脚本 track-memmap.zeek 后才会出现。功能码名称映射build_funcbuild_func(func: count): string函数负责把原始功能码数值翻译为可读字符串其核心是处理异常响应标志位function build_func(func: count): string { local masked func ~0x80; # If the function code is in function_codes, use it. Also, # if the masked value isnt in function_codes, use function_codes # default functionality. if ( func in function_codes || masked !in function_codes ) return function_codes[func]; local s function_codes[masked]; # Suffix exceptions with _EXCEPTION. if ( func 0x80 0x80 ) s _EXCEPTION; return s; }逻辑要点当功能码高位置位异常响应如0x83但对应的正常功能码已知时返回READ_HOLDING_REGISTERS_EXCEPTION这样的名称当功能码完全未知时返回function_codes表default函数生成的unknown-数值字符串。日志记录的两个优先级事件modbus_message事件被声明了两次利用事件优先级实现先填充、后输出的流水线priority5版本若连接尚无modbus记录则初始化随后依次写入tsnetwork_time()、tid来自headers$tid、unit来自headers$uid、funcbuild_func(headers$function_code)、pdu_typeis_orig ? REQ : RESP。注意这里请求/响应的判定依据是消息来源方向来自 TCP 发起方即为请求。priority-5版本如果功能码小于0x80正常响应或请求调用Log::write(LOG, c$modbus)输出日志。异常响应的记录则由modbus_exception事件承担priority5版本把exception_codes[code]翻译后的异常名称写入c$modbus$exceptionpriority-5版本输出日志并delete c$modbus$exception清理字段避免污染下一条消息。因此正常流量与异常响应最终都会进入modbus.log区别在于异常条目多了一个exception列。测试 exception_handling.test 专门验证了模糊流量下异常处理路径的正确性——binpac 生成的解析器抛出的越界异常必须被正确捕获且不影响 Zeek 稳定性。函数码与异常码常量表consts.zeekconsts.zeek 提供了两张可直接用于脚本编程的映射表二者均带default兜底函数与redef属性允许用户扩展。Modbus::function_codes标准 Modbus 功能码映射节选核心部分功能码名称功能码名称0x01READ_COILS0x10WRITE_MULTIPLE_REGISTERS0x02READ_DISCRETE_INPUTS0x11REPORT_SLAVE_ID0x03READ_HOLDING_REGISTERS0x14READ_FILE_RECORD0x04READ_INPUT_REGISTERS0x15WRITE_FILE_RECORD0x05WRITE_SINGLE_COIL0x16MASK_WRITE_REGISTER0x06WRITE_SINGLE_REGISTER0x17READ_WRITE_MULTIPLE_REGISTERS0x07READ_EXCEPTION_STATUS0x18READ_FIFO_QUEUE0x08DIAGNOSTICS0x2BENCAP_INTERFACE_TRANSPORT0x0BGET_COMM_EVENT_COUNTER0x5BOBJECT_MESSAGING0x0CGET_COMM_EVENT_LOG0x0FWRITE_MULTIPLE_COILS同时包含厂商/机型专有功能码PROGRAM_484 (0x09)、POLL_484 (0x0A)、PROGRAM_584_984 (0x0D)、POLL_584_984 (0x0E)、PROGRAM_884_U84 (0x12)、RESET_COMM_LINK_884_U84 (0x13)、PROGRAM_CONCEPT (0x28)、MULTIPLE_FUNCTION_CODES (0x29)、PROGRAM_UNITY (0x5A)、FIRMWARE_REPLACEMENT (0x7D)、PROGRAM_584_984_2 (0x7E)、REPORT_LOCAL_ADDRESS (0x7F)。这些厂商专用码来自公开的 Modbus 扩展资料与逆向分析代码注释中标注了来源。未知功能码通过defaultfunction(i: count):string { return fmt(unknown-%d, i); }呈现为unknown-n。Modbus::exception_codesModbus 异常响应码响应功能码最高位置位时携带的 1 字节异常码异常码名称异常码名称0x01ILLEGAL_FUNCTION0x08MEMORY_PARITY_ERROR0x02ILLEGAL_DATA_ADDRESS0x0AGATEWAY_PATH_UNAVAILABLE0x03ILLEGAL_DATA_VALUE0x0BGATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND0x04SLAVE_DEVICE_FAILURE0x05ACKNOWLEDGE0x06SLAVE_DEVICE_BUSY未知异常码同样回退到unknown-n格式。由于两张表均为redef可以在本地策略脚本中为私有功能码补充名称redef Modbus::function_codes { [0x6B] VENDOR_READ_REGISTERS };modbus.log 实战解读以一段典型的主站读取保持寄存器交互为例modbus.log中会产生两条记录#separator \x09 #fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p tid unit func pdu_type exception 192.168.1.10 CFG... 192.168.1.10 49152 192.168.1.20 502 4521 1 READ_HOLDING_REGISTERS REQ - 192.168.1.10 CFG... 192.168.1.10 49152 192.168.1.20 502 4521 1 READ_HOLDING_REGISTERS RESP -若从站返回异常例如功能码 0x83、异常码 0x02则响应记录变为... tid4521 unit1 funcREAD_HOLDING_REGISTERS_EXCEPTION pdu_typeRESP exceptionILLEGAL_DATA_ADDRESS关键点请求与响应的tid相同事务配对依据pdu_type区分方向异常响应的func带_EXCEPTION后缀且exception列有值。这些字段组合可用于检测异常率过高、非法从站地址访问等 ICS 安全场景。策略脚本主从设备发现与寄存器内存映射跟踪除了基础脚本包scripts/policy/protocols/modbus/ 还提供了两个可选策略脚本可按需load。known-masters-slaves.zeek发现 Modbus 主站与从站known-masters-slaves.zeek 监听modbus_message事件将连接的发起方c$id$orig_h识别为MODBUS_MASTER、响应方c$id$resp_h识别为MODBUS_SLAVE维护带 1 天过期时间的集合modbus_nodes并将新发现的设备写入known_modbus.log列ts、host、device_type。示例输出#fields ts host device_type 2026-10-08T19:40:59Z 192.168.1.10 MODBUS_MASTER 2026-10-08T19:40:59Z 192.168.1.20 MODBUS_SLAVEtrack-memmap.zeek跟踪保持寄存器内存映射变化track-memmap.zeek 是更具纵深价值的脚本它维护每个从站设备的寄存器内存映射device_registers: table[addr] of RegistersRegisters为table[count] of RegisterValue记录last_set时间与value。工作流程如下在modbus_read_holding_registers_request事件中用c$modbus$track_address start_address1记录本次读操作的起始地址注意脚本从 1 开始计数与协议中 0 基地址相差 1在modbus_read_holding_registers_response事件中若响应方匹配track_memmap选项默认ALL_HOSTS可通过option track_memmap: Host ...限制仅跟踪特定主机则逐寄存器比对旧值与新值值发生变化时触发changed_register事件计算变化时间间隔delta写入modbus_register_change.log列ts、uid、id、register、old_val、new_val、delta。该脚本对于检测 PLC 寄存器被非法改写如针对 RTU/PLC 的恶意写操作非常实用测试与日志样例可参考 testing/btest/scripts/base/protocols/modbus/ 目录下的用例。自定义检测脚本示例结合基础脚本包提供的事件与常量可以快速编写针对性检测逻辑。以下示例检测对保持寄存器的异常写入写单寄存器功能码 0x06并输出 noticeload base/protocols/modbus event modbus_write_single_register_request(c: connection, headers: ModbusHeaders, address: count, value: count) { if ( address 100 ) { NOTICE([$noteWeird::Activity, $msgfmt(Modbus write to protected register %d %d (unit %d), address, value, headers$uid), $connc]); } }而 testing/btest/scripts/base/protocols/modbus/events.zeek 展示了官方测试中如何逐一打印全部 Modbus 事件适合作为学习事件参数的参考模板coil_parsing_big.zeek 与 register_parsing.zeek 等用例则覆盖了线圈位解析、寄存器数组解析等边界场景。小结Zeek 对 Modbus 的分析是一条完整的流水线binpac 解析器src/analyzer/protocol/modbus/把 Modbus/TCP 报文解析为带强校验的结构 → 解析结果经 events.bif 的 30 余个事件派发到脚本层 → main.zeek 与 consts.zeek 完成功能码/异常码翻译并输出modbus.log。在此之上策略脚本 known-masters-slaves.zeek 与 track-memmap.zeek 提供了设备发现与寄存器变化跟踪能力所有细节均可通过 testing/btest/scripts/base/protocols/modbus/ 下的测试用例验证。无论是常规流量审计还是 ICS 安全监控这套脚本包都能直接作为基线使用并可通过redef、事件处理与Log::PolicyHookModbus::log_policy自由扩展。赞分享网络安全网络IDS【免费下载链接】zeekZeek is a powerful network analysis framework that is much different from the typical IDS you may know.项目地址https://gitcode.com/gh_mirrors/ze/zeek点击查看免费下载相关推荐Zeek 的 IRC 协议分析base/protocols/irc 包结构、日志模型与 DCC 文件提取实战指南Zeek 的 IRC 协议分析base/protocols/irc 包结构、日志模型与 DCC 文件提取实战指南 本指南围绕 Zeek 仓库中 base/pr网络安全网络IDSZeek DNP3 协议分析base/protocols/dnp3 脚本包的加载入口与实现解析Zeek DNP3 协议分析base/protocols/dnp3 脚本包的加载入口与实现解析 导读 本文围绕 Zeek 仓库中 doc/scripts/b网络安全网络IDSZeek 的 Modbus 协议分析基础脚本解析加载机制、日志字段与源码实现Zeek 的 Modbus 协议分析基础脚本解析加载机制、日志字段与源码实现 导读 本文围绕 Zeek 仓库中 doc/scripts/base/protoc网络安全网络IDS上一篇医学影像分割神器ITK-SNAP3步学会精准分割脑部MRI图像下一篇如何永久保存微信聊天记录WeChatMsg完整指南与实用技巧创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑