超级模块中有个双开功能,能够双开腾讯的大部分游戏,例如DNF。于是就下载模块下来分析了一下。
随便在网上找一个用超级模块写的双开工具。然后OD载入,下bp DeleteFileA断点。
为什么要下deleteFile断点,而不是CreateFile,是因为该模块注册驱动文件后就会将驱动文件删除。
下了断点之后点击启动双开的时候,OD断下。查看堆栈,可以看到写出的驱动路径。
将驱动文件拷贝出来,文件大小只有3kb,用IDA打开。
INIT:00010985 ; NTSTATUS __stdcall DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
INIT:00010985 public DriverEntry
INIT:00010985 DriverEntry proc near
INIT:00010985
INIT:00010985 DriverObject = dword ptr 8
INIT:00010985 RegistryPath = dword ptr 0Ch
INIT:00010985
INIT:00010985 mov edi, edi
INIT:00010987 push ebp
INIT:00010988 mov ebp, esp
INIT:0001098A mov eax, dword_10904
INIT:0001098F test eax, eax
INIT:00010991 mov ecx, 0BB40h
INIT:00010996 jz short loc_1099C
INIT:00010998 cmp eax, ecx
INIT:0001099A jnz short loc_109BF
INIT:0001099C
INIT:0001099C loc_1099C: ; CODE XREF: DriverEntry+11j
INIT:0001099C mov edx, ds:KeTickCount
INIT:000109A2 mov eax, offset dword_10904
INIT:000109A7 shr eax, 8
INIT:000109AA xor eax, [edx]
INIT:000109AC and eax, 0FFFFh
INIT:000109B1 mov dword_10904, eax
INIT:000109B6 jnz short loc_109BF
INIT:000109B8 mov eax, ecx
INIT:000109BA mov dword_10904, eax
INIT:000109BF
INIT:000109BF loc_109BF: ; CODE XREF: DriverEntry+15j
INIT:000109BF ; DriverEntry+31j
INIT:000109BF not eax
INIT:000109C1 mov dword_10900, eax
INIT:000109C6 pop ebp
INIT:000109C7 jmp sub_106F8
INIT:000109C7 DriverEntry endp
入口函数处没什么特殊的,跳到了sub_106F8,下面来看看这个函数里面具体实现。
.text:000106F8 ; int __stdcall sub_106F8(PDRIVER_OBJECT DeviceObject, int)
.text:000106F8 sub_106F8 proc near ; CODE XREF: DriverEntry+42j
.text:000106F8
.text:000106F8 SymbolicLinkName= UNICODE_STRING ptr -10h
.text:000106F8 DestinationString= UNICODE_STRING ptr -8
.text:000106F8 DeviceObject = dword ptr 8
.text:000106F8
.text:000106F8 mov edi, edi
.text:000106FA push ebp
.text:000106FB mov ebp, esp
.text:000106FD sub esp, 10h
.text:00010700 push esi
.text:00010701 mov esi, [ebp+DeviceObject]
.text:00010704 push edi
.text:00010705 mov edi, ds:RtlInitUnicodeString
.text:0001070B push offset word_106A6 ; SourceString
.text:00010710 lea eax, [ebp+DestinationString]
.text:00010713 push eax ; DestinationString
.text:00010714 mov dword ptr [esi+38h], offset sub_10486 //设置IRP处理函数
.text:0001071B mov dword ptr [esi+40h], offset sub_10486
.text:00010722 mov dword ptr [esi+70h], offset sub_104AA
.text:00010729 mov dword ptr [esi+34h], offset sub_10670
.text:00010730 call edi ; RtlInitUnicodeString //初始化设备名称
.text:00010732 lea eax, [ebp+DeviceObject]
.text:00010735 push eax ; DeviceObject
.text:00010736 push 0 ; Exclusive
.text:00010738 push 0 ; DeviceCharacteristics
.text:0001073A push 22h ; DeviceType
.text:0001073C lea eax, [ebp+DestinationString]
.text:0001073F push eax ; DeviceName
.text:00010740 push 0 ; DeviceExtensionSize
.text:00010742 push esi ; DriverObject
.text:00010743 call ds:IoCreateDevice //创建设备
.text:00010749 test eax, eax
.text:0001074B jl short loc_10780
.text:0001074D push offset word_106CA ; SourceString
.text:00010752 lea eax, [ebp+SymbolicLinkName]
.text:00010755 push eax ; DestinationString
.text:00010756 call edi ; RtlInitUnicodeString //初始化符号链接名称
.text:00010758 lea eax, [ebp+DestinationString]
.text:0001075B push eax ; DeviceName
.text:0001075C lea eax, [ebp+SymbolicLinkName]
.text:0001075F push eax ; SymbolicLinkName
.text:00010760 call ds:IoCreateSymbolicLink //创建符号链接
.text:00010766 mov esi, eax
.text:00010768 test esi, esi
.text:0001076A jge short loc_10779 //创建成功就跳过去
.text:0001076C push [ebp+DeviceObject] ; DeviceObject
.text:0001076F call ds:IoDeleteDevice //创建失败则删除设备
.text:00010775 mov eax, esi
.text:00010777 jmp short loc_10780
.text:00010779 ; ---------------------------------------------------------------------------
.text:00010779
.text:00010779 loc_10779: ; CODE XREF: sub_106F8+72j
.text:00010779 call sub_105C8
.text:0001077E xor eax, eax
.text:00010780
.text:00010780 loc_10780: ; CODE XREF: sub_106F8+53j
.text:00010780 ; sub_106F8+7Fj
.text:00010780 pop edi
.text:00010781 pop esi
.text:00010782 leave
.text:00010783 retn 8
.text:00010783 sub_106F8 endp
创建设备,创建符号链接,用于与R3层通讯。那么主要的功能实现就在 call sub_105C8 这个函数里面了。
.text:000105C8 sub_105C8 proc near ; CODE XREF: sub_106F8:loc_10779p
.text:000105C8 mov eax, ds:KeServiceDescriptorTable //得到系统服务描述表的地址
.text:000105CD push esi
.text:000105CE mov esi, [eax] //得到表中第一个函数的位置
.text:000105D0 add esi, 0ACh // 加上0xAC,表中每个函数地址占4个字节,0ac / 4 = 43
.text:000105D6 mov eax, [esi]
.text:000105D8 mov dword_1090C, eax //保存ssdt表中的43号函数地址到全局变量
.text:000105DD call sub_104EA //通过cr0寄存器去掉写保护
.text:000105E2 call ds:KeRaiseIrqlToDpcLevel //提升中断请求等级
.text:000105E8 mov cl, al ; NewIrql
.text:000105EA mov dword ptr [esi], offset MyNtCreateMutant //SSDT HOOK 43号函数
.text:000105F0 call ds:KfLowerIrql
.text:000105F6 sti //恢复cr0
.text:000105F7 push eax
.text:000105F8 mov eax, dword_10908
.text:000105FD mov cr0, eax
.text:00010600 pop eax
.text:00010601 xor eax, eax
.text:00010603 inc eax
.text:00010604 pop esi
.text:00010605 retn
.text:00010605 sub_105C8 endp
通过上面可以看出驱动仅仅是对 SSDT表中的43号函数进行了hook。
用XT看了一下43号 即:NtCreateMutant函数。
而用户层的CreateMetux函数最终调用的就是内核层的NtCreateMutant。
也就是说 DNF判断游戏双开的方法之一就是互斥体。
最后再来看看HOOK函数中是怎样处理的。
.text:00010574 mov edi, edi
.text:00010576 push ebp
.text:00010577 mov ebp, esp
.text:00010579 push ecx
.text:0001057A push ecx
.text:0001057B push esi
.text:0001057C push offset SourceString ; sourceString = "\\basenamedobjects\\dbefeuate_ccen_khxfor"...
.text:00010581 lea eax, [ebp-4] ; 局部变量1
.text:00010584 push eax ; DestinationString
.text:00010585 call ds:RtlInitUnicodeString ; 初始化一个内容为\\basenamedobjects\\dbefeuate_ccen_khxfor的PUNICODE_STRING到局部变量1
.text:0001058B mov esi, [ebp-8] ; 局部变量2
.text:0001058E test esi, esi
.text:00010590 jz short loc_105A5 ; 如果局部变量2为0,跳
.text:00010592 push 0 ; 不区分大小写
.text:00010594 push dword ptr [esi+8] ; String2 ;
.text:00010597 lea eax, [ebp-8] ; 局部变量2
.text:0001059A push eax ; String1
.text:0001059B call ds:RtlEqualUnicodeString ;变量2 与参数3中的Unicode_String比较
.text:000105A1 test al, al
.text:000105A3 jnz short loc_105BB ; 如果两个字符串相等 函数直接返回
.text:000105A5
.text:000105A5 loc_105A5: ; 如果字符串不相等
.text:000105A5 push [ebp+arg_C]
.text:000105A8 mov eax, dword_1090C ;dword_1090C 保存真实的NtCreateMutant函数地址
.text:000105AD push esi
.text:000105AE push [ebp+arg_4]
.text:000105B1 mov dword_10910, eax
.text:000105B6 push [ebp+arg_0]
.text:000105B9 call eax ; dword_1090C ;调用真实的NtCreateMutant
.text:000105BB
.text:000105BB loc_105BB: ; CODE XREF: sub_10574+2Fj
.text:000105BB xor eax, eax
.text:000105BD pop esi
.text:000105BE leave
.text:000105BF retn 10h
.text:000105BF sub_10574 endp
到这里之后可以看出在 hook NtCreateMutant的 处理函数当中 进行了判断。
NtCreateMutant的原型为:
NTSTATUS
NtCreateMutant (
__out PHANDLE MutantHandle,
__in ACCESS_MASK DesiredAccess,
__in_opt POBJECT_ATTRIBUTES ObjectAttributes,
__in BOOLEAN InitialOwner
)
typedef struct _OBJECT_ATTRIBUTES {
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor; // Points to type SECURITY_DESCRIPTOR
PVOID SecurityQualityOfService; // Points to type SECURITY_QUALITY_OF_SERVICE
} OBJECT_ATTRIBUTES;
参数3中的UNICODE_STRING进行对比,如果值为\\basenamedobjects\\dbefeuate_ccen_khxfor,那么NtCreateMetux返回失败。
另外,根据双开工具上面的 隐藏游戏窗口功能 可以看出DNF双开限制是 通过互斥体 跟 FindWindow()来进行判断。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课