用DeviceTree 可以看到PS/2键盘的端口驱动是i8042prt,USB键盘的端口驱动是Kbdhid。无论是PS/2 键盘还是USB键盘,在端口驱动处理完IRP之后都会调用上层处理的回调函数,即KbdClass 处理输入数据的函数。Hook 这个回调函数,不但可以实现兼容PS/2 键盘和USB 键盘的Logger,而且比分层驱动的方法更加隐蔽。
Kbdclass的这个回调函数是未导出的,不过在DDK的代码中,我们可以找到这个函数,即kbdclass.c中的KeyboardClassServiceCallback函数。在内核中寻找这个函数可以用特征码搜索,不过楚狂人提出一种改进的方法。我们用这个方法来实现。
根据前人的指示(以PS/2 键盘为例):
(1)KeyboardClassServiceCallback 函数指针应该保存在i8042prt 生成的设备的自定义扩展中。
(2)KeyboardClassServiceCallback的开始地址应该在内核模块KbdClass中。
(3)内核模块KbcClass生成的一个设备对象的指针也保存在端口驱动的设备扩展中,并且在KeyboardClassServiceCallback的指针之前。
根据这三点,我们可以打开端口驱动的驱动对象,得到其设备扩展。然后遍历KdbClass下的所有设备,查找其中一个被保存在端口设备扩展中的设备对象指针,然后在从这个指针开始寻找回调函数。
实现代码如下:
NTSTATUS SearchOldFunctionAddress(IN PDRIVER_OBJECT DriverObject)
{
//定义一些局部变量
NTSTATUS status = STATUS_UNSUCCESSFUL;
int i = 0;
UNICODE_STRING uniNtNameString;
PDEVICE_OBJECT pTargetDeviceObject = NULL; //目标设备
PDRIVER_OBJECT KbdDriverObject = NULL; //类驱动
PDRIVER_OBJECT KbdhidDriverObject = NULL; //USB 端口驱动
PDRIVER_OBJECT Kbd8042DriverObject = NULL; //PS/2 端口驱动
PDRIVER_OBJECT UsingDriverObject = NULL;
PDEVICE_OBJECT UsingDeviceObject = NULL;
PVOID KbdDriverStart = NULL; //类驱动起始地址
ULONG KbdDriverSize = 0;
PVOID UsingDeviceExt = NULL;
//这部分代码打开PS/2键盘的驱动对象
RtlInitUnicodeString(&uniNtNameString,PS2KBD_DRIVER_NAME);
status = ObReferenceObjectByName(
&uniNtNameString,
OBJ_CASE_INSENSITIVE,
NULL,
0,
IoDriverObjectType,
KernelMode,
NULL,
&Kbd8042DriverObject
);
if (!NT_SUCCESS(status))
{
DbgPrint("Couldn't get the PS/2 driver Object/n");
}
else
{
//解除引用
ObDereferenceObject(Kbd8042DriverObject);
DbgPrint("Got the PS/2 driver Object/n");
}
//打开USB 键盘的端口驱动
RtlInitUnicodeString(&uniNtNameString,USBKBD_DRIVER_NAME);
status = ObReferenceObjectByName(
&uniNtNameString,
OBJ_CASE_INSENSITIVE,
NULL,
0,
IoDriverObjectType,
KernelMode,
NULL,
&KbdhidDriverObject
);
if (!NT_SUCCESS(status))
{
DbgPrint("Couldn't get the USB driver Object/n");
}
else
{
ObDereferenceObject(KbdhidDriverObject);
DbgPrint("Got the USB driver Object/n");
}
//如果同时有两个键盘 返回失败
if (Kbd8042DriverObject && KbdhidDriverObject)
{
DbgPrint("More than one keyboard!/n");
return STATUS_UNSUCCESSFUL;
}
//两种键盘都没有 也返回失败
if (!Kbd8042DriverObject && KbdhidDriverObject)
{
DbgPrint("Not found keyboard!/n");
return STATUS_UNSUCCESSFUL;
}
//找到合适的驱动对象
UsingDriverObject = Kbd8042DriverObject ? Kbd8042DriverObject : KbdhidDriverObject;
//找到该驱动对象下的第一个设备对象
UsingDeviceObject = UsingDriverObject->DeviceObject;
//找到该设备的设备扩展
UsingDeviceExt = UsingDeviceObject->DeviceExtension;
//搜索过程,遍历KdbClass 下面的所有设备,这些设备中有一个被保存在端口驱动
//的设备扩展,然后在设备扩展中找回调函数
RtlInitUnicodeString(&uniNtNameString, KBD_DRIVER_NAME);
status = ObReferenceObjectByName (
&uniNtNameString,
OBJ_CASE_INSENSITIVE,
NULL,
0,
IoDriverObjectType,
KernelMode,
NULL,
&KbdDriverObject
);
// 如果失败了就直接返回
if(!NT_SUCCESS(status))
{
DbgPrint("Couldn't get the MyTest Device Object/n");
return STATUS_UNSUCCESSFUL;
}
else
{
// 这个打开需要解应用。
ObDereferenceObject(DriverObject);
}
//遍历KbdDriverObject 下的设备对象
pTargetDeviceObject = KbdDriverObject->DeviceObject;
while (pTargetDeviceObject)
{
PBYTE DeviceExt;
DeviceExt = (PBYTE)UsingDeviceObject;
//遍历我们先找到的端口驱动的设备扩展下的每个指针
for (;i<4096;i++,DeviceExt += sizeof(PBYTE))
{
PVOID tmp;
if (!MmIsAddressValid(DeviceExt))
{
break;
}
//找到后填写到下面的全局变量中,如果已经找到 直接退出
if (gKbdCallBack.classDeviceObject && gKbdCallBack.serviceCallBack)
{
status = STATUS_SUCCESS;
break;
}
//在端口驱动的设备扩展中,找到了类驱动的设备对象,填好类驱动设备对象后继续
tmp = *(PVOID*)DeviceExt;
if (tmp == pTargetDeviceObject)
{
gKbdCallBack.classDeviceObject = (PDEVICE_OBJECT)tmp;
DbgPrint("classDeviceObject %8x/n",tmp);
continue;
}
//如果在设备扩展中找到一个地址位于KbdClass 模块中,就认为这是我们要的回调函数地址
KbdDriverStart = (ULONG)GetModlueBaseAdress( "kbdclass.sys",0 );
DbgPrint("kbdclass.sys: 0x%08lx/n", (PVOID)KbdDriverStart);
KbdDriverSize = 0x2000; //为了搜索快点,Kbdclass.sys的模块大小实际上不是这么小
if ((tmp > KbdDriverStart)&&(tmp < (PBYTE)KbdDriverStart+KbdDriverSize)&&MmIsAddressValid(tmp))
{
//记录回调函数的地址
gKbdCallBack.serviceCallBack = (KeyboardClassServiceCallback)tmp;
//PVOID AddrServiceCallBack = (PVOID *)DeviceExt;
DbgPrint("serviceCallBack :%8x/n",tmp);
}
}
//换下一个设备,继续遍历
pTargetDeviceObject = pTargetDeviceObject->NextDevice;
}
//如果成功找到 可以返回了
return status;
}
找到函数地址后,inline hook之即可。