xfocus logo xfocus title
首页 焦点原创 安全文摘 安全工具 安全漏洞 焦点项目 焦点论坛 关于我们
添加文章 Xcon English Version

支持 PS/2 与 USB 的键盘过滤驱动(可卸载)


创建时间:2007-02-26
文章属性:原创
文章提交:sinister (jiasys_at_21cn.com)

Author:  sinister
Email:   sinister@whitecell.org
Homepage:http://www.whitecell.org
Date:    2007-02-26


/*******************************************************************

这个键盘过滤驱动是一个定时锁定计算机程序的功能部分,以前 lgx
写过一个 linux 版,现在我们需要实现一个 windows 版。这部分的
功能要求如下:

1、强制锁定键盘/鼠标。
2、可动态加/解锁
3、兼容所有 NT 系列的操作系统。

就这个需求而言,能马上能想到的就有7,8种方案,这些方案可以说都能够实
现,但如何更合理,更稳定、更彻底的实现,如何尽量少的消耗系统资源,如
何保证其兼容性,等一系列问题不得不让我们去重新评估这几种方法。首先在
上层实现,一是怕被饶过,二是怕调用频繁影响系统性能。在底层实现,一是
怕考虑不周有兼容性问题,二是怕过多使用未公开方法导致系统不稳定。下面
就来看一下我想到的几种实现方法:

1、全局键盘/鼠标钩子
2、BlockInput() API
3、使用 setupapi 进行控制
4、全局键盘/鼠标钩子+远线程插入 WINLOGON 进程屏蔽 CTRL+AL+DEL
5、拦截 win23k!RawInputThread() 函数
6、修改 DDK 中自带的 kbfilter 的键盘(Port Driver)过滤驱动
7、拦截 kdbclass 驱动的 driver dispatch routine
8、实现一个 PS/2 与 USB 键盘过滤驱动


我们先看一下以上这些方案的实用性与通用性。第1,2套方案不在考虑
之内了,因为有办法解锁,屏蔽不了 CTRL+ALT+DEL 组合键。第3套方
案经过我的测试使用 Keyboard 的 CLASSID 不是什么环境都好使,存在
兼容性的问题。第4套方案系统效率会大大降低,而且存在很多不稳定因
素,对于全局钩子这种东西我一直很排斥。第5套方案,同样存在兼容性
问题和不稳定因素。第6套方案看似完美,但无法实现动态卸载,无法卸
载就意味着你需要一个开关来控制是否锁定,这样还要与应用层通讯,我
的目的是不让应用层与驱动有任何交互。且使用 WDM 形式这种安装起来
也很麻烦,要么 INF 要么自己 setupapi,这都不是我想看到的,还有如
果仅为实现这么一个功能,就让一个核心驱动一直存在系统中的话,我有
障碍。第7套方案看似实现起来很简单,其实有很多问题。如仅是拦截
IRP_MJ_READ 并粗暴的返回拒绝后,系统无法恢复到初始状态。且对于
USB 键盘存在兼容性问题。那么最后只有自己实现一个 PS/2 与 USB 键
盘过滤驱动了,既然要实现这么一个驱动,那么就必须能实现到第6套方
案的全部功能且不存在它所带来的问题,否则就没有什么意义了。


我们都知道实现一个可直接使用 SERVICE API 来动态装载的 KMD 键盘过
滤驱动,首先需要先 ATTACH 到 \\Device\\KeyboardClass0 设备上再进
行按键过滤。但如果仅 ATTACH 这个设备的话,会存在很多问题,那就是
只能过滤到 PS/2 键盘,而对于使用 USB 键盘的机器毫无作用。现在越
来越多的品牌机都预配的是 USB 键盘(如:DELL)。大家可能会想,从
KeyboardClass0 一直到 N 都 ATTACH 不就可以了么?总有一个是 USB
键盘设备,经过实践这是不可行的,只要是 USB 键盘设备的话,在使用
IoGetDeviceObjectPointer() 函数从设备名得到设备对象都会获取失败,
我还曾尝试使用 USB 键盘设备名来转换,还是一样失败。还有一个问题
就是 USB 键盘设备不是都有名称的,即使有它的名称也都是动态生成的
而不是固定的。那么这就带来了一个问题,既然系统提供的函数无法使
用,且我们又是为了动态安装/卸载,使用的是 KMD 类型驱动,无法通
过 AddDevice 例程来获得 USB 键盘的设备对象进行挂接。那么如何来
屏蔽 USB 键盘按键?要达到这个目的只有自己分析下 USB 协议栈,通
过使用 DriverTree 观察发现,所有 USB 外设都挂在了 \Driver\hidusb
上面,USB 键盘驱动名为 \Driver\kbdhid,而它则是 \Driver\kbdhid
的一个 FilterDriver,且这个 \Driver\kbdhid 没有设备名称,也就意
味着,我们无法 IoGetDeviceObjectPointer() 得到设备对象并 ATTACH。
经过对多个系统多台使用 USB 键盘机器的分析,可以确定让我使用它们
来作为得到 USB 键盘设备的依据。(这里仅是对 USB 键盘设备很功利
的分析,如果想了解 WINDOWS 的 USB 设备栈如何组建,请阅读 tiamo
的 《Windows 的 USB 体系结构》。在此向所有公开研究成果的人致
敬!)有了这些依据,下面就没什么好说的了,自己遍历 USB 设备栈,
根据驱动名称获得 USB 设备对象,然后 ATTACH,过滤按键。具体流程
见下面代码。

这里有必要说下动态卸载,我尝试了两种方式,一种是在 UNLOAD 例程
里直接取消 IRP,这种方法在 W2K 系统下,无论是 PS/2 还是 USB 键
盘都可以很好的实现。但在 XP/2003 系统上则无法成功,在 XP/2003
上暂时使用一个 IRP 计数器,在 UNLOAD 例程里判断如果还有一个没有
完成的 IRP 则等待,这样的话,需要在 UNLOAD 后用户按下任意键才可
继续,虽然能够安全卸载但还需要一次用户介入。考虑实现的仅是一个
锁定功能,这样也算是能够忍受了。以后考虑在驱动中直接模拟用户按
键来实现,当然这种按键要有通用性和兼容性,支持 PS/2 与 USB 键盘。


完成了上述工作后看起来好象完美了,其实不然,当你屏蔽了当前使用
的键盘时别忘了还可以再插入一个 USB 键盘,而后续插入的这个键盘是
可以被识别的。这就需要我们处理 IRP_MJ_PNP 选项,对其中我们有兴趣
的 IRP_MN_XXX 做相应处理,可以处理 IRP_MN_START_DEVICE,在这时我
们动态挂接。还可以处理其他的 IRP,索性返回错误,让识别失效。但我
们的驱动是 KMD 类型,只要加上一句对 DriverObject->DriverExtension
->AddDevice 的赋值操作则无法直接使用 SERVICE API 来动态加载了。
如何保持 KMD 又可以获得 PNP 的处理权呢?这可能要直接对 PnpManager
进行操作了。这个问题有待大家来完善了。


要问为什么把文章插在代码当中,那可能是我觉得,既然把全部代码都贴出
来了,写文章就不如直接看代码来的真切。我这里所写也仅仅是对这些天的
分析做个记录而已。我更愿意把它看做是一段注释。

最后在此代码中要

感谢:PolyMeta,它在放假前提醒我 USB 键盘的不同。

感谢:lgx,过节前给我找了些事,以至于没有让我觉得过节那么无聊。

感谢:齐佳佳,过节请我吃好吃的。

崞類浶愭蝰櫉炈棡溫滠飲钕唹徕熸唹戹樤樯殢餀膑?崞烊旡唹澾崞懽碡飲嵯
愥撫剢淦泮嵯駨耒栃撣

******************************************************************/



