不用想,游戏打开这些文件肯定是在检测虚拟机,这里将文件添加到一个 set 中,每次打开遍历一遍,遇到它检测的文件就直接返回无效句柄。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
HANDLE gh_CreateFileW(...){ for (auto it : DeviceFileBlacklist) { if (CaseInsensitiveContains(lpFileName, it)) { DBG_PRINT("black device \"%ws\" not allowed to open\n", lpFileName); return INVALID_HANDLE_VALUE; } } HANDLE hFile = CreateFileW(...); bool flag = true; for(auto it:FileBlacklist){ if (CaseInsensitiveContains(lpFileName,it)) { flag = false; break; } } DBG_PRINT("CreateFileW called with %ws return value %p\n", lpFileName, hFile); return hFile; }
[Debug Info]black device "\\.\vmmemctl"not allowed to open [Debug Info]black device "C:\Windows\system32\DRIVERS\vm3dmp.sys"not allowed to open [Debug Info]black device "C:\Windows\system32\drivers\vm3dmp_loader.sys"not allowed to open ...
BOOL gh_ProcessNextW(HANDLE hSnapshot, LPPROCESSENTRY32W lppe){ BOOL ret = Process32NextW(hSnapshot, lppe); WCHAR *szExeFile = lppe->szExeFile; while (CaseInsensitiveContains(szExeFile, L"vm")||CaseInsensitiveContains(szExeFile,L"VGAuthService") && ret) { DBG_PRINT("Found Vm in Process name %ws,try to execute again\n", szExeFile); ret = Process32NextW(hSnapshot, lppe); szExeFile = lppe->szExeFile; DBG_PRINT("new Process Name %ws pid=%d ret=%d\n", lppe->szExeFile, lppe->th32ProcessID, ret); } DBG_PRINT("ProcessNextW called with %ws pid=%d ret=%d\n", lppe->szExeFile,lppe->th32ProcessID ,ret); return ret; }
如果找到 vm 相关进程则持续调用,直到进程名不包含 vm 或者为 VGAuthService 即可。下面是一些拦截成功的日志:
1 2 3 4 5
[Debug Info]Found Vm in Process name vm3dservice.exe,try to execute again [Debug Info]new Process Name vmtoolsd.exe pid=3916 ret=1 [Debug Info]Found Vm in Process name vmtoolsd.exe,try to execute again [Debug Info]new Process Name svchost.exe pid=3928 ret=1 [Debug Info]ProcessNextW called with svchost.exe pid=3928 ret=1
ULONGLONG gh_GetTickCount64(){ auto ret = GetTickCount64(); if (st == 0) { DBG_PRINT("GetTickCount64 called %lld\n", ret); st = ret; } else { DBG_PRINT("GetTickCount64 called change %lld to %lld\n", ret, st); ret = st; st = 0; } return ret; }
下面是日志
1 2 3 4
[Debug Info]GetTickCount64 called 4117687 [Debug Info]GetTickCount64 called change 4117718 to 4117687 [Debug Info]GetTickCount64 called 4117734 [Debug Info]GetTickCount64 called change 4117812 to 4117734
该函数调用了,但是没进行检测,提前写好以免后面加这个检测,检测的方式通常是检查 MAC 地址前三字节的信息看厂商是否为 Vmware 之类的。
1 2 3 4 5 6 7 8 9
ULONG gh_GetAdaptersInfo(...){ auto ret = GetAdaptersInfo(AdapterInfo, SizePointer); DBG_PRINT("GetAdaptersInfo called with %p %p return %d\n",...); //换成intel的MAC地址60:45:2E AdapterInfo->Address[0] = 0x60; AdapterInfo->Address[1] = 0x45; AdapterInfo->Address[2] = 0x2E; return ret; }
注册表检测
hook 注册表相关的 api,拦截对应 open 的 key 的名字,实际上也是有调用没检测。
这里输出了一些相关log
1 2 3
[Debug Info]RegOpenKeyExA called with FFFFFFFF80000002 "SYSTEM\CurrentControlSet\services\vm3dmp_loader"0131353000000702B0FF5B0 return0 [Debug Info]CreateFileW called with C:\Program Files (x86)\mihoyo\games\Genshin Impact Game\yuanshen_Data\Persistent\base_res_version_hash return value 0000000000000CDC [Debug Info]black device "C:\Windows\system32\drivers\vm3dmp_loader.sys"not allowed to open
case ThreadHideFromDebugger: if (ThreadInformationLength != 0) { return STATUS_INFO_LENGTH_MISMATCH; } st = ObReferenceObjectByHandle (...); if (!NT_SUCCESS (st)) { return st; } PS_SET_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_HIDEFROMDBG); ObDereferenceObject (Thread); return st; break;
可以看出当 class 为 ThreadHideFromDebugger 时,若 ThreadInformationLength 不为 0 则返回一个错误。因此过这个反调试不能无脑拦截 class 为 ThreadHideFromDebugger 的调用,而应注意这里的 Length 是否为 0。根据拦截 yxxxshen.exe 的调用可以看出。
1 2
[Debug Info]NtSetInformationThread called with handle fffffffe 17 at ... length 1return c0000004 [Debug Info]NtSetInformationThread called with handle fffffffe 17 at ... length 0return0
UINT64 gh_NtSetInformationThread(...){ if(ThreadInformationClass==0x11 && ThreadInformationLength==0){ DBG_PRINT("Try to set ThreadHideFromDebugger,Stop it\n"); return0; } auto ret = NtSetInformationThread(...); DBG_PRINT("lasterror=%d\n", GetLastError()); DBG_PRINT("NtSetInformationThread called with handle %x %d at %p length %d return %x\n",...); return ret; }
[Debug Info]NtQueryInformationThread called with handle fffffffe 17 at ... length 4return c0000004 [Debug Info]NtQueryInformationThread called with handle fffffffe 17 at ... length 1return0 [Debug Info]NtSetInformationThread called with handle fffffffe 17 at ... length 1return c0000004 [Debug Info]NtSetInformationThread called with handle fffffffe 17 at ... length 0return0 [Debug Info]NtQueryInformationThread called with handle fffffffe 17 at ... length 4return c0000004 [Debug Info]NtQueryInformationThread called with handle fffffffe 17 at ... length 1return0
可以看到在前后各成功调用一次 NtQueryInformationThread,并且将 class 设为了 ThreadHideFromDebugger。
这不对吧,query 它能干什么呢,对了,查询信息,可能是需要查询跟隐藏线程调试器相关的字段,那么会不会是因为成功 set 了和没成功 set 了情况不太一样呢?
这里 hook 掉看看前后查询的数据的区别。
1 2 3 4 5 6 7
[Debug Info]past information=34 [Debug Info]after information=00 [Debug Info]NtQueryInformationThread called with handle fffffffe 17 at ... length 1return0 [Debug Info]NtSetInformationThread called with handle fffffffe 17 at ... length 0return0 [Debug Info]past information=95 [Debug Info]after information=01 [Debug Info]NtQueryInformationThread called with handle fffffffe 17 at ... length 1return0
这里我保留关键的 LOG,也可以看出来,它在 set 前后分别查询了一次,第一次查询得知的结果是 0,而成功调用 set 之后得到的结果会是 1,如果仅仅 hook set 不让它调用则会在第二次查询也得到 0 的结果,这便是之前闪退的原因了。
[TID:0000a3dc] Driver Base at 7ff7bb200000 [TID:0000b058] [Warning] Read with execute at 00007FF7BB571B7A [TID:0000b058] Getting data @ ntoskrnl.exe!KdDebuggerEnabled [TID:0000b058] [Warning] Read with execute at 00007FF7BB553B1C [TID:0000b058] Getting data @ ntoskrnl.exe!KdDebuggerNotPresent [TID:0000b058] [Warning] Read with execute at 00007FF7BB53E5C9 [TID:0000b058] Getting data @ ntoskrnl.exe!KdDebuggerEnabled [TID:0000b058] [Warning] Read with execute at 00007FF7BB56B94C [TID:0000b058] Getting data @ ntoskrnl.exe!KdDebuggerNotPresent
在四处地方都有读取调试标志位的操作,计算得到偏移如下
1 2 3 4
0x371b7a 0x353b1c 0x33e5c9 0x36b94c
模拟器中没跑出写 KdDebuggerEnabled 的操作,大概率因为在模拟器中该标志为0才不会执行写的操作,这里在读的 case 这里判断一下将 KdDebuggerEnabled 读取的数值修改为 1。
执行结果如下:
1 2 3 4 5 6 7
[TID:00005aac] Driver Base at 7ff7bb200000 [TID:0000a8c8] [Warning] Read with execute at 00007FF7BB571B7A [TID:0000a8c8] Getting data @ ntoskrnl.exe!KdDebuggerEnabled [TID:0000a8c8] [Warning] change KdDebuggerEnabled flags 00007FF7BB571B7A [TID:0000a8c8] [Info] Write Violation at 00007FF7BB481D20 [TID:0000a8c8] Getting data @ ntoskrnl.exe!KdDebuggerEnabled [TID:0000a8c8] Unhandled Mnemonic.