*******************************************************
*标题:【原创】逆向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