/*****************************************************************
文件名        : WssLockKey.c
描述          : 键盘过滤驱动
作者          : sinister
最后修改日期  : 2007-02-26
*****************************************************************/


#include "WssLockKey.h"

NTSTATUS
DriverEntry( IN PDRIVER_OBJECT KeyDriverObject,
             IN PUNICODE_STRING RegistryPath )
{
  UNICODE_STRING KeyDeviceName;
  PDRIVER_OBJECT KeyDriver;
  PDEVICE_OBJECT UsbDeviceObject;
  NTSTATUS ntStatus;
  ULONG i;

  //
  // 保存设备名,调试使用
  //
  WCHAR szDeviceName[MAXLEN + MAXLEN] =
  {
    0
  };

  KeyDriverObject->DriverUnload = KeyDriverUnload;

  //
  // 先尝试获得 USB 键盘设备对象,如果成功则挂接 USB 键盘
  //
  // 注意:因为 USB 键盘设备名不固定,且即使得到名称也无法
  // 使用 IoGetDeviceObjectPointer() 函数根据设备名称得到其
  // 设备对象,所以这里我们只能自己枚举 USB 设备栈,并得到
  // USB 键盘设备来进行挂接
  //
  ntStatus = GetUsbKeybordDevice( &UsbDeviceObject );
  if ( NT_SUCCESS( ntStatus ) && UsbDeviceObject != NULL )
  {
    //
    // 调试使用,USB 键盘设备 kbdhid 没有设备名只有驱动名
    // 所以这里打印为空
    //
    RtlInitUnicodeString( &KeyDeviceName, szDeviceName );  // USB KEYBOARD
    DbgPrint( "KeyDeviceName:%S\n", KeyDeviceName.Buffer );

    //
    // 挂接 USB 键盘设备
    //
    ntStatus = AttachUSBKeyboardDevice( UsbDeviceObject, KeyDriverObject );
    if ( !NT_SUCCESS( ntStatus ) )
    {
      DbgPrint( "Attach USB Keyboard Device to failed!\n" );
      return STATUS_INSUFFICIENT_RESOURCES;
    }
  }
  else
  {
    //
    // 如果没有 USB 键盘,则尝试挂接 PS/2 键盘设备
    //
    RtlInitUnicodeString( &KeyDeviceName, PS2KEYBOARDNAME );

    ntStatus = AttachPS2KeyboardDevice( &KeyDeviceName,
                                        KeyDriverObject,
                                        &KeyDriver );
    if ( !NT_SUCCESS( ntStatus ) || KeyDriver == NULL )
    {
      DbgPrint( "Attach PS2 Keyboard Device to failed!\n" );
      return STATUS_INSUFFICIENT_RESOURCES;
    }
  }

  //
  // 这里没有过滤其他例程,仅处理了按键操作。这样处理会禁止
  // 休眠。因在锁定时不允许休眠,所以也就无须处理其他例程
  //
  KeyDriverObject->MajorFunction[IRP_MJ_READ] = KeyReadPassThrough;

  return STATUS_SUCCESS;
}

