驱动外挂的原理及检测本领
因为PatchGuard技能的存在导致游戏在驱动层的保护不能像从前那样通过SSDT Hook或者IDT Hook来做了,游戏厂家不能擅自关闭PatchGuard来强行Hook.这样留给驱动挂的空间就很大了
我将以一个自瞄挂的原理为例子展示驱动外挂的几种实现方式及检测本领
相同点
要想实现自瞄驱动挂基本上都是读取游戏数据然后直接操作鼠标,差别之处就是操纵鼠标的方式
下面是所有驱动挂的几个相同之处
相同点1 - 获取鼠标的驱动对象
一个自瞄驱动挂的实现起首必然须要获得鼠标的驱动对象,在任何驱动挂里应该都是一样的
可以通过ObReferenceObjectByName来获取鼠标驱动对象
其中ObReferenceObjectByName是未公开的函数,声明一下就能用.其声明如下
NTSTATUS ObReferenceObjectByName( PUNICODE_STRING ObjectName, ULONG Attributes, PACCESS_STATE AccessState, ACCESS_MASK DesiredAccess, POBJECT_TYPE ObjectType, KPROCESSOR_MODE AccessMode, PVOID ParseContest, PVOID* Object);extern POBJECT_TYPE* IoDriverObjectType; // 这是ObjectType参数的实参其中鼠标驱动的名称为\\Driver\\MouClass
那么具体获取鼠标驱动对象过程如下:
NTSTATUS status = STATUS_SUCCESS; UNICODE_STRING mouseName; RtlInitUnicodeString(&mouseName, L"\\Driver\\MouClass"); // 获取到鼠标驱动对象将保存至此 PDRIVER_OBJECT mouseDriver; status = ObReferenceObjectByName(&mouseName, OBJ_CASE_INSENSITIVE, NULL, 0, *IoDriverObjectType, KernelMode, NULL, &mouseDriver); if (!NT_SUCCESS(status)) { return status; } else { // 获取失败了须要解引用 ObDereferenceObject(mouseDriver); }这样一个鼠标驱动对象就在mouseDriver变量中了
相同点2 - 控制驱动挂的本领
用户要想控制驱动起首要通过CreateFile来打开驱动装备
也就是说在驱动里必然先要创建好一个装备供用户打开,具体步骤如下
// 装备名和符号名的定义#define ITRUTH_DEVICE_NAME L"\\Device\\iTruth_Device_20d04fe0"#define ITRUTH_SYMB_NAME L"\\DosDevice\\iTruth_Device" // 函数里装备创建过程 UNICODE_STRING dev_name; RtlInitUnicodeString(&dev_name, ITRUTH_DEVICE_NAME); UNICODE_STRING sddl; // SDDL语法请参考https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/sddl-for-device-objects RtlInitUnicodeString(&sddl, L"D:P(A;;GA;;;BU)"); // 这个请自己定,每个驱动都不能一样 GUID dev_guid = { 0x3cff2c3aL, 0x320f, 0xf5aa, "iTruth" }; // 创建装备 status = IoCreateDeviceSecure( DriverObject, 0, &dev_name, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &sddl, &dev_guid, &iTruth_Device ); if (NT_SUCCESS(status)) { UNICODE_STRING dos_dev_name; RtlInitUnicodeString(&dos_dev_name, ITRUTH_SYMB_NAME); // 为装备绑定符号链接,用户只能通过这个符号链接打开装备 IoCreateSymbolicLink(&dos_dev_name, &dev_name); // 绑定处理函数 DriverObject->MajorFunction[IRP_MJ_CREATE] = iTruth_DriverDispatch; DriverObject->MajorFunction[IRP_MJ_CLOSE] = iTruth_DriverDispatch; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = iTruth_DriverDispatch; }在用户层控制驱动基本上就是DeviceIoControl函数了,此函数向指定驱动发送IO控制码(CTL_CODE),其中控制码可以由一个名为CTL_CODE的宏来定义,这个宏的声明如下
#define CTL_CODE(DeviceType, Function, Method, Access) ( ((DeviceType) Flink) { PITRUTH_DEV_ENTRY dev_entry = CONTAINING_RECORD(pl, ITRUTH_DEV_ENTRY, dev_list); if (dev_entry->flt_dev_obj == DeviceObject) { if (irp_loc->MajorFunction == IRP_MJ_DEVICE_CONTROL) { switch (irp_loc->Parameters.DeviceIoControl.IoControlCode) { //这里可以看到我们判断当前须要的功能部分 case IOCTL_ID_DBGPRINT: KdPrint((Irp->AssociatedIrp.SystemBuffer)); Irp->IoStatus.Status = STATUS_SUCCESS; break; case IOCTL_ID_FUNCHOOK: KdPrint(("Kernel: Hook\n")); Irp->IoStatus.Status = STATUS_SUCCESS; break; default: IoSkipCurrentIrpStackLocation(Irp); return IoCallDriver(dev_entry->next_dev_obj, Irp); } } IoSkipCurrentIrpStackLocation(Irp); return IoCallDriver(dev_entry->next_dev_obj, Irp); } } }}可以看到里面有根据我们的CTL_CODE来实行功能的switch语句,这个就是具体的控制原理
差别的鼠标控制本领
最简单的本领 - 通过过滤装备来控制鼠标
原理
这种本领本质上是使用名为IoAttachDeviceToDeviceStack的函数将自己创建的装备对象绑定到装备对象链中的最高层,然后使用自己Driver Dispatch回调函数来修改鼠标输入
绑定过程如下:
// 在相同点那里已经展示了鼠标驱动装备的获取方式,以是这里省略 // 获取鼠标驱动装备链中的第一个装备 targetDevice = mouseDriver->DeviceObject; // 遍历装备链中的所有装备 while (targetDevice) { // 创建一个过滤装备 status = IoCreateDevice(pDriver, sizeof(DEV_EXTENSION), NULL, targetDevice->DeviceType, targetDevice->Characteristics, FALSE, &filterDevice); if (!NT_SUCCESS(status)) { filterDevice = targetDevice = NULL; return status; } // 在这步绑定 nextDevice = IoAttachDeviceToDeviceStack(filterDevice, targetDevice); if (!nextDevice) { IoDeleteDevice(filterDevice); filterDevice = NULL; return status; } targetDevice = targetDevice->NextDevice; }到了这步绑定好读取的分发函数那么鼠标的读取IRP就会发送到我们的驱动然后我们即可对其处理
pDriver->MajorFunction[IRP_MJ_READ] = MouseIRPMJRead;
下面是鼠标读取IRP的处理
NTSTATUS MouseIRPMJRead(PDEVICE_OBJECT pDevice, PIRP pIrp, PVOID Context){ UNREFERENCED_PARAMETER(pDevice); UNREFERENCED_PARAMETER(Context); PIO_STACK_LOCATION stack; PMOUSE_INPUT_DATA myData; stack = IoGetCurrentIrpStackLocation(pIrp); if (NT_SUCCESS(pIrp->IoStatus.Status)) { // 获取鼠标数据 myData = pIrp->AssociatedIrp.SystemBuffer; // 这里即可开始读取游戏数据并更改鼠标的IRP } if (pIrp->PendingReturned) { IoMarkIrpPending(pIrp); } return pIrp->IoStatus.Status;}检测本领
毕竟是在装备栈上添加自己的装备,那么只须要一个装备黑名单即可.
通过遍历装备栈只要找到了外挂创建的装备即判定非法
判断的焦点代码:
// 装备对象的拓展结构typedef struct _DEVICE_EXTENSION { PDEVICE_OBJECT pDevice; UNICODE_STRING ustrDeviceName; UNICODE_STRING ustrSymLinkName;} DEVICE_EXTENSION, * PDEVICE_EXTENSION; // 检测代码 targetDevice = mouseDriver->DeviceObject; // 遍历装备链中的所有装备 while (targetDevice) { PDEVICE_EXTENSION exdev = (PDEVICE_EXTENSION)targetDevice; // 现在targetDevice->ustrDeviceName就是装备名了,下面即可自行判断这个装备名是否合法 targetDevice = targetDevice->NextDevice; }比较好的方式 - Hook或直接调用MouseServiceClassCallBack
MouseClassServiceCallback是Mouclass提供的类服务回调函数。 驱动程序在其ISR dispatch completion routine中调用类服务回调。 类服务回调将输入数据从装备的输入数据缓冲区传输到类数据队列。
以是这种方式更底层,目前见过的所有模仿鼠标的驱动基本也都是用的这种方式
关键在于找到这个函数,探求这个函数可以遍历装备对象也可以搜特征码
遍历装备对象的探求目标函数的方式如下
// 全局定义MouseClassServiceCallbacktypedef VOID(*MouseClassServiceCallback) ( PDEVICE_OBJECT DeviceObject, PMOUSE_INPUT_DATA InputDataStart, PMOUSE_INPUT_DATA InputDataEnd, PULONG InputDataConsumed );// 保存原始函数MouseClassServiceCallback orig_MouseClassServiceCallback = NULL;// 我们已经获取过鼠标的装备驱动保存在了mouseDriver中,现在获取鼠标的端口驱动UNICODE_STRING mouNtName;PDRIVER_OBJECT mouhidDriverObj;RtlInitUnicodeString(&mouNtName, L"\\Driver\\Mouhid");status = ObReferenceObjectByName( &mouNtName, OBJ_CASE_INSENSITIVE, NULL, 0, IoDriverObjectType, KernelMode, NULL, &mouhidDriverObj );if (!NT_SUCCESS(status)) { return status;}// 遍历mouclass下所有装备PDRIVER_OBJECT targetDriverObj = mouseDriver->DeviceObject;ULONG mouDriverStart = (ULONG)GetModlueBaseAdress("mouclass.sys", 0);ULONG mouDriverSize = 0x2000;MouseClassServiceCallback* MouSrvAddr = NULL;while(targetDriverObj){ // 遍历我们先找到的端口驱动的装备扩展下的每个指针 for(PBYTE exdev = (PBYTE)mouhidDriverObj; i mouDriverStart)&&(tmp < (PBYTE)mouDriverStart+mouDriverSize)) { orig_MouseClassServiceCallback = (MouseClassServiceCallback)tmp; MouSrvAddr = (PVOID*)exdev; goto Done; } } targetDriverObj = targetDriverObj->NextDevice;}Done:// 这里就获取到了哪个函数的地址并保存到了orig_MouseClassServiceCallback中现在我介绍那两种方式以及检测本领
Hook方式
原理
直接改函数地址即可
*MouSrvAddr = myHookFuncAddr;
这种方式比较方便的地方是不用自己构建MOUSE_INPUT_DATA
检测本领
这种方式直接检测函数地址即可
直接调用方式
原理
我们刚刚获取了函数指针那么直接调用就能使鼠标移动,贫苦的是要自己构造MOUSE_INPUT_DATA
检测本领
这种方式目前没有很好的检测本领,假如各位大佬有办法请务必让本菜见识下
杂谈
固然也是有通过Hook IDT的鼠标中断来实现的,这种方式贫苦的地方在于要为CPU里每个焦点都做一遍Hook操作.而且也能简单的通过特征码的方式检测出来
最重要的一点其实还在于假如做IDT Hook那么还不如直接修改空闲中断的DPL和中断程序地址来做中断提权,让我们的外挂程序有Ring0权限.我感觉这样才是更好的办法
来源:http://www.12558.net
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |