Gh0st-La6z-exe/null-sigma

GitHub: Gh0st-La6z-exe/null-sigma

一个用纯 Rust 编写的高性能 Sigma 规则流式编译与评估引擎,针对大规模安全事件日志实现超高速检测匹配。

Stars: 1 | Forks: 0

# null-sigma [![CI](https://static.pigsec.cn/wp-content/uploads/repos/cas/ad/ad5834178f7599af9fdda11629d49cae07f2997beec49821b2920eff5bfd50e7.svg)](https://github.com/Gh0st-La6z-exe/null-sigma/actions/workflows/ci.yml) [![crates.io](https://img.shields.io/crates/v/null-sigma.svg)](https://crates.io/crates/null-sigma) [![docs.rs](https://docs.rs/null-sigma/badge.svg)](https://docs.rs/null-sigma) [![License: Apache 2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE) 一个纯 Rust 编写的 [Sigma](https://sigmahq.io) 规则评估引擎。 只需解析一次 YAML 规则,将其编译为优化的内部表示, 然后针对完整的规则集评估安全事件流, 单核(Apple M4,release 模式)速度可达 **427 000 events/sec × 1 000 rules**。 ``` [dependencies] null-sigma = "0.1" ``` ## 快速开始 ``` use null_sigma::SigmaEngine; use std::collections::HashMap; let yaml = r#" title: Detect Encoded PowerShell logsource: {} detection: sel: CommandLine|contains: '-EncodedCommand' condition: sel "#; let mut engine = SigmaEngine::new(); engine.load_rule(yaml).unwrap(); let mut event = HashMap::new(); event.insert("CommandLine".to_string(), "powershell -EncodedCommand SQBFAFgA".to_string()); let matches = engine.evaluate_event(&event); // &self — safe to call concurrently assert_eq!(matches[0].rule_title, "Detect Encoded PowerShell"); ``` ``` $ cargo run --example detect_powershell Loaded rule: d7da0a5c-0001-0000-0000-000000000001 Engine has 1 rule(s) [ALERT] Matched 1 rule(s) - Suspicious Encoded PowerShell [high] score=0.70 tags: attack.execution, attack.t1059.001 Batch (2 events): event[0]: 1 match(es) event[1]: 0 match(es) ``` ## 为什么它很快 该引擎通过四重叠加优化实现了高吞吐量。 基准测试套件可以对每一项优化进行独立测量。 ### 1 — 热/冷数据结构分离 多规则引擎中最昂贵的操作是逐规则循环:对于每个加载的规则,尽可能低成本地判断当前事件是否可能匹配。 最简单的方法是将规则的所有信息存储在一个 struct 中并进行迭代。以每条规则 200–500 字节计算,1 000 条规则 = 每个事件需要流式传输 200–500 KB 的数据——每次迭代都会导致 L2/L3 缓存未命中。 相反,引擎将每条规则分为两个区域: **`RuleHotData`** —— 24 字节,每条规则的每个事件都会触及: ``` struct RuleHotData { cat_hash: u32, // 4 bytes — FNV-1a hash of logsource.category prod_hash: u32, // 4 bytes — FNV-1a hash of logsource.product svc_hash: u32, // 4 bytes — FNV-1a hash of logsource.service ac_start: u32, // 4 bytes — index into flat AC pattern slice ac_len: u32, // 4 bytes — number of AC patterns for this rule fully_ac_covered: bool, // 1 byte — safe-skip gate // 3 bytes padding → total 24 bytes } ``` **`CompiledRule`** —— 200–500 字节,仅在规则通过*两个*预过滤器(通常是 1 000 条中的 1 条)时才会被解引用: ``` struct CompiledRule { rule: SigmaRule, // parsed YAML fields identifiers: Vec, conditions: Vec, // compiled boolean AST ac_pattern_indices: Vec, has_regex: bool, } ``` 按每条规则 24 字节计算,1 000 条规则占用 24 KB——刚好装入 L1 缓存(Apple M4 为 64 KB)。整个预过滤器扫描只触及这个数组;对于被跳过的规则,绝不会解引用冷数据的 `CompiledRule` 堆内存分配。 logsource 字段在规则加载时使用 32 位 FNV-1a 进行了预哈希,因此每条规则的检查只需进行三次整数比较,而不是三次字符串比较: ``` #[inline(always)] fn logsource_ok(rule_hash: u32, event_hash: u32) -> bool { rule_hash == 0 || (event_hash != 0 && rule_hash == event_hash) // ↑ ↑ // wildcard rule field absent in event } ``` 0 被保留作为“无约束”的哨兵值。通过将 FNV 哈希冲突 `h == 0 → 1` 进行重映射,确保了非零的事件哈希值。 哈希比较只是预过滤器,并非最终定论:通过哈希比较的规则会在冷路径(`LogSource::matches`)中针对**实际的 logsource 字符串**进行再次检查,然后才进行评估。因此,32 位哈希冲突无法将事件路由到错误的规则——这种复查在热路径上没有任何成本,因为它只为已经通过两个预过滤器的规则运行。 **实测结果:** 1 000 条规则,具有错误 logsource category 的事件 → 总计 **912 ns**。所有 1 000 条规则在触及任何 `CompiledRule` 之前就被全部拒绝。 ### 2 — Aho-Corasick 批量预过滤器 大多数 Sigma 规则包含一个或多个 `|contains`、`|startswith` 或 `|endswith` 字符串条件。如果事件字段不包含这些字符串中的任何一个,则规则不可能匹配——无需评估完整的条件 AST。 在规则加载时,从每条规则中提取出每个符合 AC 条件的字符串模式,放入一个扁平的 `Vec` 中。编译一个统一的 [`aho-corasick`](https://docs.rs/aho-corasick) 自动机跨越所有这些模式。在评估时: ``` run_ac_scan(event) for each event field value: for each match in ac_automaton.find_iter(value): hits[match.pattern_id] = true // O(n) over text, not O(n × rules) ``` 结果是一个以模式 ID 为索引的密集布尔位图。然后,热循环针对每条规则测试该位图的连续切片——没有指针间接寻址,也没有 HashMap 查找: ``` let start = hot.ac_start as usize; let end = start + hot.ac_len as usize; if !flat_ac_indices[start..end] .iter() .any(|&idx| ac_hits[idx as usize]) { continue; // rule cannot match — skip cold eval } ``` **为什么 100 条规则比 1 条规则更快:** | 基准测试 | 中位数 | |---|---| | `single_rule_single_event` | 1.35 µs | | `100_rules_single_event` | **856 ns** | 无论规则数量多少,AC 自动机都会对事件字段扫描一次。一百条规则共享重叠的字符串词汇表意味着,对 100 条规则进行预过滤的总工作量比简单评估单条规则的条件还要少。 只有当满足两个安全条件时,规则才会进行 AC 预过滤: 1. **每个**标识符组中的**每个**字段条件都符合 AC 条件。 包含 `|re`、`|cidr`、数值比较、`|exists` 或转换修饰符(`|base64`、`|wide`、`|windash`)的规则会绕过预过滤器关卡,并始终进行完整评估。 2. 编译后的条件**不可能在所有标识符均为 false 时触发**。这保护了像 `condition: not selection` 这样的否定条件——一个完全没有命中 AC 的事件恰恰可能是应该匹配的事件,因此这些规则永远不会在 AC 未命中时被跳过。 这两项检查在规则加载时计算一次;违反其中任何一项都会导致漏报,因此它们由专门的回归测试(`ac_prefilter_tests`)强制执行。 ### 3 — 预编译 regex 缓存 使用 `|re` (regex) 条件的规则会在 `load_rule` 时将其模式一次性编译到每条规则专属的 `HashMap` 中。在评估时,`match_identifier_with_cache` 会直接查找预编译的对象: ``` match regex_cache.get(&pattern) { Some(re) => re.is_match(field_value), // O(1) lookup + fast match None => regex_matches(&pattern, field_value), // fallback compile } ``` 编译也是进行验证的地方:无法编译的 `|re` 模式会**在加载时拒绝整条规则**并返回 `EngineError::InvalidRegex`。无法编译的模式永远无法匹配,因此静默接受它会在没有任何操作员信号的情况下禁用检测。 如果没有缓存,`|re` 规则会在每个事件上调用 `regex::Regex::new()`——每个模式的每次调用都会产生 1–10 µs 的编译惩罚。有了缓存后,100 条规则 × 4 个模式 = 400 次 `is_match` 调用,每次约 88 ns。 **实测:** `100_regex_rules_single_event` → **35.9 µs** ### 4 — 零分配字段扩充 引擎支持 Sigma 字段命名(`CommandLine`)和应用程序规范命名(`command_line`)的事件。`enrich_event_cow` 不会总是克隆事件并扫描所有约 120 个映射条目来添加别名,而是返回一个 `Cow<'_, HashMap>`: - **`Borrowed`** —— 事件已使用 Sigma 名称 → 无内存分配 - **`Owned`** —— 存在应用程序规范名称 → 只克隆一次,仅插入所需的别名 反向查找是在 `FieldMapping::new()` 时预先构建的,因此扩充操作会迭代 O(n_event) 个条目,而不是 O(n_mappings): ``` for (key, value) in event { if let Some(sigma_name) = self.reverse.get(&key.to_lowercase()) { if !event.contains_key(sigma_name.as_str()) { aliases.push((sigma_name.clone(), value.clone())); } } } if aliases.is_empty() { return Cow::Borrowed(event); // hot path — zero allocation } ``` **实测隔离:** | 路径 | 中位数 | 内存分配 | |---|---|---| | `enrich_event_cow` (Sigma 键) | **193 ns** | 无 | | `enrich_event_cow` (规范键) | **739 ns** | 一次克隆 | 仅此一项更改就将 `single_rule_single_event` 从 2.08 µs 降低到了 **1.25 µs**——性能提升了 40%——因为该内存分配存在于每个 `evaluate_event` 调用的关键路径上。 ## 基准测试摘要 ![null-sigma 基准测试图表:随着规则数量的增长,每个事件的延迟保持在比朴素线性缩放低数百倍的水平,以及按场景划分的单核吞吐量](https://static.pigsec.cn/wp-content/uploads/repos/cas/41/4196785f6c2ff991503aefaa66607738d059d9643586b9abce9ce3770efc9d98.svg) 左图是核心特性:增加规则几乎没有成本。 在 1 000 条规则下,每个事件的延迟比朴素的逐规则评估**低 576 倍**,因为预过滤器在触及条件树之前就拒绝了几乎所有的规则。该图表由 `scripts/gen_benchmark_chart.py` 根据下面的 Criterion 中位数生成。 所有数据环境:Apple M4,单核,`cargo bench`(release profile), Criterion 100 次采样的统计测量。 ``` single_rule_single_event time: [1.3463 µs 1.3489 µs 1.3517 µs] 100_rules_single_event time: [855.53 ns 856.34 ns 857.20 ns] 1000_rules_single_event time: [2.3385 µs 2.3405 µs 2.3428 µs] 1000_rules_mixed_field_noise time: [15.916 µs 15.934 µs 15.958 µs] 100_rules_100_events_batch time: [93.203 µs 93.298 µs 93.399 µs] 1000_rules_logsource_mismatch time: [909.78 ns 912.15 ns 915.89 ns] 1000_rules_ac_prefilter_zero_match time: [1.7766 µs 1.7881 µs 1.8083 µs] 100_regex_rules_single_event time: [35.783 µs 35.877 µs 35.975 µs] enrich_event_cow_sigma_keys time: [192.36 ns 193.29 ns 195.25 ns] enrich_event_cow_canonical_keys time: [732.02 ns 738.83 ns 752.37 ns] ``` 单核推导吞吐量: | 场景 | Events/sec | |---|---| | 1 000 rules, 匹配的事件 | **427 000** | | 1 000 rules, 错误的 logsource | **1 096 000** | | 1 000 rules, 正确的 logsource, 无 AC 命中 | **559 000** | | 100 rules × 100 event 批处理 | **1 072 000** | 这些数据包含了 2026 年 7 月添加的正确性强化(logsource 字符串复查、条件感知的 AC 门控、符合规范通配符转义) ——与强化前相比有 1–7% 的性能损耗,但以此换取了对几类漏报情况的消除。 Criterion 生成的交互式 HTML 报告位于 运行 `cargo bench` 后的 `target/criterion/report/index.html`。 ## Sigma 功能覆盖 ### SigmaHQ 语料库兼容性 已针对完整的官方 [SigmaHQ/sigma](https://github.com/SigmaHQ/sigma) 规则语料库(2026 年 7 月快照)进行了验证: | 规则集 | 文件 | 已加载 | |---|---|---| | `rules` + `rules-emerging-threats` + `rules-threat-hunting` + `rules-compliance` | 3 745 | **3 745 (100%)** | | `rules-placeholder` (`\|expand` —— 设计上需要外部占位符目录) | 17 | 0 (文档记录的排除项) | 所有 3 745 条规则在 **~270 ms** 内批量加载到单个引擎中(一次 AC 重建)。 重现方式: ``` git clone --depth 1 https://github.com/SigmaHQ/sigma.git corpus/sigmahq cargo run --release --example corpus_report -- corpus/sigmahq/rules ``` ### 条件语言 条件编译器实现了一个完整的递归下降解析器,具有正确的 运算符优先级(`NOT > AND > OR`): ``` (selection_process and selection_cmdline) and not filter 1 of selection* all of them 3 of (sel_a, sel_b, sel_c) ``` AST 在加载时对每条规则编译一次,生成一个 `ConditionNode` 树, 每个事件的评估时间为 O(n_identifiers)。 ### 值修饰符 (19) | 类别 | 修饰符 | |---|---| | 字符串匹配 | `contains` `startswith` `endswith` | | 模式 | `re` (+ 标志子修饰符 `re\|i` `re\|m` `re\|s`) `cidr` | | 量词 | `all` | | 转换 | `base64` `base64offset` `wide` `windash` | | 数值 | `gt` `gte` `lt` `lte` | | 存在性 | `exists` | | 字段引用 | `fieldref` (与另一个事件字段比较,Sigma v2) | `fieldref` 将条件值解释为字段*名称*,并比较两个事件字段的值(`ParentImage|fieldref: Image` 在进程执行自身时触发);它与 `contains`/`startswith`/`endswith` 组合使用。 Regex 标志子修饰符必须跟在 `re` 之后(`field|re|i:`)——单独的 `|i` 是解析错误。注意:在本引擎中,默认情况下 `|re` 是大小写不敏感的,因此 `re|i` 是一个无操作确认;`re|m`(多行锚点)和 `re|s`(点号匹配换行符)会改变编译行为。 转换修饰符(`base64`、`wide`、`windash`)在匹配前扩展值集合: - `base64offset` 生成所有三种 base64 边界偏移变体,并从**两端**修剪不稳定的字符,因此即使值嵌入在较长的 base64 数据流中间,也能被检测到(不仅仅是位于编码数据末尾的值)。 - `windash` 扩展为完整的 Sigma 变体集:`-`、`/`、`–` (en dash)、 `—` (em dash) 和 `―` (horizontal bar) ——可捕获从带有排版连字符的文档中复制粘贴的命令。扩展是双向的:使用任何变体编写的规则都可以匹配所有变体。 - `wide` 将值重新编码为 UTF-16LE。 ### 条件形式 ``` # 单字符串 condition: selection # Boolean 表达式 condition: selection and not filter # 量词 condition: 1 of selection* condition: all of them condition: 3 of (sel_a, sel_b, sel_c) # 多个独立条件(任一满足即触发规则) condition: - selection_a - selection_b and filter ``` ### 字段匹配语义 - **大小写不敏感**的字段名称和字符串值(Sigma 规范) - 字段条件组内的 **AND** - 标识符内跨组的 **OR** - 条件内跨值的 **OR**(除非使用 `|all`) - **关键字搜索**(空字段名)匹配所有事件值 - 值中的**通配符**:`*` (任意序列) 和 `?` (单个字符) - 根据 Sigma 规范**转义**:`\*` 和 `\?` 是字面量字符, `\\` 是单个反斜杠,普通字符前的单独反斜杠保持不变 —— `\cmd.exe` 这样的 Windows 路径不需要转义。 转义后的字面量依然符合 Aho-Corasick 预过滤器的条件(自动机存储的是未转义的字节)。 ## 类型系统 ``` SigmaRule ├── LogSource { category, product, service } ├── Detection │ ├── ConditionExpr Single(String) | Multiple(Vec) │ └── identifiers HashMap │ └── SearchIdentifier │ └── Vec ← OR across groups │ └── Vec ← AND within group │ ├── field: String │ ├── values: Vec │ └── modifiers: Vec └── metadata title, id, status, level, tags, author, … ``` `SigmaValue` 是强类型的:`String | Integer(i64) | Float(f64) | Boolean | Null`。 当双方都能解析为整数时,数值比较使用 `i64` 整数运算,避免了大型计数器和时间戳在 `f64` 下遇到的 2^53 精度边界问题。 ## 线程安全 在规则加载后,`evaluate_event` 和 `evaluate_batch` 都使用 `&self`。 引擎可以被包装在 `Arc` 中,并从 N 个线程并发评估,无需加锁: ``` use std::sync::Arc; let engine = Arc::new(engine_with_rules_loaded); let handles: Vec<_> = events .chunks(chunk_size) .map(|chunk| { let eng = Arc::clone(&engine); std::thread::spawn(move || eng.evaluate_batch(chunk)) }) .collect(); ``` `rebuild_ac` 调用(Aho-Corasick 自动机编译)是在 `load_rule` / `load_rules` 内部即时(eagerly)执行的——绝不会在 `evaluate_event` 内部延迟执行。 ## 测试 ``` cargo test running 196 tests 0 unit (lib) 10 corpus_tests — parse known-good and known-bad YAML fixtures 10 property_tests — proptest: invariants proven on 10,000 random inputs each 174 sigma_tests — modifiers, conditions, engine, hardening, concurrency 2 doc tests ``` 由 proptest 证明的部分属性不变量: - **无 panic** —— 对 `parse_rule` 输入任意可打印字符绝不会引发 panic - **确定性** —— 相同的事件始终产生相同的结果 - **可靠性** —— 缺失的必填字段绝不会触发规则 - **单调性** —— 额外的事件字段绝不会抑制匹配 - **通配符 logsource** —— 带有空 logsource 的规则匹配任何事件源 精选的强化测试: - 字段值中的空字节、RTL 覆盖字符和零宽空格 - 10 000 字符的字段值 - 单个事件中的 1 000 个字段 - Unicode 字段名 - 重复的规则加载(均被加载,无静默去重) - 暴力破解的 FNV-1a logsource 哈希冲突——被证明不会导致事件路由错误 - 否定条件(`not selection`)——被证明绝不会 AC 预过滤器跳过 - 无效的 `|re` 模式——在加载时被拒绝,引擎状态保持不变 ## 安全性 - **零 `unsafe` 代码** —— crate 级别强制执行 `#![forbid(unsafe_code)]` - **无 C 扩展** —— 纯 Rust,无 pyo3,无 bindgen,无 FFI - **设计上无 panic** —— 格式错误的 YAML 返回 `ParseError`,无效的条件返回 `CompileError`,而无效的 `|re` 模式在加载时返回 `EngineError::InvalidRegex`(响亮地失败,绝不静默失败) - **无静默的检测丢失** —— 每次加载失败都会使引擎状态保持不变;`load_rules` 收集每条规则的错误,因此批量导入可以准确报告哪些规则被拒绝及其原因 - **`#![deny(missing_docs)]`** —— 所有公开项均已编写文档 - **Clippy pedantic** —— 在 `-W clippy::pedantic` 下零警告 ## Cargo 特性 没有可选特性。依赖范围被刻意保持在最小: | Crate | 作用 | |---|---| | `serde` + `serde_norway` | YAML → `SigmaRule`(处于维护状态的 `serde_yaml` 继任者) | | `aho-corasick` | SIMD 加速的多模式批量扫描 | | `regex` | `\|re` 修饰符模式匹配 | ## 许可证 基于 [Apache License, Version 2.0](LICENSE) 授权。 版权所有 2026 Gh0st-La6z-exe
标签:Rust, Sigma规则, 可视化界面, 目标导入, 编译器, 网络流量审计, 通知系统