/////////////////////////////////////////////////////////////////
// 函数类型 : 系统函数
// 函数模块 : 键盘过滤模块
////////////////////////////////////////////////////////////////
// 功能 : 尝试取消队列里的异步 IRP,如果失败则等待用户按键,
//        卸载键盘过滤驱动
// 注意 : 取消 IRP 操作在 2000 系统上可以成功,在 XP / 2003 上
//        则需要等待用户按键,以后有待完善
/////////////////////////////////////////////////////////////////
// 作者 : sinister
// 发布版本 : 1.00.00
// 发布日期 : 2005.12.27
/////////////////////////////////////////////////////////////////
// 重   大   修   改   历   史
////////////////////////////////////////////////////////////////
// 修改者 :
// 修改日期 :
// 修改内容 :
/////////////////////////////////////////////////////////////////

VOID
KeyDriverUnload( PDRIVER_OBJECT KeyDriver )
{
  PDEVICE_OBJECT KeyFilterDevice ;      
  PDEVICE_OBJECT KeyDevice ;
  PDEVICE_EXTENSION KeyExtension;
  PIRP Irp;
  NTSTATUS ntStatus;

  KeyFilterDevice = KeyDriver->DeviceObject;
  KeyExtension = ( PDEVICE_EXTENSION ) KeyFilterDevice->DeviceExtension;
  KeyDevice = KeyExtension->TargetDevice;

  IoDetachDevice( KeyDevice );

  //
  // 如果还有 IRP 未完成,且当前 IRP 有效则尝试取消这个 IRP
  //
  if ( KeyExtension->IrpsInProgress > 0 && KeyDevice->CurrentIrp != NULL )
  {
    if ( CancelKeyboardIrp( KeyDevice->CurrentIrp ) )
    {
      //
      // 成功则直接退出删除键盘过滤设备
      //
      DbgPrint( "CancelKeyboardIrp() is ok\n" );
      goto __End;
    }
  }

  //
  // 如果取消失败,则一直等待按键
  //
  while ( KeyExtension->IrpsInProgress > 0 )
  {
    DbgPrint( "Irp Count:%d\n", KeyExtension->IrpsInProgress );
  }

  __End:
  IoDeleteDevice( KeyFilterDevice );

  return ;
}

/////////////////////////////////////////////////////////////////
// 函数类型 : 自定义工具函数
// 函数模块 : 键盘过滤模块
/////////////////////////////////////////////////////////////////
// 功能 : 取消 IRP 操作
// 注意 : 这个函数仅是为配合在 UNLOAD 例程使用,其他例程中不能
//        使用此方法来取消 IRP
/////////////////////////////////////////////////////////////////
// 作者 : sinister
// 发布版本 : 1.00.00
// 发布日期 : 2007.02.20
/////////////////////////////////////////////////////////////////
// 重   大   修   改   历   史
/////////////////////////////////////////////////////////////////
// 修改者 :
// 修改日期 :
// 修改内容 :
/////////////////////////////////////////////////////////////////

BOOLEAN
CancelKeyboardIrp( IN PIRP Irp )
{
  if ( Irp == NULL )
  {
    DbgPrint( "CancelKeyboardIrp: Irp error\n" );
    return FALSE;
  }


  //
  // 这里有些判断应该不是必须的,比如对 CancelRoutine 字段,
  // 因为 IoCancelIrp() 函数中有判断了。但只有偏执狂才能生存 :)。
  // 小波说 低智、偏执、思想贫乏是最不可容忍的。我这一行代码就占
  // 了两条 :D,不知 xiaonvwu 看过后会作何感想?:DDD
  //

  //
  // 如果正在取消或没有取消例程则直接返回 FALSE
  //
  if ( Irp->Cancel || Irp->CancelRoutine == NULL )
  {
    DbgPrint( "Can't Cancel the irp\n" );
    return FALSE;
  }

  if ( FALSE == IoCancelIrp( Irp ) )
  {
    DbgPrint( "IoCancelIrp() to failed\n" );
    return FALSE;
  }

  //
  // 取消后重设此例程为空
  //
  IoSetCancelRoutine( Irp, NULL );

  return TRUE;
}

/////////////////////////////////////////////////////////////////
// 函数类型 : 自定义工具函数
// 函数模块 : 设备栈信息模块
/////////////////////////////////////////////////////////////////
// 功能 : 遍历 DEVICE_OBJECT 中 AttachedDevice 域,找到 USB 键盘
//        设备上名为 kbdhid 的过滤驱动(Upper Filter Driver)
// 注意 :
/////////////////////////////////////////////////////////////////
// 作者 : sinister
// 发布版本 : 1.00.00
// 发布日期 : 2005.06.02
/////////////////////////////////////////////////////////////////
// 重   大   修   改   历   史
/////////////////////////////////////////////////////////////////
// 修改者 : sinister
// 修改日期 : 2007.2.12
// 修改内容 : 为匹配 USB 键盘驱动做了相应的修改
/////////////////////////////////////////////////////////////////

BOOLEAN
GetAttachedDeviceInfo( IN PDEVICE_OBJECT DevObj )
{
  PDEVICE_OBJECT DeviceObject;
  BOOLEAN bFound = FALSE;

  if ( DevObj == NULL )
  {
    DbgPrint( "DevObj is NULL!\n" );
    return FALSE;
  }

  DeviceObject = DevObj->AttachedDevice;

  while ( DeviceObject )
  {
    //
    // 一些 OBJECT 的名称都存在分页区,虽然大部分时候不会被交换出去,但
    // 有一次足够了。这算是经验之谈
    //
    if ( MmIsAddressValid( DeviceObject->DriverObject->DriverName.Buffer ) )
    {
      DbgPrint( "Attached Driver Name:%S,Attached Driver Address:0x%x,Attached DeviceAddress:0x%x\n",
                DeviceObject->DriverObject->DriverName.Buffer,
                DeviceObject->DriverObject,
                DeviceObject );

      //
      // 找到 USB 键盘驱动的 kbdhid 设备了么?找到了就不继续了
      //
      if ( _wcsnicmp( DeviceObject->DriverObject->DriverName.Buffer,
                      KDBDEVICENAME,
                      wcslen( KDBDEVICENAME ) ) == 0 )
      {
        DbgPrint( "Found kbdhid Device\n" );
        bFound = TRUE;
        break;
      }
    }

    DeviceObject = DeviceObject->AttachedDevice;
  }

  return bFound;
}

/////////////////////////////////////////////////////////////////
// 函数类型 : 自定义工具函数
// 函数模块 : 设备栈信息模块
/////////////////////////////////////////////////////////////////
// 功能 : 从 DEVICE_OBJECT 中得到设备与驱动名称并打印地址
// 注意 : 函数功能只是打印信息,不同环境使用中应该会做修改
/////////////////////////////////////////////////////////////////
// 作者 : sinister
// 发布版本 : 1.00.00
// 发布日期 : 2006.05.02
/////////////////////////////////////////////////////////////////
// 重   大   修   改   历   史
/////////////////////////////////////////////////////////////////
// 修改者 : sinister
// 修改日期 : 2007.2.12
// 修改内容 : 打印出 USB 键盘驱动的设备名称,仅作调试使用
/////////////////////////////////////////////////////////////////

VOID
GetDeviceObjectInfo( IN PDEVICE_OBJECT DevObj )
{
  POBJECT_HEADER ObjectHeader;
  POBJECT_HEADER_NAME_INFO ObjectNameInfo;

  if ( DevObj == NULL )
  {
    DbgPrint( "DevObj is NULL!\n" );
    return;
  }

  //
  // 得到对象头
  //
  ObjectHeader = OBJECT_TO_OBJECT_HEADER( DevObj );

  if ( ObjectHeader )
  {
    //
    // 查询设备名称并打印
    //
    ObjectNameInfo = OBJECT_HEADER_TO_NAME_INFO( ObjectHeader );

    if ( ObjectNameInfo && ObjectNameInfo->Name.Buffer )
    {
      DbgPrint( "Device Name:%S - Device Address:0x%x\n",
                ObjectNameInfo->Name.Buffer,
                DevObj );

      //
      // 复制 USB 键盘设备名到一个全局 BUFFER 里,为调试时显示
      // 用,没有实际的功能用途
      //
      RtlZeroMemory( szUsbDeviceName, sizeof( szUsbDeviceName ) );

      wcsncpy( szUsbDeviceName,
               ObjectNameInfo->Name.Buffer,
               ObjectNameInfo->Name.Length / sizeof( WCHAR ) );
    }

    //
    // 对于没有名称的设备,则打印 NULL
    //
    else if ( DevObj->DriverObject )
    {
      DbgPrint( "Driver Name:%S - Device Name:%S - Driver Address:0x%x - Device Address:0x%x\n",
                DevObj->DriverObject->DriverName.Buffer,
                L"NULL",
                DevObj->DriverObject,
                DevObj );
    }
  }
}

