反调试——9——调试器原理
调试一个程序分两种情况:
1 打开这个程序。
2:这个程序已经是一个运行状态了,将其进程进行附加。
打开进程
通过打开运行进程方式来调试进程需要调用一个API:
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 ); //dwCreatetionFlags需要设置为DEBUG_PROCESS //打开进程示例程序 STARTUPINFOA sw{ 0 }; PROCESS_INFORMATION pInfo{ 0 }; auto retCP = CreateProcessA("F:\\Sublime Text 3\\sublime_text.exe", NULL,NULL,NULL,FALSE, DEBUG_PROCESS,NULL,NULL,&sw,&pInfo); if (retCP == 00) { cout << "打开进程失败" << endl; return; }
附加进程
通过DebugActiveProcess这个API来附加到进程。
BOOL DebugActiveProcess(
DWORD dwProcessId
);
细节
无论是通过打开进程还是附加进程来实现调试,都只是开始调用的方式不一样,在调试器和操作系统之间的交互方式都是相同的。
创建了调试进程后接下来就是死循环等待调试事件:
当调试进程时,被调试进程执行的一些操作事件将会被通知给调试器,比如dll的加载和卸载,thread的创建和销毁,异常信息等等。当这些事件需要被发送到调试器时,Windows内核将首先挂起进程中的所有线程,然后把发生的事件通知给调试器,等待调试器的处理。
调试器通过WaitForDebugEvent API来等待调试事件,调试事件被封装到了DEBUG_EVENT结构体中,调试器需要处理的就是循环接受调试事件然后处理DEBUG_EVENT结构体中传递过来的不同调试信息。
在发送事件event给调试器debugger时,被调试进程会被挂起,直到调试器调用了continueDebugEvent函数。
利用调试器原理实现附加反调试
利用调试器的原理,我们可以通过创建一个调试模式下的进程,那么这个以调试模式创建的进程就不能被其它进程拿去调试了,因为它已经在被一个我们自己的进程以调试模式创建了。
#include<iostream> #include<Windows.h> using namespace std; void TestDebugger() { STARTUPINFOA sw{ 0 }; PROCESS_INFORMATION pInfo{ 0 }; auto retCP = CreateProcessA("E:\\test\\Debug\\02 CStaticText.exe", NULL, NULL, NULL, FALSE, DEBUG_PROCESS, NULL, NULL, &sw, &pInfo); if (retCP == 0) { cout << "打开进程失败" << endl; return; } while (TRUE) { DEBUG_EVENT debugEvent{ 0 }; auto rDebugEvent = WaitForDebugEvent(&debugEvent, -1); if (rDebugEvent) { cout << debugEvent.dwDebugEventCode << endl; //dwDebugEventCode是用来区分不同事件的事件码,用来判断事件 } ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, DBG_CONTINUE);//在发送事件event给调试器debugger时,被调试进程会被挂起,直到调试器调用了continueDebugEvent函数 } } int main() { TestDebugger(); system("pause"); return 0; }
然后来测试一下,这样启动后,是否还能被调试器附加上:
这样一来就不会被调试器附加上了。