YaCnDehfuli/detection-under-load

GitHub: YaCnDehfuli/detection-under-load

一个可复现的 Sigma 规则鲁棒性基准测试框架,针对真实 Windows 遥测数据测量已发布检测规则在不同攻击变体下的实际覆盖率和漏报原因。

Stars: 0 | Forks: 0

# 负载下的检测 [![CI](https://static.pigsec.cn/wp-content/uploads/repos/cas/99/993938d8ce5e902ccfb9d6747725c320d855dea3235ed9a304cedf0d94c9321f.svg)](https://github.com/YaCnDehfuli/detection-under-load/actions/workflows/ci.yml) [![Python](https://img.shields.io/badge/Python-3.11+-3776AB?logo=python&logoColor=white)](https://www.python.org/) [![Sigma](https://img.shields.io/badge/Detection-Sigma-6A5ACD)](https://sigmahq.io/) [![License: MIT](https://img.shields.io/badge/License-MIT-2ea44f.svg)](LICENSE) **技术焦点:** 检测工程 · Sigma · MITRE ATT&CK · Sysmon · KQL · Splunk SPL · 误报测量 Detection Under Load 是一个用于检测规则鲁棒性的可复现基准测试。 它针对真实的 Windows 遥测数据运行已发布的 Sigma 规则,解释了规则为何 漏报,然后测量了在操作员可控的细微选择下,有多少覆盖率能够保留下来:包括名称、路径、PE 元数据以及记录的工件标识。 首次研究聚焦于 LSASS 凭据转储(ATT&CK T1003.001)。它 刻意缩小了范围,因为数据集受到了异常严格的控制:在相同的实验室和 Sysmon 配置下,对同一技术的七次捕获, 其中转储工具是主要变量。 ![Detection Under Load 基准测试概览](https://static.pigsec.cn/wp-content/uploads/repos/cas/81/81681187e4cbd6ca73ebae2c0a85b16d6a85db0768b4a786b019618ff6b77d53.svg) ## 当前结果 80 条已发布的 Sigma 规则被筛选用于 T1003.001。在 354,229 条记录的 Windows 事件中针对七种真实的转储 工具进行运行,每种工具触发了 3 到 8 条 已发布的规则,中位数为 5 条。没有任何单一已发布的规则能检测到 7 种工具中的 4 种以上。 | 测量项 | 结果 | |---|---| | 筛选出的已发布 T1003.001 规则 | 80 | | LSASS 转储捕获 | 7 | | 攻击捕获中的 Windows 事件 | 354,229 | | 每个工具触发的已发布规则 | 3 到 8 | | 每个工具的已发布覆盖率中位数 | 5 条规则 | | 重命名后丢失的基线检测 | 35 个中的 12 个 | | 重新定位后额外丢失的检测 | 8 | | 重命名后的 nanodump | 0 个已发布检测 | 主要发现并不是说“Sigma 失败了”。它更具体:相当一部分 已发布的覆盖率取决于操作员可以自由选择的字符串,或者 取决于将它们旨在保护的活动排除在外的目录过滤器。 该仓库将遥测缺口、超出范围的工具特定规则以及 规则逻辑漏报区分开来,因此这个数字是可解释的,而不仅仅是耸人听闻的。 对于 procdump,如果你不再仅通过技术标签进行筛选,覆盖率会上升。基于标签的筛选 在重命名后从 7 条规则下降到 3 条,而标记为另一种技术的三条规则因为抑制过滤器停止 生效而开始触发。这是一个关于覆盖率映射的发现,而不是盲目放宽所有 筛选的理由。 [发现](findings.md) | [方法](docs/method.md) | [结果](benchmark/results.md) | [筛选范围](benchmark/selection.md) | [鲁棒性](benchmark/robustness.md) | [决策](docs/decisions.md) | [贡献](contrib/) ## 为什么要进行测量 检测规则的编写、标记、审查和部署在很大程度上取决于 它们的描述强度。一条规则说它检测凭据转储,它 带有 `attack.t1003.001`,然后它进入了 pipeline。几乎从未发生过的事情是 针对以多种不同方式执行的同一技术运行它,并计算结果。 这个缺口值得填补,因为它隐藏的失败模式是具体的。一条 规则可能是正确的、编写良好的,但仍然键入到操作员可以自由选择的东西上, 例如二进制文件的名称。静态审查中没有任何东西能捕捉到这一点。 只有执行才能做到。 因此,这个仓库提出了一个带有可验证答案的狭窄问题:给定一种 以七种方式执行的技术,有多少已发布的规则会在每种方式上触发? ## 为什么选择这种技术和这个语料库 T1003.001 能够深入探讨是因为公共数据的一个偶然。 OTRF/Security-Datasets 包含了七次 LSASS 内存窃取的记录,这些记录是使用 七种不同的工具,在同一个实验室、同一台受害者主机上,在同一个 Sysmon 配置下执行的。工具是它们之间唯一的变量,这使得它们 具有可比性,而这在从其他地方拼凑起来的捕获中是做不到的。 | 捕获 | 事件 | 工具 | |---|---|---| | campaign 01 | 53,698 | logonpasswords,mimikatz 风格的进程内读取 | | campaign 02 | 42,482 | procdump,已签名的 Sysinternals 二进制文件 | | campaign 03 | 41,954 | comsvcs,rundll32 调用 MiniDump 导出 | | campaign 04 | 40,568 | out-minidump,PowerShell 反射式转储 | | campaign 05 | 59,707 | sharpdump,out-minidump 的 .NET 移植版 | | campaign 06 | 58,096 | outflank-dumpert,直接系统调用 | | campaign 07 | 57,724 | nanodump,系统调用和手动编写的写入器 | 每次捕获都是一个完整的记录窗口,因此与转储无关的事件 是真实的后台活动,而不是经过精心挑选的切片。 ## 架构解决的问题 有三件事可能导致规则未能触发,其中只有一件是规则本身的 错误。 1. 捕获从未记录规则读取的字段。 2. 规则针对的是未运行的工具。 3. 规则拥有所需的一切,但未匹配。 一个无法区分这些情况测试框架会产生一个数字,这个数字更多反映的是 Sysmon 配置,而不是检测内容。下面的每一个设计决策 都源于需要将它们区分开来,并且源于需要让不信任我的人能够验证 这种区分。 ## Pipeline ``` flowchart LR M[manifest.yml
pinned commits, sha256,
mutation targets] --> C[eval/corpus.py
fetch, split] M --> U[eval/mutate.py
tiers + control] C -->|attack captures| A[eval/runner.py
compile + match] C -->|benign captures| A S[SigmaHQ rules
pinned] --> A U --> P[eval/prescreen.py
drop what cannot match] P --> A A --> K[eval/classify.py
why it missed] A --> L[eval/selection.py
populations x tiers] K --> R[eval/report.py
score + emit] R --> O[results.json] L --> N[selection.json] N --> D[eval/sensitivity.py
eval/robustness.py
derived, not measured again] A -.independent check.-> Z[eval/crosscheck.py
Zircolite] ``` ### benchmark/manifest.yml 固定每一个输入。源代码仓库通过 commit 固定,七个 campaign 归档则通过 sha256 固定。在另一台机器上重新运行会读取相同的字节,或者 明确报错。 ### eval/corpus.py 通过无 blob 克隆和 cone 模式的 sparse-checkout 获取固定的源, 然后将捕获分为攻击集和良性集。 重要的契约是良性资格,因为它决定了什么算作 误报。对于技术 T,如果某个捕获的元数据 列出了 ATT&CK 技术,并且其中没有一个是 T,也没有与 T 共享父级技术,则该捕获对于技术 T 是良性的。兄弟测试 确保 T1003.002 捕获不会被计入针对 T1003.001 规则的评分。 没有 ATT&CK 映射的捕获将被丢弃,而不是被假定为干净的。122 个 Windows 主机捕获中有 13 个未标记,其中一个是 LSASS 转储变体,这正是该规则存在的核心理由。对于 T1003.001,剩下 91 个捕获和 514,202 个事件。 ### eval/runner.py 使用 pySigma 解析规则,并将它们的条件树编译为谓词, 然后针对事件字典运行它们。没有任何内容被转换为查询 语言。 这是核心的设计决策。通过第三方的 Sigma-to-SQL 后端路由每一条规则,会将该后端的覆盖缺口折叠到 以规则名义发布的结果中。掌握匹配意味着承担 出错的风险,这就是为什么语义由测试固定并针对 另一个引擎进行检查的原因。 每条规则都通过链式的 `sysmon` 和 `windows-logsources` pipeline 运行,因此 `process_access` 规则会获得其 EventID 10,而 Security 规则 会获得其 Channel。没有任何规则会在通过其未请求的 pipeline 运行后 受到评判。 ### eval/classify.py 决定规则为何未触发。每对规则和捕获都会落入以下四种 状态之一。 | 类别 | 含义 | |---|---| | `detected` | 至少匹配了一个事件 | | `miss-telemetry` | 捕获缺少规则所需的事件类型或字段 | | `out-of-scope` | 规则键入到捕获中从未运行过的命名二进制文件 | | `miss-logic` | 规则所需的一切都存在,但仍然没有匹配 | 只有条件 AND 主干上的要求才会被计算。仅出现在 过滤器内部的字段不能解释漏报,因为过滤器根本不 适用。弄错这一点并非假设:早期版本计算了 仅过滤字段,并因为 `Provider_Name` 将规则归类为 `miss-telemetry`, 而该规则仅使用 `Provider_Name` 来排除事件。 对于 `out-of-scope`,如果要求中的每个字段都命名了一个二进制文件,则该要求被视为工具标识。 访问掩码和调用堆栈被故意排除,因为 未能匹配这些意味着检测逻辑本身不足。 ### eval/report.py 选择针对某种技术的规则,对它们进行评分,针对 良性语料库测量误报,并输出 `results.json` 以及 markdown 表格。本 README 中的每个数字都来自该 json。`--check` 会重新运行基准测试,如果 提交的结果发生了偏移则会报错,这正是 CI 运行的内容。 标题是针对每个工具而不是每个规则的。对规则进行评分需要决定 procdump 特定的规则是否应该捕捉 nanodump,而没有任何 机械标准能完美解决这一问题。计算有多少规则介于 操作员和给定工具之间则不需要这样的决定。这两个数字都在 json 中;只有无可辩驳的那个数字占主导地位。 来自该仓库的规则与已发布的规则分开计算,因为它们是 在阅读这些结果之后编写的。 ### eval/mutate.py 将捕获重放为更谨慎执行的同一入侵:操作员 带来的工件获得操作员选择的名称,然后移动,接着丢失 其版本资源,最后丢失其记录的指纹。某个层级 可以重写哪些字段取决于它们值的来源,而不是一个列表,并且操作系统报告的关于行为的任何内容都不会被重写。 在阶梯旁边,而不是其中的一环,是对照组。它重写了一个 未选中规则读取的字段,并且它之后的覆盖率必须与 基线相同。如果不是,说明该框架破坏了事件,而不是 规则脆弱,运行将拒绝写入其输出。 ### eval/prescreen.py 丢弃无法匹配捕获的规则,因此广泛的规则集在纯 python 中是可处理的。三值逻辑且仅在单方向上可靠:规则仅在 被证明不可能匹配时被排除,其他所有情况均被接纳,包括该模块 不会推理的所有构造。合理性论证中廉价的那一半在每次 commit 时运行,详尽的另一半在发布时运行。 ### eval/selection.py 唯一昂贵的通过。三种已发布的规则集,加上本仓库 自己的规则,在相同的七次捕获、相同的层级和相同的对照组上运行。覆盖率 仅在命名操作员带来或写入的工件的事件上被认可, 因为捕获是一个完整的记录窗口,其中 98% 是后台活动。 `benchmark/sensitivity.json` 和 `benchmark/robustness.json` 派生自 它所写入的内容,而不是重新测量的,这就是什么阻止了两个已发布的 表格在覆盖率的含义上产生分歧。 ### eval/crosscheck.py 针对我未编写的引擎进行交叉检查。相同的规则和捕获 通过 Zircolite 进行,它将 Sigma 转换为 SQL 并查询 SQLite。在 七个 campaign 和 23 条 `process_access` 规则中,两者在哪些规则触发 以及每个规则匹配了多少事件上达成一致,没有分歧。 这种比较正是让基准测试能够声称漏报属于规则本身的原因。它 涵盖 `process_access` 规则集,而不是所有 83 条选定的规则。 ## 发现了什么 80 条已发布的规则被筛选用于 T1003.001,其中 79 条通过 ATT&CK 标签,1 条通过 logsource。 | 工具 | 触发的已发布规则 | 包含本仓库 | |---|---|---| | out-minidump | 8 | 10 | | procdump | 7 | 8 | | comsvcs | 6 | 8 | | outflank-dumpert | 5 | 6 | | logonpasswords | 3 | 4 | | sharpdump | 3 | 4 | | nanodump | 3 | 4 | 在 581 对规则和捕获中,44 个被检测到,207 个是逻辑漏报,273 个 超出范围,57 个是遥测缺口。 这些漏报呈现三种形态。 **键入到操作员选择的字符串的检测。** nanodump 的所有三个 检测都匹配字面量 `dump`:在映像名称中 (`nanodump.x64.exe`),在输出文件名(`lsass_dump.dmp`)中,以及在 带有该文件名的命令行中。重命名两者,其发布的覆盖率就降为 零。sharpdump 紧随其后,三个检测中的两个需要映像名称中包含 `dump`。 **因过于嘈杂而被移除的掩码。** nanodump 以 `GrantedAccess` `0x1010` 打开 LSASS,而 `0x1010`、`0x1400` 和 `0x1410` 都被注释 排除在两条主要的进程访问掩码规则之外。这是规则作者 知情的情况下做出的权衡,它牺牲了 nanodump 和进程内的 mimikatz 读取,也就是 使用被移除掩码的工具。同一 子技术的 Security 频道规则仍然选择 `0x1010`,因此该仓库以两种方式处理相同的掩码; [contrib/lsass-access-mask-exclusions.md](contrib/lsass-access-mask-exclusions.md) 包含具体的统计数据。 **工具直接穿透的目录过滤器。** `Potentially Suspicious GrantedAccess Flags On LSASS` 丢弃了 `Program Files`、 `System32` 和 `SysWOW64` 下的所有源。在这些捕获中,procdump 从 `C:\Program Files\procdump64.exe` 运行,SharpDump 从 `C:\Program Files\SharpDump.exe` 运行,nanodump 从 `C:\Windows\System32\nanodump.x64.exe` 运行。 ## 在重命名后保留下来的是什么 上述覆盖率是针对这些操作员碰巧选择的名称进行测量的,这 使其成为一个上限。每次捕获都通过五个层级的对抗性工作进行重放,每个层级都是其下方的超集:重命名操作员带来的工件,将其移动到掩码规则排除的目录中,清除 PE 版本资源,轮换记录的指纹。在任何层级,操作系统报告的关于行为的任何内容都不会被重写。 | 工具 | T0 | T1 重命名 | T2 重定位 | T3 剥离 PE | T4 新身份 | |---|---|---|---|---|---| | out-minidump | 8 | 7 | 4 | 4 | 4 | | procdump | 7 | 3 | 3 | 3 | 3 | | comsvcs | 6 | 5 | 4 | 4 | 4 | | outflank-dumpert | 5 | 3 | 1 | 1 | 1 | | logonpasswords | 3 | 3 | 1 | 1 | 1 | | sharpdump | 3 | 2 | 2 | 2 | 2 | | nanodump | 3 | 0 | 0 | 0 | 0 | 重新定位导致访问掩码规则失效,这些规则根本不读取文件名。它们 被自己的过滤器击败,即那些排除 `Program Files`、 `System32` 和 `SysWOW64` 下所有源的过滤器。logonpasswords 单独展示了这一点:它从 注入的线程读取 LSASS,因此重命名对其没有影响,而移动使其失去了三分之二。 清除版本资源和轮换指纹在这里没有产生任何改变。 这并不是因为这些层级没有做任何事情,而是因为读取版本 资源或指纹以捕捉重命名工具的层并不在被 测量的规则集中。 在阶梯旁边,而不是其中的一环,对照组重写了一个在技术范围选择中没有任何规则读取的字段。它在所有七次捕获中都不会产生任何改变。它确实改变了 广泛规则集中的一条规则,该规则读取了它重写的字段,这就是 什么使它成为对照组而不是形式主义:一个什么也看不见的突变 在构造上是可以通过的。 这测量了在一个语料库上对重命名和重新定位的敏感性,使用的是 操作员的模型,而不是某个操作员的记录。改变工具如何 读取内存而不是其名称的人,超出了所有这一切所展示的范围。 ## 覆盖率取决于你选择了哪些规则 上述所有内容都限定于带有 `attack.t1003.001` 的规则。这种限定 并非中立。三个规则集在相同的捕获和层级上运行:仅标签集、基准测试评分的扩充集,以及每一个能够编译的、属于 `product: windows` 或与产品无关、且读取语料库包含的事件类型的 SigmaHQ 规则。覆盖率仅在命名操作员带来或写入的工件的事件上被认可,因为捕获是一个完整的记录窗口,其中 98% 是后台活动。 | 工具 | `S-tag` T0 | `S-tag` T1 | `W` T0 | `W` T1 | `W` 在 T1 增加的内容 | |---|---|---|---|---|---| | procdump | 7 | 3 | 14 | 13 | 重命名的 ProcDump 执行,以及两条读取 Sysinternals 注册表项的规则 | | outflank-dumpert | 5 | 3 | 14 | 12 | 一条基于工具导入哈希的规则,仅在 T4 丢失 | | nanodump | 3 | 0 | 12 | 9 | 没有任何关于凭据访问的内容 | 完整的表格,以及补偿层中按名称列出的每一条规则,都在 [benchmark/selection.md](benchmark/selection.md) 中。`rules/` 中的规则是 第四个规则集,与另外三个分开报告,因为它们是在 阅读上述结果之后编写的。 ## 我作为回应写了什么 `rules/` 中的六条规则,每一条都是针对这些捕获以及针对 限定于其自身技术的良性语料库进行测量的。 | 规则 | 技术 | 检测到 | fp/100k | |---|---|---|---| | Process Started From A User Download Directory | T1204.002 | 7/7 | 0.99 | | LSASS Handle Request From Unexpected Process | T1003.001 | 7/7 | 1.56 | | SeDebugPrivilege Enabled On A Token | T1134.001 | 4/7 | 1.48 | | Remote Thread Started From Unbacked Memory | T1055.002 | 3/7 | 1.02 | | LSASS Dump Via Comsvcs MiniDump Export | T1003.001 | 1/7 | 0.00 | | PowerShell Script Block Calling MiniDumpWriteDump | T1003.001 | 1/7 | 0.00 | LSASS 规则基于调用者,而不是基于操作员控制的名称, 并且它的过滤器将二进制文件固定在其预期的目录中,而不是完全排除 目录。它在 514,202 个 良性事件中以 8 个误报检测到了 7 个目标中的 7 个,其中 6 个是同一个 Azure guest agent,2 个是 PowerShell。 PowerShell 未被过滤,因为 Out-Minidump 就是 PowerShell。 低计数是语料库的问题而不是规则的问题。七次入侵中只有三次 注入到另一个进程,只有一次使用了 comsvcs。 ## 是否有任何迁移 整个规则集都指向了 APT29 评估捕获,包含跨越 两天和几台主机的 783,367 个事件,这些规则都不是专门为此编写的。 六条规则中有三条在两天内触发而无需任何调整。保持安静的三条 是那些狭窄的规则,没有一条描述了该入侵是如何移动的。 这是一个迁移测试和另一个数据集,而不是一次部署,这里 没有任何内容重建入侵的步骤。`docs/decisions.md` 说明了为什么执行此操作的模块 不再被称为 chain。 ## 提供给上游的内容 [`contrib/`](contrib) 中的三个草案,按争议程度递增的顺序排列,没有 发送到任何地方。一个是编码缺陷:`HackTool - Dumpert Process Dumper Execution` 将导入哈希读取为 MD5,因此无法在它 所命名的工具上触发,这在单行修正前后都进行了测量。一个是调整权衡:访问掩码 排除是一个有据可查的选择,而提供的测量是关于一个已经被同一存储库中的三条规则以两种不同方式处理的掩码。一个提案:捕捉到 重命名 ProcDump 的规则被正确标记为伪装,问题 是没有任何东西将它与它所补充的凭据转储规则联系起来。 ## 规则在哪里运行 每个规则都被转换为 Splunk SPL 和 Kusto(Sentinel 和 Defender XDR 使用的查询语言),并且两者都在 [`rules/converted/`](rules/converted) 下提交,以便生成的查询可以在 diff 中阅读,而不仅仅是在 CI 步骤内部。`scripts/convert_rules.py --check` 在与全新转换发生偏移时会报错。 转换不是部署。它表明检测逻辑在每种查询语言中都能清晰地表达, 但与任何特定环境中的字段可用性、许可或调整无关。本仓库中的测量是由 `eval/` 中的框架进行的,而不是由任何一个 SIEM 进行的。 ## 运行它 ``` python -m pip install -r requirements.txt pyyaml python -m eval.corpus --fetch # about 1.5 GB, pinned by commit and sha256 python -m eval.report --run # benchmark/results.json and results.md python -m eval.transfer --run # benchmark/chain.json and chain.md python -m eval.crosscheck # agreement against Zircolite python -m pytest tests -q # unit tests, no corpus needed # 那次昂贵的 pass,以及从中派生出的两条 records python -m eval.report --run-selection # benchmark/selection.json and .md python -m eval.report --run-sensitivity # benchmark/sensitivity.json and .md python -m eval.report --run-robustness # benchmark/robustness.json and .md scripts/ci-local.sh --fast # what a push runs, minus the corpus scripts/ci-local.sh # add the drift checks scripts/ci-local.sh --release # add the wide run and the exhaustive prescreen ``` ## 限制 七个工具就是七个工具,一个实验室就是一个实验室。这些层级是 操作员的模型,而不是操作员的记录,模型拒绝重写的一切都使得测量的损失比 原本看起来要小。捕获中缺少的字段 并不证明它在生产环境中也会缺失,这就是为什么遥测 缺口被与逻辑漏报分开,而不是计入规则劣势的原因。良性语料库是 91 次原子攻击模拟,是真实的主机遥测,但相对安静:514,202 个事件可以将触发少数几次的规则与 经常触发的规则区分开来,但它不支持将 0.1 与 0.3 每 100k 进行比较。 这些都不是对 SigmaHQ 的判决。它们的规则涵盖的范围远比 这七次捕获所能展示的要广,在这里漏报的规则可能在 该语料库无法看到的地方发挥着自己的作用。完整的限制在 [docs/method.md](docs/method.md) 中。 ## 许可证 [MIT](LICENSE)。第三方数据集和 Sigma 规则保留其原始条款。
标签:Cloudflare, MITRE ATT&CK, Sigma规则, Windows遥测, 安全, 目标导入, 超时处理, 逆向工具