*******************************************************
*标题:【原创】逆向BackInC驱动,实现虚拟键鼠模拟 *
*作者:踏雪流云                                                     *
*日期:2011年3月6号                                            *
*声明:本文章的目的仅为技术交流讨论                       *
*******************************************************
由于最近在做应用层程序开发,已经好几个月没搞逆向了,感觉有些生疏了,这样不好,于是便有了这篇文章;花了一周时间来分析,希望能给我等菜鸟一点帮助。
第一次逆向驱动,有什么不对的地方还望指正。
BackInC是一个想搞游戏外挂的朋友发给我的,BackInC.sys驱动实现了驱动级的键鼠模拟,NT式的,可动态卸载。为什么我会对这个技术这么感兴趣呢?这可以追溯到本人在上大学三年级时,做的一个叫做“手控鼠标”的项目。原理很简单:首先,摄像头采集到用户做出的不同手势动作;然后,进行手势识别(图像处理+BP神经网络);最后,将其转换为控制系统的不同动作,以达到操控windows的目的。因此,需要实现驱动级键鼠模拟(由于需要使用3D游戏来演示效果,因此应用层API就不太靠谱),当时的做法是将DDK Sample下的kbfiltr和moufiltr直接改造来实现的,但是这个是wdm的,感觉很不爽……
本文只是分享和探讨技术,希望BackInC作者见谅!
闲话说了不少,下面言归正传。
以键盘为例:KbdClass.sys是键盘的类驱动,无论是USB键盘,还是PS/2键盘都要经过它的处理;在键盘类驱动之下,和实际硬件打交道的驱动叫做“端口驱动”,比如:i8042prt.sys是ps/2键盘的端口驱动,Kbdhid.sys是USB键盘的端口驱动。
端口驱动与类驱动之间的协助机制:
键盘中断导致键盘中断服务例程被执行,导致最终i8042prt的I8042KeyboardInterruptService被执行。
在I8042KeyboardInterruptService中,从端口读取扫描码,放到一个KEYBOARD_INPUT_DATA结构中。并把这个结构放到i8042prt的输入队列中。最后会调用内核api函数KeInsertQueueDpc。
在这个调用中会调用上层KbdClass.sys中处理输入的回调函数KeyboardClassServiceCallback,取走i8042prt的输入数据队列里的数据。
因此,主动调用KeyboardClassServiceCallback就可以实现虚拟键盘模拟;Hook KeyboardClassServiceCallback当然就可以实现键盘记录了(kbdclass驱动是在I8042prt和kbdhid这两个驱动之上的,因此键盘记录能对PS/2和USB键盘同时生效)。
本文的重点来了,如何定位KeyboardClassServiceCallback的地址和kbdclass的DeviceObject指针呢?
代码如下:

代码:
#pragma INITCODE
NTSTATUS SearchKbdDevice()
{
  //定义一些局部变量
  NTSTATUS status = STATUS_UNSUCCESSFUL;
  UNICODE_STRING uniNtNameString;
  PDEVICE_OBJECT pUsingDeviceObject = NULL;//目标设备
  PDRIVER_OBJECT KbdDriverObject = NULL;//类驱动
  PDRIVER_OBJECT KbdhidDriverObject = NULL;//USB 端口驱动
  PDRIVER_OBJECT Kbd8042DriverObject = NULL;//PS/2 端口驱动
  PDRIVER_OBJECT UsingDriverObject = NULL;

  PVOID KbdDriverStart = NULL;//类驱动起始地址
  ULONG KbdDriverSize = 0;
  PBYTE UsingDeviceExt = NULL;

  //这部分代码打开PS/2键盘的驱动对象
  RtlInitUnicodeString(&uniNtNameString,PS2KBD_DRIVER_NAME);
  status = ObReferenceObjectByName(
    &uniNtNameString,
    OBJ_CASE_INSENSITIVE,
    NULL,
    0,
    IoDriverObjectType,
    KernelMode,
    NULL,
    (PVOID*)&Kbd8042DriverObject
    );
  if (!NT_SUCCESS(status))
  {
    DbgPrint("Couldn't get the PS/2 driver Object\n");
  }
  else
  {
    //解除引用
    ObDereferenceObject(Kbd8042DriverObject);
    DbgPrint("Got the PS/2 driver Object\n");
  }

  //打开USB 键盘的端口驱动
  RtlInitUnicodeString(&uniNtNameString,USBKBD_DRIVER_NAME);
  status = ObReferenceObjectByName(
    &uniNtNameString,
    OBJ_CASE_INSENSITIVE,
    NULL,
    0,
    IoDriverObjectType,
    KernelMode,
    NULL,
    (PVOID*)&KbdhidDriverObject
    );
  if (!NT_SUCCESS(status))
  {
    DbgPrint("Couldn't get the USB driver Object\n");
  }
  else
  {
    ObDereferenceObject(KbdhidDriverObject);
    DbgPrint("Got the USB driver Object\n");
  }

  //如果同时有两个键盘,使用i8042prt
  if (Kbd8042DriverObject && KbdhidDriverObject)
  {
    DbgPrint("More than one keyboard!\n");
  }

  //两种键盘都没有 也返回失败
  if (!Kbd8042DriverObject && KbdhidDriverObject)
  {
    DbgPrint("Not found keyboard!\n");
    return STATUS_UNSUCCESSFUL;
  }

  //找到合适的驱动对象
  UsingDriverObject = Kbd8042DriverObject ? Kbd8042DriverObject : KbdhidDriverObject;

  RtlInitUnicodeString(&uniNtNameString, KBD_DRIVER_NAME);
  status = ObReferenceObjectByName ( 
    &uniNtNameString, 
    OBJ_CASE_INSENSITIVE, 
    NULL, 
    0, 
    IoDriverObjectType, 
    KernelMode, 
    NULL, 
    (PVOID*)&KbdDriverObject 
    ); 
  // 如果失败了就直接返回
  if(!NT_SUCCESS(status)) 
  { 
    DbgPrint("Couldn't get the MyTest Device Object\n"); 
    return STATUS_UNSUCCESSFUL; 
  }
  else
  {
    // 这个打开需要解应用。
    ObDereferenceObject(KbdDriverObject);
  }
  //如果成功,找到Kbdclass开始地址和大小 
  KbdDriverStart =KbdDriverObject->DriverStart; 
  KbdDriverSize = KbdDriverObject->DriverSize; 

  //遍历UsingDriverObject下的设备对象,找到Kbdclass Attach的那个设备对象
  pUsingDeviceObject = UsingDriverObject->DeviceObject;
  PDEVICE_OBJECT pAttachedKbdDevice;
  while (pUsingDeviceObject)
  {
Label_Continue:
    pAttachedKbdDevice=KbdDriverObject->DeviceObject;
    while(pAttachedKbdDevice)
    {
      PDEVICE_OBJECT pAttached=pUsingDeviceObject->AttachedDevice;
      while(pAttached)
      {
        if(pAttachedKbdDevice==pAttached)
        {
          DbgPrint("pAttachedKbdDevice :%8x\n",pAttachedKbdDevice);
          
          UsingDeviceExt=(PBYTE)pUsingDeviceObject->DeviceExtension;
          //遍历找到的端口驱动设备扩展下的每个指针
          for (int i=0;i<4096;i++,UsingDeviceExt += sizeof(PBYTE))
          {
            if (!MmIsAddressValid(UsingDeviceExt))
            {
              pUsingDeviceObject=pUsingDeviceObject->AttachedDevice;
              goto Label_Continue;
            }

            //在端口驱动的设备扩展中,找到了类驱动的设备对象,填好类驱动设备对象后继续
            PVOID pTemp;
            pTemp = *(PVOID*)UsingDeviceExt;
            if (pTemp == pAttachedKbdDevice)
            {
              g_KbdCallBack.classDeviceObject = (PDEVICE_OBJECT)pTemp;
              DbgPrint("classDeviceObject %8x\n",pTemp);

              pTemp = *(PVOID*)(UsingDeviceExt+4);
              if ((pTemp > KbdDriverStart)&&(pTemp < (PBYTE)KbdDriverStart+KbdDriverSize)&&MmIsAddressValid(pTemp))
              {
                //记录回调函数的地址
                g_KbdCallBack.serviceCallBack = (Kbd_ServiceCallback)pTemp;
                g_KbdCallBack.bSearch=true;
                status=STATUS_SUCCESS;
                DbgPrint("serviceCallBack :%8x\n",pTemp);
                goto Label_Exit;
              }
              break;
            }
          }
          pUsingDeviceObject=pUsingDeviceObject->AttachedDevice;
          goto Label_Continue;
        }
        pAttached=pAttached->AttachedDevice;
      }
      pAttachedKbdDevice=pAttachedKbdDevice->NextDevice;
    }
    pUsingDeviceObject=pUsingDeviceObject->NextDevice;
  }

Label_Exit:
  //如果成功找到,可以返回了
  return status;
}
原理如下:
1.KeyboaredClassServiceCallback函数指针应该保存在i8042prt生成的设备的自定义设备扩展中。
2.KeyboaredClassServiceCallback函数的地址应该应该在KbdClass的地址空间范围内。
3.KbdClass生成的一个设备对象的指针也保存在那个设备扩展中,而且在我们要找的函数指针之前。
事实证明,KeyboaredClassServiceCallback确实保存在i8042prt生成的设备的自定义设备扩展中;而MouseClassServiceCallback则保存在i8042prt生成的设备的AttachedDevice设备的自定义设备扩展中。
参考文章:http://hi.baidu.com/eith/blog/item/3...e6538dbb3.html

逆向的源代码及BackInC.sys的IDA分析文件打包如下(可替换原BackInC.sys,DDK2600编译下通过,运行环境WinXP、Win2003):
BackInC.rar