前面的文章可以算是反调试的一些铺垫了,现在算是正式开始。
调试器的工作流程
调试器调试分两种情况,一种是附加进程,一种是通过可执行程序来创建进程调试。
创建进程:
通过CreateProcess并设置为DEBUG_PROCESS模式来启动。
BOOL CreateProcessA(
LPCSTR lpApplicationName,
LPSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCSTR lpCurrentDirectory,
LPSTARTUPINFOA lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
附加进程:
通过调用DebugActiveProcess函数来附加进程。
在进程被调试时,进程执行的一些操作会以事件的方式通知给调试器。。
当有事件需要通知调试器时,操作系统会挂起进程所有线程,然后把事件发送给调试器。
调试器通过WaitForDebugEvent来等待事件。
BOOL WaitForDebugEvent(
LPDEBUG_EVENT lpDebugEvent, //操作系统传递给调试器的事件
DWORD dwMilliseconds //等待时间
);
程序被调试的特征
调试程序时,调试器会对被调试程序的程序设置标志。
标志 | 描述 | 位置 | 函数 |
BeginDebugged | 被创建/附加进程都会被设置为1 | PEB+02偏移处 | IsDebuggerPresent() |
NtGlobalFlag | 创建进程会被设置为0x70,但是以附加进程设置时该值不会发生变化 | PEB+0x68处 | 没有API |
ProcessDebugPort | 程序处于调试状态下可以获取调试端口,通过该端口也可以判断是否被调试。 | 无 | checkRemoteDebugPresent |
代码实现简单的反调试:
#include<Windows.h>
#include<iostream>
using namespace std;
void testBeginDebugged()
{
if (IsDebuggerPresent())
{
MessageBoxA(0, "BeginDebugged验证失败,程序被调试", 0, 0);
}
else
{
MessageBoxA(0, "BeginDebugged验证正常", 0, 0);
}
}
void testNtGlobalFlag()
{
DWORD IsDebug = 1;
__asm
{
push eax
mov eax,fs:[0x30]
mov eax,[eax+0x68]
mov IsDebug,eax
pop eax
}
if (IsDebug == 0x70)
{
MessageBoxA(0, "NtGlobalFlag验证失败,程序被调试", 0, 0);
}
else
{
MessageBoxA(0, "NtGlobalFlag验证正常", 0, 0);
}
}
void testProcessDebugPort()
{
BOOL IsDebug = FALSE;
CheckRemoteDebuggerPresent(GetCurrentProcess(), &IsDebug);
if(IsDebug == TRUE)
{
MessageBoxA(0, "ProcessDebugPort验证失败,程序被调试", 0, 0);
}
else
{
MessageBoxA(0, "ProcessDebugPort验证正常,程序未被调试", 0, 0);
}
}
int main()
{
cout << "Welcome" << endl;
testBeginDebugged();
testNtGlobalFlag();
testProcessDebugPort();
return 0;
}
通过xdbg反汇编来分析下:
testBeginDebugged()
这个是我们自己写的void testBeginDebugged()函数,然后通过该函数查看IsDebuggerPresent函数:
就很明显的可以看到了这个就是一个简答的赋值语句,将fs:[30]也就是PEB偏移0x2的内容作为返回值赋值给eax。
testNtGlobalFlag()
这里就很明显了,因为本来就是我们自己写的汇编代码,哈哈。
testProcessDebugPort()
这个函数跟我们源代码比较一下可以看到有一个比较重要的函数CheckRemoteDebuggerPresent:
通过这样一层一层解析下来,其实也还是很明确了。它最后是调用了一个比较关键的NtQueryInformationProcess查询进行信息的一个API,这个是NtDll里面的: