欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[原创]windows内核编程之[键盘信息过滤]

[原创]windows内核编程之[键盘信息过滤]

由于前几次发帖没有注明,很多网友在问我,于是我就注明一下:本文参考《windows内核安全与驱动开发》,是一篇读书后的学习笔记。
  概念:键盘过滤,和上次我发过的串口过滤是一样的。同样也是绑定键盘设备,然后拦截过往信息,就能够实现对键盘的监控了。
  技术原理:首先我们需要知道如何绑定设备,绑定设备,在上次串口绑定时是直接通过设备名字找到了设备指针,然后绑定。这次我们的思路是,通过设备栈最顶层的驱动对象名字来打开驱动对象,然后绑定此驱动对象之下的所有设备。一般针对于PS/2键盘,最顶层的驱动对象名字是“KbdClass”。
  那么,如何根据驱动对象名字来打开驱动对象呢?使用API:


NTSTATUS

ObReferenceObjectByName(

  IN PUNICODE_STRING ObjectName,

  IN ULONG Attributes,

  IN PACCESS_STATE PassedAccessState OPTIONAL,

  IN ACCESS_MASK DesiredAccess OPTIONAL,

  IN POBJECT_TYPE ObjectType,

  IN KPROCESSOR_MODE AccessMode,

  IN OUT PVOID ParseContext OPTIONAL,

  OUT PVOID *Object

);


  具体就不一一介绍了,这里关注两点,第一个与最后一个参数,分别是驱动对象的名字和用于接收返回的驱动对象指针,当此函数执行完毕时,会将打开的驱动对象地址写入最后一个参数里。
  那么,现在我们知道了如何打开一个驱动对象,在驱动对象的结构中,有一个“DeviceObject”成员,这个成员指向了这个驱动对象设备链的第一个设备。而在设备对象结构中,又有一个成员“NextDevice”,这指向下一个设备,通过这样的结构,构成了一个设备链表。通过链表,我们可以遍历驱动对象之下的所有设备,并绑定。
  好了,我们也知道怎么绑定了,下面我们还需要知道怎么储存绑定设备的信息,比如被绑定的设备指针,过滤设备的下一层设备指针还有本身过滤设备的指针。在这里可以使用设备扩展,关于设备扩展,他是由设备对象下的一个成员“DeviceExtension”指向的一片内存空间,这片内存空间的大小由创建这个设备对象的时候指定。我们可以随意编辑这片空间,让它储存我们想要的信息。比如在里面保存一个结构体等等。每个设备的设备扩展都是这个设备独有的。
  以上我们介绍了如何通过驱动名字找到设备并绑定,还谈了如何保存单个设备的特有信息,虽然没有涉及具体代码,但思路已经很清晰了,我们遍历驱动对象下的所有设备,对每一个设备,我们都创建一个新的设备来绑定,其中信息储存在“DeviceExtension”指向的设备扩展里。
  那么,有了以上步骤,就可以针对发往键盘的消息进行拦截了。如何处理拦截的消息,我们需要再添加一个分发函数。这个分发函数通过几个if语句来判断不同的信息并进行针对性处理。如果我们需要拦截键盘上的按键信息,那么我们就要针对读请求进行特别处理,应用发来读取键盘的请求,但这个时候我们并不会知道键盘会读取到什么信息,所以我们需要等待键盘读取完毕之后,在拦截已经读取到的信息。这个时候,我们可以使用IoSetCompletionRoutine来设置某个irp的回调函数,当这个irp请求处理完毕的时候,就会调用我们设置的回掉函数,我们在给回调函数传入的irp缓冲区里可以找到按下键盘的扫描码。然后要怎么处理就看你自己了。因为时间篇幅等诸多原因,这里不介绍如何把扫描码转换成ascii码,不过也挺简单。
  至此,所有的技术原理就讲解完了,下面来谈谈设计思路。
  设计思路:
  在整个程序开始,我们需要先把分发函数填写了

  for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)

  {

    KS_driver->MajorFunction[i] = KS_Dispatch;

  }


  通过一个简单的for循环。
  之后我们直接开始绑定设备,绑定设备之前,我们先打开驱动对象,找到设备链头,开始循环遍历。每一个循环,我们都创建一个新的设备:

 IoCreateDevice(KS_driver, sizeof(KS_C2P_DEV_EXT), NULL, KS_Ldevice->DeviceType,KS_Ldevice->Characteristics, FALSE, &KS_Ndevice);


  注意参数中的sizeof(KS_C2P_DEV_EXT),其中KS_C2P_DEV_EXT是我定义的一个结构体,把结构体大小当作参数传入,表示你想要开辟这么大的一块空间作为这个设备的设备扩展。
  然后我们将创建的设备与设备链中的设备绑定,之后在设备扩展里储存一些必要的信息:过滤设备地址,被绑定设备地址,绑定API返回的下一层设备地址。最后我们再拷贝一些重要的属性,整个绑定就算完成了。
  至于分发函数,个人觉得没什么可以讲的,因为并没有复杂的思路与新知识点。于是我就直接放出完整代码了,代码功能:拦截按键扫描码并通过dbgprint打印出来。

