*******************************************************
*标题:【原创】逆向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/392fae13d3f48f8e6538dbb3.html
逆向的源代码及BackInC.sys的IDA分析文件打包如下(可替换原BackInC.sys,DDK2600编译下通过,运行环境WinXP、Win2003):
BackInC.rar
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课