[异常分发]二:WaitForDebugEvnet哪里获取Debug_Evnet信息
调试异常事件结构体:
typedef struct _DEBUG_EVENT { //出自MSDN DWORD dwDebugEventCode; //异常代码 DWORD dwProcessId; //PID 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; //载入DLL UNLOAD_DLL_DEBUG_INFO UnloadDll; //卸载DLL OUTPUT_DEBUG_STRING_INFO DebugString; RIP_INFO RipInfo; } u; } DEBUG_EVENT, *LPDEBUG_EVENT;
主要函数的调用过程:WaitForDebugEvent DbgUiWaitStateChange NtWaitForDebugEvent
DbgUiWaitStateChange 传入要获取异常事件的结构体地址、等待时间及调试对象等信息调用NtWaitForDebugEvent 进入R0, 调用代码如下:
.text:7C975E0A push [ebp+pDebug_Event] ;要传出调试异常信息的结构体 .text:7C975E0D mov eax, large fs:_TEB.NtTib.Self ; 获取TEB .text:7C975E13 push [ebp+seconds] .text:7C975E16 push 1 .text:7C975E18 push [eax+(_TEB.DbgSsReserved+4)] ; _teb.DbgSsReserved[1]保存Debug_Object .text:7C975E1E call _NtWaitForDebugEvent@16 ; 获取Debug_Evnet结构体信息
NtWaitForDebugEvent主要完成获取R0下的Debug_Event结构体信息并转换成用户下的结构
1)获取R0下的Debug_Event结构体信息
关键过程,通过KeWaitForSingleObject返回值判断来判断要进行遍历异常事件链表的操作,在循环中判断调试对象的标志不是结束调试进程,则进行遍历Debug_Object.EventList中的Debug_Event链表信息。
遍历过程:取出Debug_Evnt一成员判断是否结束,不结束判断调试对象标志是否为
DEBUG_EVENT_READ|DEBUG_EVENT_INACTIVE,不是则用取出的成员与EventList其他不同节点的PID作比较,有
相等的(说明不是唯一)则修改Debug_Event的状态信息及局部标志值, 没有相同的就转换成用户模式下的结构体, 也就是
WaitForDebugEvent的参数Debug_Event结构体。
;根据KeWaitForSingleObject的返回值, 决定是否需要做遍历动作。如果返回值小于0,或者为STATUS_TIMEOUT或为STATUS_ALERTED或者是STATUS_USER_APC则结束循环遍历过程。
PAGE:005905E0 call _KeWaitForSingleObject@20 ; KeWaitForSingleObject(x,x,x,x,x) PAGE:005905E5 mov ebx, eax PAGE:005905E7 cmp ebx, esi ; if(返回值EAX >= 0) 就继续循环 PAGE:005905E9 jl END_WHILE_LOOP ; 函数返回值如果是<0,则退出循环结构 PAGE:005905EF jmp short BEGIN_WHILE_LOOP WHILE_NEXT_LOOP: PAGE:005905F1 xor esi, esi ; ESI清零 BEGIN_WHILE_LOOP: PAGE:005905F3 cmp ebx, STATUS_TIMEOUT PAGE:005905F9 jz END_WHILE_LOOP ; { 执行循环体,否则跳出循环 } PAGE:005905FF cmp ebx, STATUS_ALERTED PAGE:00590605 jz END_WHILE_LOOP PAGE:0059060B cmp ebx, STATUS_USER_APC ;这里也是判断函数返回值,成立就结束循环 PAGE:00590611 jz END_WHILE_LOOP PAGE:00590617 mov [ebp+isGotEvent_LV], 0 ; 局部标志变量清零 PAGE:0059061B lea ecx, [edi+_DEBUG_OBJECT.Mutex] PAGE:0059061E call @ExAcquireFastMutex@4 ; 请求互斥对象 PAGE:00590623 ; 1 ==> 直到调试对象被删除 PAGE:00590623 ; 2 ==> 结束关闭所有调试进程 PAGE:00590623 test byte ptr [edi+_DEBUG_OBJECT.Flags], 1 ; if(DebugObject->Flags == 1) PAGE:00590627 jnz short RealseFastMutex ;
;取出调试事件list的头节点,Debug_Evnt首成员为ListEntry保存的前后节点指针
;GE:00590629 lea eax, [edi+_DEBUG_OBJECT.EventList] ; eax = &pDebug_Object->EventList PAGE:0059062C mov ecx, [eax] ; 取下一个List_Entry到ecx ( ecx = pDebug_Ob->EventList.Flink ) PAGE:0059062E jmp short IS_LIST1_IS_END LIST1_NOT_END: PAGE:00590630 mov esi, ecx ; ;esi = debug_object.EventList.Flink ;DEBUG_EVENT_READ|DEBUG_EVENT_INACTIVE 1|4 = 5 PAGE:00590632 test byte ptr [ecx+Debug_event.Flags], 5 ; PAGE:00590636 jnz short EQU_EventRead_OR_EventInactive ; 取下一个DebugEvent PAGE:00590638 mov [ebp+isGotEvent_LV], 1 ; 局部标志值 = 1 PAGE:0059063C jmp short IS_LIST2_IS_END LIST2_IS_NOT_END: PAGE:0059063E mov edx, [esi+Debug_event.ClientId.UniqueProcess] PAGE:00590641 cmp edx, [eax+Debug_event.ClientId.UniqueProcess] PAGE:00590644 jz short UniquePRocess_EQU ; 如果进程相等,跳去做点活,也就结束的List2的遍历 IS_LIST2_IS_END: PAGE:00590646 mov eax, [eax+Debug_event.EventList.Flink] PAGE:00590648 cmp eax, ecx ; if(pDebug_ob->Event.Flink != pDebug_ob->Event.Flink ) PAGE:0059064A jnz short LIST2_IS_NOT_END ; 这是Entry2的下一个循环判断 PAGE:0059064C jmp short LOOP2_IS_END PAGE:0059064E UniquePRocess_EQU: PAGE:0059064E or [esi+Debug_event.Flags], DEBUG_EVENT_INACTIVE ; PAGE:00590652 and [esi+Debug_event.BackoutThread], 0 ; 清零 PAGE:00590656 mov [ebp+isGotEvent_LV], 0 ; 标志设置为0用来判断是否唯一进程 LOOP2_IS_END: PAGE:0059065A cmp [ebp+isGotEvent_LV], 0 ; GotEvent = 1 ==> 遍历List2时没有相等的UniqueProcess PAGE:0059065A ; GotEvent = 0 ==> 有UniqueProcess相等的情况 PAGE:0059065E jnz short GOT_EVENT_NOT_EQU_0 PAGE:00590660 EQU_EventRead_OR_EventInactive: PAGE:00590660 mov ecx, [ecx+Debug_event.EventList.Flink] ; 取下一个DebugEvent结构体地址 PAGE:00590662 lea eax, [edi+_DEBUG_OBJECT.EventList] ; 取EventList的头地址 PAGE:00590665 IS_LIST1_IS_END: PAGE:00590665 cmp ecx, eax ; 判断 pDebug_Ob-Event.Flink != &Debug_Ob->Event 即是否遍历完 PAGE:00590667 jnz short LIST1_NOT_END ; List没有结束,则继续 PAGE:00590669 cmp [ebp+isGotEvent_LV], 0 ; 判断局部标志是否为0 PAGE:0059066D jz short GOT_EVENT_EQU_0 ; GotEvent = 1 ==> 遍历List2时没有相等的UniqueProcess PAGE:0059066D ; GotEvent = 0 ==> 有UniqueProcess相等的情况
主要操作就是遍历Debug_Object里EventList保存的异常事件链表, 查找UniqueProcess是否唯一,参考如下图示。
GOT_EVENT_NOT_EQU_0: PAGE:0059066F mov ebx, [esi+Debug_event.Process] PAGE:00590672 mov [ebp+Process_LV], ebx PAGE:00590675 mov ecx, [esi+Debug_event.Thread]; 将Debug_event中进程和线程保存到局部变量中 PAGE:00590678 mov [ebp+Thread_LV], ecx PAGE:0059067B call @ObfReferenceObject@4 ; ECX传参,增加线程对像的引用计数 PAGE:00590680 mov ecx, ebx ; Object PAGE:00590682 call @ObfReferenceObject@4 ; 增加进程的引用计数 PAGE:00590687 push esi ; DebugEvent PAGE:00590688 lea eax, [ebp+tWaitStateChange_LV] PAGE:0059068E push eax ; WaitStateChange ;DbgkpConvertKernelToUserStateChange将R0的转成R3的Debug_Event结构体信息 PAGE:0059068F call _DbgkpConvertKernelToUserStateChange@8 ; PAGE:00590694 or [esi+Debug_event.Flags], DEBUG_EVENT_READ
2):DbgkpConvertKernelToUserStateChange
将R0下和R3下的Debug_event调试异常结构体成员不同需要转换成用户需要的格式。根据异常类型对不同的异常类型的结构体信息作信息填充,结构体赋值给传出参数。
对应操作,如下图简单标示
根据ApiNumber对应的异常类型作相应的赋值处理。
PAGE:00590443 mov edx, [ebp+DebugEvent] PAGE:00590446 mov ecx, [edx+Debug_event.ClientId.UniqueProcess] PAGE:00590449 mov eax, [ebp+WaitStateChange] ; 将R0的AppClientId保存到R3下的结构中 PAGE:0059044C mov [eax+_DBGUI_WAIT_STATE_CHANGE.AppClientId.UniqueProcess], ecx PAGE:0059044F mov ecx, [edx+Debug_event.ClientId.UniqueThread] ; 转存线程信息 PAGE:00590452 mov [eax+_DBGUI_WAIT_STATE_CHANGE.AppClientId.UniqueThread], ecx PAGE:00590455 mov ecx, [edx+Debug_event.ApiMsg.ApiNumber] ;根据 Msg类型作处理 PAGE:00590458 sub ecx, 0 PAGE:0059045B push esi PAGE:0059045C push edi PAGE:0059045D jz short DbgKmExceptionApi_0 ; 异常信息结构体 PAGE:0059045F dec ecx PAGE:00590460 jz short DbgKmCreateThreadApi_1 ; 创建线程 PAGE:00590462 dec ecx PAGE:00590463 jz short DbgKmCreateProcessApi_2 ; 创建进程 PAGE:00590465 dec ecx PAGE:00590466 jz short DbgKmExitThreadApi_3 ; 退出线程 PAGE:00590468 dec ecx PAGE:00590469 jz short DbgKmExitProcessApi_4 ; 退出进程 PAGE:0059046B dec ecx PAGE:0059046C jz short DbgKmLoadDllApi_5 ; 载入DLL PAGE:0059046E dec ecx PAGE:0059046F jnz DEFAULT_7 ; 为0则是第6个为UnLoadDll PAGE:00590475 mov [eax+_DBGUI_WAIT_STATE_CHANGE.NewState], 0Ah ; DbgUnloadDllStateChange PAGE:0059047B jmp short DbgKmUnloadDllApi_6 ; 卸载DLL PAGE:0059047D ; ---------------------------------------------------------------------------
;根据异常信息类型,对相应的消息结构体作赋值处理,设置新的状态值,保存ApiMsg的信息等操作,下面是只列出LoadDll操作,其他消息类型操作类似。
DbgKmLoadDllApi_5: PAGE:0059047D add edx, 58h ; 偏移到ApiMsg的共同体 PAGE:00590480 push 5 ;9 = DbgLoadDllStateChange PAGE:00590482 mov [eax+_DBGUI_WAIT_STATE_CHANGE.NewState], 9 ; PAGE:00590488 lea edi, [eax+_DBGUI_WAIT_STATE_CHANGE.StateInfo] PAGE:0059048B pop ecx PAGE:0059048C mov esi, edx ; esi = &Debug_Event.ApiMsg PAGE:0059048E rep movsd ; Debug_Wait_State_Change.StateInfo = Debug_Event.ApiMsg PAGE:00590490 and [edx+_DBGKM_UNLOAD_DLL.BaseAddress], 0 PAGE:00590493 jmp short DEFAULT_7
那么调试对象Debug_Object中的调试事件信息Debug_Event又是什么时候添加进的呢?
备注:
关于调试对象的创建等相关信息,请参考其人的分析