0.前言
很久前写过一些应用层的反调试的文章,这类反调试方法的好处是易于实现,但缺点是很容易被绕过----保护因此失效。我们的危机公关手段是:将反调试功能放到内核中用驱动程序实现,以增强程序的反调试能力。由于vista后patch guard的影响,因此本文以Xp系统为例,其他系统需要对代码中的硬编码进行调整。
1.调试对象(DEBUG_OBJECT)介绍
当调试器创建进程时,会创建一系列调试消息,如:进程创建消息,模块加载消息。对于调试器附加到运行中进程,亦会依次产生这些消息(确切的说是杜撰调试消息)。内核将这些消息通过Debug Port对象发送给调试器。另外当被调试进程触发异常时,内核收到异常消息后,亦会通过Debug Port把消息传给调试器。可见Debug Port是联系调试器和被调试器的纽带。内核会为调试器进程创建一个DEBUG_OBJECT对象,并将对象地址保存在进程_EPROCESS!DebugPort中;当调试器创建/附加被调试进程时,内核会把同一个DEBUG_OBJECT对象的地址保存到被调试进程的_EPROCESS!DebugPort字段。
kd> dt _EPROCESS -y DebugPort
nt!_EPROCESS
+0x0bc DebugPort : Ptr32 Void
2.方法1:DebugPort清零
基于上面的介绍,我们可以这样实现反调试功能:检测所有进程的_EPROCESS!DebugPort字段,如果发现该字段非空,可以当前系统中至少存在着调试器。出于演示目的,我简单的把_EPROCESS!DebugPort字段清0,这就把调试器或者被调试进程进行消息传递的纽带的一端给切断了。下面来看下代码:
#include <Ntifs.h>
#include <wdm.h>
#ifdef __cplusplus
extern "C" {
#endif
KSTART_ROUTINE DetectDbgThd;
HANDLE detectThdHnd = 0UL;
VOID DriverUnload(PDRIVER_OBJECT);
NTSTATUS DriverEntry(PDRIVER_OBJECT drvObj, PUNICODE_STRING regPath)
{
NTSTATUS ldStatus = STATUS_SUCCESS;
OBJECT_ATTRIBUTES thdAttr;
CLIENT_ID cid;
_asm int 3;
UNREFERENCED_PARAMETER(regPath);
drvObj->DriverUnload = DriverUnload;
memset(&thdAttr, 0, sizeof(OBJECT_ATTRIBUTES));
thdAttr.Length = sizeof(OBJECT_ATTRIBUTES);
ldStatus = PsCreateSystemThread(&detectThdHnd, 0,
&thdAttr, 0,
&cid,
DetectDbgThd, NULL);
if (!NT_SUCCESS(ldStatus))
return ldStatus;
return STATUS_SUCCESS;
}
VOID DriverUnload(IN PDRIVER_OBJECT drvObj)
{
UNREFERENCED_PARAMETER(drvObj);
ZwClose(detectThdHnd);
return;
}
PLIST_ENTRY GetPsActiveProcessHeadAddr()
{
PLIST_ENTRY PsActiveProcessHeadAddr = NULL;
NTSTATUS status;
PEPROCESS eProc = NULL;
status = PsLookupProcessByProcessId((HANDLE)4, &eProc);
if (!NT_SUCCESS(status))
return NULL;
#ifdef WIN32_XP
PsActiveProcessHeadAddr = ((PLIST_ENTRY)(((char*)eProc) + 0x88))->Blink;
#elif WIN32_7
PsActiveProcessHeadAddr = ((PLIST_ENTRY)(((char*)eProc) + 0xb8))->Blink;
#endif
//PsActiveProcessHeadAddr = eProc->ActiveProcessLinks->Blink;
ObDereferenceObject(eProc);
return PsActiveProcessHeadAddr;
}
VOID DetectDbgThd(void* thdCtx)
{
PEPROCESS eProc = NULL;
PLIST_ENTRY PsActiveProcessHeadAddr = NULL;
LIST_ENTRY* pos = NULL;
LARGE_INTEGER period = RtlConvertLongToLargeInteger(-10*1000);
DWORD32* procDebugPortAddr = NULL;
UNREFERENCED_PARAMETER(thdCtx);
PsActiveProcessHeadAddr = GetPsActiveProcessHeadAddr();
if (!PsActiveProcessHeadAddr)
return;
while(1)
{
pos = PsActiveProcessHeadAddr->Blink;
while (pos != PsActiveProcessHeadAddr)
{
//eProc = (PEPROCESS)CONTAINER_OF(pos, EPROCESS, ActiveProcessLinks);
#ifdef WIN32_XP
eProc = (PEPROCESS)((char*)pos - 0x88);
#elif WIN32_7
eProc = (PEPROCESS)((char*)pos - 0xb8);
#endif
//0xBC==EPROCESS!DebugPort
#ifdef WIN32_XP
procDebugPortAddr = (DWORD32*)(((char*)eProc) + 0xBC);
#elif WIN32_7
procDebugPortAddr = (DWORD32*)(((char*)eProc) + 0xeC);
#endif
if(*procDebugPortAddr != 0x00UL)
{
//KillProcess(eProc);
*procDebugPortAddr = 0x00UL;
}
pos = pos->Blink;
}
KeDelayExecutionThread(KernelMode, FALSE, &period);
}
}
#ifdef __cplusplus
}
#endif
kd> vertarget
Windows XP Kernel Version 2600 (Service Pack 3) UP Free x86 compatible
Product: WinNt, suite: TerminalServer SingleUserTS
Built by: 2600.xpsp.080413-2111
kd> dt _EPROCESS -y ActiveProcessLinks
nt!_EPROCESS
+0x088 ActiveProcessLinks : _LIST_ENTRY
获得ActiveProcessLinks域的地址后可以马上获得_EPROCESS对象地址和_EPROCESS!DebugPort地址,之后直接把_EPROCESS!DebugPort指针值改为0即可实现DebugPort清零。
kd> dt _EPROCESS -y DebugPort
nt!_EPROCESS
+0x0bc DebugPort : Ptr32 Void
图1.加载驱动,并分别启动windbg.exe和calc.exe,准备附加到进程。
图2.windbg附加到calc后,每次中断调试目标,都会显示红框中的内容,意思是 调试器进入了挂起中断状态(准调试状态,对这个状态的解释可以参考张银奎<软件调试> 第10.6.7节)。此时calc虽然处于被挂起状态,可以查看内存值,但不能被跟踪(如单步)和下断点。再次运行(F5)调试目标,calc恢复到运行状态。
附注:调试进程被挂起和恢复是因为调试子系统调用了DbgkpSuspendProcess/DbgkpResumeProcess将calc.exe挂起/恢复,这不需要经过DebugPort即可实现。至于可以查看内存值,是因为windbg只需通过ReadProcessMemory就能获得calc的内存值,也不需要DebugPort参与。
3.方法2:Teb!DbgSsReserve句柄清零
这个方法其实是上面DebugPort清零的衍生版,网上暂时没有找到同样的实现,应该独我一家
(没仔细找是否有雷同)~不过这个标志位和DebugPort有点区别,首先只有调试器进程才有这个标志位;其次,它位于用户空间,由TEB保存。更进一步讲,位于调试器工作线程内部,调试器的UI线程也没有这个标志位;最后,它是一个句柄,指向内核为调试器进程创建的DEBUG_OBJECT对象。
要实现Teb!DbgSsReserve句柄清零,首先要搜索具有调试特征的进程;搜索到目标进程后,要从当前线程空间Attach到目标进程空间,这样才能从目标进程空间读到有效的内存(虚拟内存);最后,用ZwClose关闭句柄。下面,我们来看下代码:
VOID DetectDbgThd(void* thdCtx)
{
PEPROCESS eProc = NULL;
PETHREAD eThd = NULL;
PLIST_ENTRY PsActiveProcessHeadAddr = NULL;
LIST_ENTRY* procPos = NULL;
LIST_ENTRY* thdPos = NULL;
LIST_ENTRY thdListHead;
KAPC_STATE apcState;
LARGE_INTEGER period = RtlConvertLongToLargeInteger(-10*1000);
DWORD32* thdTebAddr = NULL;
DWORD32* dbgSsReserved = NULL;
UNREFERENCED_PARAMETER(thdCtx);
PsActiveProcessHeadAddr = GetPsActiveProcessHeadAddr();
if (!PsActiveProcessHeadAddr)
return;
while(1)
{
procPos = PsActiveProcessHeadAddr->Blink;
while (procPos != PsActiveProcessHeadAddr)
{
//eProc = (PEPROCESS)CONTAINER_OF(pos, EPROCESS, ActiveProcessLinks);
#ifdef WIN32_XP
//+0x088 EPROCESS!ActiveProcessLinks : _LIST_ENTRY
eProc = (PEPROCESS)((char*)procPos - 0x88);
#endif
#ifdef WIN32_XP
//+0x190 EPROCESS!ThreadListHead : _LIST_ENTRY
//thdListHead = (LIST_ENTRY*)((char*)eProc+0x190);
//memcpy(&thdListHead,((char*)eProc+0x190),sizeof(LIST_ENTRY));
#endif
//pos = ListHead->Blink;
thdPos = (LIST_ENTRY*)(*(DWORD32*)((char*)eProc+0x190));
//while(pos != &ListHead)
while(thdPos != (LIST_ENTRY*)((char*)eProc+0x190))
{
#ifdef WIN32_XP
//+0x22c ETHREAD!ThreadListEntry : _LIST_ENTRY
eThd = (PETHREAD)((char*)thdPos - 0x22c);
//+0x020 ETHREAD!KTHREAD!Teb : Ptr32 Void
thdTebAddr = (*(DWORD32*)((char*)eThd+0x20));
if(!thdTebAddr)
goto Next;
#endif
KeStackAttachProcess(eProc, &apcState);
#ifdef WIN32_XP
//+0xf20 Teb!DbgSsReserved : [2] Ptr32 Void
dbgSsReserved = ((char*)thdTebAddr+0xf20);
#endif
/*
DbgSsReserved[0]:is reserved
DbgSsReserved[1]:for debuggee process, DbgSsReserved[0] stand for handler of debug-object
*/
if(dbgSsReserved[1])
{
ZwClose(dbgSsReserved[1]);
dbgSsReserved[1] = 0x00UL;
}
KeUnstackDetachProcess(&apcState);
Next:
thdPos = thdPos->Blink;
}
procPos = procPos->Blink;
}
KeDelayExecutionThread(KernelMode, FALSE, &period);
}
}
加载上面的代码后,调试器就无法打开和附加到目标进程。不过有个限制windbg.exe的实现有点特殊,DbgSsReserve字段一直为空,所以上面的代码对windbg无能为力~
标签:eProc,DebugPort,PsActiveProcessHeadAddr,用户,内核,ENTRY,EPROCESS,调试,调试器 From: https://blog.51cto.com/u_13927568/5831397