- 网络安全
- 网络
- IDS
【免费下载链接】zeek
Zeek is a powerful network analysis framework that is much different from the typical IDS you may know.
本文围绕 Zeek 内置Zeek::BitTorrent插件导出的 18 个 BitTorrent 事件,完整梳理事件签名、参数语义、协议对应关系与底层实现原理。读完本文,你将能够:在自己的 Zeek 脚本中正确挂接bittorrent_peer_*与bt_tracker_*事件族,理解握手校验、Peer 消息分发、Tracker 的 HTTP + Bencode 解析机制,并掌握如何用 DPD 签名激活 Tracker 分析。
事件总览:一份摘要表
Zeek::BitTorrent插件通过 events.bif 声明了 18 个事件,全部位于GLOBAL命名空间,可分为两个家族:Peer 协议事件(bittorrent_peer_*,14 个)与Tracker 会话事件(bt_tracker_*,4 个)。官方 API 文档 Zeek_BitTorrent.events.bif.zeek.rst 以摘要表形式列出了全部事件:
| 事件 | 类型签名(简写) |
|---|---|
bittorrent_peer_bitfield | event (c: connection, is_orig: bool, bitfield: string) |
bittorrent_peer_cancel | event (c: connection, is_orig: bool, index: count, begin: count, length: count) |
bittorrent_peer_choke | event (c: connection, is_orig: bool) |
bittorrent_peer_handshake | event (c: connection, is_orig: bool, reserved: string, info_hash: string, peer_id: string) |
bittorrent_peer_have | event (c: connection, is_orig: bool, piece_index: count) |
bittorrent_peer_interested | event (c: connection, is_orig: bool) |
bittorrent_peer_keep_alive | event (c: connection, is_orig: bool) |
bittorrent_peer_not_interested | event (c: connection, is_orig: bool) |
bittorrent_peer_piece | event (c: connection, is_orig: bool, index: count, begin: count, piece_length: count) |
bittorrent_peer_port | event (c: connection, is_orig: bool, listen_port: port) |
bittorrent_peer_request | event (c: connection, is_orig: bool, index: count, begin: count, length: count) |
bittorrent_peer_unchoke | event (c: connection, is_orig: bool) |
bittorrent_peer_unknown | event (c: connection, is_orig: bool, message_id: count, data: string) |
bittorrent_peer_weird | event (c: connection, is_orig: bool, msg: string) |
bt_tracker_request | event (c: connection, uri: string, headers: bt_tracker_headers) |
bt_tracker_response | event (c: connection, status: count, headers: bt_tracker_headers, peers: bittorrent_peer_set, benc: bittorrent_benc_dir) |
bt_tracker_response_not_ok | event (c: connection, status: count, headers: bt_tracker_headers) |
bt_tracker_weird | event (c: connection, is_orig: bool, msg: string) |
几乎所有事件的参数都带is_orig: bool,用于标识该消息来自连接发起方(originator,值为T)还是响应方(responder,值为F);c: connection则给出承载该消息的 TCP 连接上下文。所有事件均可在同一连接上反复触发(如一次会话中多条request/piece消息),脚本中可依据is_orig与连接状态做精细分流。
插件架构:两个 Analyzer 组件
在深入逐个事件之前,先理解事件从何而来。Plugin.cc 中插件Zeek::BitTorrent注册了两个分析器组件:
AddComponent(new zeek::analyzer::Component("BitTorrent", zeek::analyzer::bittorrent::BitTorrent_Analyzer::Instantiate)); AddComponent(new zeek::analyzer::Component("BitTorrentTracker", zeek::analyzer::bittorrent::BitTorrentTracker_Analyzer::Instantiate));BitTorrent:处理 Peer 之间交换的 wire 协议(握手 + 带长度的消息帧),对应全部 14 个bittorrent_peer_*事件。它继承analyzer::tcp::TCP_ApplicationAnalyzer(内部名称"BITTORRENT",见 BitTorrent.cc),解析引擎由 binpac 生成的binpac::BitTorrent::BitTorrent_Conn承担。BitTorrentTracker:处理客户端与 Tracker 之间的 HTTP 会话(GET 请求 + 响应体 Bencode),对应 4 个bt_tracker_*事件。该组件是一个手工实现的轻量 HTTP + Bencode 解析器(见 BitTorrentTracker.cc)。
两个组件的启用方式不同:Peer 分析器通常直接绑定默认端口或由 DPD 签名触发;Tracker 分析器没有默认端口绑定,必须依赖 DPD 签名(详见下文“实战”章节)。
Peer 协议事件族:与 wire 格式一一对应
Peer 消息的二进制布局定义在 binpac 语法文件 bittorrent-protocol.pac 中,消息类型枚举与事件名的对应关系非常直观:
TYPE_CHOKE=0, TYPE_UNCHOKE=1, TYPE_INTERESTED=2, TYPE_NOT_INTERESTED=3, TYPE_HAVE=4, TYPE_BITFIELD=5, TYPE_REQUEST=6, TYPE_PIECE=7, TYPE_CANCEL=8, TYPE_PORT=9每条消息都采用统一的帧结构:4 字节大端长度前缀(BitTorrent_MessageLength)+ 1 字节消息 ID + 载荷。长度前缀为 0 时表示 Keep-Alive。解析器对每条消息做防御性校验:validate_message_length会拒绝超过MSGLEN_LIMIT(0x40000,即 256 KiB,定义于 bittorrent.pac)的长度前缀,直接判定为协议违规。
会话建立:bittorrent_peer_handshake
event bittorrent_peer_handshake(c: connection, is_orig: bool, reserved: string, info_hash: string, peer_id: string)
握手是 Peer 连接上的第一帧,固定 68 字节,bittorrent-protocol.pac 中BitTorrent_Handshake记录精确描述了布局:
pstrlen(1 字节)+pstr(19 字节协议标识);reserved(8 字节扩展位图)→ 事件参数reserved;info_hash(20 字节,torrent 元数据哈希)→ 事件参数info_hash;peer_id(20 字节,对端自报身份)→ 事件参数peer_id。
握手并非“见 19 字节就收”:binpac 侧的validate_handshake会校验pstrlen == 19且负载等于魔数"BitTorrent protocol",不匹配则抛出"invalid handshake"异常。BitTorrent.cc 的DeliverStream捕获该异常后,会静默地从父分析器移除本子分析器(Parent()->RemoveChildAnalyzer(this)),即不把非 BitTorrent 流量误判为协议违规。握手成功后调用AnalyzerConfirmation()完成协议确认,并置位handshake_ok,后续BitTorrent_PDU才会以消息帧(而非握手)方式解析。
脚本中可用reserved的前 8 字节做扩展能力探测,用info_hash关联具体 torrent,用peer_id做客户端指纹识别。
连接控制:keep_alive / choke / unchoke / interested / not_interested
这五个事件均只携带c与is_orig,对应零载荷消息:
bittorrent_peer_keep_alive(c, is_orig):长度前缀为 0 的保活帧,唯一一个不带消息 ID 的帧;bittorrent_peer_choke(c, is_orig):ID=0,对端宣告不再向本端发送数据;bittorrent_peer_unchoke(c, is_orig):ID=1,解除阻塞,允许传输;bittorrent_peer_interested(c, is_orig):ID=2,本端表示对对方持有数据的兴趣;bittorrent_peer_not_interested(c, is_orig):ID=3,兴趣解除。
由于这些消息只有 ID 没有载荷,binpac 中直接用empty类型描述并在&let块里触发对应deliver_*函数(如deliver_choke、deliver_interested),这些函数最终调用zeek::BifEvent::enqueue_bittorrent_peer_choke(...)之类的事件入队接口。这五个事件组合在一起可以刻画一个 Peer 会话的“节流状态机”,例如用于检测异常的频繁 choke/unchoke 抖动。
数据交换:have / bitfield / request / piece / cancel
这四个事件携带块定位参数,直接反映 BitTorrent 的分片下载模型:
bittorrent_peer_have(c, is_orig, piece_index: count):ID=4,通知对端“我拥有了第piece_index个分片”,载荷为 4 字节uint32;bittorrent_peer_bitfield(c, is_orig, bitfield: string):ID=5,仅在握手后第一条消息出现,以位图(每个 bit 对应一个分片)声明已拥有分片集合,长度由帧长决定(BitTorrent_Bitfield(len));bittorrent_peer_request(c, is_orig, index: count, begin: count, length: count):ID=6,请求对端发送指定分片区间,三个参数均为 4 字节uint32;bittorrent_peer_piece(c, is_orig, index: count, begin: count, piece_length: count):ID=7,携带真实数据块;注意piece_length是len - 8(减去 index 与 begin 各 4 字节),即实际数据字节数,见BitTorrent_Piece的deliver_piece调用;bittorrent_peer_cancel(c, is_orig, index: count, begin: count, length: count):ID=8,撤销先前发出的 request,参数语义与request完全一致。
bitfield以string形式整体交付,脚本侧可用 Zeek 的字符串操作按字节展开为位视图。request/piece/cancel三个事件联动,可用于重建一个连接内的“请求-响应”流水线,例如检测请求了却迟迟不收 piece 的异常对端。
附加消息与兜底:port / unknown / weird
bittorrent_peer_port(c, is_orig, listen_port: port):ID=9,DHT 扩展消息,对端通告自己的监听端口。binpac 解析 2 字节uint16后,在 bittorrent-analyzer.pac 的deliver_port中构造zeek::val_mgr->Port(listen_port, TRANSPORT_TCP),即事件参数恒为 TCP 端口类型。这是建立 P2P 对等连接图谱(谁在监听哪个端口)的重要来源。bittorrent_peer_unknown(c, is_orig, message_id: count, data: string):解析器遇到 ID 不在 0–9 枚举内的消息时触发(BitTorrent_MessageID的default分支),message_id是原始 ID 字节,data是剩余载荷。它保证未来新增消息类型不会导致解析崩溃,是协议的“前向兼容兜底”。bittorrent_peer_weird(c, is_orig, msg: string):协议层面的异常通知。触发路径见 BitTorrent.cc 的DeliverWeird:当 binpac 抛出的异常不是“invalid handshake”时,分析器格式化"Stopping BitTorrent analysis: protocol violation (%s)"并连同方向一起入队;此外内容缺口(content gap)与协议违规叠加时还会调用AnalyzerViolation上报 Zeek 核心的协议违规机制。
注意bittorrent_peer_weird与 Zeek 通用的weird事件是两套独立机制——源码注释中专门留了 “why does bittorrent have a different set of weirds?” 的疑问,实践中两类事件都可能出现,排查时需分开订阅。
Tracker 事件族:HTTP 会话与 Bencode 解析
Tracker 组件在 BitTorrentTracker.cc 中实现了一个不依赖 HTTP 分析器的状态机:请求侧按GET → HEADER → DONE三态推进,响应侧按STATUS → HEADER → BODY → DONE四态推进,并内嵌一个完整的 Bencode 解析器(ResponseParseBenc,支持d字典、l列表、i整数、<len>:<str>字符串四种类型)。
bt_tracker_request:客户端公告请求
event bt_tracker_request(c: connection, uri: string, headers: bt_tracker_headers)
当客户端向 Tracker 发起 HTTP GET(正则^GET[ \t]+匹配,URL 与HTTP/x.y版本号逐段提取)且头部结束后(空行)触发。uri是去掉GET前缀与 HTTP 版本后缀后的原始请求路径(含 query string),通常形如/announce?info_hash=...&peer_id=...。headers是bt_tracker_headers(table[string] of string)类型的头部表。解析器还会根据Connection: close头判定是否 keep-alive,从而决定是否复用解析状态处理后续请求。
bt_tracker_response 与 bt_tracker_response_not_ok:服务端应答
event bt_tracker_response(c: connection, status: count, headers: bt_tracker_headers, peers: bittorrent_peer_set, benc: bittorrent_benc_dir)
event bt_tracker_response_not_ok(c: connection, status: count, headers: bt_tracker_headers)
响应状态行HTTP/x.y <status>解析出status;随后进入头部与 BODY 阶段:
- 若
status != 200,触发bt_tracker_response_not_ok,事件只带状态码与头部,不再解析正文; - 若
status == 200,进入 Bencode 正文解析,成功后触发bt_tracker_response。
peers(类型bittorrent_peer_set,set[bittorrent_peer])来自响应体中名为"peers"的键:其值是 6 字节一组的紧凑列表(4 字节 IPv4 + 2 字节端口,均按网络字节序)。BitTorrentTracker.cc 的ResponseBenc逐组提取地址与端口,构造bittorrent_peer记录(h: addr+p: port)加入集合,并保证不足 6 字节时不越界读取。
benc(类型bittorrent_benc_dir,table[string] of bittorrent_benc_value)则保存其余所有 Bencode 键值对:bittorrent_benc_value是含i(int)、s(string)、d(dict)、l(list)四个可选字段的记录,实际填充哪个字段取决于值的编码类型。四个类型的定义见 init-bare.zeek。由此,脚本可以直接读取failure reason、interval、tracker id等标准公告字段。
bt_tracker_weird:Tracker 层异常
event bt_tracker_weird(c: connection, is_orig: bool, msg: string)
与bittorrent_peer_weird对位,负责 Tracker 会话中的非致命异常。触发场景包括:keep-alive 连接上出现 POST 请求残留数据(bittorrent_tracker_data_post_request类 weird)、Bencode 解析失败等。致命的格式错误(如非法 HTTP 状态行、请求头无冒号)则走AnalyzerViolation路径,不会落到此事件。
关联类型:事件参数的底层定义
四个 Tracker 相关事件使用的复合类型均定义在 init-bare.zeek,与事件签名配套使用:
## A BitTorrent peer. type bittorrent_peer: record { h: addr; ##< The peer's address. p: port; ##< The peer's port. }; ## A set of BitTorrent peers. type bittorrent_peer_set: set[bittorrent_peer]; ## BitTorrent "benc" value. Note that "benc" = Bencode ("Bee-Encode"). type bittorrent_benc_value: record { i: int &optional; ##< TODO. s: string &optional; ##< TODO. d: string &optional; ##< TODO. l: string &optional; ##< TODO. }; ## A table of BitTorrent "benc" values. type bittorrent_benc_dir: table[string] of bittorrent_benc_value; ## Header table type used by BitTorrent analyzer. type bt_tracker_headers: table[string] of string;C++ 侧通过id::find_type<...>()按名查找这些类型并构造TableVal/RecordVal填充事件参数,因此在脚本中直接以 Zeek 类型使用即可,无需任何额外 import。
实战:订阅事件与激活 Tracker 分析
事件订阅示例
仓库自带的 Tracker 测试 tracker.zeek 展示了最直接的订阅方式:
event bt_tracker_request(c: connection, uri: string, headers: bt_tracker_headers) { print c$id, uri, headers; }Peer 事件同理,例如记录所有握手与异常:
event bittorrent_peer_handshake(c: connection, is_orig: bool, reserved: string, info_hash: string, peer_id: string) { print fmt("handshake %s orig=%s info_hash=%s peer_id=%s", c$id, is_orig, hexstr(info_hash), peer_id); } event bittorrent_peer_weird(c: connection, is_orig: bool, msg: string) { print fmt("bittorrent weird %s orig=%s: %s", c$id, is_orig, msg); }用 DPD 签名激活 Tracker 分析
Peer 分析器可由端口/签名触发,但Zeek 自带脚本不包含 BitTorrent 的 DPD 签名(测试注释明确说明 “Zeek doesn't ship with scripts or DPD sigs for Bittorrent”),Tracker 分析器没有默认端口绑定,必须显式提供签名。仓库测试文件中的签名可直接复用:
signature dpd_bittorrenttracker_client { ip-proto == tcp payload /^.*\/announce\?.*info_hash/ tcp-state originator } signature dpd_bittorrenttracker_server { ip-proto == tcp payload /^HTTP\/[0-9]/ tcp-state responder requires-reverse-signature dpd_bittorrenttracker_client enable "bittorrenttracker" }签名文件保存为bittorrent.sig后,通过-s bittorrent.sig参数加载;运行 Tracker 测试的命令为:
zeek -C -b -r $TRACES/bittorrent/tracker.pcap -s bittorrent.sig %INPUT(其中-C忽略校验和、-r读取 pcap、-b减少冗余输出;对应测试断言见 tracker.zeek。)注意requires-reverse-signature要求先在 originator 方向观察到 announce 请求,再在 responder 方向确认 HTTP 响应,从而精准避免与 HTTP 分析器抢流量。
运行时防护机制要点
从源码可确认的几条防护行为,排查异常时很有用:
- 握手魔数校验:
pstrlen != 19 || memcmp("BitTorrent protocol", ...)即抛异常,非 BT 流量会被静默丢弃而非误报(bittorrent-analyzer.pac); - 消息长度上限:超过 256 KiB(
MSGLEN_LIMIT = 0x40000)的长度前缀直接判定违规(bittorrent.pac); - 方向独立停机:每个方向的协议违规只停该方向(
stop_orig/stop_resp),只有双向都违规才触发AnalyzerViolation(BitTorrent.cc); - Tracker 缓冲上限:请求/响应行缓冲超长(
sizeof(req_buf) - 1)或遇到内容缺口(Undelivered)时直接AnalyzerViolation并停止该方向,不做猜测性恢复(BitTorrentTracker.cc)。
结语
bittorrent_peer_*与bt_tracker_*两族事件完整覆盖了 BitTorrent 生态的两条关键链路:Peer 间基于长度前缀帧的二进制协议,以及客户端与 Tracker 间基于 HTTP + Bencode 的公告协议。理解事件签名与 bittorrent-protocol.pac、BitTorrentTracker.cc 中解析逻辑的对应关系,即可在 Zeek 脚本中准确提取 info_hash、peer_id、piece 流水线与 Tracker 公告的 peers 集合,进而构建 P2P 态势感知、恶意 torrent 追踪或协议异常检测等应用。
- 网络安全
- 网络
- IDS
【免费下载链接】zeek
Zeek is a powerful network analysis framework that is much different from the typical IDS you may know.
相关推荐
Zeek 协议分析器脚本接口全索引:Analyzer::Tag、插件组件与事件函数详解
Zeek 协议分析器脚本接口全索引:Analyzer::Tag、插件组件与事件函数详解 本篇技术指南以 Zeek 官方自动生成的协议分析器索引( autogen
网络安全网络IDSZeek 文件分析器(File Analyzers)完全指南:Files::Tag 枚举、内置插件事件与函数参考
Zeek 文件分析器(File Analyzers)完全指南:Files::Tag 枚举、内置插件事件与函数参考 导读 本文基于 Zeek 官方脚本参考文档 d
网络安全网络IDSZeek 报文分析器(Packet Analyzer)全量参考:Tag 索引、事件函数与源码实现解析
Zeek 报文分析器(Packet Analyzer)全量参考:Tag 索引、事件函数与源码实现解析 本文以仓库中自动生成的 autogenerated pac
网络安全网络IDS
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考