3.调试事件的处理
调试器会有一个循环一直判断EventList有没有调试事件有就取出来处理。
调试器创建形式
- 关联调试器与被调试器进程
- 调试循环
每一种收集调试事件API都不一样,是因为它要收集调试事件的类型是不一样的。
这是异常要收集的调试事件
typedef struct _EXCEPTION_DEBUG_INFO {
EXCEPTION_RECORD ExceptionRecord;
DWORD dwFirstChance;
} EXCEPTION_DEBUG_INFO, *LPEXCEPTION_DEBUG_INFO;
这是创建线程要收集的调试事件
typedef struct _CREATE_THREAD_DEBUG_INFO {
HANDLE hThread;
LPVOID lpThreadLocalBase;
LPTHREAD_START_ROUTINE lpStartAddress;
} CREATE_THREAD_DEBUG_INFO, *LPCREATE_THREAD_DEBUG_INFO;
.....
.....
下面代码就类似拖拽程序到OD的框架。(W10记得以管理员身份运行VS)
#define dbgProcessName L"C:\\Users\\Administrator\\Desktop\\012.exe"
int main()
{
BOOL nIsConinue = TRUE;
DEBUG_EVENT debugEvent = { 0 };
//1.创建调试进程
STARTUPINFO startupInfo = { 0 };
PROCESS_INFORMATION pInfo = {0};
GetStartupInfo(&startupInfo);
BOOL bRet = CreateProcess(dbgProcessName,NULL,NULL,NULL,TRUE,DEBUG_PROCESS||DEBUG_ONLY_THIS_PROCESS,NULL,NULL,&startupInfo,&pInfo);
if (bRet == FALSE)
{
printf("CreateProcess error:%d\n", GetLastError());
return 0;
}
//调试循环
while (nIsConinue)
{
bRet = WaitForDebugEvent(&debugEvent, INFINITE);//取DEBUG_EVENT
if (!bRet)
{
printf("WaitForDebugEvent error:%d\n", GetLastError());
return 0;
}
switch (debugEvent.dwDebugEventCode)
{
case EXCEPTION_DEBUG_EVENT:
printf("异常:发生异常的地址:%X \n",debugEvent.u.Exception.ExceptionRecord.ExceptionAddress);
break;
case CREATE_THREAD_DEBUG_EVENT:
printf("创建线程\n");
break;
case CREATE_PROCESS_DEBUG_EVENT:
printf("创建进程\n");
break;
case EXIT_THREAD_DEBUG_EVENT:
printf("退出线程\n");
break;
case EXIT_PROCESS_DEBUG_EVENT:
printf("退出进程\n");
break;
case LOAD_DLL_DEBUG_EVENT:
printf("加载DLL\n");
break;
case UNLOAD_DLL_DEBUG_EVENT:
printf("卸载DLL\n");
break;
default:
break;
}
//DBG_CONTINUE 表示调试器已处理该异常
//DBG_EXCEPTION_NOT_HANDLED 表示调试器没有处理该异常,转回到用户态中执行,寻找可以处理该异常的异常处理器
bRet = ContinueDebugEvent(debugEvent.dwProcessId,debugEvent.dwThreadId,DBG_CONTINUE);
//ContinueDebugEvent 告诉被调试程序让被调试程序继续执行
}
}
运行会发现有一个异常我们取这个地址看看
异常来自ntdll 的int 3,它为什么会在来个int 3呢?
进程创建过程:
<1>任何进程都是别的进程创建的: CreateProcess()
<2>进程的创建过程
1、映射EXE文件
2、创建内核对象_EPROCESS
3、映射系统DLL(ntdll.dll)
4、创建线程内核对象_ETHREAD
5、系统启动线程
映射DLL(ntdll.LdrInitalizeThunk())
线程开始执行
ntdll 比较特殊它在内核的时候就已经创建好了。
LdrInitalizeThunk()
判断这个进程是不是这个进程的第一条线程,如果是先调用 LdrpInitializeProcess 初始化进程,再将这个进程用到的其他DLL映射进来。
追上去
如果被用户模式调试器附加了就会call DbgBreakPoint() ,如果没有调试器附加就不会调用这个int 3。
调试器附加
int main()
{
BOOL nIsConinue = TRUE;
DEBUG_EVENT debugEvent = { 0 };
PROCESS_INFORMATION pInfo = {0};
//1.附加进程
if (!DebugActiveProcess(14148))
{
return 0;
}
//调试循环
while (nIsConinue)
{
BOOL bRet = WaitForDebugEvent(&debugEvent, INFINITE);//取DEBUG_EVENT
if (!bRet)
{
printf("WaitForDebugEvent error:%d\n", GetLastError());
return 0;
}
switch (debugEvent.dwDebugEventCode)
{
case EXCEPTION_DEBUG_EVENT:
printf("异常:发生异常的地址:%X \n",debugEvent.u.Exception.ExceptionRecord.ExceptionAddress);
break;
case CREATE_THREAD_DEBUG_EVENT:
printf("创建线程\n");
break;
case CREATE_PROCESS_DEBUG_EVENT:
printf("创建进程\n");
break;
case EXIT_THREAD_DEBUG_EVENT:
printf("退出线程\n");
break;
case EXIT_PROCESS_DEBUG_EVENT:
printf("退出进程\n");
break;
case LOAD_DLL_DEBUG_EVENT:
printf("加载DLL\n");
break;
case UNLOAD_DLL_DEBUG_EVENT:
printf("卸载DLL\n");
break;
default:
break;
}
//DBG_CONTINUE 表示调试器已处理该异常
//DBG_EXCEPTION_NOT_HANDLED 表示调试器没有处理该异常,转回到用户态中执行,寻找可以处理该异常的异常处理器
bRet = ContinueDebugEvent(debugEvent.dwProcessId,debugEvent.dwThreadId,DBG_CONTINUE);
//ContinueDebugEvent 告诉被调试程序让被调试程序继续执行
}
}
我们通过创建的形式能捕获到他的创建信息,但现在我通过附加,这些信息都已经执行完了,为什么也能捕获到这些信息呢?
杜撰的调试消息
当我们附加了一个进程后,有人给我们发送了假的消息。
DebugActiveProcess 最终到内核会调用 NtDebugActiveProcess 。
它为什么要发送这些假的消息?
调试器都能看到所属进程的DLL,是因为每个模块加载的时候都能收集到调试事件,这个API在它的路上加了这些假消息就是希望给调试器提供一些必要的信息。
但这些假信息并不靠谱,比如这个模块别人有意隐藏,实际OD它是查了PEB+0xc的位置Ldr 0xc到0x1c ,成员如下。
要点
<1>事件
<2>系统断点
LdrinitializeThunk
LdrpInitializeProcess
开头位置找到text:7C9415F1 mov ebx, [eax+30h]
DbgBreakPoint()找到LdrpInitializeProcess引用的地址
DbgBreakPoint()就是int 3
<3> ContinueDebugEvent 函数