From a described adversary behavior, generate a single vendor-neutral Sigma rule with an LLM, compile it to three backends — Splunk SPL, Microsoft KQL (Defender/Sentinel), and Elastic — using sigma-cli/pySigma, and prove it fires on known-bad test events while staying quiet on known-good ones.
The trap this lab teaches you to avoid: an AI will happily emit a Sigma rule with a hallucinated field, an invalid logsource, or a condition that references a selection that does not exist. pySigma is a deterministic compiler — it will refuse to convert a malformed rule, which makes it the perfect gate on AI-authored detections. Your artifact is one Sigma rule, three passing conversions, and a test log proving true-positive and true-negative behavior.
Sigma is the source of truth; the SIEM query is a build artifact. Author once, compile everywhere, and let the compiler reject what the model got wrong.
~90 minutes hands-on · Core tier (maps to module M2 · AI-Assisted Detection Engineering).
Offline, $0, no cloud SIEM. Everything is local OSS. Install sigma-cli and the three backends:
python -m venv .venv && . .venv/bin/activate
pip install sigma-cli pysigma-backend-splunk pysigma-backend-kusto \
pysigma-backend-elasticsearch pysigma-pipeline-sysmon
sigma version && sigma list targets
Confirm the target list shows splunk, kusto, and lucene. Vendor-neutral by construction: pySigma has community backends for QRadar, CarbonBlack, and more — the three here are representative, and the method is identical for any backend.
The behavior to detect (your "threat brief"): rundll32.exe spawning a command interpreter — cmd, powershell, or pwsh — a common living-off-the-land defense-evasion pattern (MITRE ATT&CK T1218.011). You will have the LLM draft the Sigma rule for this behavior, then verify it against the docs and the compiler before trusting it.
process_creation logsource on Windows that detects rundll32.exe as the parent spawning cmd.exe, powershell.exe, or pwsh.exe. Use ParentImage|endswith and Image|endswith. Include an ATT&CK tag for T1218.011." Save the output as rundll32_spawn_shell.yml. Do not run it yet.ParentProcessName instead of ParentImage), broke the condition, or dropped the logsource.category.title: Rundll32 Spawning a Command Interpreter
id: 8a1b2c3d-0001-4a5b-9c8d-1122334455aa
status: experimental
description: Detects rundll32.exe launching cmd/powershell/pwsh, a common LOLBin evasion.
logsource:
category: process_creation
product: windows
detection:
selection:
ParentImage|endswith: '\rundll32.exe'
Image|endswith:
- '\cmd.exe'
- '\powershell.exe'
- '\pwsh.exe'
condition: selection
falsepositives:
- Legitimate installers that shell out via rundll32
level: high
tags:
- attack.defense_evasion
- attack.t1218.011
# Splunk SPL (Sysmon-sourced)
sigma convert -t splunk -p sysmon -p splunk_windows rundll32_spawn_shell.yml
# Microsoft KQL (Defender XDR tables)
sigma convert -t kusto -p microsoft_xdr rundll32_spawn_shell.yml
# Elastic (ECS Windows, Lucene)
sigma convert -t lucene -p ecs_windows rundll32_spawn_shell.yml
Splunk: source="WinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=1
ParentImage="*\\rundll32.exe" Image IN ("*\\cmd.exe","*\\powershell.exe","*\\pwsh.exe")
KQL: DeviceProcessEvents
| where InitiatingProcessFolderPath endswith "\\rundll32.exe"
and (FolderPath endswith "\\cmd.exe" or FolderPath endswith "\\powershell.exe"
or FolderPath endswith "\\pwsh.exe")
Elastic: process.parent.executable:*\\rundll32.exe AND
(process.executable:(*\\cmd.exe OR *\\powershell.exe OR *\\pwsh.exe))
Notice the compiler translated the same Sigma field to ParentImage (Splunk/Sysmon), InitiatingProcessFolderPath (KQL/Defender), and process.parent.executable (ECS). That cross-schema mapping is the value pySigma delivers and the thing an LLM cannot be trusted to do by hand.
test_events.csv — two known-bad, two known-good — and check the rule's logic against it with DuckDB (a local stand-in for "run it in the SIEM"):ParentImage,Image
C:\Windows\System32\rundll32.exe,C:\Windows\System32\cmd.exe
C:\Windows\System32\rundll32.exe,C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
C:\Windows\explorer.exe,C:\Windows\System32\cmd.exe
C:\Windows\System32\rundll32.exe,C:\Windows\System32\notepad.exe
python -c "import duckdb; duckdb.sql(\"\"\"SELECT *, \
(ParentImage LIKE '%\\rundll32.exe' AND (Image LIKE '%\\cmd.exe' OR Image LIKE '%\\powershell.exe' OR Image LIKE '%\\pwsh.exe')) AS fires \
FROM read_csv_auto('test_events.csv')\"\"\").show()"
Rows 1–2 should show fires = true; rows 3 (wrong parent) and 4 (benign child) should show false. That is your passing test data.
ParentImage becomes ParentProcessName (a field that does not exist in the Sysmon pipeline mapping) and re-run the Splunk conversion. Observe how the pipeline either drops the mapping or the query no longer references the right field — a compile-time signal that the rule is wrong. Revert.rundll32_spawn_shell.yml, the three converted queries in conversions.md, test_events.csv, and a short test-results.md recording the true-positive/true-negative outcome.A Sigma condition that names a selection block which is not defined (e.g. condition: selection and filter with no filter:) is a classic AI error. pySigma raises SigmaConditionError — memorize that the compiler, not your eyes, is the reliable catch.
sigma convert succeeds for splunk, kusto, and lucene with no error, producing three queries.fires = true for both rundll32→shell rows and false for both benign rows.conversions.md and test-results.md are saved with the passing evidence.A folder containing rundll32_spawn_shell.yml, conversions.md (Splunk + KQL + Elastic), test_events.csv, and test-results.md. Self-grading rubric (pass = 8/10):
Backend requires a pipeline error: you omitted -p; each backend needs its mapping pipeline (sysmon+splunk_windows, microsoft_xdr, ecs_windows).sigma list targets is empty: the backend plugins did not install; re-run the pip install line inside the activated venv.DeviceProcessEvents but you use Sentinel, not Defender: swap -p microsoft_xdr for -p sentinel_asim or -p azure_monitor to target the tables you actually have.LIKE backslash confuses you: Windows paths use \; the pattern %\rundll32.exe matches any path ending in that executable.sigma-cli, pySigma, and DuckDB are entirely local OSS and never see an LLM — so the compile-and-test half of this lab is already client-safe with zero changes. For the authoring step, run the LLM draft on the client-approved endpoint (Bedrock/Azure OpenAI) or local Ollama, and describe the behavior abstractly rather than pasting client hostnames or sample logs. Because the deterministic compiler and your test events are the real gate, an AI-drafted rule is defensible on a client engagement only after it survives the same conversion and true-positive/true-negative checks you ran here.
ParentImage and Image with |endswith. Common AI substitutions ParentProcessName/ProcessName are not SigmaHQ process_creation fields and will misconvert.ParentImage → ParentImage (Sysmon), InitiatingProcessFolderPath (Defender), process.parent.executable (ECS).\rundll32.exe and child ends in \cmd.exe or \powershell.exe. True negatives: benign parent (explorer→cmd) and benign child (rundll32→notepad).condition, or a field the pipeline cannot map, is caught at convert time — which is why you never ship an AI-authored Sigma rule that has not been compiled.