#include <wdm.h>

#include <ntddkbd.h>

//未文档化的函数,全局变量声明。

NTKERNELAPI

NTSTATUS

ObReferenceObjectByName(

  IN PUNICODE_STRING ObjectName,

  IN ULONG Attributes,

  IN PACCESS_STATE PassedAccessState OPTIONAL,

  IN ACCESS_MASK DesiredAccess OPTIONAL,

  IN POBJECT_TYPE ObjectType,

  IN KPROCESSOR_MODE AccessMode,

  IN OUT PVOID ParseContext OPTIONAL,

  OUT PVOID *Object

);

extern POBJECT_TYPE IoDeviceObjectType;

//KdbClass设备名

#define KS_KDBCLASS_NAME L"\\Driver\\Kbdclass"

//全局变量,便于随时调用驱动对象

PDRIVER_OBJECT KS_gDriver;

//键盘计数,用于标记是否irp完成

ULONG KS_KeyCount;

//设备扩展结构

typedef struct KS_DEV_EXT

{

  ULONG Size;

  //过滤设备指针

  PDEVICE_OBJECT KS_Ndevice;

  //保护锁

  KSPIN_LOCK KS_Lock;

  //事件,用于通信

  KEVENT KS_Event;

  //被绑定设备指针

  PDEVICE_OBJECT KS_Ldevice;

  //绑定前顶端设备指针

  PDEVICE_OBJECT KS_Rdevice;

}KS_C2P_DEV_EXT,*KS_PC2P_DEV_EXT;

//卸载函数

VOID KS_driverUnload(PDRIVER_OBJECT KS_driver)

{

  PRKTHREAD KS_pThread;

  LARGE_INTEGER KS_Delay;

  PDEVICE_OBJECT KS_device;

  KS_PC2P_DEV_EXT KS_dev;

  DbgPrint("Let's begin to unload\r\n");

  KS_Delay = RtlConvertLongToLargeInteger(-10 * 3000000);  

  //获取当前线程

  KS_pThread = KeGetCurrentThread();

  //低实时模式,待会还需进行等待

  KeSetPriorityThread(KS_pThread, LOW_REALTIME_PRIORITY);

  KS_device = KS_driver->DeviceObject;

  while (KS_device)

  {

    __try 

    {

      KS_dev = (KS_PC2P_DEV_EXT)KS_device->DeviceExtension;

      IoDetachDevice(KS_dev->KS_Ldevice);

      KS_dev->KS_Ldevice = NULL;

      IoDeleteDevice(KS_device);

      KS_dev->KS_Ndevice = NULL;

    }

    __except (EXCEPTION_EXECUTE_HANDLER) {}

    KS_device = KS_device->NextDevice;

  }

  while (KS_KeyCount)

  {

    KeDelayExecutionThread(KernelMode, FALSE, &KS_Delay);

  }

  DbgPrint("Unload finish!\r\n");

  return;

}

//设备扩展结构构造函数

VOID KS_pDEV_Init(KS_PC2P_DEV_EXT pDEV, PDEVICE_OBJECT KS_Ndevice, 

  PDEVICE_OBJECT KS_Ldevice, PDEVICE_OBJECT KS_Rdevice)

