-
Notifications
You must be signed in to change notification settings - Fork 0
feat: implement threat-informed detection cycle 1 #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| $ErrorActionPreference='Stop' | ||
| $root='C:\Windows\Temp\dc1-campaign' | ||
| New-Item -ItemType Directory $root -Force|Out-Null | ||
| $source=Join-Path $root 'source.hta';$staged=Join-Path $root 'staged.hta';$marker=Join-Path $root 'campaign-marker.txt' | ||
| $pidFile=Join-Path $root 'process-ids.txt';if(Test-Path $pidFile){throw 'prior process record exists; run cleanup before reuse'};Remove-Item $source,$staged,$marker,(Join-Path $root 'result.json') -Force -ErrorAction SilentlyContinue | ||
| $body=@' | ||
| <html><head><hta:application showintaskbar="no" windowstate="minimize" /><script language="VBScript"> | ||
| Set s=CreateObject("WScript.Shell") | ||
| s.Run "powershell.exe -NoProfile -NonInteractive -Command Set-Content -LiteralPath C:\Windows\Temp\dc1-campaign\campaign-marker.txt -Value benign-campaign",0,True | ||
| window.close | ||
| </script></head></html> | ||
| '@ | ||
| Set-Content $source $body -Encoding Ascii | ||
| $server=Start-Job -ScriptBlock {param($file)$l=[Net.HttpListener]::new();$l.Prefixes.Add('http://127.0.0.1:18769/');$l.Start();try{$c=$l.GetContext();$b=[IO.File]::ReadAllBytes($file);$c.Response.StatusCode=200;$c.Response.ContentType='text/html';$c.Response.ContentLength64=$b.Length;$c.Response.OutputStream.Write($b,0,$b.Length);$c.Response.OutputStream.Close()}finally{$l.Stop();$l.Close()}} -ArgumentList $source | ||
| try{Start-Sleep 2;Invoke-WebRequest -Uri 'http://127.0.0.1:18769/stage.hta' -OutFile $staged -UseBasicParsing;if(-not(Wait-Job $server -Timeout 30)){throw 'server timeout'};$sourceHash=(Get-FileHash $source).Hash;$stagedHash=(Get-FileHash $staged).Hash;if($sourceHash -ne $stagedHash){throw 'staged HTA hash mismatch'};$p=Start-Process mshta.exe -ArgumentList $staged -PassThru;[string]$p.Id|Set-Content $pidFile;if(-not$p.WaitForExit(30000)){try{$p.Kill()}catch{};throw 'mshta timeout'};$deadline=(Get-Date).AddSeconds(10);do{Start-Sleep -Milliseconds 250}until((Test-Path $marker)-or(Get-Date)-gt$deadline);if(-not(Test-Path $marker)){throw 'campaign marker missing'};[pscustomobject]@{SourceHash=$sourceHash;StagedHash=$stagedHash;MarkerHash=(Get-FileHash $marker).Hash;Completed=$true}|ConvertTo-Json -Compress|Set-Content (Join-Path $root 'result.json')}finally{Get-Job|Stop-Job -ErrorAction SilentlyContinue;Get-Job|Remove-Job -Force -ErrorAction SilentlyContinue} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| $root='C:\Windows\Temp\dc1-campaign' | ||
| $ids=@(Get-Content (Join-Path $root 'process-ids.txt') -ErrorAction SilentlyContinue|Where-Object {$_ -match '^\d+$'}|ForEach-Object {[int]$_}) | ||
| @(Get-CimInstance Win32_Process -ErrorAction SilentlyContinue|Where-Object {$ids -contains [int]$_.ParentProcessId}|ForEach-Object {[int]$_.ProcessId})+$ids|Sort-Object -Unique|ForEach-Object {Stop-Process -Id $_ -Force -ErrorAction SilentlyContinue} | ||
| Remove-Item $root -Recurse -Force -ErrorAction SilentlyContinue | ||
| $remaining=@(Get-ChildItem $root -Recurse -ErrorAction SilentlyContinue) | ||
| [pscustomobject]@{RecordedProcessIds=$ids;Remaining=@($remaining|ForEach-Object FullName);Clean=($remaining.Count -eq 0)}|ConvertTo-Json -Compress |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| $root='C:\Windows\Temp\dc1-t1105' | ||
| Remove-Item $root -Recurse -Force -ErrorAction SilentlyContinue | ||
| $remaining=@(Get-ChildItem $root -Recurse -ErrorAction SilentlyContinue) | ||
| [pscustomobject]@{Remaining=@($remaining|ForEach-Object FullName);Clean=($remaining.Count -eq 0)}|ConvertTo-Json -Compress |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Get-Command Invoke-WebRequest | Select-Object Name,CommandType,Version | ConvertTo-Json -Compress |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| try{Invoke-WebRequest -Uri 'http://127.0.0.1:9/' -Method Head -TimeoutSec 1 -ErrorAction Stop}catch{Write-Output 'expected bounded local refusal'} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| $client=New-Object System.Net.WebClient;[pscustomobject]@{Type=$client.GetType().FullName;NoTransfer=$true}|ConvertTo-Json -Compress;$client.Dispose() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| $ErrorActionPreference='Stop';Set-ExecutionPolicy -Scope Process Bypass -Force;Import-Module Invoke-AtomicRedTeam -Force | ||
| $guid='42dc4460-9aa6-45d3-b1a6-3955d34e1fe8';$root='C:\Windows\Temp\dc1-t1105';New-Item -ItemType Directory $root -Force|Out-Null;$source=Join-Path $root 'source.txt';$dest=Join-Path $root 'atomic-transfer.txt';Set-Content $source 'benign Detection Cycle 1 transfer fixture' -Encoding Ascii | ||
| $server=Start-Job -ScriptBlock {param($file)$l=[Net.HttpListener]::new();$l.Prefixes.Add('http://127.0.0.1:18765/');$l.Start();try{$c=$l.GetContext();$b=[IO.File]::ReadAllBytes($file);$c.Response.ContentLength64=$b.Length;$c.Response.OutputStream.Write($b,0,$b.Length);$c.Response.Close()}finally{$l.Stop();$l.Close()}} -ArgumentList $source | ||
| try{Start-Sleep 2;Invoke-AtomicTest T1105 -TestGuids $guid -PathToAtomicsFolder 'C:\Tools\AtomicRedTeam\atomics' -InputArgs @{remote_file='http://127.0.0.1:18765/source.txt';destination_path=$dest} -Confirm:$false;if(-not(Wait-Job $server -Timeout 30)){throw 'server timeout'};if(-not(Test-Path $dest)){throw 'destination missing'};$sourceHash=(Get-FileHash $source).Hash;$destinationHash=(Get-FileHash $dest).Hash;if($sourceHash -ne $destinationHash){throw 'transfer hash mismatch'};[pscustomobject]@{AtomicGuid=$guid;SourceHash=$sourceHash;DestinationHash=$destinationHash;Completed=$true}|ConvertTo-Json -Compress|Set-Content (Join-Path $root 'positive.json')}finally{Get-Job|Stop-Job -ErrorAction SilentlyContinue;Get-Job|Remove-Job -Force -ErrorAction SilentlyContinue} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| $ErrorActionPreference='Stop';$root='C:\Windows\Temp\dc1-t1105';New-Item -ItemType Directory $root -Force|Out-Null;$source=Join-Path $root 'variant-source.txt';$dest=Join-Path $root 'variant-transfer.txt';Set-Content $source 'benign modified transfer fixture' -Encoding Ascii | ||
| $server=Start-Job -ScriptBlock {param($file)$l=[Net.HttpListener]::new();$l.Prefixes.Add('http://127.0.0.1:18766/');$l.Start();try{$c=$l.GetContext();$b=[IO.File]::ReadAllBytes($file);$c.Response.ContentLength64=$b.Length;$c.Response.OutputStream.Write($b,0,$b.Length);$c.Response.Close()}finally{$l.Stop();$l.Close()}} -ArgumentList $source | ||
| try{Start-Sleep 2;Invoke-WebRequest -Uri 'http://127.0.0.1:18766/variant.txt' -OutFile $dest -UseBasicParsing;if(-not(Wait-Job $server -Timeout 30)){throw 'server timeout'};if(-not(Test-Path $dest)){throw 'destination missing'};$sourceHash=(Get-FileHash $source).Hash;$destinationHash=(Get-FileHash $dest).Hash;if($sourceHash -ne $destinationHash){throw 'transfer hash mismatch'};[pscustomobject]@{SourceHash=$sourceHash;DestinationHash=$destinationHash;Completed=$true}|ConvertTo-Json -Compress|Set-Content (Join-Path $root 'variant.json')}finally{Get-Job|Stop-Job -ErrorAction SilentlyContinue;Get-Job|Remove-Job -Force -ErrorAction SilentlyContinue} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| $root='C:\Windows\Temp\dc1-t1218-005';$ids=@(Get-ChildItem (Join-Path $root 'process-ids-*.txt') -ErrorAction SilentlyContinue|Get-Content -ErrorAction SilentlyContinue|Where-Object {$_ -match '^\d+$'}|ForEach-Object {[int]$_});@(Get-CimInstance Win32_Process -ErrorAction SilentlyContinue|Where-Object {$ids -contains [int]$_.ParentProcessId}|ForEach-Object {[int]$_.ProcessId})+$ids|Sort-Object -Unique|ForEach-Object {Stop-Process -Id $_ -Force -ErrorAction SilentlyContinue};Remove-Item $root -Recurse -Force -ErrorAction SilentlyContinue;$remaining=@(Get-ChildItem $root -Recurse -ErrorAction SilentlyContinue);[pscustomobject]@{RecordedProcessIds=$ids;Remaining=@($remaining|ForEach-Object FullName);Clean=($remaining.Count -eq 0)}|ConvertTo-Json -Compress | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| $root='C:\Windows\Temp\dc1-t1218-005-negative';New-Item -ItemType Directory $root -Force|Out-Null;$hta=Join-Path $root 'display-only.hta';Set-Content $hta '<html><script language="VBScript">window.close</script></html>' -Encoding Ascii;$p=Start-Process mshta.exe -ArgumentList $hta -PassThru;if(-not $p.WaitForExit(15000)){try{$p.Kill()}catch{}};Remove-Item $root -Recurse -Force |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Get-Command mshta.exe | Select-Object Name,Source,Version | ConvertTo-Json -Compress |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Get-AuthenticodeSignature $env:WINDIR\System32\mshta.exe | Select-Object Status,StatusMessage | ConvertTo-Json -Compress |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| $ErrorActionPreference='Continue' | ||
| $guid='8707a805-2b76-4f32-b1c0-14e558205772' | ||
| $atomicYaml='C:\Tools\AtomicRedTeam\atomics\T1218.005\T1218.005.yaml' | ||
| if(-not (Select-String -LiteralPath $atomicYaml -SimpleMatch $guid -Quiet)){throw 'Pinned Atomic UUID not present in staged definition'} | ||
| $root='C:\Windows\Temp\dc1-t1218-005';New-Item -ItemType Directory $root -Force|Out-Null | ||
| $start=(Get-Date).ToUniversalTime();$arg='about:<hta:application><script language="VBScript">Close(Execute("CreateObject(""Wscript.Shell"").Run%20""powershell.exe%20-nop%20-Command%20Write-Host%20Detection%20Cycle%201;Start-Sleep%20-Seconds%201"""))</script>' | ||
| try{$p=Start-Process "$env:WINDIR\System32\mshta.exe" -ArgumentList ('"{0}"' -f $arg) -PassThru -ErrorAction Stop;[string]$p.Id|Set-Content (Join-Path $root 'process-ids-atomic.txt');if(-not$p.WaitForExit(20000)){try{$p.Kill()}catch{};throw 'timeout'};$started=$true;$exit=$p.ExitCode}catch{$started=$false;$exit=$null;$launchError=$_.Exception.Message} | ||
| Start-Sleep 2 | ||
| $defender=@(Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-Windows Defender/Operational';StartTime=$start} -ErrorAction SilentlyContinue|Where-Object {$_.Id -in 1116,1117 -and $_.Message -match 'mshta'}) | ||
| $result=[pscustomobject]@{AtomicGuid=$guid;ProcessStarted=$started;ExitCode=$exit;PreventiveEvents=$defender.Count;Outcome=$(if(-not$started -and $defender.Count){'prevented'}elseif($started){'executed'}else{'failed-unconfirmed'});Completed=$true} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Useful? React with 👍 / 👎. |
||
| $result|ConvertTo-Json -Compress|Set-Content (Join-Path $root 'positive.json');$result|ConvertTo-Json -Compress | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| $ErrorActionPreference='Stop';$root='C:\Windows\Temp\dc1-t1218-005';New-Item -ItemType Directory $root -Force|Out-Null;$marker=Join-Path $root 'variant-marker.txt';$hta=Join-Path $root 'variant.hta' | ||
| $body=@' | ||
| <html><head><hta:application showintaskbar="no" windowstate="minimize" /><script language="VBScript"> | ||
| Set s=CreateObject("WScript.Shell") | ||
| s.Run "cmd.exe /c echo benign-mshta-variant> C:\Windows\Temp\dc1-t1218-005\variant-marker.txt",0,True | ||
| window.close | ||
| </script></head></html> | ||
| '@ | ||
| Remove-Item $marker,$hta,(Join-Path $root 'process-ids-variant.txt') -Force -ErrorAction SilentlyContinue;Set-Content $hta $body -Encoding Ascii; $p=Start-Process mshta.exe -ArgumentList $hta -PassThru;[string]$p.Id|Set-Content (Join-Path $root 'process-ids-variant.txt'); if(-not $p.WaitForExit(30000)){try{$p.Kill()}catch{};throw 'mshta timeout'};if(-not(Test-Path $marker)){throw 'marker missing'} | ||
| [pscustomobject]@{MarkerHash=(Get-FileHash $marker).Hash;Completed=$true}|ConvertTo-Json -Compress|Set-Content (Join-Path $root 'variant.json') |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| any where process.parent.executable:"*\\mshta.exe" and (process.executable like~ ("*\\cmd.exe", "*\\powershell.exe", "*\\pwsh.exe", "*\\wscript.exe", "*\\cscript.exe")) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| any where (powershell.file.script_block_text:"*WebClient*" and powershell.file.script_block_text:"*DownloadFile*") or (powershell.file.script_block_text:"*Invoke-WebRequest*" and powershell.file.script_block_text:"*OutFile*") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| search index=main source="WinEventLog:Microsoft-Windows-Sysmon/Operational" _raw="*<EventID>1</EventID>*" (_raw="*<Data Name='ParentImage'>*\mshta.exe*</Data>*") AND ((_raw="*<Data Name='Image'>*\cmd.exe*</Data>*" OR _raw="*<Data Name='Image'>*\powershell.exe*</Data>*" OR _raw="*<Data Name='Image'>*\pwsh.exe*</Data>*" OR _raw="*<Data Name='Image'>*\wscript.exe*</Data>*" OR _raw="*<Data Name='Image'>*\cscript.exe*</Data>*")) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| search index=main source="WinEventLog:Microsoft-Windows-PowerShell/Operational" _raw="*<EventID>4104</EventID>*" ((_raw="*<Data Name='ScriptBlockText'>*WebClient*" AND _raw="*<Data Name='ScriptBlockText'>*DownloadFile*")) OR ((_raw="*<Data Name='ScriptBlockText'>*Invoke-WebRequest*" AND _raw="*<Data Name='ScriptBlockText'>*OutFile*")) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ParentImage="*\\mshta.exe" Image IN ("*\\cmd.exe", "*\\powershell.exe", "*\\pwsh.exe", "*\\wscript.exe", "*\\cscript.exe") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Channel IN ("Microsoft-Windows-PowerShell/Operational", "PowerShellCore/Operational") EventID=4104 (ScriptBlockText="*WebClient*" ScriptBlockText="*DownloadFile*") OR (ScriptBlockText="*Invoke-WebRequest*" ScriptBlockText="*OutFile*") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| title: Suspicious Child Process Spawned By Mshta | ||
| id: 8b36d6e2-16e6-4ae2-b86a-d396aac273c1 | ||
| status: test | ||
| description: Detects script interpreters or command shells launched directly by mshta.exe. | ||
| references: | ||
| - https://attack.mitre.org/techniques/T1218/005/ | ||
| - https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1218.005/T1218.005.yaml | ||
| author: mell0wx | ||
| logsource: | ||
| product: windows | ||
| category: process_creation | ||
| detection: | ||
| selection_parent: | ||
| ParentImage|endswith: '\mshta.exe' | ||
| selection_child: | ||
| Image|endswith: | ||
| - '\cmd.exe' | ||
| - '\powershell.exe' | ||
| - '\pwsh.exe' | ||
| - '\wscript.exe' | ||
| - '\cscript.exe' | ||
| condition: selection_parent and selection_child | ||
| falsepositives: | ||
| - Legacy enterprise HTML applications that intentionally launch approved child processes | ||
| - Controlled security validation activity | ||
| date: 2026-08-03 | ||
| level: high | ||
| tags: | ||
| - attack.t1218.005 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| title: Suspicious PowerShell Web Download to File | ||
| id: 4f18fcd8-8a39-4aa2-a71d-9b247d3ea475 | ||
| status: test | ||
| description: Detects PowerShell web retrieval that writes remote content to a local file. | ||
| references: | ||
| - https://attack.mitre.org/techniques/T1105/ | ||
| - https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1105/T1105.yaml | ||
| author: mell0wx | ||
| logsource: | ||
| product: windows | ||
| category: ps_script | ||
| detection: | ||
| selection_webclient: | ||
| ScriptBlockText|contains|all: | ||
| - 'WebClient' | ||
| - 'DownloadFile' | ||
| selection_iwr: | ||
| ScriptBlockText|contains|all: | ||
| - 'Invoke-WebRequest' | ||
| - 'OutFile' | ||
| condition: selection_webclient or selection_iwr | ||
| falsepositives: | ||
| - Approved software deployment or administration scripts that retrieve files | ||
| - Controlled security validation activity | ||
| date: 2026-08-03 | ||
| level: medium | ||
| tags: | ||
| - attack.t1105 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| { | ||
| "scenario_id": "PT-2026-014", | ||
| "validation_run_id": "VAL-2026-014", | ||
| "technique_id": "T1105", | ||
| "atomic_test_guid": "42dc4460-9aa6-45d3-b1a6-3955d34e1fe8", | ||
| "atomic_test_name": "Windows - PowerShell Download", | ||
| "validation_date_utc": "2026-08-03", | ||
| "target": "approved-windows-victim", | ||
| "rollback_snapshot": "private-rollback-reference", | ||
| "scope": { | ||
| "loopback_only": true, | ||
| "benign_content": true, | ||
| "external_c2": false, | ||
| "credential_access": false, | ||
| "lateral_movement": false | ||
| }, | ||
| "preflight": { | ||
| "authorization_confirmed": true, | ||
| "rollback_ready": true, | ||
| "endpoint_sensors_healthy": true, | ||
| "siem_current": true, | ||
| "domain_healthy": true, | ||
| "opencti_semantic_enrichment_healthy": true | ||
| }, | ||
| "original": { | ||
| "start_time_utc": "2026-08-03T14:34:32.288656Z", | ||
| "end_time_utc": "2026-08-03T14:34:43.741449Z", | ||
| "source_destination_hash_equal": true, | ||
| "detection_times_utc": [ | ||
| "2026-08-03T14:34:40Z" | ||
| ], | ||
| "detection_fired": true, | ||
| "detection_latency_seconds": 7.711344 | ||
| }, | ||
| "variant": { | ||
| "start_time_utc": "2026-08-03T14:35:39.497829Z", | ||
| "end_time_utc": "2026-08-03T14:35:44.348059Z", | ||
| "source_destination_hash_equal": true, | ||
| "detection_times_utc": [ | ||
| "2026-08-03T14:35:40Z" | ||
| ], | ||
| "detection_fired": true, | ||
| "detection_latency_seconds": 0.502171 | ||
| }, | ||
| "negatives": [ | ||
| { | ||
| "name": "WebClient object construction without transfer", | ||
| "detection_fired": false | ||
| }, | ||
| { | ||
| "name": "bounded Invoke-WebRequest HEAD request without OutFile", | ||
| "detection_fired": false | ||
| }, | ||
| { | ||
| "name": "Invoke-WebRequest command discovery", | ||
| "detection_fired": false | ||
| } | ||
| ], | ||
| "cleanup": { | ||
| "exec_output": "PowerShell transfer artifacts, loopback server jobs, and completion records removed; first and idempotent second cleanup passes verified zero residue.", | ||
| "first_pass_clean": true, | ||
| "second_pass_clean": true, | ||
| "remaining": [] | ||
| }, | ||
| "status": "validated" | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If
Stop-Processfails—for example because a recorded process cannot be terminated—the error is suppressed andCleanis still calculated solely from files remaining beneath$root. Once the directory is removed, this reportsClean=trueeven while the process remains alive, undermining the scenario's required cleanup confirmation; re-query the recorded process IDs and include them in the clean predicate.Useful? React with 👍 / 👎.