测试环境:
Windows7 x64 虚拟机 win10 1809 x64 虚拟机
0x0 起因
小伙伴 @DBDig 最近也在玩APEX这个游戏,因为这个游戏有EAC保护所以有反调试,遂准备安排它。R3降权pass以后,下断没反应还又卡死又崩溃的。于是他自写了一个exe来模拟调试器的行为,万万没想到,EAC竟然对我们的exe做了这种事!他居然hook了我们的 WaitForDebugEvent这个函数,
头部 retn 10 简单粗暴,怪不得啥都没反应,都收不到调试事件了还调试个锤子?据实测,CE、x64dbg均被同样处理了。且只有游戏被调试了才会被处理,如果就开在那里则不会被处理。可以排除是定时扫进程这种操作了。
问题来了,EAC是怎么找到我们的调试器进程呢?甚至连“自写调试器”也不能幸免?
0x1 猜测
喜闻乐见的瞎蒙时间。被调试程序和调试器之间一定存在着某种神秘的联系,才能让EAC顺藤摸瓜找到我们的调试器。首先想到的就是父进程,父进程是一种手段,然而在调戏游戏的时候大多数采用附加而没有多少机会直接使用调试器启动游戏,因此父进程被排除了。
既然我们从模拟调试器的行为上发现了游戏对我们的程序做了手脚,就继续从这方面入手吧。于是我找到了这个函数:DebugActiveProcess。翻一下msdn,该函数定义如下:
调用完这个函数以后发现它做了一件很重要的事情,那就是创建了一个DEBUG_OBJECT对象。
实际上DEBUG_OBJECT这个东西实际上是由NtCreateDebugObject这个函数创建的。那么DEBUG_OBJECT究竟是什么,我们挂上windbg来找找看吧。
uf nt!NtCreateDebugObject
所以这个“DbgkDebugObjectType”就是我们要找的东西了。
dq fffff800`04057f40
dt _OBJECT_TYPE fffffa80`18dd8080
dt _OBJECT_TYPE_INITIALIZER 0xfffffa80`18dd8080
0x3 实验
那么,我们大概了解DEBUG_OBJECT了。经常和游戏保护打交道的同学应该知道,某P保护有一个名为“DebugPort清零”的行为,实际上是干了“ValidAccessMask”。所以,DebugPort里面存了什么?就是我们的DEBUG_OBJECT了。
打开一个记事本程序,使用原版OD附加,然后:
dt _eprocess fffffa8018fd7b30 -y DebugPort
哦豁,发现DebugPort并不是null(ptr)。
其实,不止这里有这个DEBUG_OBJECT,在每条线程的TEB里面也存了这个东西:
就在这个DbgSsReserved里面
到这里只能说明“我被调试了”,但是不能说明“谁调试了我”。至此,想要从DebugPort定位调试器这条路好像走不通了。
前文已经说了,调试器和被调试程序存在某种必要的联系,要想看清楚其中的关键,还得从调试流程入手。
所以我们来看下喜闻乐见的csrss:
DebugActiveProcess -> CsrClientCallServer
所以调试器使用这个方式向CSRSS请求DebugProcess服务,然后呢,有这样一个结构叫做: CSR_PROCESS, 这个结构中包含了调试器进程的进程ID和被调试程序的进程ID。然而,这个结构是没有导出的,以下是定义:
typedef struct _CSR_PROCESS
{
CLIENT_ID ClientId;
struct _LIST_ENTRY ListLink;
struct _LIST_ENTRY ThreadList;
struct _CSR_NT_SESSION* NtSession;
ULONG ExpectedVersion;
void* ClientPort;
char* ClientViewBase;
char* ClientViewBounds;
void* ProcessHandle;
ULONG SequenceNumber;
ULONG Flags;
ULONG DebugFlags;
ULONG ReferenceCount;
ULONG ProcessGroupId;
ULONG ProcessGroupSequence;
ULONG fVDM;
ULONG ThreadCount;
ULONG LastMessageSequence;
ULONG NumOutstandingMessages;
ULONG ShutdownLevel;
ULONG ShutdownFlags;
LUID Luid;
void* ServerDllPerProcessData[1];
} CSR_PROCESS, *PCSR_PROCESS;
所以我的猜测就是EAC定位到了这个存储这个结构的链表,然后从里面找到了调试器进程。
以上猜测已被证伪。
0x4 结论
实际上,EAC仅仅通过一个Obcall就取到了调试进程的信息,当调试器附加时,CreateRemoteThread创建的线程句柄会被Obcall捕获,从而检测到调试器,至于从csrss里面爆搜结构特征,然后定位链表再枚举,这种使用未公开结构的方法EAC还没有做到那么高深的地步,是我们想多了。
参考资料
《软件调试》 张银奎
MSDN
WRK
ReactOS
代码见:
附上一份素材,见附件。
虽然BDBig基于愧疚心里,删除了他的贴子仅仅留下代码,然而我们的 山总 还在疯狂的骂他,所以我觉得,还是应该让这份记录永久保留。
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2019-3-6 04:59
被黑洛编辑
,原因: