☰
Zeek 分析器框架内部接口解析:深入 `Analyzer::__` 系列 BIF 函数与动态协议分析机制
2026/9/28 2:41:44 网站建设 项目流程
  • 网络安全
  • 网络
  • IDS

【免费下载链接】zeek

Zeek 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/bif/analyzer.bif.zeek这一自动生成的脚本接口文档,系统讲解分析器框架(Analyzer Framework)的 8 个内部 BIF 函数(__enable_analyzer、__disable_analyzer、__disable_all_analyzers、__register_for_port、__schedule_analyzer、__name、__tag、__has_tag)及其背后的 C++ 实现。读者将掌握 Zeek 如何动态启用/禁用协议分析器、如何为分析器注册知名端口、如何为即将到来的连接预调度分析器,以及这些内部函数如何被Analyzer脚本框架封装成可直接调用的公共接口。

一、背景:分析器框架与 BIF 的桥接作用

Zeek 的分析器(Analyzer)是解析具体协议的核心组件。每支持一种协议,Zeek 就有一个从analyzer::Analyzer派生的类;一旦确定某条连接的载荷要按某种协议解析,Zeek 就实例化对应的分析器,并将其作为子节点加入连接的"分析器树"(analyzer tree)。同时,每种协议还有一个从analyzer::Component派生的"元类",描述该分析器并记录其是否处于启用状态(src/analyzer/Manager.h)。

脚本层与 C++ 内核之间的沟通依靠BIF(Built-In Function)机制:src/analyzer/analyzer.bif中定义的函数会被编译进 Zeek 二进制,从而在 Zeek 脚本中直接可用。文档base/bif/analyzer.bif.zeek正是由该.bif文件生成的脚本接口文档(对应仓库中的 doc/scripts/base/bif/analyzer.bif.zeek.rst),文档开头的说明"Internal functions and types used by the analyzer framework"表明这些函数被定位为框架内部接口——它们通常不直接面向用户脚本,而是由 scripts/base/frameworks/analyzer/main.zeek 这一公共框架封装后对外提供。

这些函数分布在两个命名空间中:

  • Analyzer命名空间:__enable_analyzer、__disable_analyzer、__disable_all_analyzers、__register_for_port、__schedule_analyzer;
  • GLOBAL命名空间:__name、__tag、__has_tag(它们面向所有分析器类型,包括协议、报文、文件分析器)。

二、分析器的启用与禁用

2.1Analyzer::__enable_analyzer(id: Analyzer::Tag) : bool

该函数接受一个Analyzer::Tag(如Analyzer::ANALYZER_HTTP)并返回bool,用于启用某个协议分析器。其 C++ 实现直接委托给分析器管理器:

// src/analyzer/analyzer.bif function Analyzer::__enable_analyzer%(id: Analyzer::Tag%) : bool %{ return zeek::analyzer_mgr->EnableAnalyzer(id->AsEnumVal()); %}

EnableAnalyzer在 src/analyzer/Manager.cc 中通过Lookup按 Tag 找到对应Component,若不存在返回false,否则调用p->SetEnabled(true)并返回true。注意Lookup(tag, false)的第二个参数false表示不跟随映射(见下文__name的说明),即直接操作传入 Tag 对应的组件本身。

2.2Analyzer::__disable_analyzer(id: Analyzer::Tag) : bool

与启用对称,禁用某个协议分析器。其实现调用analyzer_mgr->DisableAnalyzer(id->AsEnumVal()),底层同样是Lookup后调用p->SetEnabled(false)(src/analyzer/Manager.cc)。被禁用的分析器不会再为新的连接实例化,但已存在的连接不受影响。

一个有意思的细节是:在 src/analyzer/Analyzer.cc 的单元测试中,Zeek 通过DisableAnalyzer禁用 SSH 分析器并配合AddComponentMapping将 SSH 映射到 IMAP,验证了映射机制——禁用后InstantiateAnalyzer("SSH", ...)得到的实际是 IMAP 分析器。这说明"禁用"与"映射"这两个机制可以协同工作,是框架内部测试直接使用该接口的实证。

2.3Analyzer::__disable_all_analyzers() : any

无参、返回any(实际返回nullptr),一次性禁用所有已注册的分析器:

function Analyzer::__disable_all_analyzers%(%) : any %{ zeek::analyzer_mgr->DisableAllAnalyzers(); return nullptr; %}

底层实现遍历GetComponents()返回的全部Component列表并逐个SetEnabled(false)(src/analyzer/Manager.cc)。这与Analyzer::disable_all选项配合:当disable_all = T时,zeek_init阶段会调用本函数关闭一切分析器,再通过requested_analyzers集合选择性启用(见 scripts/base/frameworks/analyzer/main.zeek)。

2.4 脚本框架封装:Analyzer::enable_analyzer与disable_analyzer

内部__函数不区分分析器类型,而脚本框架提供了更友好的统一入口。scripts/base/frameworks/analyzer/main.zeek中的公共函数接受AllAnalyzers::Tag(一个包含协议、报文、文件三类分析器 Tag 的全局枚举),并按类型分发:

