【软件逆向-某二次元开放世界冒险游戏反作弊分析报告】此文章归类为:软件逆向。
好久没碰某二次元开放世界冒险游戏了,听说新升级了反作弊,故来一探究竟,并尝试实现一些简单的功能。
这种级别的游戏首先不考虑静态分析,直接跑起来。不出意外肯定不能直接内存读写,想附加调试器也是附加不上的,所以选择先从驱动入手,游戏加载时会加载驱动。
先尝试简单的拦截,方法很多:注册 LoadImage 回调拦截,改驱动名等等等。后者比较好实现,但是运行游戏一段时间会弹窗强制退出。
而如果说让保护加载,自己起一个句柄提权的驱动,则会被弹窗退出。
尝试过在虚拟机里直接启动游戏,不出意外也是弹窗。
使用启动时注入的方式,手动 Create 进程挂起,再远线程注入,可以将 DLL 注入,因为游戏刚运行的时候是没有驱动保护的,自然可以获得正常的游戏句柄。
DLL 直接用 imgui 做 hook 就行,网上框架巨多,先浅浅尝试一下改锁帧的功能,由于这个游戏锁 60 帧,因此玩的很难受,尝试找一下这个值。
反复修改反复找可以找到四个值,地址较小的那个是真实值
imgui里面直接用这个值绑定滑动条,实现帧率解锁。
面临的难点主要是反调试和反虚拟机。
先说结论:R3程序使用了多种类型的反虚拟机技术,大部分通过hook api 的形式可以直接过掉。
Hook CreateFileW
和 CreateFileA
这两个 API,可以看出在尝试打开如下的设备和文件
不用想,游戏打开这些文件肯定是在检测虚拟机,这里将文件添加到一个 set 中,每次打开遍历一遍,遇到它检测的文件就直接返回无效句柄。
只需要对 yxxxshen.exe
和 mxxxbase.dll
两个模块做 IAT hook 即可。下面是拦截成功的一些日志,实际上还有更多的设备,这里不一一展示:
运行过程中会有一段调用了进程遍历的关键函数 Process32NextW
,应该是检测虚拟机的相关进程,这里直接匹配当前虚拟机存在的一些虚拟机特有的进程不让它返回即可。
如果找到 vm 相关进程则持续调用,直到进程名不包含 vm 或者为 VGAuthService
即可。下面是一些拦截成功的日志:
游戏调用了 NtOpenDirectoryObject
和 NtQueryDirectoryObject
两个 API,经过测试发现它打开了 \Device
路径,也就是开始遍历了驱动对象。
这两个 api 可以先hook打印,但是单纯绕过检测 hook 后者即可。
这里也给出一些拦截成功的日志
注意到 mxxxbase.dll 的一个函数
GetTickCount64 获取系统启动以来经过的毫秒数。
它做了 10 次测试,每次测试 10000 条 cpuid 指令运行所需的时间,在虚拟机里,它很大,物理机中几乎每次都为 0。
那么便可以:
强制将两次运行的 cpuid 的时间设为一致。
下面是日志
可以对比得到,hook 前和 hook 后的差距大概是有几十毫秒的,这里会被检测到,通常物理机的间隔都是 0。
该函数调用了,但是没进行检测,提前写好以免后面加这个检测,检测的方式通常是检查 MAC 地址前三字节的信息看厂商是否为 Vmware 之类的。
hook 注册表相关的 api,拦截对应 open 的 key 的名字,实际上也是有调用没检测。
这里输出了一些相关log
但是预计可能是两个一起检测的,即:注册表判断服务是否存在,再判断驱动文件是否存在,有一样不成立就不认为检测到了虚拟机。
过完这些虚拟机检测之后,也是成功可以在虚拟机中启动 yxxxshen.exe 了。
R3的反调试相对比较简单,除了众所周知的 IsDebuggerPresent
之外,早期的版本似乎 hook
了 DbgBreakPoint
和 DbgUiRemoteBreakin
两个 API 来防止调试器附加,现在仍有 hook
,不过只 hook
了 DbgBreak
,并且同样也有 ThreadHideFromDebugger
检测。
NtSetInformationThread
这个 API 本意是设置线程优先级的,其中有一个参数 ThreadInformationClass
,这是一个 THREADINFOCLASS
的枚举类型。
其中注意到 0x11 即为 ThreadHideFromDebugger
,字面意思也不难理解,就是从调试器中隐藏该线程,据看雪一篇文章的分析,该函数关于 ThreadHideFromDebugger
的实现如下
可以看出当 class
为 ThreadHideFromDebugger
时,若 ThreadInformationLength
不为 0 则返回一个错误。因此过这个反调试不能无脑拦截 class
为 ThreadHideFromDebugger
的调用,而应注意这里的 Length 是否为 0。根据拦截 yxxxshen.exe 的调用可以看出。
它连续调用了两次,第一次估计设置 Length
为 1,看是否调用失败,第二次才是真正的反调试,因此需要辨别出这一点。
似乎也不难写出它的 hook 函数?
但是很不幸的是,你会得到一个闪退。
思路似乎中断了,于是考虑看看与之相近的 API,也就是 NtQueryInformationThread
。
可以看到在前后各成功调用一次 NtQueryInformationThread
,并且将 class 设为了 ThreadHideFromDebugger
。
这不对吧,query 它能干什么呢,对了,查询信息,可能是需要查询跟隐藏线程调试器相关的字段,那么会不会是因为成功 set 了和没成功 set 了情况不太一样呢?
这里 hook 掉看看前后查询的数据的区别。
这里我保留关键的 LOG,也可以看出来,它在 set 前后分别查询了一次,第一次查询得知的结果是 0,而成功调用 set 之后得到的结果会是 1,如果仅仅 hook set 不让它调用则会在第二次查询也得到 0 的结果,这便是之前闪退的原因了。
因此对于这个反调试,需要同时 hook NtQueryInformationThread
和 NtSetInformationThread
,严格判断参数,并合理过滤掉一些检测反调试和反-反-反调试的东西。
这个已经被玩烂了的 API 相信是第一个被考虑到的,hook它永远返回 0 就行了。
可以在虚拟机中,附加调试器的情况下运行该二次元开放世界冒险游戏且不报错。
主要尝试分析检测逻辑,尽可能地在不影响功能的情况下过掉检测。
先给结论:反调试主要由驱动创建的一个线程实现,入口点在 0x2f0c0
,重复顺序执行以下逻辑:
下面给出笔者的分析步骤和对应的解决方案。
R0 层的反调试其实反而没那么难,因为 API 就那么几个,HxxxKProtect.sys 的反调试具体表现为,在双机调试的情况下成功加载之后会导致调试器无响应。
根R0调试相关的API找一下即可,通过 IDA 直接搜索导入表或者字符串,得到以下几个跟调试器相关的
根据查阅 MSDN 可知,这两个是内核中的标志位,尝试 hook 将它修改到其它位置。运行之后发现调试器依旧被剥离,但是虚拟机似乎也卡死,并没有蓝屏,在游戏终端中发现了上传日志。
路径中可以看到上传了由于驱动导致的蓝屏(dmp),和自己的信息文件。
info.txt 包含了操作系统的信息,硬件信息和uid信息。
这些信息大概率都是注册表或者一个 API GetSystemFirmwareTable
读出来的,这里为了防止被上传,最好把注册表处理干净,所有跟 Vmware 相关的全部替换掉。
其它特征去除直接用大表哥的 vmloader(大表哥nb),不知道这里怎么访问这两个标志的,所以先尝试 Hook MmGetSystemRoutineAddress,再去导入表替换两个标志位。
创建一个线程,持续输出两个标志位
附加调试器的情况下,输出应当是 1 0
。
加载游戏之后,会发现标志位变为了 0 1
,而 KdDebuggerEnabled
标志位一旦被复位,windbg 会直接被剥离,因此需要阻止。
这里本想尝试加载驱动后,设置硬件断点在 KdDebuggerEnabled 字符串和对应的标志位中,但是似乎会有检测,如果设置了硬断驱动则会加载失败。
通过动调,还是找到了关键的指令。
这一步 RCX 读取了自身驱动导入表的那个指针,存到了 r11 指向的内存
经过多次调试,最终确定写入的指令为
如下所示(本次调试截图与上面截图不是同一次调试)
这里再次确认一下:
第一点很好判断,直接软件断点走过来,观察这条指令前后标志位的变化,从下图来看,基本可以确认了,虽然 KdDebuggerEnabled 和 RAX 指向的地址不同,但是它们一定是映射了同一个物理页。
第二点经过确认,至少第一次触发该指令就是用于修改这个标志位的,把这个指令 Patch 掉之后,会被蓝屏,蓝屏模块为 kdcom.dll
,蓝屏代码为 IRQL_NOT_LESS_OR_EQUAL
。
毫无疑问,调试出问题了,看起来内核调试不仅仅是靠这一个标志位决定的(之前一直不知道)。于是想着去分析一下官方的 API,来看看剥离内核调试器需要做什么样的步骤。分析了一会发现想的都是错的,应该是用了一些我们平时想不到的操作去做的。
也是此时,猛然回首,感觉自己可能正往错误的方向行进,vmp 调起来太累,又不会还原。这时候想到了模拟执行,在队里师傅的帮助下,找到 KACE 这个模拟执行的工具,但是编译什么的都很有问题,且需要自己装 zydis 库,而最新的版本又没适配最新的 zydis,甚至很多结构都改变了,遂尝试自己修一下,下面给出我修好的版本 b6dK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6^5K9h3p5H3K9X3V1J5x3K6y4Q4x3V1k6w2b7f1y4q4i4@1f1K6i4K6R3H3i4K6R3J5
模拟执行最关键的一点就是驱动要能独立加载,比如能直接使用 monitor
或者 sc
这种简单的工具加载,很不幸这个二次元开放世界冒险游戏并不能。分析其 R3 的行为,发现游戏运行的时候会往注册表写下一个 ConfigData
,加载驱动之后会立刻删除,由于中间延时还挺高,所以可以捕获这一过程。
分析这串数据时间成本过高,所以可以选择,先运行 yxxxshen.exe,等到写上去之后再马上运行模拟器,就可以成功在模拟器中跑起来
下面分析 DriverEntry
的执行步骤。
直到这里,模拟执行已经跑不出什么更加细节的东西了,转动态调试。
调试器中手动 rdmsr 拿到返回的地址下硬件读断点,截取到驱动的读取操作,驱动拿到了 MSR 返回的 syscall 地址之后,先判断了该地址是否合法,再尝试读取其中的四字节数,并且有一个循环,循环每次加1,从图中可以清晰看到。
随后将拿到的四字节整数进行了 &0x00F0FFFF
的操作,最后和 0x00108D4C
判断是否相等,转小端序来看,它需要找到类似这样的特征码 4C 8D 1? ??
,这里不管直接在条件满足的分支下断点,在 jne
下方下断点再过来看看它找到了什么位置。
也就是说拿到了 KiSystemServiceRepeat
的地址,通过搜索找到了一篇文章,里面提到了通过 MSR 寻找得到未导出的 KeServiceDescriptorTable
和 KeServiceDescriptorTableShadow
,文章里面使用了特征偏移的方法寻找这两个表,当然不同的系统版本这个值必然也不同,因此该驱动使用了兼容性更好的特征 KiSystemServiceRepeat
函数头的方式去寻找这个偏移,拿到对应的两个表的地址。
随后必然读了两个表,之前模拟执行也得到过该结论
下两个硬件断点发现仅仅是判断了一下两张表的表头内容是否一致
然后读走了熟悉的 0x1d8
(表中系统服务的数量)。
随后调用了 MmIsAddressValid
,在模拟器中可以看到,模拟调用了一个0,因为模拟器的局限性,不太可能还原真实的内核情况,那来看看实际上它取的 0 来自哪里。
是 FFFFF8052FCC79F0
,也就是 SSDT 存的 KiServiceTable
,也许很大概率是发现 SSDT 存的 KiServiceTable
为 0 了,所以模拟器加载驱动才会失败。
在模拟器中简单实现这两个结构之后,驱动就照着走后续加载的逻辑
通过模拟器打印对应的指令去trace,最终在驱动文件找到了对应的调用函数,幸运的是这个函数没被v,看一下大致逻辑
其中,sub_140029100
是它封装的打开文件的函数。这里的调用链路也很符合模拟器跑的结果,既然没 v,那就直接拦截,断点,调试一气呵成。
最终发现对以下 8 个文件进行了打开操作。
并且获取了它们的完整名称,后续进行的操作都 v 了,不过看后面有类似验 hash
的操作,感觉可能是检查这些进程的签名,如果是白签名那么不限制获取游戏的句柄。
(以上分析皆来自 5.2 版本,后续分析使用了 5.3 版本)
随后使用模拟器跑
无非就是注册回调,创建线程,后续主线程退出,因为没有写 wdf 对应的框架的 API,所以模拟器里面不会正确返回 0,但是DriverEntry 的逻辑确实是跑完了。可见反调试并不在 DriverEntry 当中,应该在创建的线程中,创建线程的地址在 +0x2f0c0
的地址上,这个地址虽然是 .text
段,但是 v 了,不太好静态分析,选择模拟器分析。
直接把 DriverEntry 设置为线程入口点试试看跑的结果,关键反调试的 Log 如下:
在四处地方都有读取调试标志位的操作,计算得到偏移如下
模拟器中没跑出写 KdDebuggerEnabled 的操作,大概率因为在模拟器中该标志为0才不会执行写的操作,这里在读的 case 这里判断一下将 KdDebuggerEnabled 读取的数值修改为 1。
执行结果如下:
得到写入 KdDebuggerEnabled 的指令偏移为 0x281d20
将它改为 mov r11d, [r9]
指令,再来看看会不会被剥离调试器,经测试发现 kdcom 还是蓝屏,那么尝试在读取的指令入手,经测试,0x371b7a
若读到了 1 则会写,因此尝试把这里的
改为 xor dx,dx
,同理改掉四个读取的位置,让它们读的值分别为未调试状态读取的值,但是会导致虚拟机被卡死,同时调试器也是未响应的状态,似乎没什么思路了,于是再次考虑动态调试。
因为当时会蓝屏,所以修改系统设置获取完整的 Memory.dmp,一通分析发现 data
段被清零了。
最后通过动态调试发现了一个神秘的函数
看到这个函数基本可以验证刚才的猜想了,那就直接把这个函数覆盖 0xC3
,观察是否蓝屏。
发现已经完全不会蓝屏,调试器也可以正常工作,那就意味着后面可以正常调试游戏驱动保护了。
尝试分析这个函数,研究它是怎么干掉调试器的。
通过 ZwQuerySystemInformation
获取 kdcom.dll
的基址和模块大小。
随后通过解析 PE 文件找到 .data
段的基址和大小。
随后获取到该段的物理地址,使用 MDL 映射该内存(虽然+0x39240函数被 v 了,但可以合理怀疑这个函数就是申请 MDL 使用的,后续通过动态调试也能得到这个结论)。
进入这个 memset 函数,可以发现将 kdcom.dll
的 magic
清零之后 kdcom
工作将不正常,其中 fffff8033e455000
为模块本身的虚拟内存,RCX
指向了 MDL
分配的虚拟内存,此时汇编代码通过 RCX
写入 0
,在现在这种情况下,单步调试会导致调试器直接断开。
绕过的思路也很简单,抹 PE 头是最简单粗暴 & 安全的方式,因为在获取 NT 头的时候会进行 magic 判断,判断不成功自然不会去搞 kdcom 了。
但是都 inline hook 了那么多,再多这一个又有什么所谓呢
不是,这这这这是是是是谁把 C3 放到我游戏驱动的反调试函数头了,这这这是谁不成心……
嗯,一定是太阳黑子射到了内存,把它改成C3了,总不能是一个黑签名驱动调用了 PsSetLoadImageNotifyRoutine
注册了加载模块回调,然后识别 HxxxYOKProtect.sys
再用 MDL
把这个内存给改了吧。
注册了如下回调
yxxxshen 以前是有主动的句柄降权的,但是这个版本测试下来没有开,只能等开了再分析,并且其它回调通过 hook 和模拟执行等手段并没有发现做了什么操作。
不管是直接拦还是 ARK 工具看都很方便,这里还是选择hook,拿到句柄之后直接取消这个回调,看看 CE 能否直接读写内存。
用 ARK 工具可以看到,这里驱动正常加载,且正常注册了进程/线程创建回调和模块加载回调,去特征 CE 不加载 DBK 可直接读取游戏内存,且驱动不会降权句柄。
再来具体分析一下降了哪些权限,虽然回调函数被 v 了,但是不妨碍可以做 hook
,只要比较一下打开 PROCESS_ALL_ACCESS
的游戏句柄,看看最终得到的权限就行了,这里选择hook注册回调的函数,在注册回调的时候拦截,注册上自己的回调,自己的回调再调用真正的回调函数即可。
自己写的驱动记得加 bypass check sign,不然会返回 0xC0000022
。
最终打开游戏进程得到以下 LOG
跟宏定义比对一下。
可以发现降了如下的权限:
没研究出这个回调干了什么,一般情况下应该是会拦截一些黑工具的使用的,比如 CE 和 xdbg
之类的,没想到没拦,为了保证安全把回调直接取消了也可以。
同样也是注册了但是貌似并没有使用,经测试,只把句柄回调去掉之后可以直接远线程注入游戏
感谢 @Qfrost 和 @上学困难户 在分析过程中提供技术支持,本篇报告发布的时候,游戏已经升级到 5.4 版本。
\\.\vmmemctl
C:\Windows\system32\DRIVERS\vm3dmp.sys
C:\Windows\system32\drivers\vm3dmp_loader.sys
...
\\.\vmmemctl
C:\Windows\system32\DRIVERS\vm3dmp.sys
C:\Windows\system32\drivers\vm3dmp_loader.sys
...
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;
}
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
...
[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;
}
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;
}
[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
[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
NTSTATUS gh_NtQueryDirectoryObject(...) {
auto
ret = NtQueryDirectoryObject(...);
auto
info = (POBJECT_DIRECTORY_INFORMATION)Buffer;
for
(
auto
it:DeviceBlackList){
if
(CaseInsensitiveEqual(info->Name.Buffer,it)){
DBG_PRINT(
"NtQueryDirectoryObject name=\"%wZ\" return %d Deny to open!\n"
,
info->Name, info->TypeName, ret);
info->Name = DeniedDevice;
return
0;
}
}
DBG_PRINT(
"NtQueryDirectoryObject name=\"%wZ\",Type=\"%wZ\" return %d\n"
,
info->Name, info->TypeName, ret);
return
ret;
}
NTSTATUS gh_NtQueryDirectoryObject(...) {
auto
ret = NtQueryDirectoryObject(...);
auto
info = (POBJECT_DIRECTORY_INFORMATION)Buffer;
for
(
auto
it:DeviceBlackList){
if
(CaseInsensitiveEqual(info->Name.Buffer,it)){
DBG_PRINT(
"NtQueryDirectoryObject name=\"%wZ\" return %d Deny to open!\n"
,
info->Name, info->TypeName, ret);
info->Name = DeniedDevice;
return
0;
}
}
DBG_PRINT(
"NtQueryDirectoryObject name=\"%wZ\",Type=\"%wZ\" return %d\n"
,
info->Name, info->TypeName, ret);
return
ret;
}
[Debug Info]NtQueryDirectoryObject name=
"gpuenergydrv"
,Type=
"Device"
return
0
[Debug Info]NtQueryDirectoryObject name=
"VMCIHostDev"
return
697297488 Deny to open!
[Debug Info]NtQueryDirectoryObject name=
"00000068"
,Type=
"Device"
return
0
[Debug Info]NtQueryDirectoryObject name=
"gpuenergydrv"
,Type=
"Device"
return
0
[Debug Info]NtQueryDirectoryObject name=
"VMCIHostDev"
return
697297488 Deny to open!
[Debug Info]NtQueryDirectoryObject name=
"00000068"
,Type=
"Device"
return
0
ULONGLONG
st=40000;
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;
}
ULONGLONG
st=40000;
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;
}
[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
[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
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;
}
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;
}
[Debug Info]RegOpenKeyExA called with FFFFFFFF80000002
"SYSTEM\CurrentControlSet\services\vm3dmp_loader"
0 131353 000000702B0FF5B0
return
0
[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
[Debug Info]RegOpenKeyExA called with FFFFFFFF80000002
"SYSTEM\CurrentControlSet\services\vm3dmp_loader"
0 131353 000000702B0FF5B0
return
0
[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
typedef
enum
_THREADINFOCLASS {
ThreadBasicInformation = 0,
//...
ThreadPriorityBoost = 14,
ThreadSetTlsArrayAddress = 15,
// Obsolete
ThreadIsIoPending = 16,
ThreadHideFromDebugger = 17,
//...
MaxThreadInfoClass = 51,
} THREADINFOCLASS;
typedef
enum
_THREADINFOCLASS {
ThreadBasicInformation = 0,
//...
ThreadPriorityBoost = 14,
ThreadSetTlsArrayAddress = 15,
// Obsolete
ThreadIsIoPending = 16,
ThreadHideFromDebugger = 17,
//...
MaxThreadInfoClass = 51,
} THREADINFOCLASS;
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
;
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
;
[Debug Info]NtSetInformationThread called with handle fffffffe 17 at ... length 1
return
c0000004
[Debug Info]NtSetInformationThread called with handle fffffffe 17 at ... length 0
return
0
[Debug Info]NtSetInformationThread called with handle fffffffe 17 at ... length 1
return
c0000004
[Debug Info]NtSetInformationThread called with handle fffffffe 17 at ... length 0
return
0
UINT64
gh_NtSetInformationThread(...) {
if
(ThreadInformationClass==0x11 && ThreadInformationLength==0){
DBG_PRINT(
"Try to set ThreadHideFromDebugger,Stop it\n"
);
return
0;
}
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;
}
UINT64
gh_NtSetInformationThread(...) {
if
(ThreadInformationClass==0x11 && ThreadInformationLength==0){
DBG_PRINT(
"Try to set ThreadHideFromDebugger,Stop it\n"
);
return
0;
}
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 4
return
c0000004
[Debug Info]NtQueryInformationThread called with handle fffffffe 17 at ... length 1
return
0
[Debug Info]NtSetInformationThread called with handle fffffffe 17 at ... length 1
return
c0000004
[Debug Info]NtSetInformationThread called with handle fffffffe 17 at ... length 0
return
0
[Debug Info]NtQueryInformationThread called with handle fffffffe 17 at ... length 4
return
c0000004
[Debug Info]NtQueryInformationThread called with handle fffffffe 17 at ... length 1
return
0
[Debug Info]NtQueryInformationThread called with handle fffffffe 17 at ... length 4
return
c0000004
[Debug Info]NtQueryInformationThread called with handle fffffffe 17 at ... length 1
return
0
[Debug Info]NtSetInformationThread called with handle fffffffe 17 at ... length 1
return
c0000004
[Debug Info]NtSetInformationThread called with handle fffffffe 17 at ... length 0
return
0
[Debug Info]NtQueryInformationThread called with handle fffffffe 17 at ... length 4
return
c0000004
[Debug Info]NtQueryInformationThread called with handle fffffffe 17 at ... length 1
return
0
[Debug Info]past information=34
[Debug Info]after information=00
[Debug Info]NtQueryInformationThread called with handle fffffffe 17 at ... length 1
return
0
[Debug Info]NtSetInformationThread called with handle fffffffe 17 at ... length 0
return
0
[Debug Info]past information=95
[Debug Info]after information=01
[Debug Info]NtQueryInformationThread called with handle fffffffe 17 at ... length 1
return
0
[Debug Info]past information=34
[Debug Info]after information=00
[Debug Info]NtQueryInformationThread called with handle fffffffe 17 at ... length 1
return
0
[Debug Info]NtSetInformationThread called with handle fffffffe 17 at ... length 0
return
0
[Debug Info]past information=95
[Debug Info]after information=01
更多【软件逆向-某二次元开放世界冒险游戏反作弊分析报告】相关视频教程:www.yxfzedu.com