Skip to content

Commit

Permalink
Adding Invoke-ATHInjectedThread
Browse files Browse the repository at this point in the history
  • Loading branch information
mgraeber-rc committed May 24, 2021
1 parent 4fd52ec commit 882a2b2
Show file tree
Hide file tree
Showing 4 changed files with 933 additions and 9 deletions.
10 changes: 8 additions & 2 deletions AtomicTestHarnesses.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
RootModule = 'AtomicTestHarnesses.psm1'

# Version number of this module.
ModuleVersion = '1.4.0.0'
ModuleVersion = '1.5.0.0'

# ID used to uniquely identify this module
GUID = '195a1637-d4a4-4cb3-8d80-5b5d4e3e930a'
Expand All @@ -16,7 +16,7 @@ Author = 'Mike Haag, Jesse Brown, Matt Graeber'
CompanyName = 'Red Canary, Inc.'

# Copyright statement for this module
Copyright = '2020 Red Canary, Inc. All rights reserved.'
Copyright = '2021 Red Canary, Inc. All rights reserved.'

# Description of the functionality provided by this module
Description = 'A module to facilitate the testing of attack techniques and their corresponding procedures.'
Expand All @@ -27,6 +27,7 @@ PowerShellVersion = '5.0'
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = 'Invoke-ATHHTMLApplication',
'Invoke-ATHCompiledHelp',
'Invoke-ATHInjectedThread',
'Invoke-ATHMSBuild',
'Invoke-ATHRemoteFXvGPUDisablementCommand',
'Out-ATHPowerShellCommandLineParameter',
Expand All @@ -49,6 +50,11 @@ PrivateData = @{

# ReleaseNotes of this module
ReleaseNotes = @'
1.5.0
-----
Added:
* Invoke-ATHInjectedThread
1.4.0
-----
Added:
Expand Down
19 changes: 12 additions & 7 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ Rather than placing the `AtomicTestHarnesses` root directory in a module path, i
Import-Module C:\Users\Test\Desktop\AtomicTestHarnesses\AtomicTestHarnesses.psd1
```

The -SkipPublisherCheck and -Force option is typically required for machines that only have v3.4.0 of Pester installed. That particular version of the module was signed by Microsoft to be shipped in-box, which is not the case for later versions which are community maintained and signed with a different certificate. Attempting to install the module without that option may fail.

## Exploring Exposed Functionality

Upon the `AtomicTestHarnesses` module being loaded, to get a sense of what functionality is exposed, you can run the following command:
Expand Down Expand Up @@ -88,20 +90,23 @@ If you do not have Pester v5+, you will need to update it.
Install-Module -Name Pester -MinimumVersion 5.0.0 -Scope CurrentUser
```

The `-SkipPublisherCheck` and `-Force` option is typically required for machines that only have `v3.4.0` of Pester installed. That particular version of the module was signed by Microsoft to be shipped in-box, which is not the case for later versions which are community maintained and signed with a different certificate. Attempting to install the module without that option may fail.

## Implemented Test Tags

Specific groups of tests can be run rather than running all available tests. The following tags are exposed:

1. `Module` - Module-wide tests designed to ensure consistency across all exported fcuntions.
1. `Module` - Module-wide tests designed to ensure consistency across all exported functions.
2. `Unit` - Unit tests for exported functions
3. `Technique` - Tests that exercise specific attack technique functionality
4. `T1055` - [Process Injection](https://attack.mitre.org/techniques/T1055/)
5. `T1059.001` - [Command and Scripting Interpreter: PowerShell](https://attack.mitre.org/techniques/T1059/001/)
6. `T1127.001` - [Trusted Developer Utilities Proxy Execution: MSBuild](https://attack.mitre.org/techniques/T1127/001/)
7. `T1134.004` - [Access Token Manipulation: Parent PID Spoofing](https://attack.mitre.org/techniques/T1134/004/)
8. `T1218.001` - [Signed Binary Proxy Execution: Compiled HTML File](https://attack.mitre.org/techniques/T1218/001/)
9. `T1218` - [Signed Binary Proxy Execution](https://attack.mitre.org/techniques/T1218/)
10. `T1218.005` - [Signed Binary Proxy Execution: Mshta](https://attack.mitre.org/techniques/T1218/005/)
5. `T1055.002` - [Process Injection: Portable Executable Injection](https://attack.mitre.org/techniques/T1055/002/)
6. `T1059.001` - [Command and Scripting Interpreter: PowerShell](https://attack.mitre.org/techniques/T1059/001/)
7. `T1127.001` - [Trusted Developer Utilities Proxy Execution: MSBuild](https://attack.mitre.org/techniques/T1127/001/)
8. `T1134.004` - [Access Token Manipulation: Parent PID Spoofing](https://attack.mitre.org/techniques/T1134/004/)
9. `T1218.001` - [Signed Binary Proxy Execution: Compiled HTML File](https://attack.mitre.org/techniques/T1218/001/)
10. `T1218` - [Signed Binary Proxy Execution](https://attack.mitre.org/techniques/T1218/)
11. `T1218.005` - [Signed Binary Proxy Execution: Mshta](https://attack.mitre.org/techniques/T1218/005/)

## Running Tests

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
Set-StrictMode -Version Latest

$TestScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
$ModuleRoot = Resolve-Path "$TestScriptRoot\..\..\"
$ModuleManifest = "$ModuleRoot\AtomicTestHarnesses.psd1"

Remove-Module [A]tomicTestHarnesses
Import-Module $ModuleManifest -Force -ErrorAction Stop

Describe 'Invoke-ATHInjectedThread' {
BeforeAll {
$Help = Get-Help -Name Invoke-ATHInjectedThread -Full

$ExpectedTechniqueID = $null

if ($Help.Synopsis.Split("`r`n")[-1] -match '^(?-i:Technique ID: )(?<TechniqueID>\S+) (?<TechniqueDescription>\(.+\))$') {
$ExpectedTechniqueID = $Matches['TechniqueID']
}
}

Context 'Validating error conditions' -Tag 'Unit', 'T1055.002' {
It 'should execute custom position-independent code' -Tag 'Unit', 'T1055.002' {
$Result = Invoke-ATHInjectedThread -PositionIndependentCodeBytes @(0x90, 0x90, 0x90, 0xC3) # NOP, NOP, NOP, RET

$Result | Should -Not -BeNullOrEmpty

$Result.TechniqueID | Should -BeExactly $ExpectedTechniqueID
$Result.TestSuccess | Should -BeNullOrEmpty
$Result.TestGuid | Should -Not -BeNullOrEmpty
$Result.InjectedCodeBytes | Should -Not -BeNullOrEmpty
$Result.InjectedCodeHash | Should -BeExactly '97E3BFAD17932F638A894351239CA24CB76467E080C5B268307547D36366FE10'
$Result.SourceProcessId | Should -Be $PID
$Result.SourceExecutablePath | Should -Not -BeNullOrEmpty
$Result.SourceCommandLine | Should -Not -BeNullOrEmpty
$Result.TargetProcessId | Should -Not -BeNullOrEmpty
$Result.TargetExecutablePath | Should -Match 'notepad\.exe$'
$Result.TargetCommandLine | Should -BeExactly 'notepad.exe'
$Result.TargetProcessAccess | Should -BeExactly 'PROCESS_CREATE_THREAD, PROCESS_VM_OPERATION, PROCESS_VM_READ, PROCESS_VM_WRITE, PROCESS_QUERY_INFORMATION'
$Result.TargetProcessAccessValue | Should -Be 1082
$Result.TargetBaseAddressHex | Should -Match '^[0-9A-F]{16}$'
$Result.TargetAllocationPageProtect | Should -BeExactly 'PAGE_EXECUTE_READWRITE'
$Result.TargetAllocationPageProtectValue | Should -Be 64
$Result.TargetThreadId | Should -Not -BeNullOrEmpty
$Result.TargetChildProcessId | Should -BeNullOrEmpty
$Result.TargetChildProcessCommandLine | Should -BeNullOrEmpty
}

It 'should inject into itself (the current process)' -Tag 'Unit', 'T1055.002' {
$Result = Invoke-ATHInjectedThread -ProcessId $PID

$Result | Should -Not -BeNullOrEmpty

$Result.TechniqueID | Should -BeExactly $ExpectedTechniqueID
$Result.TestSuccess | Should -BeTrue
$Result.TestGuid | Should -Not -BeNullOrEmpty
$Result.InjectedCodeBytes | Should -Not -BeNullOrEmpty
$Result.InjectedCodeHash | Should -Not -BeNullOrEmpty
$Result.SourceProcessId | Should -Be $PID
$Result.SourceExecutablePath | Should -BeExactly $Result.TargetExecutablePath
$Result.SourceCommandLine | Should -BeExactly $Result.TargetCommandLine
$Result.TargetProcessId | Should -Be $PID
$Result.TargetExecutablePath | Should -Not -BeNullOrEmpty
$Result.TargetCommandLine | Should -Not -BeNullOrEmpty
$Result.TargetProcessAccess | Should -BeExactly 'PROCESS_CREATE_THREAD, PROCESS_VM_OPERATION, PROCESS_VM_READ, PROCESS_VM_WRITE, PROCESS_QUERY_INFORMATION'
$Result.TargetProcessAccessValue | Should -Be 1082
$Result.TargetBaseAddressHex | Should -Match '^[0-9A-F]{16}$'
$Result.TargetAllocationPageProtect | Should -BeExactly 'PAGE_EXECUTE_READWRITE'
$Result.TargetAllocationPageProtectValue | Should -Be 64
$Result.TargetThreadId | Should -Not -BeNullOrEmpty
$Result.TargetChildProcessId | Should -Not -BeNullOrEmpty
$Result.TargetChildProcessCommandLine | Should -Not -BeNullOrEmpty
}

It 'should not inject into a non-existant process ID' -Tag 'Unit', 'T1055.002' {
{ Invoke-ATHInjectedThread -ProcessId 1 -ErrorAction Stop } | Should -Throw
}

It 'should not accept an empty array of position-independent code' -Tag 'Unit', 'T1055.002' {
{ Invoke-ATHInjectedThread -PositionIndependentCodeBytes @() -ErrorAction Stop } | Should -Throw
}

It 'should fail to inject when the template notepad.exe target fails to launch' -Tag 'Unit', 'T1055.002' {
Mock Invoke-CimMethod { return @{ ReturnValue = 1 } }

{ Invoke-ATHInjectedThread -ErrorAction Stop } | Should -Throw
}

It 'should not have access to inject into the System process' -Tag 'Unit', 'T1055.002' {
{ Invoke-ATHInjectedThread -ProcessId 4 -ErrorAction Stop } | Should -Throw
}

It 'should not inject into a 32-bit process' -Tag 'Unit', 'T1055.002' {
$Wow64Notepad = Start-Process -FilePath $Env:windir\SysWOW64\notepad.exe -WindowStyle Hidden -PassThru

{ $Wow64Notepad | Invoke-ATHInjectedThread -ErrorAction Stop } | Should -Throw

$Wow64Notepad | Stop-Process -Force
}

It 'should indicate that the powershell.exe child process failed to launch' -Tag 'Unit', 'T1055.002' {
Mock Wait-Event { return $null }

{ Invoke-ATHInjectedThread -ErrorAction Stop } | Should -Throw
}
}

Context 'Expected artifacts and behaviors when exercising the attack technique' -Tag 'Technique', 'T1055.002' {
BeforeAll {
$Script:FixedTestGuid = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'

$Script:TargetNotepadProc = Start-Process -FilePath $Env:windir\System32\notepad.exe -WindowStyle Hidden -PassThru
}

It 'should inject into a process (IsRWXMemory: <IsRWXMemory>, MinimumProcessAccess: <MinimumProcessAccess>, InjectIntoSpecificProcess: <InjectIntoSpecificProcess>)' -Tag 'Technique', 'T1055.002' {
$Arguments = @{}

if ($IsRWXMemory) {
$ExpectedPageProtection = 'PAGE_EXECUTE_READWRITE'
$ExpectedPageProtectionValue = 64

$Arguments['MemoryProtectionType'] = 'ReadWriteExecute'
} else {
$ExpectedPageProtection = 'PAGE_EXECUTE_READ'
$ExpectedPageProtectionValue = 32

$Arguments['MemoryProtectionType'] = 'ReadExecute'
}

if ($MinimumProcessAccess) {
$ExpectedProcessAccess = 'PROCESS_CREATE_THREAD, PROCESS_VM_OPERATION, PROCESS_VM_READ, PROCESS_VM_WRITE, PROCESS_QUERY_INFORMATION'
$ExpectedProcessAccessValue = 1082

$Arguments['ProcessAccessType'] = 'MinimumAccess'
} else {
$ExpectedProcessAccess = 'PROCESS_ALL_ACCESS'
$ExpectedProcessAccessValue = 2097151

$Arguments['ProcessAccessType'] = 'AllAccess'
}

if ($InjectIntoSpecificProcess) {
$ExpectedProcessId = $TargetNotepadProc.Id

$Arguments['ProcessId'] = $TargetNotepadProc.Id
} else {
$ExpectedProcessId = $null
}

$Result = Invoke-ATHInjectedThread -TestGuid $FixedTestGuid @Arguments

$Result | Should -Not -BeNullOrEmpty

$Result.TechniqueID | Should -BeExactly $ExpectedTechniqueID
$Result.TestSuccess | Should -BeTrue
$Result.TestGuid | Should -BeExactly $FixedTestGuid
$Result.InjectedCodeBytes | Should -Not -BeNullOrEmpty
$Result.InjectedCodeHash | Should -Not -BeNullOrEmpty
$Result.SourceProcessId | Should -Be $PID
$Result.SourceExecutablePath | Should -Not -BeNullOrEmpty
$Result.SourceCommandLine | Should -Not -BeNullOrEmpty

if ($InjectIntoSpecificProcess) {
$Result.TargetProcessId | Should -Be $ExpectedProcessId
} else {
$Result.TargetProcessId | Should -Not -BeNullOrEmpty
}

$Result.TargetExecutablePath | Should -Match 'notepad\.exe'
$Result.TargetCommandLine | Should -Match 'notepad\.exe'
$Result.TargetProcessAccess | Should -BeExactly $ExpectedProcessAccess
$Result.TargetProcessAccessHex | Should -BeExactly $ExpectedProcessAccessHex
$Result.TargetBaseAddressHex | Should -Match '^[0-9A-F]{16}$'
$Result.TargetAllocationPageProtect | Should -BeExactly $ExpectedPageProtection
$Result.TargetAllocationPageProtectValue | Should -Be $ExpectedPageProtectionValue
$Result.TargetThreadId | Should -Not -BeNullOrEmpty
$Result.TargetChildProcessId | Should -Not -BeNullOrEmpty
$Result.TargetChildProcessCommandLine | Should -Match $FixedTestGuid
} -TestCases @(
@{ IsRWXMemory = $False; MinimumProcessAccess = $False; InjectIntoSpecificProcess = $False },
@{ IsRWXMemory = $True; MinimumProcessAccess = $False; InjectIntoSpecificProcess = $False },
@{ IsRWXMemory = $False; MinimumProcessAccess = $True; InjectIntoSpecificProcess = $False },
@{ IsRWXMemory = $True; MinimumProcessAccess = $True; InjectIntoSpecificProcess = $False },
@{ IsRWXMemory = $False; MinimumProcessAccess = $False; InjectIntoSpecificProcess = $True },
@{ IsRWXMemory = $True; MinimumProcessAccess = $False; InjectIntoSpecificProcess = $True },
@{ IsRWXMemory = $False; MinimumProcessAccess = $True; InjectIntoSpecificProcess = $True },
@{ IsRWXMemory = $True; MinimumProcessAccess = $True; InjectIntoSpecificProcess = $True }
)

AfterAll {
$Script:TargetNotepadProc | Stop-Process -Force
}
}
}
Loading

0 comments on commit 882a2b2

Please sign in to comment.