Zeek DNS 协议分析模块全解析:base/protocols/dns 的加载结构、常量映射与查询响应跟踪机制
网络安全网络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 仓库中base/protocols/dns脚本包含 index.rst 所描述的__load__.zeek、consts.zeek、main.zeek、check-event-handlers.zeek四个文件为核心系统讲解 Zeek 如何对 Domain Name System (DNS) 协议进行开箱即用的分析从脚本包的加载顺序、DNS 数值码点QTYPE/QCLASS/RCCODE/Opcode 等到人类可读字符串的映射表再到日志记录DNS::Info的字段定义、查询/响应的配对跟踪状态机与可调优的运行时选项。读完本文你将能够读懂dns.log的每一列来源知道如何通过redef/option调整 DNS 分析行为并能依据源码级证据扩展自己的 DNS 分析脚本。脚本包结构四个文件如何协同工作Zeek 的脚本包以__load__.zeek为统一入口。scripts/base/protocols/dns/__load__.zeek的内容非常精简仅有三行load指令load ./consts load ./main load ./check-event-handlers也就是说加载base/protocols/dns等价于按以下顺序加载三个子模块文件职责consts.zeek定义 DNS 分析所需的类型数值码点、错误码与字段常量是helper file供其他 DNS 分析脚本复用main.zeek基础 DNS 分析脚本跟踪并记录 DNS 查询及其响应定义日志流DNS::LOG与核心数据结构check-event-handlers.zeek校验脚本当某些 DNS 事件处理器因配置原因永远不会被触发时向用户发出警告三个文件均声明module DNS;所有导出符号都位于DNS::命名空间下。consts.zeek是纯数据层只定义常量与映射表main.zeek是逻辑层注册日志流、挂接事件、维护配对状态check-event-handlers.zeek是健康检查层职责边界非常清晰。consts.zeekDNS 数值码点的权威映射表consts.zeek源码是理解dns.log中qtype_name、qclass_name、rcode_name、opcode_name等可读字段的唯一事实来源。这些字段之所以能自动变成字符串正是因为每张映射表都带有default兜底函数当遇到表中未收录的新码点时会返回query-N、rcode-N、qclass-N这类占位字符串保证日志字段永不缺失。标量常量常量值语义DNS::PTR12RR TYPE 值域名的指针记录反向解析DNS::EDNS41OPT 伪资源记录的 RR TYPE 值代表 EDNS(0) 扩展DNS::NONE254表示无类别的 class 值用于动态更新RFC 2136DNS::ANY255QTYPE 值表示请求所有记录操作码Opcode常量consts.zeek定义了DNS::DNS_OP_QUERY 0、DNS::DNS_OP_IQUERY 1、DNS::DNS_OP_SERVER_STATUS 2、DNS::DNS_OP_NOTIFY 4、DNS::DNS_OP_DYNAMIC_UPDATE 5、DNS::DNS_OP_DSO 6并配套两张映射表DNS::opcodes标准 DNS 操作码 → 可读名如[0]query、[5]dynamic-update、[6]dsoDNS::netbios_opcodesNetBIOS Name ServiceNBNSRFC 1002专用操作码 → 可读名如[0]netbios-query、[5]netbios-registration、[8]netbios-refresh。main.zeek的set_session钩子中正是依据msg$is_netbios标志来决定查哪张表从而正确渲染 NetBIOS 查询的 opcode 名称。查询类型表DNS::query_types这是 Zeek 维护的一张覆盖面很广的 QTYPE 映射表源码位置从经典类型到 DNSSEC、再到现代加密 DNS 类型均有收录。节选如下完整定义以源码为准码点名称码点名称1A28AAAA2NS33SRV5CNAME35NAPTR6SOA43DS12PTR46RRSIG15MX48DNSKEY16TXT64SVCB41OPT65HTTPS250TSIG257CAA252AXFR255*任意类型此外还收录了WINS/WINS-R65281/65282、INTEGRITY65521等私有或扩展码点。dns_request事件处理中qtype_name直接通过query_types[qtype]获得。类别表DNS::classes与错误码表DNS::base_errorsDNS::classes覆盖 CLASS/QCLASS 字段[1]C_INTERNET、[2]C_CSNET、[3]C_CHAOS、[4]C_HESIOD、[254]C_NONE、[255]C_ANYDNS::base_errors覆盖除 TSIG/EDNS 之外的标准 RCODE[0]NOERROR、[1]FORMERR、[2]SERVFAIL、[3]NXDOMAIN、[4]NOTIMP、[5]REFUSED、[6]YXDOMAIN、[8]NXRRSet、[9]NOTAUTH、[16]BADVERS、[17]BADKEY、[18]BADTIME、[19]BADMODE、[23]BADCOOKIE、[3842]BADSIG等未分配码点11–15显式标注为unassigned-N。DNSSEC 相关表DNS::algorithmsDNSKEY/DS/RRSIG 记录中的签名算法如[1]RSA_MD5、[5]RSA_SHA1、[8]RSA_SHA256、[10]RSA_SHA512、[13]ECDSA_curveP256withSHA256、[15]Ed25519、[16]Ed448、[254]PrivateOIDDNS::digestsDS 记录使用的摘要类型如[1]SHA1、[2]SHA256、[3]GOST_R_34_11_94、[4]SHA384DNS::edns_zfieldEDNS 扩展头中 Z 字段的取值[0]NOVALUE、[32768]DNS_SEC_OK表示接受 DNSSEC RR。SVCB/HTTPS 参数表DNS::svcparam_keysDNS::svcparam_keys依据 RFC 9460 定义 SVCB/HTTPS 记录的 SvcParam 键[0]mandatory、[1]alpn、[2]no-default-alpn、[3]port、[4]ipv4hint、[5]ech、[6]ipv6hint。源码注释明确要求Keep in sync with src/analyzer/protocol/dns/DNS.h SVCPARAM_Key即与 C 分析器侧的枚举保持一致这也是验证映射正确性的关键线索。main.zeek查询/响应跟踪与 dns.log 的生成main.zeek源码是整个 DNS 分析的核心。它的主要工作分为三层注册日志流、维护连接级配对状态、通过事件/钩子持续填充日志记录。日志流与端口注册redef enum Log::ID { LOG }; const ports { 53/udp, 53/tcp, 137/udp, 5353/udp, 5355/udp } redef; event zeek_init() priority5 { Log::create_stream(DNS::LOG, Log::Stream($columnsInfo, $evlog_dns, $pathdns, $policylog_policy)); Analyzer::register_for_ports(Analyzer::ANALYZER_DNS, ports); }要点新增枚举DNS::LOG作为日志流标识创建日志流时指定$columnsInfo列结构、$evlog_dns写日志前触发的可观察事件、$pathdns即dns.log、$policylog_policy默认策略钩子通过Analyzer::register_for_ports让 DNS 分析器监听一组众所周知端口默认集合为{ 53/udp, 53/tcp, 137/udp, 5353/udp, 5355/udp }覆盖标准 DNS、NetBIOS(137)、mDNS(5353) 与 LLMNR(5355)。这是一个redef常量可通过redef DNS::ports { ... };扩充。日志记录结构DNS::InfoDNS::Info是dns.log的列定义源码位置逐列含义如下字段类型是否记录语义tstime是该连接上最早观测到 DNS 协议消息的时间uidstring是传输 DNS 消息的连接的唯一标识idconn_id是连接的 4 元组两端地址/端口prototransport_proto是传输层协议UDP/TCPtrans_idcount是可选发起方分配的 16 位事务 ID用于配对响应rttinterval是可选查询到响应开始之间的往返时延querystring是可选查询的域名qclass/qclass_namecount / string是可选查询类别值及其可读名qtype/qtype_namecount / string是可选查询类型值及其可读名rcode/rcode_namecount / string是可选响应码值及其可读名AA/TC/RD/RAbool是默认 F权威应答、截断、期望递归、可用递归四个标志位Zcount是默认 0RFC 1035 规定的 3 位保留字段DNSSEC 时可能非零answersvector of string是可选应答区中的资源描述集合TTLsvector of interval是可选与answers对应的缓存生存期rejectedbool是默认 F查询是否被服务器拒绝opcode/opcode_namecount / string是可选请求/响应的操作码值及可读名total_answerscount否可选应答区资源记录总数total_repliescount否可选应答权威附加区资源记录总数saw_query/saw_replybool否默认 F是否已观测到完整查询/响应另有两个仅当加载特定 policy 脚本时才出现的可选字段加载 scripts/policy/protocols/dns/auth-addl.zeek 后会出现auth权威应答与addl附加应答加载 scripts/policy/protocols/dns/log-original-query-case.zeek 后会出现original_query保留原始大小写的查询名。这种字段按需扩展的机制是 Zeek 脚本生态的典型模式。连接状态DNS::State与待配对消息队列DNS::State源码位置按连接跟踪 DNS 查询状态type State: record { pending_query: Info optional; # 尚未配对的单条查询性能快路径 pending_queries: PendingMessages optional; # 按事务 ID 索引的未配对查询队列 pending_replies: PendingMessages optional; # 按事务 ID 索引的未配对响应队列 };其中PendingMessages是table[count] of Queue::Queue——即以事务 ID 为键、值为base/utils/queue.zeek提供的 FIFO 队列。之所以引入队列而非单条记录是因为同一连接上可能并发出现多个共享同一事务 ID 的查询/响应例如大型 AXFR 区域传输过程中会有大量同 ID 消息。该状态通过redef record connection { dns: Info optional; dns_state: State optional; };挂接到每个连接记录上并在set_session钩子中随连接生命周期创建、通过Conn::register_removal_hook(c, finalize_dns)注册连接移除回调。配对逻辑set_session钩子set_session钩子源码位置是查询/响应配对的核心由dns_message事件以priority5触发并传入is_query ! msg$QR。其分支逻辑可以归纳为多播目的地短路若响应方地址属于DNS::multicast_subnets默认{ 224.0.0.0/4, ff00::/8 }则不做配对直接将saw_query与saw_reply置真让dns_end立即记录该消息。这是因为 mDNS/LLMNR 的响应来自不同连接按事务 ID 配对会得到错误结果查询方向若该事务 ID 已有待配对响应则直接取队列头部响应与之配对否则新建会话并放入pending_query快路径或pending_queries队列响应方向优先与pending_query中同 ID 的查询配对否则查pending_queries队列仍无匹配则放入pending_replies等待后续查询填充公共字段响应方向补充rcode/rcode_name、total_answers、total_replies、rejectedrcode ! 0且无查询消息时置真双向都填充opcode/opcode_name。超限兜底两个运行时选项为防止异常流量如服务器损坏、Zeek 丢包、AXFR 进行中导致内存膨胀main.zeek提供两个可用option调整的阈值enqueue_new_msg函数中生效DNS::max_pending_msgs默认50单个事务 ID 的待配对查询/响应达到该数量后放弃继续匹配先把队列中已有的未配对消息全部写日志再清空重建DNS::max_pending_query_ids默认50待配对查询/响应横跨的不同事务 ID 数量达到该值后认为这些消息永远无法配对整体记录后丢弃。应答收集do_reply钩子与各dns_*_reply事件main.zeek为每种资源记录类型实现了对应的dns_*_reply事件处理器均以priority5挂接它们把 RR 的具体内容格式化为字符串后统一交给DNS::do_reply钩子dns_A_reply/dns_AAAA_reply/dns_A6_reply输出地址字符串对动态更新消息额外带上ans$query前缀dns_TXT_reply/dns_SPF_reply逐条输出TXT 长度 内容/SPF 长度 内容多条以空格连接dns_NS_reply、dns_CNAME_reply、dns_MX_reply、dns_PTR_reply、dns_SRV_reply输出名称/目标dns_SOA_reply输出soa$mname主域名服务器dns_NAPTR_reply编码 order/preference/flags/service/regexp/replacementDNSSEC 系列dns_RRSIGRRSIG 覆盖类型 签名者根签名者显示为Root、dns_DNSKEY算法、dns_NSECNSEC 查询 下一名称、dns_NSEC3/dns_NSEC3PARAM、dns_DS算法与摘要类型、dns_SSHFP十六进制指纹、dns_LOCsize/精度动态更新dns_dynamic_update_pre与dns_dynamic_update_del依据 qclass/qtype 组合生成NameNotInUse、NameInUse、NoRRSet、RRSetExists、RRSet *、RR ...等描述。DNS::do_reply钩子默认实现在收集阶段完成以下工作填充query若尚未设置、AA/RA标志、计算rttnetwork_time() - c$dns$ts为 0 时删除以免误报、追加answers与TTLs。do_reply是一个hook意味着用户脚本可以在其默认实现之前/之后插入额外逻辑。记录时机dns_end与finalize_dnsdns_end事件priority5负责置位saw_query/saw_reply另一重载priority-5在两者均为真时执行Log::write(DNS::LOG, c$dns)并清除从而保证查询与响应成对后只记一行DNS::finalize_dns是Conn::RemovalHook在连接状态到期时被调用把仍滞留的pending_query、pending_queries、pending_replies全部写日志避免状态被丢弃导致日志缺失。事件与钩子速查符号类型用途DNS::log_dnsevent(rec: Info)每条记录写入日志框架前触发供用户脚本访问/修改DNS::InfoDNS::set_sessionhook(c, msg, is_query)每次建立 DNS 会话查询或响应时调用可做附加初始化DNS::do_replyhook(c, msg, ans, reply)各类dns_*_reply收集应答的统一入口DNS::log_policyLog::PolicyHook日志流的默认策略钩子DNS::finalize_dnsConn::RemovalHook连接移除时冲刷未配对消息check-event-handlers.zeek预防静默失效的事件处理器校验check-event-handlers.zeek 的职责非常独特当全局选项dns_skip_all_addl为真时附加区additional section相关事件不会被分析器触发如果用户脚本仍然使用了这些事件会产生看起来没问题、实际上从不执行的静默失效。该脚本在zeek_initpriority20早于一般脚本中检查dns_TSIG_addl、dns_EDNS_addl、dns_EDNS_ecs、dns_EDNS_tcp_keepalive、dns_EDNS_cookie五个事件是否被用户处理是则输出Reporter::warning同时警告dns_TKEY事件在dns_skip_all_addl下ans参数将不含数据。这体现了 Zeek 脚本框架中可观测性优先的设计习惯——宁可显式警告也不让脚本静默失效。结合 policy 脚本的扩展用法base层的 DNS 分析默认只记录基础列仓库在scripts/policy/protocols/dns/目录提供以下按需加载的增强脚本auth-addl.zeek为DNS::Info增加auth与addl字段记录权威区与附加区的应答log-original-query-case.zeek增加original_query字段保留查询名的原始大小写对检测基于大小写差异的隐蔽通道有价值detect-external-names.zeek 与 disable-opcode-log-fields.zeek前者检测外部域名解析后者可在不需要 opcode 字段时缩减日志体积。典型使用方式是在local.zeek或自定义脚本中load policy/protocols/dns/auth-addl。这些脚本通过redef扩展DNS::Info记录或挂接DNS::log_policy展示了 base 层为上层策略预留的扩展点。实战验证与调试建议跑通基线对任意包含 DNS 流量的 pcap 运行zeek -r capture.pcap或zeek -C -r ...检查生成的dns.log是否包含qtype_name、rcode_name等可读列并对照 doc/scripts/base/protocols/dns/main.zeek.rst 中DNS::Info的字段定义逐一核对观察配对行为对正常流量检查rtt与trans_id的对应关系对 mDNS/LLMNR 流量多播地址应能看到逐条独立记录而非配对记录这正是multicast_subnets逻辑生效的表现调整分析范围若需监控自定义端口上的 DNS使用redef DNS::ports { 5353/udp };若担心异常流量导致队列膨胀可调低DNS::max_pending_msgs或DNS::max_pending_query_ids扩展日志字段在脚本中hook DNS::log_dns或直接event DNS::log_dns读取/改写rec即可在记录落盘前附加自定义信息需要权威/附加区信息时加载policy/protocols/dns/auth-addl。从源码结构看base/protocols/dns的设计目标可以概括为以 consts 提供数字 ↔ 语义的确定性翻译以 main 提供健壮的跨连接配对状态机以 hook/event 暴露全部扩展点再以 check-event-handlers 兜底配置陷阱——四个文件各司其职共同构成 Zeek 对 DNS 协议分析的开箱即用能力。赞分享网络安全网络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 DNS 协议分析脚本base/protocols/dns/main.zeek深度解析日志字段、查询/响应关联与源码实现Zeek DNS 协议分析脚本base/protocols/dns/main.zeek深度解析日志字段、查询/响应关联与源码实现 导读 scripts/b网络安全网络IDSZeek 中 SMB 协议分析包base/protocols/smb加载机制与脚本架构深度解析Zeek 中 SMB 协议分析包base/protocols/smb加载机制与脚本架构深度解析 本文以 Zeek 仓库中的 doc/scripts/base网络安全网络IDSZeek base/packet-protocols 包解析__load__.zeek 加载机制与 30 数据链路/隧道协议分析器全景Zeek base/packet protocols 包解析__load__.zeek 加载机制与 30 数据链路/隧道协议分析器全景 base/packe网络安全网络IDS创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考