BeginDebugged
利用调试器加载程序时调试器会通过CreateProcess()创建调试进程,同时会创建调试内核对象并保存在当前线程环境块的DbgSsReserved[1]字段中,此时此线程就不同于普通线程了(一般称为调试器线程)。接着CreateProcess()会判断是否有调试器,无则直接返回,有则调用PspCreateProcess(),此函数会根据DbgSsReserved[1]字段设置创建进程的EPROCESS的DebugPort字段,这样此被创建进程就可以称为被调试进程。接着PspCreateProcess()会调用MmCreatePeb(),此函数会根据EPROCESS的Debugport字段设置PEB的BeingDebuged字段。(无调试器时其值为0)
我们可以直接通过内联汇编来访问BeingDebuged字段,也可以通过IsDebuggerPresent()这个API来得到此字段的值。下面是示例程序。
int main(int argc, char *argv[])
{
DWORD dwBeingDebuged;
TCHAR szTest[] = TEXT("已检测到调试器!");
TCHAR szSuccess[] = TEXT("运行正常!");
dwBeingDebuged = IsDebuggerPresent();
if(0 != dwBeingDebuged)
{
MessageBox(NULL, szTest, NULL, MB_OK);
ExitProcess(NULL);
}
MessageBox(NULL, szSuccess, NULL, MB_OK);
return 0;
}
NtGlobalFlag
当BeingDebuged被设为“TRUE”后会将PEB的NtGlobalFlag设置0x70h标志,我们通过内联汇编检测进程环境快的NtGlobalFlag字段。
int main(int argc, char *argv[])
{
DWORD dwNtGlobalFlag;
TCHAR szTest[] = TEXT("已检测到调试器!");
TCHAR szSuccess[] = TEXT("运行正常!");
dwNtGlobalFlag = _AntiDebug();
if(0 != dwNtGlobalFlag)
{
MessageBox(NULL, szTest, NULL, MB_OK);
ExitProcess(NULL);
}
MessageBox(NULL, szSuccess, NULL, MB_OK);
return 0;
}
//反调试
DWORD _AntiDebug()
{
_asm{
mov eax,fs:[0x30]
mov eax,[eax + 0x68]
and eax,0x70
}
}
ProcessHeap的Flags和ForceFlags
当NtGlobalFlag字段被设置0x70标志后,其会改变PEB的字段ProcessHeap所指向的Flags和ForceFlags的值。
对于x86平台而言Flags和ForceFlags偏移地址分别是相对于ProcessHeap指向地址的0x40和0x44处。当程序未被调试器调试时Flags的值应包含0x00000002标志,ForceFlags的值应为0,下面是通过内联汇编检测二者的值。
int main(int argc, char *argv[])
{
DWORD dwReturn;
TCHAR szTest[] = TEXT("已检测到调试器!");
TCHAR szSuccess[] = TEXT("运行正常!");
dwReturn = _AntiDebug();
if(0 != dwReturn)
{
MessageBox(NULL, szTest, NULL, MB_OK);
ExitProcess(NULL);
}
MessageBox(NULL, szSuccess, NULL, MB_OK);
return 0;
}
//反调试
DWORD _AntiDebug()
{
_asm{
push ebx
mov eax,fs:[0x30]
mov eax,[eax + 0x18]
mov ebx,dword ptr [eax + 0x40] //Flags
and ebx,0x00000002
cmp ebx,0
je end0
mov ebx,dword ptr [eax + 0x44] //ForceFlags
cmp ebx,0
jne end0
xor eax,eax
jmp end
end0:
mov eax,1
end:
pop ebx
}
}
自写OD插件绕过
此插件时基于OD1.10版本的,当然也可以参考此插件写法编写基于OD2.0版本的。插件思路为:我们通过IATHOOK OD的WaitForDebugEvent( )函数,将WaitForDebugEvent函数替换为我们的MyWaitForDebugEvent(),当检测到第一次接收到EXCEPTION_DEBUG_EVENT异常调试事件时将BeginDebugged,
NtGlobalFlag,Flags和ForceFlags清除。
替换WaitForDebugEvent()的函数即MyWaitForDebugEvent()代码如下。
BOOL _stdcall MyWaitForDebugEvent(LPDEBUG_EVENT lpDebugEvent, DWORD dwMilliseconds)
{
static DWORD stPid; //进程PID
static DWORD dwFlag = 0;
BOOL Return;
Return = WaitForDebugEvent(lpDebugEvent,dwMilliseconds); //调用原WaitForDebugEvent
switch(lpDebugEvent->dwDebugEventCode)
{
case CREATE_PROCESS_DEBUG_EVENT: //创建进程
stPid = lpDebugEvent->dwProcessId;
break;
case EXCEPTION_DEBUG_EVENT: //调试异常
dwFlag++;
if(dwFlag == 1)
_DetourAnti(stPid); //清除BeingDebugged,NtGlobalFlag,HeapFlags,ForceFlags
break;
case EXIT_PROCESS_DEBUG_EVENT: //程序退出
dwFlag = 0;
}
return Return;
}
_DetourAnti( )函数利用OD接口将BeingDebugged,NtGlobalFlag,HeapFlags,ForceFlags标志清除。
//绕过反调试
void _DetourAnti(DWORD dwPid)
{
HANDLE hProcess; //进程句柄
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid); //获取权限
DWORD dwFlag = 0;
t_thread * stMainThread ;
stMainThread = _Findthread(_Plugingetvalue(VAL_MAINTHREADID));
DWORD dwTibAddress = stMainThread->datablock;
DWORD dwPebAddress;
DWORD dwAddress;
_Readmemory(&dwPebAddress,(dwTibAddress + 0x30), 4, MM_RESTORE); //PEB
_Writememory(&dwFlag,(dwPebAddress + 0x02), 1, MM_RESTORE); //DebugPresent
_Writememory(&dwFlag,(dwPebAddress + 0x68), 4, MM_RESTORE); //NtGlobalFlag
_Readmemory(&dwAddress,(dwPebAddress + 0x18), 4, MM_RESTORE); //ProcessHeap
_Readmemory(&dwFlag,(dwAddress + 0x40), 4, MM_RESTORE);
dwFlag = dwFlag | 0x2;
_Writememory(&dwFlag,(dwAddress + 0x40), 4, MM_RESTORE); //HeapFlags
_Readmemory(&dwFlag,(dwAddress + 0x44), 4, MM_RESTORE);
dwFlag = 0;
_Writememory(&dwFlag,(dwAddress + 0x44), 4, MM_RESTORE); //ForceFlags
}
IATHook代码在这里就不贴出了,可自行查看附件代码。将插件DLL放入OD1.10版本的plugin目录中运行OD会弹出DLL成功加载。
我们利用BeingDebugged,NtGlobalFlag,HeapFlags,ForceFlags标志写了一个综合反调试程序,将测试程序用OD加载其将绕过所有反调试正常运行。
实际我们相当于实现了和知名插件HideOD的HideNtDebugBit选项的功能
参考资料:《软件调试》《加密与解密》