{

  pDEV->KS_Ndevice = KS_Ndevice;

  pDEV->KS_Rdevice = KS_Rdevice;

  pDEV->KS_Ldevice = KS_Ldevice;

  pDEV->Size = sizeof(KS_C2P_DEV_EXT);

  KeInitializeSpinLock(&(pDEV->KS_Lock));

  //初始化一个用于通信的未激发对象

  KeInitializeEvent(&(pDEV->KS_Event), NotificationEvent, FALSE);

}

//KbdClass设备对象绑定

NTSTATUS KS_deviceAttach(PDRIVER_OBJECT KS_driver)

{

  UNICODE_STRING KS_kbdName;

  PDRIVER_OBJECT KS_KbdDriver = NULL;

  NTSTATUS status;

  //原始设备指针,临时变量

  PDEVICE_OBJECT KS_Ldevice = NULL;

  //过滤设备指针,临时变量

  PDEVICE_OBJECT KS_Ndevice = NULL;

  //用于接收绑定函数返回的顶端设备

  PDEVICE_OBJECT KS_Rdevice = NULL;

  //设备扩展

  KS_PC2P_DEV_EXT pExtension = NULL;

  DbgPrint("I will attach the device...\r\n");

  RtlInitUnicodeString(&KS_kbdName, KS_KDBCLASS_NAME);

  //通过设备名字获取打开驱动对象

  status = ObReferenceObjectByName(&KS_kbdName, OBJ_CASE_INSENSITIVE, NULL,

    FILE_ALL_ACCESS, IoDeviceObjectType, KernelMode, NULL, &KS_KbdDriver);

  if (!NT_SUCCESS(status))

  {

    DbgPrint("Open driver object failed!");

    return status;

  }

  else

  {

    ObDereferenceObject(KS_driver);

  }

  //通过打开的驱动对象获得他的第一个设备对象

  KS_Ldevice = KS_KbdDriver->DeviceObject;

  //开始遍历,并绑定驱动对象之下的所有设备

  while (KS_Ldevice)

  {

    //打开一个过滤设备,并让KS_Ndevice指向它

    status = IoCreateDevice(KS_driver, sizeof(KS_C2P_DEV_EXT), NULL, KS_Ldevice->DeviceType,

      KS_Ldevice->Characteristics, FALSE, &KS_Ndevice);

    if (!NT_SUCCESS(status))

    {

      DbgPrint("Create device failed");

      return status;

    }

    //将过滤设备与之前得到的KS_Ldevice设备绑定起来

    KS_Rdevice = IoAttachDeviceToDeviceStack(KS_Ndevice, KS_Ldevice);

    if (!KS_Rdevice)

    {

      DbgPrint("Attach faild!");

      //删除新创建的设备

      IoDeleteDevice(KS_Ndevice);

      KS_Ndevice = NULL;

      return status;

    }

    //将设备扩展结构指针pExtension指向KS_Ndevice的设备扩展

    pExtension = (KS_PC2P_DEV_EXT)KS_Ndevice->DeviceExtension;

    //初始化

    KS_pDEV_Init(pExtension, KS_Ndevice, KS_Ldevice, KS_Rdevice);

    //关键属性拷贝,将原来设备链中的顶端设备属性拷贝到过滤设备中

    KS_Ndevice->Characteristics = KS_Rdevice->Characteristics;

    KS_Ndevice->DeviceType = KS_Rdevice->DeviceType;

    KS_Ndevice->StackSize = KS_Rdevice->StackSize + 1;

    KS_Ndevice->Flags |= KS_Rdevice->Flags & (DO_BUFFERED_IO | DO_DIRECT_IO | DO_POWER_PAGABLE);

    KS_Ldevice = KS_Ldevice->NextDevice;

  }

  return status;

}

//读请求回调函数

NTSTATUS KS_Completiom(PDEVICE_OBJECT KS_device, PIRP Irp, PVOID KS_text)

{

  PIO_STACK_LOCATION KS_sIrp = IoGetCurrentIrpStackLocation(Irp);

  PUCHAR KS_buf = NULL;

  ULONG len,i;

  if (NT_SUCCESS(Irp->IoStatus.Status))

  {

    //获取缓冲区

    KS_buf = Irp->AssociatedIrp.SystemBuffer;

    len = Irp->IoStatus.Information;

    for (i = 0; i < len; i++)

    {

      //打印扫描码

      DbgPrint("KS_the keyboard:%2x\r\n", KS_buf[i]);

    }

  }

  KS_KeyCount--;

  //如果还没有处理完,则标记pending

  if (Irp->PendingReturned)

  {

    IoMarkIrpPending(Irp);

  }

  return Irp->IoStatus.Status;

}

//分发函数

NTSTATUS KS_Dispatch(PDEVICE_OBJECT KS_deviceObj, PIRP Irp)

{

  KS_PC2P_DEV_EXT KS_pDEV;

  PIO_STACK_LOCATION KS_sIrp = IoGetCurrentIrpStackLocation(Irp);

  NTSTATUS status;

  //获得设备扩展地址

  KS_pDEV = (KS_PC2P_DEV_EXT)KS_deviceObj->DeviceExtension;

  //如果是电源操作

  if (KS_sIrp->MajorFunction == IRP_MJ_POWER)

  {

    PoStartNextPowerIrp(Irp);

    IoSkipCurrentIrpStackLocation(Irp);

    return PoCallDriver(KS_pDEV->KS_Rdevice, Irp);

  }

  

  if (KS_sIrp->MajorFunction == IRP_MJ_PNP)

  {

    //如果设备被拔出,我们需要解除绑定,删除过滤设备

    if (KS_sIrp->MinorFunction == IRP_MN_REMOVE_DEVICE)

    {

      IoSkipCurrentIrpStackLocation(Irp);

      IoCallDriver(KS_pDEV->KS_Rdevice, Irp);

      IoDetachDevice(KS_pDEV->KS_Rdevice);

      IoDeleteDevice(KS_deviceObj);

      return STATUS_SUCCESS;

    }

    else

    {

      IoSkipCurrentIrpStackLocation(Irp);

      return IoCallDriver(KS_pDEV->KS_Rdevice, Irp);

    }

    

  }

  //如果是读操作

  if (KS_sIrp->MajorFunction == IRP_MJ_READ)

  {

    KEVENT KS_waitEvent;

    KeInitializeEvent(&KS_waitEvent, NotificationEvent, FALSE);

    //如果已经到达底层,则返回错误。

    if (Irp->CurrentLocation == 1)

    {

      status = STATUS_INVALID_DEVICE_REQUEST;

      Irp->IoStatus.Status = status;

      Irp->IoStatus.Information = 0;

      IoCompleteRequest(Irp, IO_NO_INCREMENT);

      return status;

    }

    KS_KeyCount++;

    KS_pDEV = (KS_PC2P_DEV_EXT)KS_deviceObj->DeviceExtension;

    IoCopyCurrentIrpStackLocationToNext(Irp);

    IoSetCompletionRoutine(Irp, KS_Completiom, KS_deviceObj, TRUE, TRUE, TRUE);

    return IoCallDriver(KS_pDEV->KS_Rdevice, Irp);

  }

}

NTSTATUS DriverEntry(PDRIVER_OBJECT KS_driver, PUNICODE_STRING KS_ustr)

{

  ULONG i;

#if DBG

  __asm int 3

#endif

  //填写分发函数

  DbgPrint("Begin...\r\n");

  for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)

  {

    KS_driver->MajorFunction[i] = KS_Dispatch;

  }

  KS_driver->DriverUnload = KS_driverUnload;

  KS_gDriver = KS_driver;

  return KS_deviceAttach(KS_driver);

}


  注释里面也说的很详细了。
  最后说点题外话,这篇文章确实不能作为教程,因为很多细节并没有讲清楚,但它适合作为一篇用于复习的文章,对于复习来说,重要的是思路而非细节与代码,所以在思路方面我也特意花了些时间写了写,希望对大家有帮助。
  qq37794802,希望能和各路大佬一起交流学习

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功