158 endpoint detections are evadable or have logic gaps — most trace to hardcoded process_name instead of the existing process_* macros

Aperta
#4,409 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
5/5
Tempo stimato
Più di una settimana
Idoneità per principianti
35/100
Tipo di issue
Bug
Chiarezza
Abbastanza chiara
Stato di attività
Attiva
Ambito
security

Direzione di ricerca

Start with the SPL rules and process_* macros in the repository, then inspect the affected rule tests and their tests.attack_data. Use the linked evidence repository's scanner and replay harness to reproduce a detection miss and verify whether the proposed fix restores it. Done means addressing the confirmed gaps with regression coverage; the report spans 158 rules and several distinct fix patterns, so first narrow work to one class.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Summary

An audit of the endpoint ruleset found 158 production detections that an attacker can evade without changing their behaviour, or that fail on default modern Windows:

62 TTP · 67 Anomaly · 29 Hunting

Most trace to one root cause: a rule hardcodes Processes.process_name=X instead of using the process_X macro ESCU already ships — and a couple of those macros are themselves incomplete. The rest are case-sensitive match(), bypassable NOT exclusions, and command-line obfuscation (/↔-, quote insertion) the rules don't account for.

Method: a static scanner flags the pattern; a replay harness takes each rule's own attack_data, mutates it to the evasion, and re-runs it to confirm the detection miss and that the fix recovers. 14 rules are confirmed end-to-end in a live Splunk; the attack side is anchored to ArgFuscator's lab-validated models and to behaviour tested directly on Windows 11 and Server. Counts are
after excluding scanner false positives.

Breakdown by class

# Class Rules TTP Anom Hunt Fix
1 Renamed binary (no original_file_name) 89 36 37 16 use process_* macro
2 Option-char substitution (/↔-) 29 15 9 5 match both prefixes
3 Sibling binary not covered 13 5 1 7 process_powershell / add sibling
4 Bypassable NOT exclusions (name/path/cmdline) 33 14 15 4 anchor / qualify the exclusion
5 process_net matches net1.exe only 8 0 5 3 macro += net.exe
6 Space-bound token (quote/char insertion) 7 1 3 3 normalize command line
7 Case-sensitive match() 4 3 1 0 add (?i)

(Some rules appear in more than one class; 158 is the distinct total, 183 counting each class separately.)

proof: https://github.com/Diablo-research/splunk-escu-evidence/tree/main/escu-evasion-gaps

Don't let the number put you off — most of this is a small, mechanical fix

