网络安全网络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 内置的 Modbus 分析器用于监控工业控制系统中最常见的工控协议之一 Modbus/TCP。本文以 base/protocols/modbus/main.zeek 为核心骨架完整讲解其端口注册、Modbus::Info日志记录结构、modbus.log日志流的创建、函数码/异常码的语义化映射以及底层 binpac 解析器与 btest 测试的验证方式。读完本文你将能够读懂并扩展 Zeek 的 Modbus 日志输出掌握利用Modbus::log_policy钩子定制日志、结合Modbus::log_modbus事件做实时告警的方法。一、模块概览与加载方式Modbus 分析脚本位于 scripts/base/protocols/modbus/ 目录属于 Zeek 的 base 脚本集随 Zeek 默认加载。该目录的入口文件load.zeek 只有两行load ./consts load ./main即依次加载两个脚本consts.zeek定义 Modbus 函数码function codes与异常码exception codes的语义化名称表main.zeek核心分析脚本负责注册端口、定义日志记录类型Modbus::Info、创建modbus日志流并消费底层分析器产生的事件。main.zeek声明的命名空间为Modbus并导入 consts.zeek。Zeek 官方文档对它的定位就是 Base Modbus analysis script即 Modbus 协议分析的基础支撑层。二、端口注册Modbus::portsmain.zeek中定义了一个可重定义redef的常量用于声明 Modbus 服务的已知端口## Well-known ports for Modbus. const ports { 502/tcp } redef;其完整类型为set[port]默认值为{ 502/tcp }。502 是 Modbus/TCP 的 IANA 标准端口。该选项是redefinable可重定义的这意味着部署人员可以按需扩展监听端口集合例如redef Modbus::ports { 1502/tcp };在zeek_init()事件中该端口集合被注册到 Zeek 的 TCP 应用层分析器分发机制event zeek_init() priority5 { Log::create_stream(Modbus::LOG, Log::Stream($columnsInfo, $evlog_modbus, $pathmodbus, $policylog_policy)); Analyzer::register_for_ports(Analyzer::ANALYZER_MODBUS, ports); }其中Analyzer::register_for_ports(Analyzer::ANALYZER_MODBUS, ports)告诉 Zeek 引擎凡目的端口或源端口落在ports集合内的 TCP 连接都需要尝试挂载名为MODBUS的应用层分析器。这与 Zeek 的动态协议检测DPD机制配合即使流量没有落在 502 端口只要 DPD 识别出报文特征符合 Modbus 报文结构分析器同样会被激活这一点有专门测试用例modbus_and_non_modbus_on_port_502.test验证端口与非 Modbus 流量的混用行为。三、日志记录结构Modbus::InfoModbus::Info是modbus.log的列定义它是一个 record 类型各字段均带log属性。原文档将其列为唯一的 Types 项逐字段说明如下字段类型属性含义tstimelog请求发生的时间uidstringlog连接的唯一标识符idconn_idlog连接标识四元组源/目的 IP 与端口tidcountlog optionalModbus 事务 IDTransaction IDunitcountlog optional报文的目标单元标识符Unit ID即原 slave addressfuncstringlog optional所发送功能报文的名称如READ_HOLDING_REGISTERSpdu_typestringlog optional该 PDU 是响应RESP还是请求REQexceptionstringlog optional若响应为失败记录异常名称track_addresscountdefault0 optional仅当加载了 track-memmap.zeek 策略脚本时出现用于跟踪内存映射地址其中track_address字段并不在main.zeek的定义中而是由策略脚本通过redef record Modbus::Info { track_address: count default0; }动态追加的体现了 Zeek 脚本类型可扩展的设计。源码中该记录的定义main.zeektype Info: record { ts: time log; uid: string log; id: conn_id log; tid: count log optional; unit: count log optional; func: string log optional; pdu_type: string log optional; exception: string log optional; };四、连接记录扩展与日志流创建main.zeek还做了两处类型层面的重定义Redefinitions扩展Log::ID枚举新增Modbus::LOG日志流标识redef enum Log::ID { LOG };扩展connection记录为每个连接对象增加modbus字段保存当前连接的 Modbus 状态信息redef record connection { modbus: Info optional; };modbus字段由事件处理代码在收到第一条 Modbus 报文时惰性创建随连接生命周期保存tid、unit、func、pdu_type等状态供后续日志写入使用。在zeek_init()中通过Log::create_stream注册日志流Log::create_stream(Modbus::LOG, Log::Stream($columnsInfo, $evlog_modbus, $pathmodbus, $policylog_policy));这里四个关键参数分别是$columnsInfo日志列即Modbus::Info记录$evlog_modbus日志流与log_modbus事件绑定脚本可以在事件中截获将要写入日志的记录$pathmodbus日志文件名为modbus.log默认写入 Zeek 的 logs 目录$policylog_policy挂接一个Log::PolicyHook类型的钩子用于在写入前对记录做过滤或修改。五、事件与钩子log_modbus 与 log_policy原文档列出的两个可编程扩展点如下。5.1 Modbus::log_modbus 事件类型为event(rec: Modbus::Info)在每条 Modbus 记录被发送到日志框架时触发。利用它可以对日志做二次加工例如补字段或分流event Modbus::log_modbus(rec: Modbus::Info) { if ( rec?$func rec$func READ_HOLDING_REGISTERS ) # 对读保持寄存器的事务做特殊处理 NOTICE([$noteModbus_Read_Holding_Registers, $connrec$id]); }5.2 Modbus::log_policy 钩子类型为Log::PolicyHook是 Zeek 日志框架标准的策略钩子可用来丢弃记录或改写字段。例如只保留请求方向的日志hook Modbus::log_policy(rec: Modbus::Info, id: Log::ID) { if ( rec?$pdu_type rec$pdu_type ! REQ ) break; }break会终止后续钩子并阻止该记录写入日志文件。六、核心事件处理逻辑请求/响应与异常main.zeek通过两个优先级相反的事件处理程序完成日志组装这是理解modbus.log行为的关键。6.1 组装阶段priority5modbus_message事件由 C 分析器对每一条Modbus 报文触发无论该功能码是否被进一步解析。priority5 的处理程序负责填充记录event modbus_message(c: connection, headers: ModbusHeaders, is_orig: bool) priority5 { if ( ! c?$modbus ) c$modbus Info($tsnetwork_time(), $uidc$uid, $idc$id); c$modbus$ts network_time(); c$modbus$tid headers$tid; c$modbus$unit headers$uid; c$modbus$func build_func(headers$function_code); c$modbus$pdu_type is_orig ? REQ : RESP; }headers是ModbusHeaders记录包含tid事务 ID、pid协议 ID、uid单元标识、function_code功能码与len字段is_orig表示报文方向来自 TCP 发起方client的是请求REQ来自响应方server的是响应RESPbuild_func()负责把原始功能码转换为可读名称见下节。6.2 写日志阶段priority-5同一个事件还挂了一个 priority-5 的处理程序负责真正落盘并通过异常码位决定是否推迟写入event modbus_message(c: connection, headers: ModbusHeaders, is_orig: bool) priority-5 { # Dont log now if this is an exception (log in the exception event handler) if ( headers$function_code 0x80 ) Log::write(LOG, c$modbus); }Modbus 协议约定正常响应的功能码与其请求相同0x00–0x7F而异常响应的功能码会把最高位0x80置 1。因此若function_code 0x80说明是正常请求或正常响应立即写日志若function_code 0x80说明是异常响应暂不写日志等modbus_exception事件把exception字段补全后再写。6.3 异常处理modbus_exception事件携带异常码同样分为两个优先级event modbus_exception(c: connection, headers: ModbusHeaders, code: count) priority5 { c$modbus$exception exception_codes[code]; } event modbus_exception(c: connection, headers: ModbusHeaders, code: count) priority-5 { Log::write(LOG, c$modbus); delete c$modbus$exception; }priority5 把数值异常码查表转换为语义名称如ILLEGAL_FUNCTION写入exception字段priority-5 写入日志后删除该字段避免影响同一连接后续报文的日志内容。测试用例 exception_handling.test 专门覆盖了异常码的解析路径。6.4 函数码名称转换 build_funcfunction build_func(func: count): string { local masked func ~0x80; 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; }逻辑说明若func原始功能码本身在表中直接返回其名称若剥掉异常位后的masked也不在表中即完全未知的功能码查表时触发default分支返回unknown-数值形式否则用masked查到基础功能名若func最高位为 1异常响应在名称后追加_EXCEPTION后缀。因此异常响应日志中func字段会呈现类似READ_HOLDING_REGISTERS_EXCEPTION的形态与exception字段共同刻画异常详情。七、函数码与异常码常量表consts.zeekconsts.zeek 定义了main.zeek依赖的两张表均由default兜底未知值显示为unknown-数值并允许redef扩展。7.1 function_codesModbus 标准函数码覆盖标准功能码与机器/厂商/网络专用功能码功能码名称说明0x01READ_COILS读线圈0x02READ_DISCRETE_INPUTS读离散输入0x03READ_HOLDING_REGISTERS读保持寄存器0x04READ_INPUT_REGISTERS读输入寄存器0x05WRITE_SINGLE_COIL写单个线圈0x06WRITE_SINGLE_REGISTER写单个寄存器0x07READ_EXCEPTION_STATUS读异常状态0x08DIAGNOSTICS诊断0x0BGET_COMM_EVENT_COUNTER获取通信事件计数器0x0CGET_COMM_EVENT_LOG获取通信事件日志0x0FWRITE_MULTIPLE_COILS写多个线圈0x10WRITE_MULTIPLE_REGISTERS写多个寄存器0x11REPORT_SLAVE_ID上报从站 ID0x14READ_FILE_RECORD读文件记录0x15WRITE_FILE_RECORD写文件记录0x16MASK_WRITE_REGISTER掩码写寄存器0x17READ_WRITE_MULTIPLE_REGISTERS读改写多个寄存器0x18READ_FIFO_QUEUE读 FIFO 队列0x2BENCAP_INTERFACE_TRANSPORT封装接口传输MEI0x5BOBJECT_MESSAGING对象消息0x09/0x0A/0x0D/0x0E/0x12/0x13/0x28/0x29/0x5A/0x7D/0x7E/0x7FPROGRAM_484等机器/厂商/网络专用功能码7.2 exception_codesModbus 异常码异常码名称含义0x01ILLEGAL_FUNCTION非法功能0x02ILLEGAL_DATA_ADDRESS非法数据地址0x03ILLEGAL_DATA_VALUE非法数据值0x04SLAVE_DEVICE_FAILURE从站设备故障0x05ACKNOWLEDGE已确认0x06SLAVE_DEVICE_BUSY从站设备忙0x08MEMORY_PARITY_ERROR存储器奇偶校验错误0x0AGATEWAY_PATH_UNAVAILABLE网关路径不可用0x0BGATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND网关目标设备无响应八、底层实现binpac 解析器与 C 分析器脚本层之上Modbus 报文解析由位于 src/analyzer/protocol/modbus/ 的 binpac 描述文件与 C 代码完成该分析器的开发得到了荷兰司法与安全部 Hermes、Castor、Midas 项目的资助见源码文件头注释。8.1 TCP 报文头与 PDU 结构modbus-protocol.pac 定义了 Modbus/TCP 的线格式。传输头为 7 字节大端序结构tid: uint16 # Transaction identifier事务 ID pid: uint16 # Protocol identifier协议 ID须为 0 len: uint16 # 其后负载长度须 2 uid: uint8 # Unit identifier单元标识 fc: uint8 # MODBUS function code功能码对应的 C 侧转换函数HeaderToVal()位于 modbus-analyzer.pac它把该头组装成 Zeek 侧的ModbusHeaders记录字段依次为 tid、pid、uid、fc、len供脚本层事件消费。请求与响应分别按功能码分发请求侧ModbusTCP_Request根据fc值选择对应的具体结构如ReadCoilsRequest、ReadHoldingRegistersRequest响应侧先看fc 0x80—— 为 0 走ModbusTCP_NormalResponse否则走ModbusTCP_ExceptResponse只含一个code: uint8异常码字节并在deliver_Exception中触发脚本事件modbus_exception。8.2 协议确认Confirmation逻辑modbus-analyzer.pac 中通过三个成员变量跟踪协议确认状态bool confirmed; // 是否已确认 bool orig_pdu; // 是否成功解析过发起方 PDU bool resp_pdu; // 是否成功解析过响应方 PDU只有当双向的完整 PDU 都被成功解析时IsConfirmed()才返回真分析器才会调用AnalyzerConfirmation()正式确认该连接为 Modbus。这一设计有效避免了把少量相似流量误判为 Modbus。8.3 数据完整性校验C 侧对多个功能码做了严格的长度/取值校验违反时调用AnalyzerViolation脚本层体现为weird.log中的bad_TCP_...类记录或reporter-Weird例如读保持寄存器/读输入寄存器响应的byte_count必须为偶数奇数时触发 violation写单个线圈的值必须是0x0000或0xFF00诊断FC8各子功能的数据长度与取值约束如RESTART_COMMUNICATIONS_OPTION只能是0x0000或0xFF00其余大部分子功能数据须为0x0000未知子功能触发modbus_diag_unknown_request_subfunction。九、事件体系从 modbus_message 到功能级事件由 events.bif 声明的 Modbus 事件构成两层体系通用事件modbus_message任何 Modbus 报文含未被深入解析的功能码与modbus_exception任何异常报文功能级事件按功能码细分的请求/响应事件共 20 组例如modbus_read_coils_request/response、modbus_read_holding_registers_request/response、modbus_write_single_coil_request/response、modbus_read_file_record_request/response、modbus_mask_write_register_request/response、modbus_diagnostics_request/response、modbus_encap_interface_transport_request/response等。功能级事件携带解析后的语义参数例如modbus_read_holding_registers_request带start_address与quantitymodbus_write_single_coil_request的value已被转换为bool。线圈类数据被封装为ModbusCoils向量由bytestring_to_coils()按位展开寄存器类数据封装为ModbusRegisters向量。测试脚本 events.zeek 逐个订阅了这些事件并打印参数同时用grep ^event modbus_统计覆盖度验证轨迹modbus.pcap与modbus-eit.pcap能触发的事件数。你可以照此模式编写自己的 Modbus 事件处理脚本。十、扩展实战track-memmap 策略脚本原文档在track_address字段处引用了策略脚本 scripts/policy/protocols/modbus/track-memmap.zeek。该脚本展示了对 base 脚本的典型扩展方式新增日志流Modbus::REGISTER_CHANGE_LOG输出到modbus_register_change.log新增可配置选项track_memmap: Host ALL_HOSTS可限定只跟踪特定从站地址扩展Modbus::Info追加track_address字段消费功能级事件在modbus_read_holding_registers_request中记录起始地址在modbus_read_holding_registers_response中逐寄存器比对历史值发现变化即触发Modbus::changed_register事件并写入日志记录old_val、new_val与变化时间差delta。启用方式为在local.zeek或命令行加载load policy/protocols/modbus/track-memmap其记录结构MemmapInfo包含ts、uid、id、register设备内存偏移、old_val、new_val、delta七列适合直接对接资产监控与工控异常告警场景。十一、验证与测试仓库在 testing/btest/scripts/base/protocols/modbus/ 提供了完善的测试用例events.zeek覆盖全部事件参数输出与覆盖率统计coil_parsing_big.zeek / coil_parsing_small.zeek / register_parsing.zeek验证线圈与寄存器的位级/字级解析exception_handling.test验证异常码处理与日志输出length_mismatch.zeek验证长度不匹配时的违规检测modbus_and_non_modbus_on_port_502.test验证 502 端口上 Modbus 与非 Modbus 流量的区分policy.zeek验证策略脚本行为。本地复现事件测试的命令见 events.zeek 的TEST-EXEC行zeek -b -r testing/btest/Traces/modbus/modbus.pcap testing/btest/scripts/base/protocols/modbus/events.zeek运行后查看modbus.log可以看到形如以下的记录func为语义名称、pdu_type为REQ/RESP、异常时exception字段填充ts,uid,id,tid,unit,func,pdu_type,exception ...,...,...,...,...,READ_HOLDING_REGISTERS,REQ, ...,...,...,...,...,READ_HOLDING_REGISTERS,RESP, ...,...,...,...,...,READ_HOLDING_REGISTERS_EXCEPTION,RESP,ILLEGAL_DATA_ADDRESS十二、小结从 main.zeek 这一份 Base Modbus analysis script 出发可以完整还原 Zeek Modbus 分析的全链路端口触发Modbus::ports默认502/tcp可redef扩展注册分析器报文解析binpac 描述文件解析 Modbus/TCP 传输头与各功能码 PDU双向成功解析后确认协议事件分发modbus_message/modbus_exception通用事件 20 组功能级请求/响应事件状态组装脚本层build_func()完成功能码语义化pdu_type区分请求/响应异常码查表写入exception日志输出modbus.log通过Log::create_stream注册log_modbus事件与log_policy钩子提供写入前的定制入口策略扩展如 track-memmap.zeek 所示可叠加寄存器变更跟踪等业务能力。掌握这六个环节你就拥有了从原始工控流量到结构化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点击查看免费下载相关推荐Zeek Modbus 协议分析指南base/protocols/modbus 脚本包、事件体系与 modbus.log 日志实战Zeek Modbus 协议分析指南base/protocols/modbus 脚本包、事件体系与 modbus.log 日志实战 Modbus 是工业控制系网络安全网络IDSZeek 的 Modbus 协议分析基础脚本解析加载机制、日志字段与源码实现Zeek 的 Modbus 协议分析基础脚本解析加载机制、日志字段与源码实现 导读 本文围绕 Zeek 仓库中 doc/scripts/base/protoc网络安全网络IDSZeek 中 HTTP 协议分析基础从日志模型到源码级实现指南Zeek 中 HTTP 协议分析基础从日志模型到源码级实现指南 本文基于 Zeek 仓库中 scripts/base/protocols/http/main.网络安全网络IDS上一篇Python NFC开发终极指南NFCpy从入门到实战下一篇beautiful-react-hooks轻量级 React 自定义 Hook 集合库全解——从安装、Hook 目录到源码级设计剖析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考