【转】某超级模块中游戏双开功能实现
超级模块中有个双开功能,能够双开腾讯的大部分游戏,例如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()来进行判断。