本帖最后由 untiy 于 2021-2-9 00:11 编辑
今天我就来和大家讲讲游戏攻防之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
硬盘序列号效果 :
清空硬盘序列号效果图 :
显卡序列号效果 :
主板序列号效果 :
网卡Mac效果 :
清空网卡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++] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 | bool start_hook()
{
g_original_partmgr_control = n_util::add_irp_hook(L "\\Driver\\partmgr" , my_partmgr_handle_control);
g_original_disk_control = n_util::add_irp_hook(L "\\Driver\\disk" , my_disk_handle_control);
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++] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | PDRIVER_DISPATCH add_irp_hook( const wchar_t * name, PDRIVER_DISPATCH new_func)
{
UNICODE_STRING str;
RtlInitUnicodeString(&str, name);
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;
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++] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 | 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++] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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;
request->OldContext = ioc->Context;
request->OldRoutine = ioc->CompletionRoutine;
ioc->Control = SL_INVOKE_ON_SUCCESS;
ioc->Context = request;
ioc->CompletionRoutine = routine;
return true ;
}
|
Ok,我们来看看my_storage_query_ioc完成例程的实现代码
结构体我们都知道了,直接开始解析就行,解析到序列号了就修改掉,然后再让它返回到用户层给那些应用程序
其实另外那个消息的完成例程和这个代码差不多,也就是结构体不同而已
[C++] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | NTSTATUS my_storage_query_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 (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++] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 | 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[i] = char_maps[index];
}
return str;
}
|
my_smart_data_ioc函数的代码类似,只是结构体不同而已
也就是序列号的位置不同而已,所以也是定位到后直接随机化字符串
[C++] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 | 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效果 :
我们可以看到,这里居然也有硬盘的序列号,这里我们也要修改哈!
所以我们要在内核把这个smart给禁用掉
那怎么干呢?
在disk.sys里面有一个微软官方没有发布的函数,叫做DiskEnableDisableFailurePrediction
通过这个函数可以对指定的硬盘设备禁用smart,这简直太棒了不是?
但是这个函数没有发布,所以不同的Win10上面的,这个函数在不同的位置,这就需要我们根据sig去找了
我先教大家怎么找到自己系统里面的sig吧
首先我们要查找到disk.sys驱动程序,因为是系统原本的驱动,所以一般放在C:\Windows\System32\drivers里面
找到它后,就把它复制一份到桌面来进行操作
接下来我们把disk.sys拖进64位的IDA里面进行分析,查找到DiskEnableDisableFailurePrediction函数的地方
我们查找到这个函数了,那么怎么提权sig呢?
我不知道你们大佬是怎么提取了,我其实用比较笨的方法提取的
我再把disk.sys拖进CFF Explorer_CN程序里面
我们先要再IDA里面得到虚拟地址,我这里就是0x1C000D990
然后在CFF Explorer_CN程序里计算得到文件偏移地址,我这里就是0x0000B590
得到反汇编代码和机器码,主要是反汇编和机器码对应,好计算得到sig
好,我们可以尝试提取48 89 5c 24 10 48 89 74 24 18 57 48 81 ec 90 00 00 00当sig,去验证一下
查找出一个地址而已,而且地址正确,那我们就可以用这个sig
至此,我们准备工作完成了,可以开始上代码了
[C++] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | bool disable_smart()
{
DWORD64 address = 0;
DWORD32 size = 0;
if (n_util::get_module_base_address( "disk.sys" , address, size) == false ) return false ;
n_log:: printf ( "disk address : %llx \t size : %x \n" , address, size);
DiskEnableDisableFailurePrediction func = (DiskEnableDisableFailurePrediction)n_util::find_pattern_image(address,
"\x48\x89\x5c\x24\x00\x48\x89\x74\x24\x00\x57\x48\x81\xec\x00\x00\x00\x00\x48\x8b\x05\x00\x00\x00\x00\x48\x33\xc4\x48\x89\x84\x24\x00\x00\x00\x00\x48\x8b\x59\x60\x48\x8b\xf1\x40\x8a\xfa\x8b\x4b\x10" ,
"xxxx?xxxx?xxxx????xxx????xxxxxxx????xxxxxxxxxxxxx" );
if (func == 0) return false ;
n_log:: printf ( "DiskEnableDisableFailurePrediction address : %llx \n" , func);
UNICODE_STRING driver_disk;
RtlInitUnicodeString(&driver_disk, L "\\Driver\\Disk" );
PDRIVER_OBJECT driver_object = nullptr ;
NTSTATUS status = ObReferenceObjectByName(&driver_disk, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, nullptr , 0, *IoDriverObjectType, KernelMode, nullptr , reinterpret_cast < PVOID *>(&driver_object));
if (!NT_SUCCESS(status)) return false ;
n_log:: printf ( "disk object address : %llx \n" , driver_object);
PDEVICE_OBJECT device_object_list[100]{ 0 };
RtlZeroMemory(device_object_list, sizeof (device_object_list));
ULONG number_of_device_objects = 0;
status = IoEnumerateDeviceObjectList(driver_object, device_object_list, sizeof (device_object_list), &number_of_device_objects);
if (!NT_SUCCESS(status))
{
ObDereferenceObject(driver_object);
return false ;
}
n_log:: printf ( "number of device objects is : %d \n" , number_of_device_objects);
for ( ULONG i = 0; i < number_of_device_objects; ++i)
{
PDEVICE_OBJECT device_object = device_object_list[i];
status = func(device_object->DeviceExtension, false );
if (NT_SUCCESS(status)) n_log:: printf ( "DiskEnableDisableFailurePrediction success \n" );
ObDereferenceObject(device_object);
}
ObDereferenceObject(driver_object);
return true ;
}
|
后面继续说
---------------------------------------------------------------------------------------我是一条无情的分割线
CPU序列号篇 :
这里说一下你们说的CPU序列号的问题
我为什么处理CPU的序列号呢?因为CPU的序列号是读取不到的!!!
我想,大家已经弄错了一个概念,CPUID 和 CPU序列号!
简单的CPUID是由Type,Model level,Family level和Stepping level组成的
想详细了解的话,请去www.intel.com
所以说CPUID是有重复的!!!你们可以去https://bbs.csdn.net/topics/280064435看看
所以说,反作弊厂家都获取不到CPU的序列号!!!!
------------------------------------------------------------------------------------------我是一条无情的分割线
既然那么多小伙伴想要成品,我就写了一个小demo
前面说了像兼容那么多Win10版本是很麻烦的一件事
所以我只针对我当前的系统版本写了一个
我的版本是Win10(1909),其它版本的Win10很大可能会蓝屏,Win7就不用想了!!!!!!!!!!!!!
所以Win10(1909)的小伙伴可以尝试一下哈
既然你们这么想要成品,那我就写一个兼容性强一点的成品出来哈
今天 2021.2.7 完成一部分功能后就开始放出来 连代码也放出来给你们研究一下哈
查毒链接 : https://habo.qq.com/file/showdetail?pk=ADcGY11oB2EIOls%2FU2c%3D
下载连接 : https://wwx.lanzoui.com/isx2Flbb20f
------------------------------------------------------------------------------------------------------------------我是一条无情的分割线
2021.2.9
这个帖子缓缓再更新,因为又有其它任务了哈,你们不是想要成品嘛?我在另外一个帖子发布了成品工具
传送门 : https://www.52pojie.cn/thread-1368539-1-1.html
喜欢的话可以去下载试试看哈
产品图片
|