Suricata 二进制协议检测学习指南
本文档系统讲解如何使用 Suricata 规则检测 TCP 二进制协议(如 HTTP/2、DNS、TLS 等),涵盖关键字段匹配、字节级操作、帧结构解析等核心技术。
目录
- 为什么需要二进制协议检测
- [Suricata 规则基础结构回顾](#Suricata 规则基础结构回顾)
- [content 关键字:二进制匹配的核心](#content 关键字:二进制匹配的核心)
- 偏移与深度控制:offset、depth、distance、within
- 字节级检测:byte_test、byte_jump、byte_extract
- 位掩码检测:bitmask
- [实战案例:HTTP/2 协议检测](#实战案例:HTTP/2 协议检测)
- [实战案例:TLS 协议检测](#实战案例:TLS 协议检测)
- 阈值与频率控制:threshold
- 常见问题与调试技巧
- 参考资料
1. 为什么需要二进制协议检测
许多网络协议并非基于文本(如 HTTP/1.1),而是采用二进制编码:
| 协议 | 编码方式 | 检测难点 |
|---|---|---|
| HTTP/2 | 二进制帧 | 帧头结构固定,payload 可含 HPACK 压缩数据 |
| TLS | 二进制记录层 | 加密后无法检测应用层内容,需在握手阶段检测 |
| DNS | 二进制报文 | 域名使用长度前缀编码,非零终止字符串 |
| QUIC | 二进制 + 加密 | 大部分内容加密,仅初始包可检测 |
| MQTT | 二进制报文 | 固定头部 + 可变头部 + 载荷 |
核心挑战:不能简单用字符串匹配,必须理解协议的二进制结构,精确定位字段位置和值。
2. Suricata 规则基础结构回顾
alert <protocol> <src_ip> <src_port> -> <dst_ip> <dst_port> (\
msg:"规则描述"; \
<规则选项>; \
sid:XXXXXXX; rev:1; \
)
对于二进制协议检测,关键选项包括:
content/content|--- 内容匹配(支持十六进制)offset/depth--- 绝对位置控制distance/within--- 相对位置控制byte_test--- 字节值比较byte_jump--- 字节值跳转byte_extract--- 字节值提取到变量bitmask--- 位掩码threshold--- 阈值控制
3. content 关键字:二进制匹配的核心
3.1 十六进制内容匹配
使用 | 包围的十六进制表示二进制数据:
# 匹配单个字节 0x01
content:"|01|";
# 匹配多个字节
content:"|00 04 00 00 00 00|";
# 混合文本和十六进制
content:"GET |2f| HTTP";
3.2 HTTP/2 帧头匹配示例
HTTP/2 帧头为 9 字节固定结构:
+-----------------------------------------------+
| Length (24) |
+---------------+---------------+---------------+
| Type (8) | Flags (8) |
+-+-------------+---------------+-------------------------------+
|R| Stream Identifier (31) |
+=+=============================================================+
| Frame Payload (0...) ...
+---------------------------------------------------------------+
匹配 HEADERS 帧(type=0x01):
# 帧头第4字节为帧类型,0x01 = HEADERS
content:"|01|"; offset:3; depth:1;
匹配 WINDOW_UPDATE 帧(type=0x08):
content:"|08|"; offset:3; depth:1;
3.3 否定匹配
# 不包含指定内容
content:!"|be|";
4. 偏移与深度控制:offset、depth、distance、within
4.1 绝对位置:offset 和 depth
-
offset:从 payload 起始偏移多少字节开始匹配(0-based)
-
depth:从 offset 位置开始,匹配多少字节
从第3字节开始,匹配1字节(即第4字节)
content:"|01|"; offset:3; depth:1;
从第0字节开始,匹配9字节(HTTP/2帧头)
content:"|00 00 00 01 01 00 00 00 00|"; offset:0; depth:9;
图示:
Payload: [0x00][0x00][0x00][0x01][0x01][0x00][0x00][0x00][0x00][...]
偏移: 0 1 2 3 4 5 6 7 8
^-- offset:3, depth:1 匹配 0x01 (帧类型HEADERS)
4.2 相对位置:distance 和 within
-
distance:相对于上一个 content 匹配结束位置的偏移
-
within:从 distance 位置开始,匹配多少字节
先匹配帧类型0x01(HEADERS),然后跳过4字节(stream_id),检查flags
content:"|01|"; offset:3; depth:1; # 匹配帧类型
content:"|05|"; distance:4; within:1; # 匹配flags(END_STREAM + END_HEADERS)
图示:
HTTP/2帧: [Len(3)][Type(1)][Flags(1)][StreamID(4)][Payload...]
0 1 2 3 4 5 6 7 8
^-- 第1个content匹配
^-- distance:4, within:1 匹配flags
4.3 位置控制对比
| 关键字 | 参考点 | 含义 |
|---|---|---|
offset |
payload 起始 | 从起始偏移 N 字节 |
depth |
offset 位置 | 最多匹配 N 字节 |
distance |
上一个 content 末尾 | 跳过 N 字节 |
within |
distance 位置 | 最多匹配 N 字节 |
5. 字节级检测:byte_test、byte_jump、byte_extract
5.1 byte_test --- 字节值比较
语法:
byte_test:<bytes>,<operator>,<value>,<offset>[,relative][,bitmask <mask>][,endian <endian>][,dce>];
参数说明:
| 参数 | 说明 |
|---|---|
bytes |
读取的字节数(1, 2, 4) |
operator |
比较操作符:=, !=, <, >, <=, >=, &(位与), ^(位异或) |
value |
比较值 |
offset |
偏移量(相对于 payload 起始或上一个 content 末尾) |
relative |
可选,偏移相对于上一个 content |
bitmask |
可选,先对读取值应用掩码再比较 |
endian |
可选,big(默认)或 little |
示例:
# 读取3字节,判断是否 >= 16384(0x4000)
# HTTP/2 HEADERS帧的payload长度 >= 16384 表示异常大的header block
byte_test:3,>=,16384,0;
# 读取1字节,判断是否等于0x04(END_HEADERS标志)
byte_test:1,=,4,0,relative;
# 读取4字节,判断INITIAL_WINDOW_SIZE是否为0
# SETTINGS帧payload中,settings_id=0x04后跟4字节value
byte_test:4,=,0,0,relative;
5.2 byte_jump --- 字节值跳转
语法:
byte_jump:<bytes>,<offset>[,relative][,multiplier <mult>][,post_offset <offset>][,bitmask <mask>][,endian <endian>][,dce>];
用途:读取 N 字节的值,将匹配位置跳转到该值指定的偏移。常用于跳过长度可变的字段。
示例:
# 读取HTTP/2帧头的3字节长度字段,跳过对应长度的payload
# 帧头: [Length(3)][Type(1)][Flags(1)][R+StreamID(4)]
byte_jump:3,0; # 读取前3字节作为长度,跳过
# 现在位置在帧类型字段
content:"|01|"; # 匹配HEADERS帧类型
5.3 byte_extract --- 字节值提取到变量
语法:
byte_extract:<bytes>,<offset>,<var_name>[,relative][,bitmask <mask>][,endian <endian>][,dce>];
用途 :将读取的字节值存储到命名变量中,后续可在 offset、depth、distance、within、byte_test 等中引用。
示例:
# 提取HTTP/2帧长度到变量frame_len
byte_extract:3,0,frame_len;
# 使用变量跳过帧头
content:"|01|"; offset:6; depth:1; # 匹配帧类型
6. 位掩码检测:bitmask
6.1 原理
bitmask 对读取的字节值先执行按位与(AND)操作,再进行比较。常用于检测标志位。
读取值: 0x05 (二进制: 00000101)
bitmask: 0x04 (二进制: 00000100)
AND结果: 0x04 (二进制: 00000100) → 检测第3位是否置位
6.2 HTTP/2 标志位检测示例
HTTP/2 帧头的 Flags 字段(第5字节,offset=4):
| 标志位 | 值 | 含义 |
|---|---|---|
| END_STREAM | 0x01 | 流结束 |
| END_HEADERS | 0x04 | 头部块结束 |
| PADDED | 0x08 | 存在填充 |
| PRIORITY | 0x20 | 存在优先级信息 |
# 检测END_HEADERS标志位是否置位
# 先匹配帧类型0x01(HEADERS),然后检查flags字段的bit 2
content:"|01|"; offset:3; depth:1;
byte_test:1,=,0,0,relative,bitmask 0x04;
# 等价于: flags & 0x04 == 0x04
6.3 bitmask 与 byte_test 组合
# 检测HTTP/2帧头中R位是否置位(Stream Identifier的最高位)
# Stream Identifier占4字节,最高位为Reserved位,必须为0
# 如果为1则可能是恶意构造的帧
byte_test:4,>,,0x7FFFFFFF,5,bitmask 0x80000000;
7. 实战案例:HTTP/2 协议检测
7.1 HTTP/2 帧结构回顾
+-----------------------------------------------+
| Length (24) | 字节 0-2
+---------------+---------------+---------------+
| Type (8) | Flags (8) | | 字节 3-4
+-+-------------+---------------+-------------------------------+
|R| Stream Identifier (31) | 字节 5-8
+=+=============================================================+
| Frame Payload (0...) | 字节 9+
+---------------------------------------------------------------+
7.2 检测 HTTP/2 HPACK Bomb(CVE-2026-49975)
攻击特征:
- 发送 SETTINGS 帧,设置
INITIAL_WINDOW_SIZE=0 - 发送大量 HEADERS 帧,包含极大的 HPACK 压缩头部块
- 周期性发送 increment=1 的 WINDOW_UPDATE 帧
规则1:检测 INITIAL_WINDOW_SIZE=0
# HTTP/2 SETTINGS帧: type=0x04
# SETTINGS_INITIAL_WINDOW_SIZE: id=0x04, value=0x00000000
# 帧头9字节 + payload中: [id(2字节)=0x0004][value(4字节)=0x00000000]
alert tcp any any -> any any (\
msg:"CVE-2026-49975 HTTP/2 HPACK Bomb - INITIAL_WINDOW_SIZE=0"; \
flow:established,to_server; \
content:"|04|"; offset:3; depth:1; \
content:"|00 04 00 00 00 00|"; offset:9; depth:6; \
sid:9000001; rev:1; \
)
规则2:检测异常大的 HEADERS 帧
# HEADERS帧 type=0x01
# 帧头前3字节为长度,如果 >= 16384 (0x4000) 则异常
# 使用 byte_test 检测3字节长度字段
alert tcp any any -> any any (\
msg:"CVE-2026-49975 HTTP/2 HPACK Bomb - Oversized HEADERS Frame"; \
flow:established,to_server; \
content:"|01|"; offset:3; depth:1; \
byte_test:3,>=,16384,0; \
threshold:type both, track by_src, count 5, seconds 3; \
sid:9000002; rev:1; \
)
规则3:检测 END_STREAM + END_HEADERS 标志的异常组合
# HPACK bomb中HEADERS帧通常同时设置END_STREAM和END_HEADERS
# flags = 0x05 (END_STREAM=0x01 | END_HEADERS=0x04)
# 使用byte_test + bitmask检测
alert tcp any any -> any any (\
msg:"CVE-2026-49975 HTTP/2 HPACK Bomb - HEADERS with END_STREAM flag"; \
flow:established,to_server; \
content:"|01|"; offset:3; depth:1; \
byte_test:1,=,0,0,relative,bitmask 0x04; \
threshold:type both, track by_src, count 5, seconds 3; \
sid:9000003; rev:1; \
)
规则4:检测微小 WINDOW_UPDATE(Window Stall)
# WINDOW_UPDATE帧: type=0x08
# payload为4字节window increment
# increment=1 (0x00000001) 是异常的微小值
alert tcp any any -> any any (\
msg:"CVE-2026-49975 HTTP/2 Window Stall - Minimal WINDOW_UPDATE"; \
flow:established,to_server; \
content:"|08|"; offset:3; depth:1; \
content:"|00 00 00 01|"; offset:9; depth:4; \
threshold:type both, track by_src, count 10, seconds 60; \
sid:9000004; rev:1; \
)
7.3 实际可检出的规则(已验证)
以下规则已在实际流量中验证可检出:
# 规则A: 检测HEADERS帧 + END_HEADERS标志
alert tcp any any -> any any (\
msg:"LDYVUL-2026-00093922_HTTP/2 拒绝服务漏洞"; \
content:"|01|"; offset:3; depth:4; \
byte_test:1,=,0,0,relative,bitmask 0x04; \
threshold:type both, track by_src, count 5, seconds 3; \
classtype:attempted-dos; \
sid:70005642; rev:1; \
)
# 规则B: 检测HEADERS帧 + 超大payload长度
alert tcp any any -> any any (\
msg:"LDYVUL-2026-00093922_HTTP/2 拒绝服务漏洞"; \
content:"|01|"; offset:3; depth:4; \
byte_test:3,>=,16384,0; \
threshold:type both, track by_src, count 5, seconds 3; \
classtype:attempted-dos; \
reference:cve,2024-27316; \
sid:70005642; rev:1; \
)
规则解析:
| 部分 | 含义 |
|---|---|
| `content:" | 01 |
byte_test:1,=,0,0,relative,bitmask 0x04 |
相对于content匹配位置,读取1字节,与0x04做AND后判断是否=0(检测END_HEADERS标志位) |
byte_test:3,>=,16384,0 |
从payload起始读取3字节,判断是否>=16384(检测超大帧长度) |
threshold:type both, track by_src, count 5, seconds 3 |
同一源IP在3秒内触发5次才告警(减少误报) |
8. 实战案例:TLS 协议检测
8.1 TLS 记录层结构
+-----------------------------------------------+
| ContentType (1) | Version (2) | Length (2) |
+-----------------------------------------------+
| Fragment |
+--------------------------------------------------+
8.2 检测 TLS ClientHello 中的 ALPN h2 协商
# TLS Handshake: ContentType=0x16, HandshakeType=0x01(ClientHello)
# ALPN扩展中包含"h2"
alert tls any any -> any any (\
msg:"TLS ALPN h2 Negotiation"; \
content:"|16|"; offset:0; depth:1; \
content:"h2"; \
sid:9000010; rev:1; \
)
8.3 检测异常的 TLS 握手
# 检测TLS版本低于1.2的连接
alert tls any any -> any any (\
msg:"Weak TLS Version Detected"; \
content:"|16|"; offset:0; depth:1; \
byte_test:2,<,0x0303,1; \
sid:9000011; rev:1; \
)
9. 阈值与频率控制:threshold
9.1 threshold 语法
threshold:type <threshold|limit|both>, track <by_src|by_dst|by_rule>, count <N>, seconds <T>;
| 类型 | 含义 |
|---|---|
threshold |
每 N 次/秒触发一次告警 |
limit |
在 T 秒内最多告警 N 次 |
both |
同时满足 threshold 和 limit |
9.2 二进制协议中的阈值应用
二进制协议攻击通常表现为高频异常帧,单次匹配可能误报,需要阈值控制:
# 3秒内5次异常HEADERS帧才告警
threshold:type both, track by_src, count 5, seconds 3;
# 60秒内10次微小WINDOW_UPDATE才告警
threshold:type both, track by_src, count 10, seconds 60;
10. 常见问题与调试技巧
10.1 为什么规则匹配不到?
| 原因 | 解决方案 |
|---|---|
| TLS 加密 | Suricata 无法检测加密后的 payload,需在 TLS 解密后检测或检测握手阶段 |
| offset/depth 计算错误 | 使用 Wireshark 确认字段偏移,注意 TCP payload 起始位置 |
| 字节序错误 | HTTP/2 使用大端序(网络字节序),byte_test 默认也是大端序 |
| content 位置不对 | 使用 distance/within 替代 offset/depth 做相对定位 |
| threshold 过严 | 调低 count 或调大 seconds |
| flow 方向错误 | to_server 是客户端→服务端,to_client 是服务端→客户端 |
10.2 调试步骤
- 用 Wireshark 确认流量特征:找到攻击帧,记录精确的字节偏移和值
- 逐步构建规则:先写最简单的 content 匹配,确认能触发
- 添加 byte_test:在 content 匹配成功的基础上添加字节级检测
- 添加 threshold:最后添加阈值控制,减少误报
- 使用
-l指定日志目录 :检查fast.log和eve.json确认告警
10.3 Suricata 命令行测试
bash
# 测试单条规则
suricata -S /path/to/test.rules -r /path/to/capture.pcap -l /tmp/suricata-out
# 检查告警
cat /tmp/suricata-out/fast.log
cat /tmp/suricata-out/eve.json | jq '.event_type == "alert"'
10.4 HTTP/2 检测的特殊注意事项
-
TLS 加密:HTTP/2 几乎总是运行在 TLS 之上,Suricata 默认无法检测加密后的帧内容。需要:
- 配置 SSL/TLS 解密(如使用
ssldump或中间人解密) - 或检测 TLS 握手阶段的特征(如 ALPN 协商 h2)
- 配置 SSL/TLS 解密(如使用
-
TCP 重组 :HTTP/2 帧可能跨越多个 TCP 段,确保 Suricata 的
stream.reassembly开启 -
帧边界 :一个 TCP 段可能包含多个 HTTP/2 帧,
offset是相对于 TCP payload 起始的,不是帧起始 -
HPACK 压缩:头部值经过 HPACK 压缩,无法直接匹配字符串内容
11. 参考资料
官方文档
- Suricata Rules Documentation
- Suricata Content Matching
- Suricata byte_test
- Suricata byte_jump
- Suricata byte_extract
- Suricata Thresholding
协议规范
- RFC 7540 - HTTP/2(HTTP/2 帧结构、流控、HPACK)
- RFC 7541 - HPACK(头部压缩格式)
- RFC 5246 - TLS 1.2(TLS 记录层和握手协议)
- RFC 8446 - TLS 1.3
学习资源
- Suricata Emerging Threats Rules(社区规则库,学习实战规则写法)
- OISF Suricata GitHub(源码和测试规则)
- HTTP/2 HPACK Bomb 研究(相关CVE研究)
附录:二进制检测关键字速查表
| 关键字 | 语法 | 用途 | 示例 |
|---|---|---|---|
content |
`content:" | hex | ";` |
offset |
offset:N; |
从 payload 起始偏移 N 字节 | offset:3; |
depth |
depth:N; |
匹配 N 字节长度 | depth:1; |
distance |
distance:N; |
相对上一个 content 偏移 N 字节 | distance:4; |
within |
within:N; |
相对匹配 N 字节长度 | within:1; |
byte_test |
byte_test:B,op,V,O; |
读取 B 字节,与 V 比较 | byte_test:3,>=,16384,0; |
byte_jump |
byte_jump:B,O; |
读取 B 字节,跳转到该偏移 | byte_jump:3,0; |
byte_extract |
byte_extract:B,O,var; |
读取 B 字节,存入变量 | byte_extract:3,0,len; |
bitmask |
bitmask 0xNN; |
对值执行 AND 掩码 | bitmask 0x04; |
threshold |
threshold:type X,track Y,count N,seconds T; |
阈值控制 | threshold:type both,track by_src,count 5,seconds 3; |
flow |
flow:established,to_server; |
流方向控制 | flow:established,to_server; |
nocase |
nocase; |
大小写不敏感(仅文本) | nocase; |
fast_pattern |
fast_pattern; |
指定快速匹配模式 | fast_pattern; |