function enable_analyzer(tag: AllAnalyzers::Tag) : bool { if ( is_packet_analyzer(tag) ) return PacketAnalyzer::__enable_analyzer(tag); if ( is_file_analyzer(tag) ) return Files::__enable_analyzer(tag); return __enable_analyzer(tag); }

即:报文分析器走PacketAnalyzer::__enable_analyzer、文件分析器走Files::__enable_analyzer、协议分析器才落到本文档的Analyzer::__enable_analyzer(scripts/base/frameworks/analyzer/main.zeek)。用户在脚本中自定义启用逻辑时,应优先使用这套公共接口,而非直接调用__内部函数。

三、端口注册:让知名端口自动激活分析器

3.1Analyzer::__register_for_port(id: Analyzer::Tag, p: port) : bool

将某个协议分析器与一个知名端口绑定。一旦看到使用该端口的新连接,Zeek 会自动为该连接分配对应分析器。签名接受Analyzer::Tag与port(port值本身已包含传输层协议,如80/tcp、53/udp):

function Analyzer::__register_for_port%(id: Analyzer::Tag, p: port%) : bool %{ return zeek::analyzer_mgr->RegisterAnalyzerForPort(id->AsEnumVal(), p); %}

RegisterAnalyzerForPort(EnumVal*, PortVal*)先将 Tag 解析为Component,再调用底层重载(src/analyzer/Manager.cc)。底层实现有一个值得注意的时序约束:在InitPostScript完成之前(即脚本尚未全部处理完时),端口注册无法立即生效,会被暂存在pending_analyzers_for_ports集合中,待初始化完成后统一处理(src/analyzer/Manager.cc);随后按 TCP/UDP 找到对应的报文分析器(packet_mgr->GetAnalyzer("TCP")/"UDP"),最终调用IPBasedAnalyzer::RegisterAnalyzerForPort真正注册(src/analyzer/Manager.cc)。这也解释了为什么脚本层通常只在zeek_init阶段注册端口。

3.2 脚本框架封装:register_for_port与register_for_ports

脚本框架在其封装中不仅调用内部函数,还维护了一个ports: table[AllAnalyzers::Tag] of set[port]表用于 BPF 过滤与动态协议检测(DPD):

function register_for_port(tag: Analyzer::Tag, p: port) : bool { if ( ! __register_for_port(tag, p) ) return F; if ( tag !in ports ) ports[tag] = set(); add ports[tag][p]; return T; }

而register_for_ports(tag, server_ports, non_server_ports &default=set())则批量注册,并自动把server_ports并入likely_server_ports(用于启发式服务器端口判断),non_server_ports(如客户端端口)则不会加入(scripts/base/frameworks/analyzer/main.zeek)。框架注释特别强调:注册是追加式的,不会替换已有端口。此外,analyzer_to_bpf(tag)与get_bpf()会基于该ports表生成对应的 BPF 过滤表达式,使 Zeek 可以只捕获与已注册分析器相关的流量(scripts/base/frameworks/analyzer/main.zeek)。

四、连接预调度:Analyzer::__schedule_analyzer

签名:

function Analyzer::__schedule_analyzer(orig: addr, resp: addr, resp_p: port, analyzer: Analyzer::Tag, tout: interval) : bool

该函数"预订"一个未来出现的连接:当 Zeek 看到与(orig, resp, resp_p)匹配的新连接时,会自动把analyzer加入该连接的分析器树。tout是预订的超时时间,必须非零;超时后请求被清除。其实现为:

zeek::analyzer_mgr->ScheduleAnalyzer(orig->AsAddr(), resp->AsAddr(), resp_p, analyzer->AsEnumVal(), tout); return true;

从 src/analyzer/Manager.h 的数据结构可以看出调度机制的内部细节:

  • ConnIndex以(orig, resp, resp_p, proto)四元组为索引,其中orig允许用0.0.0.0作通配符匹配任意发起方(见ScheduleAnalyzer的注释,src/analyzer/Manager.h);
  • 调度请求存储在conns_map(std::multimap<ConnIndex, ScheduledAnalyzer*>)中,同时conns_by_timeout优先队列按queue_timeout排序,用于定期过期清理(ExpireScheduledAnalyzers);
  • 当连接真正出现时,ApplyScheduledAnalyzers(Connection*, ...)检索并挂载这些预调度分析器,GetScheduled返回命中集合(src/analyzer/Manager.h)。

一个硬性约束:ScheduleAnalyzer在run_state::network_time尚未建立(即网络处理尚未开始)时会直接告警 "cannot schedule analyzers before processing begins; ignored"(src/analyzer/Manager.cc),因此该函数只能在网络运行期间使用。

脚本框架的封装Analyzer::schedule_analyzer(orig, resp, resp_p, analyzer, tout)直接透传内部函数(scripts/base/frameworks/analyzer/main.zeek)。典型应用场景是:检测到某主机将发起某种协议交互(如基于先前告警或外部情报),提前为其预订分析器。

五、名称与 Tag 的相互转换(GLOBAL 命名空间)

