NP 双机调试分析
1. 调用了NtQuerySystemInformation(SystemKernelDebuggerInformation) 来检测os是否是调试模式的进程
PROCESS ffff920744e6c300
SessionId: 1 Cid: 1a34 Peb: bc82205000 ParentCid: 111c
DirBase: 23c33c000 ObjectTable: ffffa90b3ffbbb40 HandleCount: 1098.
Image: LineageR.exe
PROCESS ffff920748ec8080
SessionId: 1 Cid: 05f4 Peb: 00262000 ParentCid: 1a34
DirBase: 20acef000 ObjectTable: ffffa90b40aae680 HandleCount: 391.
Image: GameGuard.des
PROCESS ffff9207489ef080
SessionId: 1 Cid: 0c04 Peb: 00233000 ParentCid: 1a34
DirBase: 1a4926000 ObjectTable: ffffa90b3ec27dc0 HandleCount: 469.
Image: GameMon.des
GameMon64.des
PurpleLauncher.exe
Purple.exe
purple-agent.exe
2. NtQueryObject() -> DebugObject 未发现
3. 隐藏kdcom.dll和Dgbv.sys
4. 关闭 NtQuerySystemInformation(ProcessBasicInformation 和 ProcessDebugPort)
检测当前系统是否处于调试状态, 比如双机调试
SystemKernelDebuggerInformation->DebuggerEnabled = FALSE; (双机调试 )
SystemKernelDebuggerInformation->DebuggerNotPresent = FALSE; (双机调试 )
dt _KUSER_SHARED_DATA结构成员
检测 KdDebuggerEnabled 值是否为1 (双机调试 ) 只要这个为真,则np就会退出进程
检测 KdEnteredDebugger 值是否为1 (双机调试 )
检测 KdDebuggerNotPresent 值是否为0,正常应该为1
KiDebugRoutine = KdpStub 则不可调试, 可调试的是kdpTrap (双机调试 ) 待测试
np的保护检测 KdDebuggerEnabled 分析:
首先将 KdDebuggerEnabled + 0x10 处的值设置为1, 然后把系统所有访问 KdDebuggerEnabled 的地方全部改为 KdDebuggerEnabled + 0x10, 启动游戏断下的就是游戏的驱动在访问了.
注意其中有几处mov指令的是保存 KdDebuggerEnabled指针的指针,只需要交里面的指针 + 0x10即可,而不是修改机器码
fffff801`18cd1e6b 490fb70424 movzx rax, word ptr [r12]
fffff801`18cd1e70 488b09 mov rcx, qword ptr [rcx]
fffff801`18cd1e73 4889ea mov rdx, rbp
fffff801`18cd1e76 4881c224000000 add rdx, 24h
fffff801`18cd1e7d 0302 add eax, dword ptr [rdx] // 这里就是在读 KdDebuggerEnabled, 代码应该是vmp混淆了
fffff801`18cd1e7f 8a09 mov cl, byte ptr [rcx] // 这里的cl就是 KdDebuggerEnabled 的值
fffff801`18cd1e81 0fb6f9 movzx edi, cl
fffff801`18cd1e84 418938 mov dword ptr [r8], edi // 这里将KdDebuggerEnabled写入r8, 而r8又是KdDebuggerEnabled的地址
fffff801`18cd1e87 4981c004000000 add r8, 4
fffff801`18cd1e8e 41c70000000000 mov dword ptr [r8], 0
代码附近有看到 0x7FFE0000 所以猜测是使用应用层的检测:
通过读取 KUSER_SHARED_DATA.KdDebuggerEnabled 的值来检测内核模式调试器是否处于活动状态,
KUSER_SHARED_DATA 始终位于每个进程的虚拟地址空间中地址 0x7FFE0000 的用户空间中, 偏移固定是0x2D4.
参考: https://github.com/kyREcon/IsKernelDebuggerPresent
np调用 MmGetSystemRoutineAddress 函数日志
nt!_UNICODE_STRING
"PsSuspendProcess"
+0x000 Length : 0x20
+0x002 MaximumLength : 0x22
+0x008 Buffer : 0xfffff801`6595a3d8 "PsSuspendProcess"
nt!_UNICODE_STRING
"PsResumeProcess"
+0x000 Length : 0x1e
+0x002 MaximumLength : 0x20
+0x008 Buffer : 0xfffff801`6595a408 "PsResumeProcess"
nt!_UNICODE_STRING
"PsGetThreadId"
+0x000 Length : 0x1a
+0x002 MaximumLength : 0x1c
+0x008 Buffer : 0xfffff801`6595a430 "PsGetThreadId"
nt!_UNICODE_STRING
"PsIsProtectedProcess"
+0x000 Length : 0x28
+0x002 MaximumLength : 0x2a
+0x008 Buffer : 0xfffff801`6595a458 "PsIsProtectedProcess"
nt!_UNICODE_STRING
"PsIsProtectedProcessLight"
+0x000 Length : 0x32
+0x002 MaximumLength : 0x34
+0x008 Buffer : 0xfffff801`6595a490 "PsIsProtectedProcessLight"
nt!_UNICODE_STRING
"ObReferenceObjectSafe"
+0x000 Length : 0x2a
+0x002 MaximumLength : 0x2c
+0x008 Buffer : 0xfffff801`6595a4d0 "ObReferenceObjectSafe"
nt!_UNICODE_STRING
"ObRegisterCallbacks"
+0x000 Length : 0x26
+0x002 MaximumLength : 0x28
+0x008 Buffer : 0xfffff801`6595a6c8 "ObRegisterCallbacks"
nt!_UNICODE_STRING
"ObUnRegisterCallbacks"
+0x000 Length : 0x2a
+0x002 MaximumLength : 0x2c
+0x008 Buffer : 0xfffff801`6595a6f0 "ObUnRegisterCallbacks"
解决办法有以下几种:
1. 直接双机环境启动完成后,加载我们的驱动,然后将KdDebuggerEnabled的值写为0。
这种情况下虽然无法在windbg里主动断下,但是使用irp请求通知驱动断下,或者监控进程启动,
或者之前下的断点都是可以让windbg断下。
> KdDebuggerEnabled 有两种方式,直接在代码里引用,则是使用导入表链接
> KdDebuggerEnabled 另一种是使用MmGetSystemRoutine()来获取,这种可以hook EAT表或者直接hook这个函数解决,但要解决PG
2. 修改KUSER_SHARED_DATA.KdDebuggerEnabled值失败,修改过后立即就被重置,
尝试了从物理使用pte修改页面属性为可写,也还是会被写回去,
尝试了使用MmGetPhysicalAddress()转换为物理地址后进行写入,也不能解决。
其他猜测:
使用 IoAllocateMdl 读取 KdDebuggerEnabled,
Hook KdpTrap 该函数无法断下,会导致虚拟机系统卡死
而且infinity无法hook非ssdt和sssdt内的函数。
使用IAT hook系统导出变量, (
经测试可行, 在PsCreateImageLoadNotify回调里拦截内核模块,然后IAT Hook KdDebuggerEnabled变量即可
但注意不能拦截进程,因为那个时候模块还没有加载,得到的导出RVA只是偏移,不是PE展开后的内存地址
)
要想继续研究的话,可以考虑忽略或者干掉pg,直接使用inline hook KdpTrap等其他函数来检测反获取 KdDebuggerEnabled 的方式。
干掉patchGuard过后,可以直接对访问KdDebuggerEnabled的系统函数进行位移0x10的访问, 即将KdDebuggerEnabled变量地址位移
5. 删除Purple开机启动的任务计划
schtasks /delete /tn Purple /f
6. NP应用层的dll注入
dll的注入和驱动的卸载都是由GameMon64.des来完成
且被注入的npggnt64.des应该还有检测GameMon64.des进程是否存在的检测,不存在则自动卸载自己。
7. 经测试CE7.0能使用,CE7.2不能使用,由于CE的驱动只会加载,不会卸载,
主要原因就是CE7.2的驱动会被检测到非法,不是驱动模块被发现,而是驱动内的问题.
经过测试,就算不加载CE7.2的驱动,只开CE7.2也会被检测到,
说明有可能是应用层被检测到了, 经验证只加载驱动,可以正常游戏不会被检测到,
那有可能是游戏驱动下使用KeAttachProcess()切换到用户进程空间检测到进程特征了, 可以尝试只让KeAttachProcess附加游戏目录下的进程.
所以只有CE7.2的驱动加载过了,就必须重启系统或者手动卸载CE7.2的驱动.
总结CE7.2应该是窗口有特征被检测到了,因为使用手动编译CE源码改过的CE7.4都不会被检测到