今天我就来和各人讲讲游戏攻防之Hwid Spoofer
1.什么是Hwid Spoofer呢?
我们逐个来进行解析,Hwid的完备单词也就是hardware id,也就是中文的硬件id的意思
而Spoofer也就是欺骗器的意思了,结合起来就是硬件id的欺骗器
2.Hwid Spoofer的作用是什么?
作用照旧蛮多的哈,但是主要照旧用在和反作弊的对抗上面
我们都知道,一些反作弊利用电脑的Hwid去跟踪监督那些作弊者或者开辟者,防止他们继续在游戏中作弊。
反作弊会获取电脑的Hwid信息后,发送到服务器,然后进行哈希比对,发现这电脑的Hwid作弊太多次了的话,就直接封号处理
而使用了Hwid Spoofer之后,我们电脑的硬件信息全部变了,好比说CPU序列号、显卡序列号、硬盘序列号、网卡Mac之类的全部改变了
这样子反作弊就以为当前电脑是安全的,我们就可以继续在游戏中作弊
3.Hwid Spoofer如何工作的?
Hwid Spoofer可以分为内核层模式的和用户层模式,很多情况下需要两者结合才能真正安全
不同反作弊去获取电脑Hwid信息的方式是不一样的,好比BE和EAC,它们有类似之处也有不同之处
它们有的通过DeviceIoControl函数获取Hwid
它们有的通过RegOpenKey函数获取Hwid
它们有的通过CreateFile函数获取Hwid
.......
了解了它们如何获取Hwid,我们就能通过Hook去操作,也能通过修改内存数据去操作,亦或者通过直接修改真正的硬件数据去操作......
硬盘序列号可以当Hwid
网卡Mac可以当Hwid
注册表里面的一些Guid也可以当Hwid
游戏缓存文件也可以当Hwid
.......
所以说,要应用层+内核层才能得到好的效果
内核层工作 : 修改硬盘序列号,修改网卡Mac,修改显卡Guid,修改主板序列号,修改CPU序列号....
应用层工作 : 修改注册表,删除跟踪缓存文件.....
---------------------------------------------------------------------------------------------------------------我是一条无情的分割线
我们今天将的就是内核层的Hwid Spoofer实现
其实说简单也简单,说不简单也不简单
简单在于只是修改Irp派遣函数和定位到指定内存后修改字节数据
不简单在于考虑兼容性,Windows内核里面的一些实现是随系统的更新而发生变革,两个版本的Win10或许不同
我们今天就针对一下内容进行交换 :
1.硬盘序列号
2.显卡Guid
3.主板序列号
4.网卡Mac
硬盘序列号效果 :
显卡序列号效果 :
先说怎么样获取硬盘序列号吧
硬盘序列号的获取主要是用函数CreateFile传入PhysicalDriveX打开物理硬盘对象
然后用DeviceIoControl函数传入IOCTL_STORAGE_QUERY_PROPERTY或者SMART_RCV_DRIVE_DATA来得到序列号
不懂的可以去参考 : https://blog.csdn.net/abcd19892012/article/details/12970317
在CMD里面输入 wmic diskdrive get serialnumber 也可以得到当前的硬盘序列号
我们第一步就是更换派遣函数
[C++] 纯文本查看 复制代码 // 开始执行hook操作? bool start_hook() { // 更换派遣函数,相当于对函数进行了hook // 固然也可以进行真正的hook操作,pg应该不会查抄这些地方 // hook这里主要是为了修改硬盘的一些guid g_original_partmgr_control = n_util::add_irp_hook(L"\\Driver\\partmgr", my_partmgr_handle_control); // hook这里主要是为了修改硬盘的一些序列号 g_original_disk_control = n_util::add_irp_hook(L"\\Driver\\disk", my_disk_handle_control); // hook这里主要是为了修改磁盘的一些volume g_original_mountmgr_control = n_util::add_irp_hook(L"\\Driver\\mountmgr", my_mountmgr_handle_control); return g_original_partmgr_control && g_original_disk_control && g_original_mountmgr_control; }
可以看到,add_irp_hook函数的实现也是很简单
[C++] 纯文本查看 复制代码 // 更换派遣函数实现hook操作? PDRIVER_DISPATCH add_irp_hook(const wchar_t* name, PDRIVER_DISPATCH new_func) { // 初始化一个unicode string UNICODE_STRING str; RtlInitUnicodeString(&str, name); // 根据名称得到object指针 PDRIVER_OBJECT driver_object = 0; NTSTATUS status = ObReferenceObjectByName(&str, OBJ_CASE_INSENSITIVE, 0, 0, *IoDriverObjectType, KernelMode, 0, (void**)&driver_object); if (!NT_SUCCESS(status)) return 0; // 这里就是修改派遣函数 实现hook操作 PDRIVER_DISPATCH old_func = driver_object->MajorFunction[IRP_MJ_DEVICE_CONTROL]; driver_object->MajorFunction[IRP_MJ_DEVICE_CONTROL] = new_func; n_log::printf("%ws hook %llx -> %llx \n", name, old_func, new_func); // 解除引用防止蓝屏 ObDereferenceObject(driver_object); // 返回原始的派遣函数地址 return old_func; }
Ok,回来,我们来看my_disk_handle_control函数的实现
根据用户层调用我们可以知道,我们只需要关注IOCTL_STORAGE_QUERY_PROPERTY和SMART_RCV_DRIVE_DATA消息
我另外还处理了IOCTL_ATA_PASS_THROUGH,不要也是可以的
[C++] 纯文本查看 复制代码 // 我们的处理函数 NTSTATUS my_disk_handle_control(PDEVICE_OBJECT device, PIRP irp) { PIO_STACK_LOCATION ioc = IoGetCurrentIrpStackLocation(irp); const unsigned long code = ioc->Parameters.DeviceIoControl.IoControlCode; if (code == IOCTL_STORAGE_QUERY_PROPERTY) { if (StorageDeviceProperty == ((PSTORAGE_PROPERTY_QUERY)irp->AssociatedIrp.SystemBuffer)->PropertyId) n_util::change_ioc(ioc, irp, my_storage_query_ioc); } else if (code == IOCTL_ATA_PASS_THROUGH) n_util::change_ioc(ioc, irp, my_ata_pass_ioc); else if (code == SMART_RCV_DRIVE_DATA) n_util::change_ioc(ioc, irp, my_smart_data_ioc); return g_original_disk_control(device, irp); }
可以看到,每一个消息后面的调用了一个change_ioc函数
前两个参数一样,只是后面的一个参数不一样,最后一个参数就是不同消息的不同处理例程
我们主要是要修改掉原始的完成例程为我们自己的完成例程
我们先看看change_ioc具体实现吧
[C++] 纯文本查看 复制代码 // 这个函数主要是辅助hook操作的 bool change_ioc(PIO_STACK_LOCATION ioc, PIRP irp, PIO_COMPLETION_ROUTINE routine) { // 先申请我们的一块内存空间用来保存我们后面需要的数据 PIOC_REQUEST request = (PIOC_REQUEST)ExAllocatePool(NonPagedPool, sizeof(IOC_REQUEST)); if (request == 0) return false; // 保存缓冲区,后面这个缓冲区里面的数据就是要返回用户层的数据,我们要修改掉 request->Buffer = irp->AssociatedIrp.SystemBuffer; // 保存缓冲区的大小 request->BufferLength = ioc->Parameters.DeviceIoControl.OutputBufferLength; // 保存原始的irp上下文 request->OldContext = ioc->Context; // 保存原始的完成例程函数 request->OldRoutine = ioc->CompletionRoutine; // 修改控制位以到达我们想要的效果 ioc->Control = SL_INVOKE_ON_SUCCESS; // 修改irp上下文为我们申请的内存 ioc->Context = request; // 修改为我们自定义的完成例程函数 ioc->CompletionRoutine = routine; return true; }
Ok,我们来看看my_storage_query_ioc完成例程的实现代码
布局体我们都知道了,直接开始解析就行,解析到序列号了就修改掉,然后再让它返回到用户层给那些应用步伐
其实另外那个消息的完成例程和这个代码差不多,也就是布局体不同而已
[C++] 纯文本查看 复制代码 // IOCTL_STORAGE_QUERY_PROPERTY消息的完成例程处理 NTSTATUS my_storage_query_ioc(PDEVICE_OBJECT device, PIRP irp, PVOID context) { // 上下文,其实这个就是change_ioc函数里面申请的那个内存了 if (context) { // 拿到我们前面保存好的数据 n_util::IOC_REQUEST request = *(n_util::PIOC_REQUEST)context; ExFreePool(context); // 判断数据是否是获取硬盘序列号的 if (request.BufferLength >= sizeof(STORAGE_DEVICE_DESCRIPTOR)) { // 这里就是根据布局获取到序列号的偏移 PSTORAGE_DEVICE_DESCRIPTOR desc = (PSTORAGE_DEVICE_DESCRIPTOR)request.Buffer; ULONG offset = desc->SerialNumberOffset; // 偏移有效的话,定位到地方后开始随机化序列号 if (offset && offset < request.BufferLength) { char* serial = (char*)desc + offset; n_util::random_string(serial, 0); } } // 调用原始的完成例程 if (request.OldRoutine && irp->StackCount > 1) return request.OldRoutine(device, irp, request.OldContext); } return STATUS_SUCCESS; }
我们再来看看random_string函数的实现吧,其实也是很简单的
[C++] 纯文本查看 复制代码 // 随机化字符串 char* random_string(char* str, int size) { // 取到字符串长度 if (size == 0) size = (int)strlen(str); if (size == 0) return 0; const int len = 63; const char char_maps[len] = "QWERTYUIOPASDFGHJKLZXCVBNMzxcvbnmasdfghjklqwertyuiop0123456789"; // 开始随机字符串 unsigned long seed = KeQueryTimeIncrement(); for (int i = 0; i < size; i++) { unsigned long index = RtlRandomEx(&seed) % len; str = char_maps[index]; } return str; }
my_smart_data_ioc函数的代码类似,只是布局体不同而已
也就是序列号的位置不同而已,所以也是定位到后直接随机化字符串
[C++] 纯文本查看 复制代码 // SMART_RCV_DRIVE_DATA消息的完成例程处理 NTSTATUS my_smart_data_ioc(PDEVICE_OBJECT device, PIRP irp, PVOID context) { if (context) { n_util::IOC_REQUEST request = *(n_util::PIOC_REQUEST)context; ExFreePool(context); if (request.BufferLength >= sizeof(SENDCMDOUTPARAMS)) { char* serial = ((PIDSECTOR)((PSENDCMDOUTPARAMS)request.Buffer)->bBuffer)->sSerialNumber; n_util::random_string(serial, 0); } if (request.OldRoutine && irp->StackCount > 1) return request.OldRoutine(device, irp, request.OldContext); } return STATUS_SUCCESS; }
简单吧?那除了修改派遣函数的方法之外,那还有没有其它方法呢?固然有了
那就是禁用硬盘的smart,那么什么是smart呢?
以下就是百度百科的解释 :
S.M.A.R.T.,全称为“Self-Monitoring Analysis and Reporting Technology”,即“自我监测、分析及报告技能”。是一种主动的硬盘状态检测与预警系统和规范。通过在硬盘硬件内的检测指令对硬盘的硬件如磁头、盘片、马达、电路的运行情况进行监控、记录并与厂商所设定的预设安全值进行比力,若监控情况将或已超出预设安全值的安全范围,就可以通过主机的监控硬件或软件主动向用户做出警告并进行轻微的主动修复,以提前保障硬盘数据的安全。除一些出厂时间极早的硬盘外,现在大部分硬盘均配备该项技能。
我们可以看一下图片 :
我们可以看到,这里居然也有硬盘的序列号,这里我们也要修改哈!
所以我们要在内核把这个smart给禁用掉
那怎么干呢?
在disk.sys里面有一个微软官方没有发布的函数,叫做DiskEnableDisableFailurePrediction
通过这个函数可以对指定的硬盘设备禁用smart,这简直太棒了不是?
但是这个函数没有发布,所以不同的Win10上面的,这个函数在不同的位置,这就需要我们根据sig去找了
我先教各人怎么找到自己系统里面的sig吧
首先我们要查找到disk.sys驱动步伐,由于是系统原本的驱动,所以一般放在C:\Windows\System32\drivers里面
找到它后,就把它复制一份到桌面来进行操作
我们查找到这个函数了,那么怎么提权sig呢?
我不知道你们大佬是怎么提取了,我其实用比力笨的方法提取的
我再把disk.sys拖进CFF Explorer_CN步伐里面
然后在CFF Explorer_CN步伐里计算得到文件偏移地址,我这里就是0x0000B590
好,我们可以尝试提取48 89 5c 24 10 48 89 74 24 18 57 48 81 ec 90 00 00 00当sig,去验证一下
查找出一个地址而已,而且地址正确,那我们就可以用这个sig
查毒链接 : https://habo.qq.com/file/showdetail?pk=ADcGY11oB2EIOls%2FU2c%3D
来源:http://www.12558.net
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |