Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Designed to showcase **evidence-backed security engineering skills** through rep
| PT-2026-010 | T1218.011 | Rundll32 proxy execution | Sigma + Splunk evidence | **Live validated** |
| PT-2026-011 | T1218.010 | Regsvr32 proxy execution | Sigma + Splunk evidence | **Live validated** |
| PT-2026-012 | T1569.002 | Service-launched command execution | Sigma + Splunk evidence | **Live validated** |
| PT-2026-013 | T1546.003 | Permanent WMI event subscription creation | Sigma + Splunk evidence | **Live validated** |

**Meaning of statuses in this repo**
- **Live validated**: replayed in the Mayuri lab with positive/negative evidence and cleanup confirmation.
Expand Down
45 changes: 45 additions & 0 deletions automation/execution/pt_2026_013_cleanup.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
$ErrorActionPreference = 'Continue'
$names = 'AtomicRedTeam-WMIPersistence-CommandLineEventConsumer-Example','PT-2026-013-Variant'
$namespace = 'root/subscription'
$bindingResidue = @()

function Get-NamedBindings {
param([string]$ObjectName)
$found = @()
$consumers = @(Get-WmiObject -Namespace $namespace -Class CommandLineEventConsumer -Filter "Name = '$ObjectName'" -ErrorAction SilentlyContinue)
foreach ($consumer in $consumers) {
$found += @(Get-WmiObject -Namespace $namespace -Query "REFERENCES OF {$($consumer.__RELPATH)} WHERE ResultClass = __FilterToConsumerBinding" -ErrorAction SilentlyContinue)
}
$filters = @(Get-WmiObject -Namespace $namespace -Class __EventFilter -Filter "Name = '$ObjectName'" -ErrorAction SilentlyContinue)
foreach ($filter in $filters) {
$found += @(Get-WmiObject -Namespace $namespace -Query "REFERENCES OF {$($filter.__RELPATH)} WHERE ResultClass = __FilterToConsumerBinding" -ErrorAction SilentlyContinue)
}
@($found | Sort-Object -Property __PATH -Unique)
}

