当调试器进程通过CreateProcessW(A)创建进程时,传入的dwCreationFlags为DEBUG_PROCESS|DEBUG_ONLY_THIS_PROCESS时,表明调试当前创建的进程且不调试这个进程创建的子进程。
而CreateProcessW进程检测到此标志时会创建一个调试对象,具体调用堆栈如下:
KERNELBASE!CreateProcessW
KERNELBASE!CreateProcessInternalW
ntdll!DbgUiConnectToDbg会去读数组中的数据[1]
如果句柄为空调用ntdll!NtCreateDebugObject 作为ntdll!DbgUiConnectToDbg的返回函数,在ntdll!NtCreateDebugObject 函数返回前在内核中 就对teb.DbgSsReserved[1]中写入了句柄值,类型为DebugObject
我们自己写一个进程,这个进程会以调试方式启动一个进程,在创建进程前手动加入断点,运行程序时如果有JIT调试器那么会自动中断下来,然后使用!teb命令获取TEB地址
再查看DbgSsReserved的值
并对DbgSsReserved[1] 即对DbgSsReserved指针+4得到一个地址下一个硬件写入断点,断下来后调用堆栈如下:
可知在call edx的时候就写入完成了,查看句柄值:
如果使用内核调试的话 ,还可以查看此句柄对应的内核调试对象DebugObject的内存地址。
接下来调试器调用WaitForDebugEvent即可获取调试事件,此函数界面原型如下:
WINBASEAPI BOOL APIENTRY WaitForDebugEvent(_Out_ LPDEBUG_EVENT lpDebugEvent,_In_ DWORD dwMilliseconds);
第一个参数为一个指向DEBUG_EVENT数据结构的指针
typedef struct _DEBUG_EVENT { DWORD dwDebugEventCode; DWORD dwProcessId; DWORD dwThreadId; union { EXCEPTION_DEBUG_INFO Exception; CREATE_THREAD_DEBUG_INFO CreateThread; CREATE_PROCESS_DEBUG_INFO CreateProcessInfo; EXIT_THREAD_DEBUG_INFO ExitThread; EXIT_PROCESS_DEBUG_INFO ExitProcess; LOAD_DLL_DEBUG_INFO LoadDll; UNLOAD_DLL_DEBUG_INFO UnloadDll; OUTPUT_DEBUG_STRING_INFO DebugString; RIP_INFO RipInfo; } u; } DEBUG_EVENT, *LPDEBUG_EVENT;
其中包含一个联合数据类型,当dwDebugEventCode取不同值时,u代表不同的数据结构。
这个函数的调用栈如下:
KERNEL32!WaitForDebugEventStub
KERNELBASE!WaitForDebugEvent
KERNELBASE!WaitForDebugEventWorker
KERNELBASE!BaseFormatTimeOut
DbgUiWaitStateChange 返回c0000008
ntdll!NtWaitForDebugEvent(传入多个参数 包括teb.DbgSsReserved[1]数组共两个数据)
如果当前进程不是调试器进程(即teb.DbgSsReserved[1]为空时),那么DbgUiWaitStateChange 返回0xc0000008,代表STATUS_INVALID_HANDLE句柄错误。
调试器进程的teb.DbgSsReserved[1]应该为一个句柄,才可以获取调试消息。
每个进程在内核中都有一个EPROCESS结构体,而EPRCESS结构体中有一个变量为DebugPort。如下:
对于被调试进程来说,EPROCESS结构体中DebugPort是不为空的,而之前的调试进程的teb.DbgSsReserved[1]存储的是句柄值,句柄在内核中对应的就是这个DebugPort对应的地址。
所以调试进程中有一个句柄指向了DebugObject,被调试进程的DebugPort也指向了同一个DebugObject。
当被调试进程产生了异常事件、进程启动退出事件、线程启动退出事件、模块加载卸载事件、输出调试消息、RIP_INFO报告 RIP 调试事件 (系统调试错误)这些异常消息时,都会将这些消息存储到DebugObject结构体对应的链表中。
当调试器使用WaitForDebugEvent时,内部ntdll的函数会使用teb.DbgSsReserved[1]的句柄寻找到DebugObject,进而获取到调试消息队列获取函数,返回给调试器,调试器处理完成后,再通过continueDebugEvent函数通知系统调试器处理结果:
1、DBG_CONTINUE告诉系统,已经处理了异常,让被调试进程继续运行。
2、DBG_EXCEPTION_NOT_HANDLED告诉系统,调试器不处理该异常,交给程序自己的异常处理机制处理。
3、DBG_REPLY_LATER这个没有使用过,按照MSDN解释为:Windows 10版本 1507 或更高版本中受支持,此标志会导致 dwThreadId 在目标继续后重播现有的中断事件。 通过针对 dwThreadId 调用 SuspendThread API,调试器可以在进程中恢复其他线程,稍后返回到中断状态。下次进行测试。
总结一下:调试器进程中某一个线程调用了CreateProcess时使用调试标志,那么此线程的teb.DbgSsReserved[1]会存储一个指向DebugObject的句柄,注意是线程的DbgSsReserved[1],也就是说在同一进程的其他线程中调用WaitForDebugEvent就会出现问题。
标签:句柄,桥梁,DEBUG,进程,DbgSsReserved,调试,调试器 From: https://www.cnblogs.com/ps12345678/p/17130687.html