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

我自己在看《寒江独钓》这本书的时候,书中除了给出了利用过滤的方式来拦截键盘数据之外,也提到了另外一种方法,就是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 %02x\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;
}
posted @ 2017-10-24 20:55  masimaro  阅读(488)  评论(0编辑  收藏  举报