BXUP Track progress & test yourself — free
Security & AI Security · Hands-on lab · ~5 min read
SECURITY & AI SECURITY · HANDS-ON LAB

Lab 03 · Sigma Authoring + Cross-SIEM Conversion

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.

Key Takeaway:

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.

Time & tier

~90 minutes hands-on · Core tier (maps to module M2 · AI-Assisted Detection Engineering).

Setup

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.

Guided steps

  1. Draft the rule with the LLM, then distrust it. Prompt: "Write a Sigma rule (SigmaHQ schema) for a 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.
  2. Compare against this known-good reference. The correct rule looks like the block below. Diff the model's output against it — pay attention to whether the model invented fields (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
  1. Let the compiler gate the rule. Run the three conversions. Each requires a processing pipeline that maps the generic Sigma fields to the backend's real field names — this is where hallucinated fields die:
# 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
  1. Read the three outputs and confirm the field mapping. Expected shapes:
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.

  1. Create test events and prove true-positive / true-negative. Save 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.

  1. Introduce a hallucinated field and watch it fail. Edit the rule so 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.
  2. Save the artifact bundle. Keep 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.
Exam Tip

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.

Verification

  • sigma convert succeeds for splunk, kusto, and lucene with no error, producing three queries.
  • The DuckDB test shows fires = true for both rundll32→shell rows and false for both benign rows.
  • Introducing a bad field name changes or breaks the conversion, demonstrating the compiler gate.
  • conversions.md and test-results.md are saved with the passing evidence.

Completion artifact

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):

  • Valid Sigma (0–2): rule compiles on all three backends without editing the compiler command = 2.
  • Correct field mapping (0–2): you can name why the same field became three different backend fields = 2.
  • Passing tests (0–3): true-positive on both bad rows, true-negative on both good rows, recorded = 3.
  • Compiler-gate demo (0–2): you broke a field and showed the conversion catches it = 2.
  • ATT&CK hygiene (0–1): rule carries the correct T1218.011 tag = 1.

Stuck?

  • 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.
  • KQL output uses 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.
  • The DuckDB LIKE backslash confuses you: Windows paths use \; the pattern %\rundll32.exe matches any path ending in that executable.

Client-safe variant

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.

Answer key (don't read before attempting)

  1. Correct fields:ParentImage and Image with |endswith. Common AI substitutions ParentProcessName/ProcessName are not SigmaHQ process_creation fields and will misconvert.
  2. Pipelines are mandatory: the generic Sigma field only becomes a real SIEM field through the pipeline — ParentImage → ParentImage (Sysmon), InitiatingProcessFolderPath (Defender), process.parent.executable (ECS).
  3. True positives: rows where parent ends in \rundll32.exe and child ends in \cmd.exe or \powershell.exe. True negatives: benign parent (explorer→cmd) and benign child (rundll32→notepad).
  4. Compiler gate: an undefined selection in 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.
This is the free reading view. Open BXUP to track your progress, run the hands-on labs, and prove it with original, exam-pattern assessments.
© Anvi Innovate · BXUP — Be Exponentially Upskilled · BXUP is an independent learning platform, not affiliated with or endorsed by AWS, Microsoft, Google, NVIDIA, IBM, Salesforce, Databricks, Oracle or Anthropic. All trademarks belong to their respective owners. · bxup.in