/////////////////////////////////////////////////////////////////
// 函数类型 : 自定义工具函数
// 函数模块 : 键盘过滤模块
/////////////////////////////////////////////////////////////////
// 功能 : 得到 USB 驱动 hidusb 的驱动对象,并遍历以上所有设备
//        对象,过滤出 USB 键盘设备,将其设备对象返回
// 注意 :
/////////////////////////////////////////////////////////////////
// 作者 : sinister
// 发布版本 : 1.00.00
// 发布日期 : 2007.02.13
/////////////////////////////////////////////////////////////////
// 重   大   修   改   历   史
/////////////////////////////////////////////////////////////////
// 修改者 :
// 修改日期 :
// 修改内容 :
/////////////////////////////////////////////////////////////////

NTSTATUS
GetUsbKeybordDevice( OUT PDEVICE_OBJECT* UsbDeviceObject )
{
  UNICODE_STRING DriverName;
  PDRIVER_OBJECT DriverObject = NULL;
  PDEVICE_OBJECT DeviceObject = NULL;
  BOOLEAN bFound = FALSE;

  RtlInitUnicodeString( &DriverName, USBKEYBOARDNAME );

  ObReferenceObjectByName( &DriverName,
                           OBJ_CASE_INSENSITIVE,
                           NULL,
                           0,
                           ( POBJECT_TYPE ) IoDriverObjectType,
                           KernelMode,
                           NULL,
                           &DriverObject );

  if ( DriverObject == NULL )
  {
    DbgPrint( "Not found USB Keyboard Device hidusb!\n" );
    return STATUS_UNSUCCESSFUL;
  }

  DeviceObject = DriverObject->DeviceObject;

  while ( DeviceObject )
  {
    GetDeviceObjectInfo( DeviceObject );

    if ( DeviceObject->AttachedDevice )
    {
      //
      // 查找 USB 键盘设备
      //
      if ( GetAttachedDeviceInfo( DeviceObject ) )
      {
        bFound = TRUE;
        goto __End;
      }
    }

    DeviceObject = DeviceObject->NextDevice;
  }

  __End:

  if ( bFound )
  {
    //
    // 找到则返回 USB 键盘设备对象
    //
    *UsbDeviceObject = DeviceObject;
  }
  else
  {
    *UsbDeviceObject = NULL;
  }

  return STATUS_SUCCESS;
}

/////////////////////////////////////////////////////////////////
// 函数类型 : 自定义工具函数
// 函数模块 : 键盘过滤模块
////////////////////////////////////////////////////////////////
// 功能 : 创建过滤设备将其附加到需要跟踪的设备上,保存设备相关
//        信息,返回附加后的驱动对象
// 注意 : 此函数仅挂接 USB 键盘设备
/////////////////////////////////////////////////////////////////
// 作者 : sinister
// 发布版本 : 1.00.00
// 发布日期 : 2005.12.27
/////////////////////////////////////////////////////////////////
// 重   大   修   改   历   史
////////////////////////////////////////////////////////////////
// 修改者 :
// 修改日期 :
// 修改内容 :
/////////////////////////////////////////////////////////////////

NTSTATUS
AttachUSBKeyboardDevice( IN PDEVICE_OBJECT UsbDeviceObject,
                         IN PDRIVER_OBJECT  DriverObject )
{
  PDEVICE_OBJECT DeviceObject;
  PDEVICE_OBJECT TargetDevice;
  PDEVICE_EXTENSION DevExt;
  NTSTATUS ntStatus;

  //
  // 创建过滤设备对象
  //
  ntStatus = IoCreateDevice( DriverObject,
                             sizeof( DEVICE_EXTENSION ),
                             NULL,
                             FILE_DEVICE_UNKNOWN,
                             0,
                             FALSE,
                             &DeviceObject );

  if ( !NT_SUCCESS( ntStatus ) )
  {
    DbgPrint( "IoCreateDevice() 0x%x!\n", ntStatus );
    return ntStatus;
  }

  DevExt = ( PDEVICE_EXTENSION ) DeviceObject->DeviceExtension;

  //
  // 初始化自旋锁
  //
  KeInitializeSpinLock( &DevExt->SpinLock );

  //
  // 初始化 IRP 计数器
  //
  DevExt->IrpsInProgress = 0;

  //
  // 将过滤设备对象附加在目标设备对象之上,并返回附加后的原设备对象
  //

  TargetDevice = IoAttachDeviceToDeviceStack( DeviceObject, UsbDeviceObject );
  if ( !TargetDevice )
  {
    IoDeleteDevice( DeviceObject );
    DbgPrint( "IoAttachDeviceToDeviceStack() 0x%x!\n", ntStatus );
    return STATUS_INSUFFICIENT_RESOURCES;
  }

  //
  // 保存过滤设备信息
  //
  DevExt->DeviceObject = DeviceObject;
  DevExt->TargetDevice = TargetDevice;

  //
  // 设置过滤设备相关信息与标志
  //
  DeviceObject->Flags |= ( DO_BUFFERED_IO | DO_POWER_PAGABLE );
  DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;


  return STATUS_SUCCESS;
}

