内核键盘钩子






4.78/5 (22投票s)
一篇关于如何在内核中实现键盘钩子的文章
引言
请想象一下,键盘没有任何输入信号。这可以用来保护计算机免受任何人的侵害。例如,当你离开计算机时,你可以通过这种方式锁定键盘。返回计算机后,你可以输入特定的按键来解锁计算机。这样,不知道你秘密密码的人就无法访问你的计算机。这可以通过键盘钩子来实现。
什么是键盘钩子?
我们也可以称之为键盘过滤。它可以简单地解释为:用户按下按键,键盘设备驱动程序将信号发送到用户应用程序。而我们则拦截这一信号流。
键盘钩子可以通过多种方式实现。在用户态和内核态都可以。
你可以从 Adam Roderick J 的文章 中找到用户态的示例。
他展示了用户态的键盘钩子技术,还有一些文章也解释了键盘钩子。但是使用这种技术,多个应用程序无法多次钩子,并且很容易被卸载。因此,我将介绍另一种在内核态钩子键盘的方法。本文重点介绍了驱动程序钩子。使用这种技术,你不仅可以阻止按键信号,还可以修改来自用户的信号,从而使其成为病毒或恶意软件。但我不想这样做。那么如何实现呢?让我们一起看看。
如何使用
将 Application.exe 和 HK_KBD.sys 放在同一个文件夹中,并执行 Application.exe。然后按下任意键。你会发现没有来自按键的输入。好了!就是这样!
Using the Code
首先,我们需要找到键盘设备对象的句柄。为此,你需要列出计算机上的所有设备,并从中找到键盘类设备。
你可以通过以下方式实现
NTSTATUS
KeyFlt_CreateClose(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
...
switch (stack->MajorFunction)
{
case IRP_MJ_CREATE:
{
...
RtlInitUnicodeString(&uniOa, L"\\Device");
InitializeObjectAttributes(
&oa,
&uniOa,
OBJ_CASE_INSENSITIVE,
NULL,
NULL
);
status = ZwOpenDirectoryObject(
&hDir,
DIRECTORY_ALL_ACCESS,
&oa
);
if(!NT_SUCCESS(status))
{
break;
}
pBuffer = ExAllocatePoolWithTag
(PagedPool, ALLOC_SIZE, Tag);
pContext = ExAllocatePoolWithTag
(PagedPool, ALLOC_SIZE, Tag);
memset(pBuffer, 0, ALLOC_SIZE);
memset(pContext, 0, ALLOC_SIZE);
memset(arKbdCls, 0, 0x10);
counter = 0;
g_kbdclsnum = 0;
while(TRUE)
{
status = ZwQueryDirectoryObject(
hDir,
pBuffer,
ALLOC_SIZE,
TRUE,
FALSE,
pContext,
&RetLen
);
if(!NT_SUCCESS(status))
{
break;
}
pDirBasicInfo =
(PDIRECTORY_BASIC_INFORMATION)pBuffer;
pDirBasicInfo->ObjectName.Length -= 2;
RtlInitUnicodeString(&uniKbdDrv,
L"KeyboardClass");
if(RtlCompareUnicodeString(
&pDirBasicInfo->ObjectName,
&uniKbdDrv,
FALSE
) == 0)
{
KbdclsNum = (char*) ((ULONG)
(pDirBasicInfo->
ObjectName.Length) +
(ULONG) (pDirBasicInfo->
ObjectName.Buffer));
arKbdCls[counter] = *KbdclsNum;
counter++;
}
pDirBasicInfo->ObjectName.Length += 2;
}
ExFreePool(pBuffer);
ExFreePool(pContext);
ZwClose(hDir);
for(i = 0; i < 0x10; i++)
{
if(arKbdCls[i] == 0)
break;
else if(arKbdCls[i] == 0x30)
g_kbdclsnum |= KBDCLASS_0;
else if(arKbdCls[i] == 0x31)
g_kbdclsnum |= KBDCLASS_1;
else if(arKbdCls[i] == 0x32)
g_kbdclsnum |= KBDCLASS_2;
}
if(g_kbdclsnum & KBDCLASS_0)
{
RtlInitUnicodeString(
&uniKbdDeviceName,
L"\\Device\\KeyboardClass0"
);
}
else
{
if(g_kbdclsnum & KBDCLASS_2)
{
RtlInitUnicodeString(
&uniKbdDeviceName,
L"\\Device\\KeyboardClass2"
);
}
else if(g_kbdclsnum & KBDCLASS_1)
{
RtlInitUnicodeString(
&uniKbdDeviceName,
L"\\Device\\KeyboardClass1"
);
}
else
{
g_nop = 1;
break;
}
}
status = KeyFlt_IoGetDeviceObjectPointer(
&uniKbdDeviceName,
0,
&KbdFileObject,
&KbdDeviceObject
);
...
}
break;
}
...
}
现在我们找到了指针。然后,我们需要附加到键盘设备对象。这里使用 IoAttachDeviceToDeviceStack()
函数来过滤键盘设备。
但不幸的是,如果该函数失败,那么我们需要替换键盘驱动程序的 Major 函数。
if(NT_SUCCESS(status))
{
g_KbdDeviceObject = KbdDeviceObject;
ObDereferenceObject(KbdFileObject);
KdPrint(("KeyFlt: Attach Device. \n"));
__try
{
g_TopOfStack = IoAttachDeviceToDeviceStack
(DeviceObject, KbdDeviceObject);
if (g_TopOfStack != NULL)
{
g_OldFunction = g_KbdDeviceObject->
DriverObject->MajorFunction[IRP_MJ_READ];
g_KbdDeviceObject->DriverObject->
MajorFunction[IRP_MJ_READ] = KeyFlt_HookProc;
}
else
{
g_nop = 1;
g_TopOfStack = NULL;
break;
}
}
__except(1)
{
g_nop = 1;
break;
}
}
else
{
g_nop = 1;
break;
}
...
NTSTATUS KeyFlt_IoGetDeviceObjectPointer(
IN PUNICODE_STRING ObjectName,
IN ACCESS_MASK DesiredAccess,
OUT PFILE_OBJECT *FileObject,
OUT PDEVICE_OBJECT *DeviceObject
)
{
NTSTATUS status;
OBJECT_ATTRIBUTES oa;
IO_STATUS_BLOCK iostatus;
PVOID ptr;
InitializeObjectAttributes(
&oa,
ObjectName,
OBJ_KERNEL_HANDLE,
NULL,
NULL
);
status = ZwOpenFile(
&ObjectName,
DesiredAccess,
&oa,
&iostatus,
0x07,
0x40
);
if(!NT_SUCCESS(status))
{
return status;
}
status = ObReferenceObjectByHandle(
ObjectName,
DesiredAccess,
0,
KernelMode,
&ptr,
0
);
if(!NT_SUCCESS(status))
{
ZwClose(ObjectName);
return status;
}
*FileObject = ptr;
*DeviceObject = IoGetRelatedDeviceObject(ptr);
ZwClose(ObjectName);
return status;
}
你可以在 KeyFlt_DispatchPassThrough()
函数和 KeyFlt_DispatchRead()
函数中更改按键信息。
所以剩下的代码就很简单了。
结论
你如何找到我的文章的?我希望这段代码能帮助你的开发。请为我投票。