文档中另外三个函数位于GLOBAL命名空间,接受/返回AllAnalyzers::Tag,用于在分析器名称字符串与Tag 枚举值之间转换。它们在 src/analyzer/analyzer.bif 中共享同一个辅助函数component_for_name:依次在analyzer_mgr(协议分析器)、packet_mgr(报文分析器)、file_mgr(文件分析器)中按名称查找组件,找到则返回对应plugin::Component。

5.1__name(atype: AllAnalyzers::Tag) : string

返回给定 Tag 对应的分析器规范名称(CanonicalName)。实现的关键注释是:这里不跟随映射("Note that we don't want to follow mappings here"),即用户传入什么 Tag 就返回什么名字,即使该 Tag 在内部被映射到别的分析器(如前面提到的 SSH→IMAP 映射),也不会被改写。若三类管理器都找不到组件,则返回字符串"<error>"(src/analyzer/analyzer.bif)。

5.2__tag(name: string) : AllAnalyzers::Tag

按名称反查 Tag。若名称有效,返回对应Component的Tag();否则返回空的zeek::Tag()(t.AsVal()会得到一个无效 Tag 值,src/analyzer/analyzer.bif)。注意AllAnalyzers::Tag是包含协议/报文/文件三类 Tag 的"大枚举",因此该函数能解析任意类型的分析器名称。

5.3__has_tag(name: string) : bool

检查给定名称是否为已知的分析器名,实现即component_for_name(name->CheckString()) != nullptr(src/analyzer/analyzer.bif)。脚本框架文档建议:在调用Analyzer::get_tag之前先调用Analyzer::has_tag验证名称合法性,避免得到无效 Tag(scripts/base/frameworks/analyzer/main.zeek)。

5.4 脚本框架封装

框架对应提供了Analyzer::name(atype)、Analyzer::has_tag(name)、Analyzer::get_tag(name)以及Analyzer::kind(atype)(返回"protocol"/"packet"/"file"/"unknown")等公共接口,全部直接透传上述内部函数(scripts/base/frameworks/analyzer/main.zeek)。这些接口常被用于把分析器名称写入日志、在策略脚本中按名称动态查找分析器等场景。

六、8 个内部函数速查表

函数命名空间签名底层调用说明
__enable_analyzerAnalyzer(id: Analyzer::Tag) : boolEnableAnalyzer(EnumVal*)启用协议分析器
__disable_analyzerAnalyzer(id: Analyzer::Tag) : boolDisableAnalyzer(EnumVal*)禁用协议分析器
__disable_all_analyzersAnalyzer() : anyDisableAllAnalyzers()禁用全部分析器
__register_for_portAnalyzer(id: Analyzer::Tag, p: port) : boolRegisterAnalyzerForPort(...)为分析器注册知名端口
__schedule_analyzerAnalyzer(orig: addr, resp: addr, resp_p: port, analyzer: Analyzer::Tag, tout: interval) : boolScheduleAnalyzer(...)为未来连接预调度分析器
__nameGLOBAL(atype: AllAnalyzers::Tag) : stringLookup三类管理器Tag → 名称
__tagGLOBAL(name: string) : AllAnalyzers::Tagcomponent_for_name名称 → Tag
__has_tagGLOBAL(name: string) : boolcomponent_for_name名称是否有效

七、实战建议与调用约束

综合以上源码证据,使用这套内部接口时有几点值得注意:

  1. 优先使用脚本框架公共接口:Analyzer::enable_analyzer、disable_analyzer、register_for_ports、schedule_analyzer、name、kind、has_tag、get_tag等封装(scripts/base/frameworks/analyzer/main.zeek)已处理了报文/文件分析器的分发与端口表的维护,直接使用__内部函数会绕过这些逻辑。
  2. 时序约束:端口注册在InitPostScript之前只会进入待处理队列;分析器调度在run_state::network_time建立之前会被拒绝并告警。因此端口注册应放在zeek_init阶段,调度操作应放在网络运行期间。
  3. 配合disable_all与requested_analyzers:全禁用后可通过requested_analyzers集合在启动时选择性启用(优先级 -5 的zeek_init处理),实现"默认全关、按需开启"的精细化控制(scripts/base/frameworks/analyzer/main.zeek)。
  4. 返回值的语义:__enable_analyzer/__disable_analyzer/__register_for_port在 Tag 无效或组件缺失时返回false;__tag对未知名称返回无效 Tag,务必配合__has_tag使用。

如需进一步深入,可阅读分析器管理器的完整实现 src/analyzer/Manager.cc、其接口声明 src/analyzer/Manager.h,以及生成这些内部函数文档的源定义文件 src/analyzer/analyzer.bif;而 scripts/base/frameworks/analyzer/main.zeek 则展示了全部公共封装如何在这些内部接口之上构建完整的分析器管理框架。

  • 网络安全
  • 网络
  • IDS

【免费下载链接】zeek

Zeek is a powerful network analysis framework that is much different from the typical IDS you may know.

项目地址:https://gitcode.com/gh_mirrors/ze/zeek
点击查看免费下载
上一篇:终极窗口管理革命:用FancyZones告别杂乱桌面,5分钟打造完美工作区
下一篇:从零开始构建DeepSeek R1:完整指南与核心技术解析

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

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

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

立即咨询