foreach ($name in $names) {
@(Get-NamedBindings -ObjectName $name) | Remove-WmiObject -ErrorAction SilentlyContinue
Start-Sleep -Milliseconds 250
$remainingBindings = @(Get-NamedBindings -ObjectName $name)
if ($remainingBindings.Count -eq 0) {
@(Get-WmiObject -Namespace $namespace -Class CommandLineEventConsumer -Filter "Name = '$name'" -ErrorAction SilentlyContinue) |
Remove-WmiObject -ErrorAction SilentlyContinue
@(Get-WmiObject -Namespace $namespace -Class __EventFilter -Filter "Name = '$name'" -ErrorAction SilentlyContinue) |
Remove-WmiObject -ErrorAction SilentlyContinue
} else {
$bindingResidue += "binding:$name"
}
}
Remove-Item 'C:\Windows\Temp\pt-2026-013-original-completion.json','C:\Windows\Temp\pt-2026-013-variant-completion.json' -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 2
$remaining = foreach ($name in $names) {
if (Get-WmiObject -Namespace $namespace -Class __EventFilter -Filter "Name = '$name'" -ErrorAction SilentlyContinue) { "filter:$name" }
if (Get-WmiObject -Namespace $namespace -Class CommandLineEventConsumer -Filter "Name = '$name'" -ErrorAction SilentlyContinue) { "consumer:$name" }
Comment on lines +36 to +37

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Fail cleanup when WMI verification queries error

If a final WMI query fails because the provider is unavailable, permissions changed, or the namespace cannot be queried, -ErrorAction SilentlyContinue converts that failure into an empty result, so $remaining stays empty and the script reports Clean=true even though the preceding removals may also have failed. For this persistence scenario, make the verification queries terminating or explicitly check their success before certifying cleanup.

Useful? React with 👍 / 👎.

}
$remaining = @($bindingResidue) + @($remaining)
$completionFiles = @(
'C:\Windows\Temp\pt-2026-013-original-completion.json',
'C:\Windows\Temp\pt-2026-013-variant-completion.json'
) | Where-Object { Test-Path $_ }
[pscustomobject]@{ Remaining=@($remaining); CompletionFiles=@($completionFiles); Clean=(@($remaining).Count -eq 0 -and @($completionFiles).Count -eq 0) } | ConvertTo-Json -Compress
if (@($remaining).Count -ne 0 -or @($completionFiles).Count -ne 0) { throw 'WMI subscription cleanup residue remains' }
2 changes: 2 additions & 0 deletions automation/execution/pt_2026_013_negative_cim_inventory.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
$ErrorActionPreference = 'Stop'
Get-CimInstance Win32_OperatingSystem | Select-Object Caption,Version,LastBootUpTime | ConvertTo-Json -Compress
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
$ErrorActionPreference = 'Stop'
@(Get-CimInstance -Namespace root/subscription -ClassName __EventFilter | Select-Object -ExpandProperty Name) | ConvertTo-Json -Compress
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
$ErrorActionPreference = 'Stop'
$id = 'PT-2026-013-Transient'
Register-CimIndicationEvent -Query "SELECT * FROM Win32_ProcessStartTrace WHERE ProcessName='definitely-not-created-pt-2026-013.exe'" -SourceIdentifier $id | Out-Null
Start-Sleep -Seconds 1
Unregister-Event -SourceIdentifier $id
Get-Job -Name $id -ErrorAction SilentlyContinue | Remove-Job -Force
[pscustomobject]@{ SourceIdentifier=$id; Permanent=$false } | ConvertTo-Json -Compress
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
$ErrorActionPreference = 'Stop'
Set-ExecutionPolicy -Scope Process Bypass -Force
$testGuid = '3c64f177-28e2-49eb-a799-d767b24dd1e0'
$name = 'AtomicRedTeam-WMIPersistence-CommandLineEventConsumer-Example'
Import-Module Invoke-AtomicRedTeam -Force
Invoke-AtomicTest T1546.003 -TestGuids $testGuid -PathToAtomicsFolder 'C:\Tools\AtomicRedTeam\atomics' -Confirm:$false
$deadline = (Get-Date).AddSeconds(30)
do {
$filter = Get-WmiObject -Namespace root/subscription -Class __EventFilter -Filter "Name = '$name'" -ErrorAction SilentlyContinue
$consumer = Get-WmiObject -Namespace root/subscription -Class CommandLineEventConsumer -Filter "Name = '$name'" -ErrorAction SilentlyContinue
$binding = if ($consumer) {
Get-WmiObject -Namespace root/subscription -Query "REFERENCES OF {$($consumer.__RELPATH)} WHERE ResultClass = __FilterToConsumerBinding" -ErrorAction SilentlyContinue
}
if ($filter -and $consumer -and $binding) { break }
Start-Sleep -Seconds 1
} while ((Get-Date) -lt $deadline)
$result = [pscustomobject]@{
TestGuid = $testGuid
FilterPresent = [bool]$filter
ConsumerPresent = [bool]$consumer
BindingPresent = [bool]$binding
CompletedAt = (Get-Date).ToUniversalTime().ToString('o')
}
if (-not ($result.FilterPresent -and $result.ConsumerPresent -and $result.BindingPresent)) { throw 'Atomic WMI subscription was not fully created' }
$completionPath = 'C:\Windows\Temp\pt-2026-013-original-completion.json'
$result | ConvertTo-Json -Compress | Set-Content -Path $completionPath -Encoding UTF8
$result | ConvertTo-Json -Compress
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
$ErrorActionPreference = 'Stop'
$name = 'PT-2026-013-Variant'
$namespace = 'root/subscription'
$filterArgs = @{
Name = $name
EventNameSpace = 'root\CimV2'
QueryLanguage = 'WQL'
Query = "SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System' AND TargetInstance.SystemUpTime > 99999999"
}
$consumerArgs = @{
Name = $name
CommandLineTemplate = "$env:SystemRoot\System32\cmd.exe /c exit 0"
}
$filter = New-CimInstance -Namespace $namespace -ClassName __EventFilter -Property $filterArgs
$consumer = New-CimInstance -Namespace $namespace -ClassName CommandLineEventConsumer -Property $consumerArgs
$binding = New-CimInstance -Namespace $namespace -ClassName __FilterToConsumerBinding -Property @{ Filter=[Ref]$filter; Consumer=[Ref]$consumer }
$deadline = (Get-Date).AddSeconds(30)
do {
$persistedFilter = Get-WmiObject -Namespace $namespace -Class __EventFilter -Filter "Name = '$name'" -ErrorAction SilentlyContinue
$persistedConsumer = Get-WmiObject -Namespace $namespace -Class CommandLineEventConsumer -Filter "Name = '$name'" -ErrorAction SilentlyContinue
$persistedBinding = if ($persistedConsumer) {
Get-WmiObject -Namespace $namespace -Query "REFERENCES OF {$($persistedConsumer.__RELPATH)} WHERE ResultClass = __FilterToConsumerBinding" -ErrorAction SilentlyContinue
}
$filterPresent = [bool]$persistedFilter
$consumerPresent = [bool]$persistedConsumer
$bindingPresent = [bool]$persistedBinding
if ($filterPresent -and $consumerPresent -and $bindingPresent) { break }
Start-Sleep -Seconds 1
} while ((Get-Date) -lt $deadline)
$result = [pscustomobject]@{
FilterPresent = $filterPresent
ConsumerPresent = $consumerPresent
BindingPresent = $bindingPresent
CompletedAt = (Get-Date).ToUniversalTime().ToString('o')
}
if (-not ($result.FilterPresent -and $result.ConsumerPresent -and $result.BindingPresent)) { throw 'Variant WMI subscription was not fully created' }
$completionPath = 'C:\Windows\Temp\pt-2026-013-variant-completion.json'
$result | ConvertTo-Json -Compress | Set-Content -Path $completionPath -Encoding UTF8
$result | ConvertTo-Json -Compress
3 changes: 3 additions & 0 deletions automation/validators/sigma_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,13 @@
'Image': "<Data Name='Image'>",
'ParentImage': "<Data Name='ParentImage'>",
'User': "<Data Name='User'>",
'EventType': "<Data Name='EventType'>",
'Operation': "<Data Name='Operation'>",
}
BASE_SEARCH = {
'ps_script': 'search index=main source="WinEventLog:Microsoft-Windows-PowerShell/Operational" _raw="*<EventID>4104</EventID>*"',
'process_creation': 'search index=main source="WinEventLog:Microsoft-Windows-Sysmon/Operational" _raw="*<EventID>1</EventID>*"',
'wmi_event': 'search index=main source="WinEventLog:Microsoft-Windows-Sysmon/Operational" (_raw="*<EventID>19</EventID>*" OR _raw="*<EventID>20</EventID>*" OR _raw="*<EventID>21</EventID>*")',
}


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
any where winlog.event_data.Operation:"Created" and (winlog.event_data.EventType like~ ("WmiFilterEvent", "WmiConsumerEvent", "WmiBindingEvent"))
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
search index=main source="WinEventLog:Microsoft-Windows-Sysmon/Operational" (_raw="*<EventID>19</EventID>*" OR _raw="*<EventID>20</EventID>*" OR _raw="*<EventID>21</EventID>*") (_raw="*<Data Name='Operation'>*Created*") AND ((_raw="*<Data Name='EventType'>*WmiFilterEvent*" OR _raw="*<Data Name='EventType'>*WmiConsumerEvent*" OR _raw="*<Data Name='EventType'>*WmiBindingEvent*"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bound Operation matching to its XML element

In the generated Mayuri live query, _raw="*<Data Name='Operation'>*Created*" does not require Created to be the value of Operation; it can occur in any later XML field. For example, a Sysmon Event 19 deletion whose filter name or WQL text contains Created will satisfy this clause and be reported as a creation, contrary to the Sigma rule and the negative deletion contract. Match Created before the corresponding </Data> boundary for exact-value fields.

Useful? React with 👍 / 👎.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Operation="Created" EventType IN ("WmiFilterEvent", "WmiConsumerEvent", "WmiBindingEvent")
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
title: Permanent WMI Event Subscription Created
id: 45742e29-7c43-4a65-bd91-b04fa7a8d9dc
status: test
description: >-
Detects creation of a permanent WMI event filter, consumer, or filter-to-consumer binding.
Permanent WMI subscriptions can provide stealthy event-triggered persistence.
references:
- https://attack.mitre.org/techniques/T1546/003/
- https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1546.003/T1546.003.yaml
author: mell0wx
logsource:
product: windows
category: wmi_event
detection:
selection_created:
Operation: Created
selection_type:
EventType:
- WmiFilterEvent
- WmiConsumerEvent
- WmiBindingEvent
condition: selection_created and selection_type
falsepositives:
- Legitimate endpoint-management, monitoring, or software-deployment products creating permanent WMI subscriptions
- Controlled administrative or purple-team validation activity
date: 2026-08-02
level: high
tags:
- attack.persistence
- attack.privilege-escalation
- attack.t1546.003
133 changes: 133 additions & 0 deletions detections/validation/live/VAL-2026-013-PT-2026-013.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
{
"scenario_id": "PT-2026-013",
"validation_run_id": "VAL-2026-013",
"technique_id": "T1546.003",
"atomic_test_guid": "3c64f177-28e2-49eb-a799-d767b24dd1e0",
"atomic_test_index": 1,
"atomic_test_name": "Persistence via WMI Event Subscription - CommandLineEventConsumer",
"validation_date_utc": "2026-08-03",
"target": "approved Windows victim",
"rollback_snapshot": "verified; identifier retained in private evidence",
"scope": {
"local_only": true,
"domain_controller_targeted": false,
"network_payload": false,
"credential_access": false,
"lateral_movement": false,
"payload_triggered": false
},
"preflight": {
"victim_running": true,
"secure_channel": true,
"sensors_running": ["Sysmon64", "SplunkForwarder", "Velociraptor", "WazuhSvc", "WinDefend"],
"splunk_healthy": true,
"fresh_victim_telemetry_confirmed": true,
"wmi_object_name_collisions": false,
"victim_boot_time_utc": "2026-07-17T14:40:33.501419Z",
"uptime_seconds_at_original_start": 1422387.012,
"atomic_payload_trigger_window_seconds": [240, 324]
},
"original": {
"start_time_utc": "2026-08-03T01:47:00.513788Z",
"end_time_utc": "2026-08-03T01:47:08.334013Z",
"exec_output": "Exact Atomic UUID completed; filter, CommandLineEventConsumer, and binding were verified through a durable completion record.",
"detection_results": [
{
"_time": "2026-08-03 01:47:05.000 UTC",
"host": "approved-windows-victim",
"source": "WinEventLog:Microsoft-Windows-Sysmon/Operational",
"event_codes": [19, 20],
"event_types": ["WmiFilterEvent", "WmiConsumerEvent"],
"event_count": 3
}
],
"subscription_state": "filter, consumer, and binding created and verified",
"detection_fired": true,
"detection_latency_seconds": 4.486,
"payload_verification": {
"process": "notepad.exe",
"event_id": 1,
"source": "WinEventLog:Microsoft-Windows-Sysmon/Operational",
"earliest_utc": "2026-08-03T01:46:55Z",
"latest_utc": "2026-08-03T01:47:15Z",
"query_scope": "bounded Sysmon process-creation search",
"match_count": 0
}
},
"variant": {
"start_time_utc": "2026-08-03T01:48:46.005927Z",
"end_time_utc": "2026-08-03T01:48:49.604107Z",
"exec_output": "Modified local CommandLineEventConsumer subscription completed with a deliberately non-triggering WQL condition; durable completion record verified.",
"detection_results": [
{
"_time": "2026-08-03 01:48:47.000 UTC",
"host": "approved-windows-victim",
"source": "WinEventLog:Microsoft-Windows-Sysmon/Operational",
"event_codes": [19, 20, 21],
"event_types": ["WmiFilterEvent", "WmiConsumerEvent", "WmiBindingEvent"],
"event_count": 3
}
],
"subscription_state": "filter, consumer, and binding created and verified",
"detection_fired": true,
"detection_latency_seconds": 0.994
},
"negatives": [
{
"name": "read-only CIM operating-system inventory",
"start_time_utc": "2026-08-03T01:49:52.263193Z",
"end_time_utc": "2026-08-03T01:49:54.749420Z",
"exec_output": "read-only operating-system inventory completed",
"detection_results": [],
"detection_fired": false,
"detection_latency_seconds": null
},
{
"name": "read-only permanent subscription inventory",
"start_time_utc": "2026-08-03T01:50:34.234812Z",
"end_time_utc": "2026-08-03T01:50:36.844661Z",
"exec_output": "existing subscription inventory completed",
"detection_results": [],
"detection_fired": false,
"detection_latency_seconds": null
},
{
"name": "transient in-process CIM indication subscription",
"start_time_utc": "2026-08-03T01:51:16.343586Z",
"end_time_utc": "2026-08-03T01:51:21.796557Z",
"exec_output": "temporary in-process subscription registered and removed; no permanent namespace objects created",
"detection_results": [],
"detection_fired": false,
"detection_latency_seconds": null
}
],
"deviations": [
{
"description": "The initial harness used direct CIM-reference string matching to verify and remove the filter-to-consumer binding. The Atomic created the subscription, but the harness could not reliably recognize the association and did not produce a completion record.",
"response": "Execution stopped. Cleanup was replaced with the upstream-style REFERENCES OF association query, a regression contract was added, and filter, consumer, binding, and completion-file counts were all verified as zero before rerun.",
"residue_after_cleanup": false
},
{
"description": "A subsequent controlled rerun confirmed that fixed-delay polling did not correct the binding comparison because the issue was association representation rather than ingestion delay.",
"response": "The positive verification path was changed to the same REFERENCES OF association query and tested before the successful evidence-producing replay.",
"residue_after_cleanup": false
}
],
"cleanup": {
"start_time_utc": "2026-08-03T01:52:17.025253Z",
"end_time_utc": "2026-08-03T01:52:21.487320Z",
"exec_output": "filters=0; consumers=0; bindings=0; completion_files=0; victim secure channel=true; endpoint sensors running; dcdiag quiet",
"filters_remaining": 0,
"consumers_remaining": 0,
"bindings_remaining": 0,
"completion_files_remaining": 0,
"idempotence_recheck": {
"start_time_utc": "2026-08-03T02:18:46.789298Z",
"end_time_utc": "2026-08-03T02:18:53.085340Z",
"remaining": [],
"completion_files": [],
"clean": true
}
},
"status": "validated"
}
19 changes: 19 additions & 0 deletions detections/validation/permanent-wmi-event-subscription.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Permanent WMI Event Subscription Detection Validation

- Scenario: `PT-2026-013`
- Validation: `VAL-2026-013`
- Technique: `T1546.003`
- Atomic UUID: `3c64f177-28e2-49eb-a799-d767b24dd1e0`
- Result: **passed live validation**

## Assertions
- Exact Atomic `CommandLineEventConsumer` subscription: detected.
- Modified non-triggering permanent subscription: detected.
- Read-only CIM operating-system inventory: not detected.
- Read-only permanent-subscription inventory: not detected.
- Transient in-process indication subscription: not detected.
- Explicit association cleanup and postflight health: passed.

The live Mayuri query uses raw Sysmon XML because equivalent normalized WMI fields are not yet verified in Splunk. The canonical Sigma rule remains field-based and backend-neutral.

See the [live JSON record](live/VAL-2026-013-PT-2026-013.json), [scenario results](../../purple-team/scenarios/PT-2026-013-wmi-event-subscription/RESULTS.md), and [sanitized evidence](../../evidence/sanitized/PT-2026-013/README.md).
Loading