/////////////////////////////////////////////////////////////////
// 函数类型 : 自定义工具函数
// 函数模块 : 键盘过滤模块
////////////////////////////////////////////////////////////////
// 功能 : 创建过滤设备将其附加到需要跟踪的设备上,保存设备相关
//        信息,返回附加后的驱动对象
// 注意 : 此函数仅挂接 PS/2 键盘设备
/////////////////////////////////////////////////////////////////
// 作者 : sinister
// 发布版本 : 1.00.00
// 发布日期 : 2005.12.27
/////////////////////////////////////////////////////////////////
// 重   大   修   改   历   史
////////////////////////////////////////////////////////////////
// 修改者 :
// 修改日期 :
// 修改内容 :
/////////////////////////////////////////////////////////////////

NTSTATUS
AttachPS2KeyboardDevice( IN UNICODE_STRING* DeviceName, // 需要跟踪的设备名
                         IN PDRIVER_OBJECT  DriverObject, // 过滤驱动也就是本驱动的驱动对象
                         OUT PDRIVER_OBJECT* FilterDriverObject ) // 返回附加后的驱动对象
{
  PDEVICE_OBJECT DeviceObject;
  PDEVICE_OBJECT FilterDeviceObject;
  PDEVICE_OBJECT TargetDevice;
  PFILE_OBJECT FileObject;
  PDEVICE_EXTENSION DevExt;

  NTSTATUS ntStatus;

  //
  // 根据设备名称找到需要附加的设备对象
  //
  ntStatus = IoGetDeviceObjectPointer( DeviceName,
                                       FILE_ALL_ACCESS,
                                       &FileObject,
                                       &DeviceObject );

  if ( !NT_SUCCESS( ntStatus ) )
  {
    DbgPrint( "IoGetDeviceObjectPointer() 0x%x\n", ntStatus );
    return ntStatus;
  }

  //
  // 创建过滤设备对象
  //
  ntStatus = IoCreateDevice( DriverObject,
                             sizeof( DEVICE_EXTENSION ),
                             NULL,
                             FILE_DEVICE_KEYBOARD,
                             0,
                             FALSE,
                             &FilterDeviceObject );

  if ( !NT_SUCCESS( ntStatus ) )
  {
    ObDereferenceObject( FileObject );
    DbgPrint( "IoCreateDevice() 0x%x!\n", ntStatus );
    return ntStatus;
  }

  //
  // 得到设备扩展结构,以便下面保存过滤设备信息
  //
  DevExt = ( PDEVICE_EXTENSION ) FilterDeviceObject->DeviceExtension;


  //
  // 初始化自旋锁
  //
  KeInitializeSpinLock( &DevExt->SpinLock );

  //
  // 初始化 IRP 计数器
  //
  DevExt->IrpsInProgress = 0;

  //
  // 将过滤设备对象附加在目标设备对象之上,并返回附加后的原设备对象
  //
  TargetDevice = IoAttachDeviceToDeviceStack( FilterDeviceObject,
                                              DeviceObject );
  if ( !TargetDevice )
  {
    ObDereferenceObject( FileObject );
    IoDeleteDevice( FilterDeviceObject );
    DbgPrint( "IoAttachDeviceToDeviceStack() 0x%x!\n", ntStatus );
    return STATUS_INSUFFICIENT_RESOURCES;
  }

  //
  // 保存过滤设备信息
  //
  DevExt->DeviceObject = FilterDeviceObject;
  DevExt->TargetDevice = TargetDevice;
  DevExt->pFilterFileObject = FileObject;

  //
  // 设置过滤设备相关信息与标志
  //
  FilterDeviceObject->DeviceType = TargetDevice->DeviceType;
  FilterDeviceObject->Characteristics = TargetDevice->Characteristics;
  FilterDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
  FilterDeviceObject->Flags |= ( TargetDevice->Flags & ( DO_DIRECT_IO |
                                                         DO_BUFFERED_IO ) );

  //
  // 返回附加后的驱动对象
  //
  *FilterDriverObject = TargetDevice->DriverObject;

  ObDereferenceObject( FileObject );

  return STATUS_SUCCESS;
}

/////////////////////////////////////////////////////////////////
// 函数类型 : 自定义工具函数
// 函数模块 : 键盘过滤模块
////////////////////////////////////////////////////////////////
// 功能 : 键盘过滤驱动的 IRP_MJ_READ 派遣例程,所有按键将触发
//        这个 IRP 的完成
// 注意 :
/////////////////////////////////////////////////////////////////
// 作者 : sinister
// 发布版本 : 1.00.00
// 发布日期 : 2007.2.15
/////////////////////////////////////////////////////////////////
// 重   大   修   改   历   史
////////////////////////////////////////////////////////////////
// 修改者 :
// 修改日期 :
// 修改内容 :
/////////////////////////////////////////////////////////////////