158 looks large, but the long tail is shallow:

  • ~110 rules (classes 1, 3, 5) are fixed at the macro level — swap the
    hardcoded process_name=X for the process_X macro ESCU already ships (plus
    adding a missing sibling in three cases, and one net.exe line for the
    process_net macro). These are mechanical, non-breaking changes that add
    coverage without touching detection logic, so the false-positive risk is
    near zero. Fixing one macro can close dozens of rules at once.
  • ~40 rules (classes 2, 6, 7) are a handful of shared patterns — match both
    flag prefixes, add (?i), normalize the command line. Each can be solved once
    (ideally a reusable macro, like Sigma's windash) and applied across all of them.
  • Only class 4 (33 rules) needs per-rule thought, and even there the fix is
    usually a one-token anchor on an existing exclusion.

In practice this is a few focused PRs, not 158 separate ones. I'm happy to open
them batched by class — I'd suggest leading with the macro family (classes 1/3/5),
then obfuscation (2/6), then exclusions/logic (4/7).

The root cause, in one example

ESCU already ships the correct pattern:
process_powershell:
(process_name=pwsh.exe OR process_name=powershell.exe
OR original_file_name=PowerShell.EXE OR original_file_name=pwsh.dll
OR original_file_name=powershell_ise.EXE OR process_name=powershell_ise.exe)

This defeats renaming (original_file_name) and the pwsh sibling. But
getadcomputer_with_powershell.yml hardcodes process_name="powershell.exe",
so it is evaded by both. process_wmic is the same story. Using the macro
closes classes 1 and 3 together.


Class 1 — Renamed binary (89)

original_file_name (PE version resource) doesn't change on rename and can't be forged for a signed Microsoft binary. Rules matching process_name only miss a renamed copy. Live proof (rule's own attack_data, process renamed to svc.exe, OriginalFileName intact):

Rule fires renamed +fix
Deleting Shadow Copies (vssadmin, T1490) yes 0 yes
Excessive Usage of NSLOOKUP App 407 0 407
Excessive Usage Of Taskkill 257 0 257
RDP Client Launched with Admin Session 10 0 10
Wmic NonInteractive App Uninstallation 16 0 16

Full list: results/rename_gap_89.csv. Top binaries: powershell(16), wmic(14), cmd(13), schtasks(11).

Class 2 — Option-char substitution (29)

Rule matches a switch in one prefix form (*/create* or *-enc*); the binary accepts the other. 24 affected binaries confirmed by ArgFuscator to accept both / and -. Live proof (prefix swapped):

Rule fires swapped +fix
DISM Remove Defender (T1562.001) 2 0 2
Certutil certificate extraction 12 0 12
Bcdedit Back To Normal Mode Boot 2 0 2

Full list: results/flag_form_confirmed_final.csv. Incl. Auditpol tampering,
Schtasks family, Remote Process Instantiation via WMI/PowerShell.

Class 3 — Sibling binary (13)

Separate binary, same capability. Live proof: Jscript/Vbscript via Cscript↔Wscript (fires→0→fix). 10 PowerShell rules fixed by process_powershell; process_wscript exists but omits the cscript sibling and there is no
process_cscript.

Class 4 — Bypassable exclusions (33)

  • by_name (8): NOT Image="*\<name>" → rename tool to the excluded name.
    Fix: qualify with original_file_name.
  • path_wildcard (14): NOT Image="*\windows\*" (no drive anchor) → run from
    C:\Users\Public\windows\…. Incl. CMLUA/CMSTPLUA UAC Bypass, Raw Access to
    MBR/Disk, DLL Search-Order Hijacking. Fix: anchor to drive root.
  • cmdline (11): NOT process="*<string>*" → satisfy with a dummy argument.
    Fix: anchor to the real field/position.

Class 5 — process_net matches net1.exe only (8) — coverage is OS-dependent

Whether net <verb> spawns net1.exe depends on the Windows version
(Win32_ProcessStartTrace):

verb Win11 25H2 Server rule
user (incl. /delete) no net1 net1 User Deletion / Disabled
localgroup no net1 no net1 Group Discovery
accounts no net1 net1 Password Policy

net localgroup spawns no net1 on either OS → Group Discovery is bypassed
everywhere; net user/accounts fire on Server but miss on Win11 → the same
rule's coverage flips with the OS. No evasion needed. One non-breaking macro
change (add net.exe) makes all 19 process_net rules fire uniformly.

Class 6 — Space-bound token (7)

* restore * matched with literal spaces; res""tore runs identically
(CommandLineToArgvW collapses "") but no longer contains the spaced token.
Lab-confirmed on Win11: reg qu""ery, netsh ad""vfirewall, sc.exe qu""ery,
schtasks /qu""ery all obeyed. Incl. Registry Entries Restored Via Reg, Proxy
Via Netsh. Fix: a command-line normalization macro (strip quotes, collapse
whitespace) — mirrors Sigma / Defender parse-command-line. Needs FP testing.

Class 7 — Case-sensitive match() (4)

match() without (?i) on case-insensitive Windows values: RunDLL Loading DLL
By Ordinal, Windows .Key File Creation, Windows RDP File Execution, WinDBG
Spawning AutoIt3. Fix: (?i). (SQLCMD was excluded — its flags are genuinely
case-sensitive.)


Method (reproducible)

Scanner flags the SPL pattern → pull the rule's tests.attack_data → mutate to
the evasion (rename / prefix swap / quote insert / sibling swap) → replay
original vs mutated → apply the fix to the mutated copy. Confirmed = fires on
original, 0 on mutated, recovers with fix. Attack side from ArgFuscator + direct
Windows 11/Server tests.

Full evidence — scanner, replay harness, mutated datasets, per-rule tables, and
live-Splunk screenshots; https://github.com/Diablo-research/splunk-escu-evidence/tree/main/escu-evasion-gaps

Data-source note

original_file_name is present on Sysmon EID 1 and EDR (CrowdStrike, Defender
for Endpoint). Windows Security 4688 lacks it, so on 4688-only telemetry Class 1
can't be closed this way, though some rules list 4688 as supported — a
maintainer call.

Scope / honesty

14 confirmed live; the rest share the same root cause and the replay/ArgFuscator
method. Scanner false positives were excluded on review (SQLCMD case-sensitivity,
drive-anchored path exclusions, cmd-intrinsic techniques, rules that already
cover PowerShell). Per-rule tables are in the evidence repo; I can confirm any
rule live on request. Happy to open a PR, batched by class or as one change.

Lingua principale
Python
Stelle
1.7k
Fork
507
Merge medio
2g 17h
PR unite (30g)
35

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di splunk/security_content

Tutte le issue di splunk/security_content

Issue simili

Altre issue su Python

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.