TestHarnesses/T1218.005_Mshta/InvokeHTMLApplication.Tests.ps1
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-ATHHTMLApplication' { BeforeAll { $Help = Get-Help -Name Invoke-ATHHTMLApplication -Full $ExpectedTechniqueID = $null if ($Help.Synopsis.Split("`r`n")[-1] -match '^(?-i:Technique ID: )(?<TechniqueID>\S+) (?<TechniqueDescription>\(.+\))$') { $ExpectedTechniqueID = $Matches['TechniqueID'] } $HTAFileName = 'test.hta' $FixedTestGuid = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' } Context 'Validating error conditions' -Tag 'Unit', 'T1218.005' { It 'mshta.exe execution should not execute notepad.exe' { { Invoke-ATHHTMLApplication -HTAFilePath "$Env:windir\System32\notepad.exe" -ErrorAction Stop } | Should -Throw } It 'rundll32.exe execution should not execute notepad.exe' { { Invoke-ATHHTMLApplication -UseRundll32 -Rundll32FilePath "$Env:windir\System32\notepad.exe" -ErrorAction Stop } | Should -Throw } It 'should not run test when current working directory is the same as mshta.exe path' { Push-Location # Set current directory to the same directory as mshta.exe Set-Location -Path "$Env:windir\System32" { Invoke-ATHHTMLApplication -ErrorAction Stop } | Should -Throw Pop-Location } It 'should not run test when a non-existent HTA path is specified' { $BogusPath = 'C:\dsdfsiuhsdrfsawgfds' Test-Path -Path $BogusPath -PathType Container | Should -BeFalse { Invoke-ATHHTMLApplication -HTAFilePath $BogusPath -ErrorAction Stop } | Should -Throw } It 'should not run test when an HTA file is specified without an .hta extension when -SimulateUserDoubleClick is supplied' { { Invoke-ATHHTMLApplication -HTAFilePath Test.csv -SimulateUserDoubleClick -ErrorAction Stop } | Should -Throw } It 'should indicate that the HTA runner process failed to start' { Mock Invoke-CimMethod { return @{ ReturnValue = 1 } } { Invoke-ATHHTMLApplication -ErrorAction Stop } | Should -Throw $Result = Invoke-ATHHTMLApplication -ErrorAction SilentlyContinue Should -Invoke Invoke-CimMethod -Times 2 $Result.TechniqueID | Should -BeExactly $ExpectedTechniqueID $Result.TestSuccess | Should -BeNullOrEmpty $Result.TestGuid | Should -Not -BeNullOrEmpty $Result.ExecutionType | Should -Not -BeNullOrEmpty $Result.ScriptEngine | Should -Not -BeNullOrEmpty $Result.HTAFilePath | Should -Not -BeNullOrEmpty $Result.HTAFileHashSHA256 | Should -Not -BeNullOrEmpty $Result.RunnerFilePath | Should -Not -BeNullOrEmpty $Result.RunnerProcessId | Should -BeNullOrEmpty $Result.RunnerCommandLine | Should -BeNullOrEmpty $Result.RunnerChildProcessId | Should -BeNullOrEmpty $Result.RunnerChildProcessCommandLine | Should -BeNullOrEmpty } It 'should indicate that the HTA child process failed to launch' { Mock Wait-Event { return $null } { Invoke-ATHHTMLApplication -ErrorAction Stop } | Should -Throw $Result = Invoke-ATHHTMLApplication -ErrorAction SilentlyContinue Should -Invoke Wait-Event -Times 2 $Result.TechniqueID | Should -BeExactly $ExpectedTechniqueID $Result.TestSuccess | Should -BeNullOrEmpty $Result.TestGuid | Should -Not -BeNullOrEmpty $Result.ExecutionType | Should -Not -BeNullOrEmpty $Result.ScriptEngine | Should -Not -BeNullOrEmpty $Result.HTAFilePath | Should -Not -BeNullOrEmpty $Result.HTAFileHashSHA256 | Should -Not -BeNullOrEmpty $Result.RunnerFilePath | Should -Not -BeNullOrEmpty $Result.RunnerProcessId | Should -BeNullOrEmpty $Result.RunnerCommandLine | Should -BeNullOrEmpty $Result.RunnerChildProcessId | Should -BeNullOrEmpty $Result.RunnerChildProcessCommandLine | Should -BeNullOrEmpty } It 'should not accept VBScript.Encode as a script engine when the "about" protocol handler is used' { { Invoke-ATHHTMLApplication -InlineProtocolHandler About -ScriptEngine VBScript.Encode -ErrorAction Stop } | Should -Throw } It 'should not accept JScript.Encode as a script engine when the "about" protocol handler is used' { { Invoke-ATHHTMLApplication -InlineProtocolHandler About -ScriptEngine JScript.Encode -ErrorAction Stop } | Should -Throw } } Context 'Expected artifacts and behaviors when exercising the attack technique' -Tag 'Technique', 'T1218.005' { It 'should accept and execute manually-suppled WSH wscript code' { $ScriptCode = @' var objShell = new ActiveXObject('Wscript.Shell'); objShell.Run("powershell.exe -nop -Command Start-Sleep -Seconds 2; exit", 0, true); window.close(); '@ # Hash (after being inserted into an HTA block): B6ABDFAED84EAFC826E1CF3637C3DBB0D33AB8611B9D4C32D6CEA602F091AED6 $Result = Invoke-ATHHTMLApplication -ScriptContent $ScriptCode -ScriptEngine JScript -HTAFilePath $HTAFileName -ErrorAction Stop $Result | Should -Not -BeNullOrEmpty $Result.TechniqueID | Should -BeExactly $ExpectedTechniqueID $Result.TestSuccess | Should -BeNullOrEmpty $Result.TestGuid | Should -BeNullOrEmpty $Result.ExecutionType | Should -BeExactly 'File' $Result.ScriptEngine | Should -BeExactly 'JScript' $Result.HTAFilePath.EndsWith($HTAFileName) | Should -BeTrue $Result.HTAFileHashSHA256 | Should -BeExactly 'B6ABDFAED84EAFC826E1CF3637C3DBB0D33AB8611B9D4C32D6CEA602F091AED6' $Result.RunnerFilePath | Should -Not -BeNullOrEmpty $Result.RunnerProcessId | Should -Not -BeNullOrEmpty $Result.RunnerCommandLine | Should -Not -BeNullOrEmpty $Result.RunnerChildProcessId | Should -BeNullOrEmpty $Result.RunnerChildProcessCommandLine | Should -BeNullOrEmpty $Result } It 'should append HTA content to cmd.exe and execute it' { $Result = Invoke-ATHHTMLApplication -TemplatePE -TestGuid $FixedTestGuid $Result | Should -Not -BeNullOrEmpty $Result.TechniqueID | Should -BeExactly $ExpectedTechniqueID $Result.TestSuccess | Should -BeTrue $Result.TestGuid | Should -BeExactly $FixedTestGuid $Result.ExecutionType | Should -BeExactly 'File' $Result.ScriptEngine | Should -BeExactly 'JScript' $Result.HTAFilePath.EndsWith('Test.hta') | Should -BeTrue $Result.HTAFileHashSHA256 | Should -Not -BeNullOrEmpty $Result.RunnerFilePath | Should -Not -BeNullOrEmpty $Result.RunnerProcessId | Should -Not -BeNullOrEmpty $Result.RunnerCommandLine | Should -Not -BeNullOrEmpty $Result.RunnerChildProcessId | Should -Not -BeNullOrEmpty $Result.RunnerChildProcessCommandLine | Should -Match $FixedTestGuid $FileInfo = Get-Item -Path $Result.HTAFilePath $FileInfo.VersionInfo.OriginalFilename | Should -BeExactly 'Cmd.Exe' $Result } It 'should simulate a user double-click (ScriptEngine: <ScriptEngine>)' { $Result = Invoke-ATHHTMLApplication -ScriptEngine $ScriptEngine -HTAFilePath $HTAFileName -SimulateUserDoubleClick -TestGuid $FixedTestGuid $Result | Should -Not -BeNullOrEmpty $Result.TechniqueID | Should -BeExactly $ExpectedTechniqueID $Result.TestSuccess | Should -BeTrue $Result.TestGuid | Should -BeExactly $FixedTestGuid $Result.ExecutionType | Should -BeExactly 'File' $Result.ScriptEngine | Should -BeExactly $ScriptEngine $Result.HTAFilePath.EndsWith($HTAFileName) | Should -BeTrue $Result.HTAFileHashSHA256 | Should -BeExactly $HTAHash $Result.RunnerFilePath | Should -Not -BeNullOrEmpty $Result.RunnerProcessId | Should -Not -BeNullOrEmpty $Result.RunnerCommandLine | Should -Match '{1E460BD7-F1C3-4B2E-88BF-4E770A288AF5}{1E460BD7-F1C3-4B2E-88BF-4E770A288AF5}' # Related to execution via explorer.exe $Result.RunnerChildProcessId | Should -Not -BeNullOrEmpty $Result.RunnerChildProcessCommandLine | Should -Match $FixedTestGuid $Result } -TestCases @( @{ScriptEngine = 'JScript'; HTAHash = 'F72DE17D7D8C3D357C5885B2CC7B03A94F5638BDF58A4D0C1E991D0D8254D961'}, @{ScriptEngine = 'JScript.Compact'; HTAHash = 'D400A93266771A377BCF9335F370CA29FF7D0A52FBFB90FBDA40415E7E8C7114'}, @{ScriptEngine = 'JScript.Encode'; HTAHash = '6C5AC991FF41F4B1A030829A57C82C3B048DAEB96609C848D95FE5B8CF501167'}, @{ScriptEngine = 'VBScript'; HTAHash = '6612A56932F2742852D70F151A0271C802CC56F7E2FC7B68018E3FAC264AF974'}, @{ScriptEngine = 'VBScript.Encode'; HTAHash = '5C437FAEF659BE0B9BFE6AEDE079CA4F85A16C5CF3B02AE06C235603A93C5C88'} ) It 'should permit executing from UNC paths (ScriptEngine: <ScriptEngine>)' { $Result = Invoke-ATHHTMLApplication -ScriptEngine $ScriptEngine -HTAFilePath $HTAFileName -AsLocalUNCPath -TestGuid $FixedTestGuid $Result | Should -Not -BeNullOrEmpty $Result.TechniqueID | Should -BeExactly $ExpectedTechniqueID $Result.TestSuccess | Should -BeTrue $Result.TestGuid | Should -BeExactly $FixedTestGuid $Result.ExecutionType | Should -BeExactly 'File' $Result.ScriptEngine | Should -BeExactly $ScriptEngine $Result.HTAFilePath.EndsWith($HTAFileName) | Should -BeTrue $Result.HTAFileHashSHA256 | Should -BeExactly $HTAHash $Result.RunnerFilePath | Should -Not -BeNullOrEmpty $Result.RunnerProcessId | Should -Not -BeNullOrEmpty $Result.RunnerCommandLine | Should -Match '"\\\\' # Path to HTA file starts with a UNC path $Result.RunnerChildProcessId | Should -Not -BeNullOrEmpty $Result.RunnerChildProcessCommandLine | Should -Match $FixedTestGuid $Result } -TestCases @( @{ScriptEngine = 'JScript'; HTAHash = 'F72DE17D7D8C3D357C5885B2CC7B03A94F5638BDF58A4D0C1E991D0D8254D961'}, @{ScriptEngine = 'JScript.Compact'; HTAHash = 'D400A93266771A377BCF9335F370CA29FF7D0A52FBFB90FBDA40415E7E8C7114'}, @{ScriptEngine = 'JScript.Encode'; HTAHash = '6C5AC991FF41F4B1A030829A57C82C3B048DAEB96609C848D95FE5B8CF501167'}, @{ScriptEngine = 'VBScript'; HTAHash = '6612A56932F2742852D70F151A0271C802CC56F7E2FC7B68018E3FAC264AF974'}, @{ScriptEngine = 'VBScript.Encode'; HTAHash = '5C437FAEF659BE0B9BFE6AEDE079CA4F85A16C5CF3B02AE06C235603A93C5C88'} ) It 'should simulate lateral movement (ScriptEngine: <ScriptEngine>)' { $Result = Invoke-ATHHTMLApplication -ScriptEngine $ScriptEngine -HTAFilePath $HTAFileName -SimulateLateralMovement -TestGuid $FixedTestGuid $Result | Should -Not -BeNullOrEmpty $Result.TechniqueID | Should -BeExactly $ExpectedTechniqueID $Result.TestSuccess | Should -BeTrue $Result.TestGuid | Should -BeExactly $FixedTestGuid $Result.ExecutionType | Should -BeExactly 'File' $Result.ScriptEngine | Should -BeExactly $ScriptEngine $Result.HTAFilePath.EndsWith($HTAFileName) | Should -BeTrue $Result.HTAFileHashSHA256 | Should -BeExactly $HTAHash $Result.RunnerFilePath | Should -Not -BeNullOrEmpty $Result.RunnerProcessId | Should -Not -BeNullOrEmpty $Result.RunnerCommandLine.EndsWith('-Embedding') | Should -BeTrue # Implies usage of the COM host process $Result.RunnerChildProcessId | Should -Not -BeNullOrEmpty $Result.RunnerChildProcessCommandLine | Should -Match $FixedTestGuid $Result } -TestCases @( @{ScriptEngine = 'JScript'; HTAHash = 'F72DE17D7D8C3D357C5885B2CC7B03A94F5638BDF58A4D0C1E991D0D8254D961'}, @{ScriptEngine = 'JScript.Compact'; HTAHash = 'D400A93266771A377BCF9335F370CA29FF7D0A52FBFB90FBDA40415E7E8C7114'}, @{ScriptEngine = 'JScript.Encode'; HTAHash = '6C5AC991FF41F4B1A030829A57C82C3B048DAEB96609C848D95FE5B8CF501167'}, @{ScriptEngine = 'VBScript'; HTAHash = '6612A56932F2742852D70F151A0271C802CC56F7E2FC7B68018E3FAC264AF974'}, @{ScriptEngine = 'VBScript.Encode'; HTAHash = '5C437FAEF659BE0B9BFE6AEDE079CA4F85A16C5CF3B02AE06C235603A93C5C88'} ) It 'should simulate inline rundll32.exe execution (InlineProtocolHandler: <InlineProtocolHandler>, ScriptEngine: <ScriptEngine>)' { $ScriptEngineArg = @{} if ($ScriptEngine) { $ScriptEngineArg['ScriptEngine'] = $ScriptEngine } else { if ($InlineProtocolHandler -eq 'JavaScript') { $ScriptEngine = 'JScript' } else { $ScriptEngine = $InlineProtocolHandler } } $Result = Invoke-ATHHTMLApplication -UseRundll32 -InlineProtocolHandler $InlineProtocolHandler -TestGuid $FixedTestGuid @ScriptEngineArg $Result | Should -Not -BeNullOrEmpty $Result.TechniqueID | Should -BeExactly $ExpectedTechniqueID $Result.TestSuccess | Should -BeTrue $Result.TestGuid | Should -BeExactly $FixedTestGuid $Result.ExecutionType | Should -BeExactly 'InlineRundll32' $Result.ScriptEngine | Should -BeExactly $ScriptEngine $Result.HTAFilePath | Should -BeNullOrEmpty $Result.HTAFileHashSHA256 | Should -BeNullOrEmpty $Result.RunnerFilePath.EndsWith('rundll32.exe') | Should -BeTrue $Result.RunnerProcessId | Should -Not -BeNullOrEmpty $Result.RunnerCommandLine | Should -Match $FixedTestGuid $Result.RunnerChildProcessId | Should -Not -BeNullOrEmpty $Result.RunnerChildProcessCommandLine | Should -Match $FixedTestGuid $Result } -TestCases @( @{ InlineProtocolHandler = 'JavaScript' }, @{ InlineProtocolHandler = 'VBScript' }, @{ InlineProtocolHandler = 'About'; ScriptEngine = 'JScript' }, @{ InlineProtocolHandler = 'About'; ScriptEngine = 'JScript.Compact' }, @{ InlineProtocolHandler = 'About'; ScriptEngine = 'VBScript' } ) It 'should simulate inline mshta.exe execution (InlineProtocolHandler: <InlineProtocolHandler>, ScriptEngine: <ScriptEngine>)' { $ScriptEngineArg = @{} if ($ScriptEngine) { $ScriptEngineArg['ScriptEngine'] = $ScriptEngine } else { if ($InlineProtocolHandler -eq 'JavaScript') { $ScriptEngine = 'JScript' } else { $ScriptEngine = $InlineProtocolHandler } } $Result = Invoke-ATHHTMLApplication -InlineProtocolHandler $InlineProtocolHandler -TestGuid $FixedTestGuid @ScriptEngineArg $Result | Should -Not -BeNullOrEmpty $Result.TechniqueID | Should -BeExactly $ExpectedTechniqueID $Result.TestSuccess | Should -BeTrue $Result.TestGuid | Should -BeExactly $FixedTestGuid $Result.ExecutionType | Should -BeExactly 'InlineMshta' $Result.ScriptEngine | Should -BeExactly $ScriptEngine $Result.HTAFilePath | Should -BeNullOrEmpty $Result.HTAFileHashSHA256 | Should -BeNullOrEmpty $Result.RunnerFilePath.EndsWith('mshta.exe') | Should -BeTrue $Result.RunnerProcessId | Should -Not -BeNullOrEmpty $Result.RunnerCommandLine | Should -Match $FixedTestGuid $Result.RunnerChildProcessId | Should -Not -BeNullOrEmpty $Result.RunnerChildProcessCommandLine | Should -Match $FixedTestGuid $Result } -TestCases @( @{ InlineProtocolHandler = 'JavaScript' }, @{ InlineProtocolHandler = 'VBScript' }, @{ InlineProtocolHandler = 'About'; ScriptEngine = 'JScript' }, @{ InlineProtocolHandler = 'About'; ScriptEngine = 'JScript.Compact' }, @{ InlineProtocolHandler = 'About'; ScriptEngine = 'VBScript' } ) } } |