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 条评论
登录 后参与评论

相关文章

来自专栏JackieZheng

探秘Tomcat——启动篇

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

4977
来自专栏hotqin888的专栏

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

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

1831
来自专栏JadePeng的技术博客

Docker+Jenkins持续集成环境(5): android构建与apk发布

项目组除了常规的java项目,还有不少android项目,如何使用jenkins来实现自动构建呢?本文会介绍安卓项目通过jenkins构建的方法,并设计开发一个...

5228
来自专栏Flutter知识集

Flutter 实践 MVVM

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

3.2K3
来自专栏DOTNET

ASP.NET MVC编程——单元测试

1自动化测试基本概念 自动化测试分为:单元测试,集成测试,验收测试。 单元测试 检验被测单元的功能,被测单元一般为低级别的组件,如一个类或类方法。 单元测试要满...

5335
来自专栏haifeiWu与他朋友们的专栏

golang重构博客统计服务

作为一个后端开发,在docker,etcd,k8s等新技术不断涌现的今天,其背后的功臣golang在语言排行榜上持续走高,因此楼主也就开了这次使用golang自...

1362
来自专栏青青天空树

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

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

812
来自专栏Java架构师学习

分布式消息队列Apache RocketMQ源码剖析-Producer分析正文总结

正文 首先我们看一下Producer的继承结构: ? image.png MQAdmin主要包含一些管理性的接口,比如创建topic、查询某个特定消息以方便排查...

4167
来自专栏FreeBuf

scapy在wlan中的应用

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

35610
来自专栏Android开发与分享

【Android】Realm详解

75411

扫码关注云+社区

领取腾讯云代金券