65.9K
CodeProject 正在变化。 阅读更多。
Home

内核键盘钩子

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (22投票s)

2011年5月11日

CPOL

2分钟阅读

viewsIcon

81715

downloadIcon

6192

一篇关于如何在内核中实现键盘钩子的文章

KeyboardHookingInKernel/screen_shot.GIF

引言

请想象一下,键盘没有任何输入信号。这可以用来保护计算机免受任何人的侵害。例如,当你离开计算机时,你可以通过这种方式锁定键盘。返回计算机后,你可以输入特定的按键来解锁计算机。这样,不知道你秘密密码的人就无法访问你的计算机。这可以通过键盘钩子来实现。

什么是键盘钩子?

我们也可以称之为键盘过滤。它可以简单地解释为:用户按下按键,键盘设备驱动程序将信号发送到用户应用程序。而我们则拦截这一信号流。

键盘钩子可以通过多种方式实现。在用户态和内核态都可以。
你可以从 Adam Roderick J 的文章 中找到用户态的示例。
他展示了用户态的键盘钩子技术,还有一些文章也解释了键盘钩子。但是使用这种技术,多个应用程序无法多次钩子,并且很容易被卸载。因此,我将介绍另一种在内核态钩子键盘的方法。本文重点介绍了驱动程序钩子。使用这种技术,你不仅可以阻止按键信号,还可以修改来自用户的信号,从而使其成为病毒或恶意软件。但我不想这样做。那么如何实现呢?让我们一起看看。

如何使用

Application.exeHK_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() 函数中更改按键信息。

所以剩下的代码就很简单了。

结论

你如何找到我的文章的?我希望这段代码能帮助你的开发。请为我投票。

© . All rights reserved.