12558网页游戏私服论坛

 找回密码
 立即注册
游戏开服表 申请开服
游戏名称 游戏描述 开服状态 游戏福利 运营商 游戏链接
攻城掠地-仿官 全新玩法,觉醒武将,觉醒技能 每周新区 经典复古版本,长久稳定 进入游戏
巅峰新版攻 攻城掠地公益服 攻城掠地SF 新兵种、新武将(兵种) 进入游戏
攻城掠地公 散人玩家的天堂 新开 进入游戏
改版攻城掠 上线即可国战PK 稳定新区 全新改版,功能强大 进入游戏
少年江山 高福利高爆率 刚开一秒 江湖水落潜蛟龙 进入游戏
太古封魔录 开服送10亿钻石 福利多多 不用充钱也可升级 进入游戏
神魔之道 签到送元宝 稳定开新区 送豪华签到奖励 进入游戏
神奇三国 统帅三军,招揽名将 免费玩新区 激情国战,征战四方 进入游戏
龙符 三日豪礼领到爽 天天开新区 助你征战无双 进入游戏
王者之师 免费领豪华奖励 免费玩新区 6元送6888元宝 进入游戏
三国霸业 战车-珍宝-觉醒-攻城掠地SF-全新玩法 免费玩新区 攻城掠地私服 进入游戏
手游私服盒子 各类免费游戏 0.1折送海量资源 各类手游私服 进入游戏
皇家MU2 《奇迹 2:传奇》韩国网禅公司《奇迹》正统续作。 3D锁视角Mmrpg 暗黑3+传奇+流放之路+奇迹 进入游戏
查看: 168|回复: 0

驱动外挂的原理及检测手段

[复制链接]

345

主题

345

帖子

700

积分

实习版主

Rank: 7Rank: 7Rank: 7

积分
700
发表于 2020-7-16 07:33:25 | 显示全部楼层 |阅读模式
驱动外挂的原理及检测手段

因为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
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
楼主热帖
回复

使用道具 举报

*滑块验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|12558网页游戏私服论坛 |网站地图

GMT+8, 2024-11-26 05:28 , Processed in 0.078125 second(s), 31 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表