NTSTATUS
KeyReadPassThrough( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp )
{
  NTSTATUS status;
  KIRQL IrqLevel;

  PDEVICE_OBJECT pDeviceObject;
  PDEVICE_EXTENSION KeyExtension = ( PDEVICE_EXTENSION )
                                   DeviceObject->DeviceExtension;


  IoCopyCurrentIrpStackLocationToNext( Irp );

  //
  // 将 IRP 计数器加一,为支持 SMP 使用自旋锁
  //
  KeAcquireSpinLock( &KeyExtension->SpinLock, &IrqLevel );
  InterlockedIncrement( &KeyExtension->IrpsInProgress );
  KeReleaseSpinLock( &KeyExtension->SpinLock, IrqLevel );

  IoSetCompletionRoutine( Irp,
                          KeyReadCompletion,
                          DeviceObject,
                          TRUE,
                          TRUE,
                          TRUE );

  return IoCallDriver( KeyExtension->TargetDevice, Irp );
}

/////////////////////////////////////////////////////////////////
// 函数类型 :系统回调函数
// 函数模块 : 键盘过滤模块
////////////////////////////////////////////////////////////////
// 功能 : 获得键盘按键,用无效扫描码替换,以达到屏蔽键盘的目的
// 注意 :
/////////////////////////////////////////////////////////////////
// 作者 : sinister
// 发布版本 : 1.00.00
// 发布日期 : 2007.2.12
/////////////////////////////////////////////////////////////////
// 重   大   修   改   历   史
////////////////////////////////////////////////////////////////
// 修改者 :
// 修改日期 :
// 修改内容 :
/////////////////////////////////////////////////////////////////

NTSTATUS
KeyReadCompletion( IN PDEVICE_OBJECT DeviceObject,
                   IN PIRP Irp,
                   IN PVOID Context )
{
  PIO_STACK_LOCATION IrpSp;
  PKEYBOARD_INPUT_DATA KeyData;
  PDEVICE_EXTENSION KeyExtension = ( PDEVICE_EXTENSION )
                                   DeviceObject->DeviceExtension;
  int numKeys, i;
  KIRQL IrqLevel;

  IrpSp = IoGetCurrentIrpStackLocation( Irp );


  if ( Irp->IoStatus.Status != STATUS_SUCCESS )
  {
    DbgPrint( "ntStatus:0x%x", Irp->IoStatus.Status );
    goto __RoutineEnd;
  }

  //
  // 系统在 SystemBuffer 中保存按键信息
  //
  KeyData = Irp->AssociatedIrp.SystemBuffer;
  if ( KeyData == NULL )
  {
    DbgPrint( "KeyData is NULL\n" );
    goto __RoutineEnd;
  }

  //
  // 得到按键数
  //
  numKeys = Irp->IoStatus.Information / sizeof( KEYBOARD_INPUT_DATA );
  if ( numKeys < 0 )
  {
    DbgPrint( "numKeys less zero\n" );
    goto __RoutineEnd;
  }

  //
  // 使用 0 无效扫描码替换,屏蔽所有按键
  //
  for ( i = 0; i < numKeys; i++ )
  {
    DbgPrint( "KeyDwon: 0x%x\n", KeyData[i].MakeCode );
    KeyData[i].MakeCode = 0x00;
  }


  __RoutineEnd :

  if ( Irp->PendingReturned )
  {
    IoMarkIrpPending( Irp );
  }

  //
  // 将 IRP 计数器减一,为支持 SMP 使用自旋锁
  //
  KeAcquireSpinLock( &KeyExtension->SpinLock, &IrqLevel );
  InterlockedDecrement( &KeyExtension->IrpsInProgress );
  KeReleaseSpinLock( &KeyExtension->SpinLock, IrqLevel );

  return Irp->IoStatus.Status ;
}


/*****************************************************************
文件名        : WssLockKey.h
描述          : 键盘过滤驱动
作者          : sinister
最后修改日期  : 2007-02-26
*****************************************************************/

#ifndef __WSS_LOCKKEY_H_
#define __WSS_LOCKKEY_H_

#include "ntddk.h"
#include "ntddkbd.h"
#include "string.h"
#include

#define MAXLEN 256

#define KDBDEVICENAME L"\\Driver\\kbdhid"
#define USBKEYBOARDNAME L"\\Driver\\hidusb"
#define PS2KEYBOARDNAME L"\\Device\\KeyboardClass0"

typedef struct _OBJECT_CREATE_INFORMATION
{
    ULONG Attributes;
    HANDLE RootDirectory;
    PVOID ParseContext;
    KPROCESSOR_MODE ProbeMode;
    ULONG PagedPoolCharge;
    ULONG NonPagedPoolCharge;
    ULONG SecurityDescriptorCharge;
    PSECURITY_DESCRIPTOR SecurityDescriptor;
    PSECURITY_QUALITY_OF_SERVICE SecurityQos;
    SECURITY_QUALITY_OF_SERVICE SecurityQualityOfService;
} OBJECT_CREATE_INFORMATION, * POBJECT_CREATE_INFORMATION;

typedef struct _OBJECT_HEADER
{
    LONG PointerCount;
    union
    {
        LONG HandleCount;
        PSINGLE_LIST_ENTRY SEntry;
    };
    POBJECT_TYPE Type;
    UCHAR NameInfoOffset;
    UCHAR HandleInfoOffset;
    UCHAR QuotaInfoOffset;
    UCHAR Flags;
    union
    {
        POBJECT_CREATE_INFORMATION ObjectCreateInfo;
        PVOID QuotaBlockCharged;
    };

    PSECURITY_DESCRIPTOR SecurityDescriptor;
    QUAD Body;
} OBJECT_HEADER, * POBJECT_HEADER;

#define NUMBER_HASH_BUCKETS 37

typedef struct _OBJECT_DIRECTORY
{
    struct _OBJECT_DIRECTORY_ENTRY* HashBuckets[NUMBER_HASH_BUCKETS];
    struct _OBJECT_DIRECTORY_ENTRY** LookupBucket;
    BOOLEAN LookupFound;
    USHORT SymbolicLinkUsageCount;
    struct _DEVICE_MAP* DeviceMap;
} OBJECT_DIRECTORY, * POBJECT_DIRECTORY;

typedef struct _OBJECT_HEADER_NAME_INFO
{
    POBJECT_DIRECTORY Directory;
    UNICODE_STRING Name;
    ULONG Reserved;
#if DBG
    ULONG Reserved2 ;
    LONG DbgDereferenceCount ;
#endif
} OBJECT_HEADER_NAME_INFO, * POBJECT_HEADER_NAME_INFO;

#define OBJECT_TO_OBJECT_HEADER( o ) \
    CONTAINING_RECORD( (o), OBJECT_HEADER, Body )

#define OBJECT_HEADER_TO_NAME_INFO( oh ) ((POBJECT_HEADER_NAME_INFO) \
    ((oh)->NameInfoOffset == 0 ? NULL : ((PCHAR)(oh) - (oh)->NameInfoOffset)))

typedef struct _DEVICE_EXTENSION
{
    PDEVICE_OBJECT DeviceObject;
    PDEVICE_OBJECT TargetDevice;
    PFILE_OBJECT pFilterFileObject;
    ULONG DeviceExtensionFlags;
    LONG IrpsInProgress;
    KSPIN_LOCK SpinLock;
}DEVICE_EXTENSION, * PDEVICE_EXTENSION;


VOID
KeyDriverUnload( PDRIVER_OBJECT KeyDriver );

BOOLEAN
CancelKeyboardIrp( IN PIRP Irp );

extern POBJECT_TYPE* IoDriverObjectType;

NTSYSAPI
NTSTATUS
NTAPI ObReferenceObjectByName( IN PUNICODE_STRING ObjectName,
                               IN ULONG Attributes,
                               IN PACCESS_STATE AccessState OPTIONAL,
                               IN ACCESS_MASK DesiredAccess OPTIONAL,
                               IN POBJECT_TYPE ObjectType,
                               IN KPROCESSOR_MODE AccessMode,
                               IN OUT PVOID ParseContext OPTIONAL,
                               OUT PVOID* Object );

NTSTATUS
GetUsbKeybordDevice( OUT PDEVICE_OBJECT* UsbDeviceObject );

BOOLEAN
GetAttachedDeviceInfo( IN PDEVICE_OBJECT DevObj );

VOID
GetDeviceObjectInfo( IN PDEVICE_OBJECT DevObj );

NTSTATUS
AttachUSBKeyboardDevice( IN PDEVICE_OBJECT UsbDeviceObject,
                                  IN PDRIVER_OBJECT  DriverObject );

NTSTATUS
AttachPS2KeyboardDevice( IN UNICODE_STRING* DeviceName,
                                  IN PDRIVER_OBJECT  DriverObject,
                                  OUT PDRIVER_OBJECT* FilterDriverObject );

NTSTATUS
KeyReadCompletion( IN PDEVICE_OBJECT DeviceObject,
                            IN PIRP Irp,
                            IN PVOID Context );
NTSTATUS
KeyReadPassThrough( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp );

WCHAR szUsbDeviceName[MAXLEN];

#endif


WSS(Whitecell Security Systems),一个非营利性民间技术组织,致力于各种系统安全技术的研究。坚持传统的hacker精神,追求技术的精纯。
WSS 主页:http://www.whitecell.org/
WSS 论坛:http://www.whitecell.org/forums/