3.调试事件的处理

3.调试事件的处理

调试器会有一个循环一直判断EventList有没有调试事件有就取出来处理。

调试器创建形式

  1. 关联调试器与被调试器进程
  2. 调试循环

每一种收集调试事件API都不一样,是因为它要收集调试事件的类型是不一样的。

3.调试事件的处理

这是异常要收集的调试事件
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 告诉被调试程序让被调试程序继续执行
	}
	
}

运行会发现有一个异常我们取这个地址看看
3.调试事件的处理

3.调试事件的处理

异常来自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映射进来。

3.调试事件的处理

3.调试事件的处理

3.调试事件的处理
追上去

3.调试事件的处理

如果被用户模式调试器附加了就会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 告诉被调试程序让被调试程序继续执行
	}

}

3.调试事件的处理
我们通过创建的形式能捕获到他的创建信息,但现在我通过附加,这些信息都已经执行完了,为什么也能捕获到这些信息呢?

杜撰的调试消息

当我们附加了一个进程后,有人给我们发送了假的消息。
DebugActiveProcess 最终到内核会调用 NtDebugActiveProcess 。

3.调试事件的处理
3.调试事件的处理

它为什么要发送这些假的消息?

调试器都能看到所属进程的DLL,是因为每个模块加载的时候都能收集到调试事件,这个API在它的路上加了这些假消息就是希望给调试器提供一些必要的信息。

但这些假信息并不靠谱,比如这个模块别人有意隐藏,实际OD它是查了PEB+0xc的位置Ldr 0xc到0x1c ,成员如下。
3.调试事件的处理

要点
<1>事件
<2>系统断点

LdrinitializeThunk
  LdrpInitializeProcess
  开头位置找到text:7C9415F1 mov ebx, [eax+30h] 
  DbgBreakPoint()找到LdrpInitializeProcess引用的地址
  DbgBreakPoint()就是int 3

<3> ContinueDebugEvent 函数