[异常分发]三:R0下EventList又是什么时候添加的?
1)在创建调试线程中添加异常事件信息
调用过程PspCreateThread --> PspUserThreadStartup(初始化线程的参
数) --> DbgkCreateThread (这里格式化ApiMsg作派发消息的参
数) --> DbgkpSendApiMessage
备注
这里只关心的DbgkpSendApiMeassage函数的处理过程, 关于创建进程、创建调试对象(NtCreateDebugObject),创建线程及调试线程等信息请参考其他分析。
DbgkCreateThread函数
获取当前的相关信息,如果调试端口不为NULL,则根据状态来格式化ApiMsg结构体信息,调用消息派发函数,传入参数为标志值决定是否需要挂起进程及ApiMsg结构体的指针,也是要添加到调试对象异常链中的成员信息。
PAGE:00591478 push ebx ; 参数,决定是否要挂起进程 PAGE:00591479 lea eax, [ebp+DBGKM_APIMSG] ; 取赋相应值的ApiMsg结构体地址 PAGE:0059147F push eax PAGE:00591480 call _DbgkpSendApiMessage@8 ; DbgkpSendApiMessage(x,x)
DbgkpSendApiMeassage
根据传入的标志值来决定是否要挂起进程,ApiMsg信息是用来填充Debug_Event结构体信息, 添加到;Debug_Object.EventList中主要通过DbgkpQueueMessage来实现。
代码:
PAGE:00591008 mov ecx, [ebp+pApiMsg] PAGE:0059100B mov eax, large fs:_ETHREAD.Tcb.UserAffinity PAGE:00591011 mov [ecx+DBGKM_APIMSG.ReturnedStatus], 103h ; 返回标志为: STATUS_PENDING PAGE:00591018 mov eax, [eax+_ETHREAD.Tcb.___u6.ApcState.Process] ; PAGE:0059101B xor edx, edx PAGE:0059101D inc edx ; edx = 1 PAGE:0059101E lea esi, [eax+_ETHREAD.CrossThreadFlags] ;获取标志地址 PAGE:00591024 lock or [esi], edx ;修改Flages = 1 PAGE:00591027 push ebx ; TargetDebugObject NULL PAGE:00591028 push ebx ; Flags NULL PAGE:00591029 push ecx ; ApiMsg 传入参数ApiMsg的地址 PAGE:0059102A push large dword ptr fs:_KPCR.PrcbData.CurrentThread ; Thread 结构体首地址 PAGE:00591031 push eax ; Process结构体地址 PAGE:00591032 call _DbgkpQueueMessage@20 ; 添加链表操作在这里完成
DbgkpQueueMessage
完善结构信息后添加Debug_Event到Debug_Object.EventList链表的尾节点
Debug_Event结构体赋值操作主要如下图所示。
PAGE:0058FD6F mov esi, [ebp+FunUseThisReAs_LocalValue_pDebugObject] PAGE:0058FD72 mov [ebp+Flages_LV], esi ; 将标志值保存到局部变量中 PAGE:0058FD75 and [ebp+Flages_LV], 2 ; DEBUG_EVENT_NOWAIT = 2 PAGE:0058FD79 jz short NOT_DEBUG_EVENT_NOWAIT ; ;Flag=2 则申请内核空间,这里省略 NOT_DEBUG_EVENT_NOWAIT: PAGE:0058FDC3 mov ecx, offset _DbgkpProcessDebugPortMutex ; FastMutex PAGE:0058FDC8 lea ebx, [ebp+StaticDebugEvent_var_B8_LV] ; DebugEvent = &StaticDebugEvent; PAGE:0058FDCE mov [ebp+var_8C], esi ; 修改结构体成员标识 DebugEvent->Flags = Flags; PAGE:0058FDD4 call @ExAcquireFastMutex@4 PAGE:0058FDD9 mov eax, [ebp+Process_Re]
;取调试端口保存到变量中, 取出ApiNumber,根据不同类型的序号作相应的处理
typedef enum _DBGKM_APINUMBER { //对应的序号 DbgKmExceptionApi, DbgKmCreateThreadApi, DbgKmCreateProcessApi, DbgKmExitThreadApi, DbgKmExitProcessApi, DbgKmLoadDllApi, DbgKmUnloadDllApi, DbgKmMaxApiNumber } DBGKM_APINUMBER;
PAGE:0058FDDC mov eax, [eax+_EPROCESS.DebugPort] ; 取出进程的调试端口 PAGE:0058FDE2 mov [ebp+FunUseThisReAs_LocalValue_pDebugObject], eax ; 调试端口保存到局部变量中 PAGE:0058FDE5 mov eax, [ebp+ApiMsg_Re] ; 去参数所指向的地址值 PAGE:0058FDE8 mov eax, [eax+DBGKM_APIMSG.ApiNumber] ; 取ApiMsg的序号 PAGE:0058FDEB cmp eax, 1 ; ApiNumber是否为1 DbgKmCreateThreadApi=1 PAGE:0058FDEE jz short CREATE_THREAD PAGE:0058FDF0 cmp eax, 2 ; ApiNumber是否为2 DbgKmCreateProcessApi=2 PAGE:0058FDF3 jnz short NOT_CREATE_PROCESS ; CREATE_THREAD: PAGE:0058FDF5 mov ecx, [ebp+Thread_Re] ;80 = PS_CROSS_THREAD_FLAGS_SKIP_CREATION_MSG PAGE:0058FDF8 test byte ptr [ecx+_ETHREAD.CrossThreadFlags], 80h ; PAGE:0058FDFF jz short NOT_CREATE_PROCESS ;不为80跳走 PAGE:0058FE01 and [ebp+FunUseThisReAs_LocalValue_pDebugObject], 0 ;变量=NULL NOT_CREATE_PROCESS: PAGE:0058FE05 cmp eax, 3 ; 3 = DbgKmExitThreadApi PAGE:0058FE08 jz short EXIT_THREAD PAGE:0058FE0A cmp eax, 4 ; 4 = DbgKmExitProcessApi PAGE:0058FE0D jnz short DEBUG_EVENT_NOWAIT_END PAGE:0058FE0F EXIT_THREAD: PAGE:0058FE0F mov eax, [ebp+Thread_Re] ; 1 = PS_CROSS_THREAD_FLAGS_SKIP_CREATION_MSG PAGE:0058FE12 test byte ptr [eax+(_ETHREAD.CrossThreadFlags+1)], 1 PAGE:0058FE19 jz short DEBUG_EVENT_NOWAIT_END PAGE:0058FE1B and [ebp+FunUseThisReAs_LocalValue_pDebugObject], 0 ; DEBUG_EVENT_NOWAIT_END: PAGE:0058FE1F and [ebx+Debug_event.ContinueEvent.Header.SignalState], 0 ; ebx = &Debug_Evnt PAGE:0058FE23 mov esi, [ebp+ApiMsg_Re] PAGE:0058FE26 mov [ebx+Debug_event.ContinueEvent.Header.___u0._s0.Type], 1 ; 共用体Type=1 PAGE:0058FE2A mov [ebx+Debug_event.ContinueEvent.Header.___u0._s2.Size], 4 ; Size = 4 PAGE:0058FE2E lea eax, [ebx+Debug_event.ContinueEvent.Header.WaitListHead] ; ;前后指针指向相同的地址,为初始化单节点为双向链表节点 PAGE:0058FE31 mov [eax+Debug_event.EventList.Blink], eax ; 这里是初始化debug_Event 的 List_Head PAGE:0058FE34 mov [eax+Debug_event.EventList.Flink], eax ; 头和尾都指向自身 PAGE:0058FE36 mov eax, [ebp+Process_Re] PAGE:0058FE39 push edi PAGE:0058FE3A mov [ebx+Debug_event.Process], eax ;保存Process PAGE:0058FE3D mov eax, [ebp+Thread_Re] ;DebugEvent.ApiMsg = ApiMsg(传入参数) PAGE:0058FE40 lea edi, [ebx+Debug_event.ApiMsg] ;目标地址 PAGE:0058FE43 push 1Eh ;给ecx准备值 PAGE:0058FE45 mov [ebx+Debug_event.Thread], eax ;保存Thread PAGE:0058FE48 mov [ebp+ApiMsg_LV], edi ;目标地址保存在局部变量中 PAGE:0058FE4B pop ecx ; ECX = 1EH, edi(Debug_Event->ApiMsg) <- esi(ApiMsg) PAGE:0058FE4C rep movsd ;ApiMsg的赋值,esi 为传入的ApiMsg, ;完成Debug_Event.ClientId = Ethread.cid (包含进程和线程) PAGE:0058FE4E mov ecx, [ebp+FunUseThisReAs_LocalValue_pDebugObject] PAGE:0058FE51 mov esi, eax ;eax = thread PAGE:0058FE53 mov eax, [esi+_ETHREAD.cid.UniqueProcess] PAGE:0058FE59 mov [ebx+Debug_event.ClientId.UniqueProcess], eax PAGE:0058FE5C mov eax, [esi+_ETHREAD.cid.UniqueThread] PAGE:0058FE62 xor edi, edi PAGE:0058FE64 cmp ecx, edi ; 判断变量值是否为NULL PAGE:0058FE66 mov [ebx+Debug_event.ClientId.UniqueThread], eax ;以上主要赋值操作如前图所示 PAGE:0058FE69 jnz short pDEBUG_OBJECT_NOT_QEU_0 ; 判断标志是否为0,有跳过则设置为NULL PAGE:0058FE6B mov [ebp+Thread_Re], 0C0000353h ; STATUS_PORT_NOT_SET PAGE:0058FE72 jmp short EQU_0_END pDEBUG_OBJECT_NOT_QEU_0: PAGE:0058FE74 add ecx, 10h ; FastMutex PAGE:0058FE77 mov [ebp+TargetDebugObject_Re], ecx PAGE:0058FE7A call @ExAcquireFastMutex@4 ; ExAcquireFastMutex(x) PAGE:0058FE7F mov edx, [ebp+FunUseThisReAs_LocalValue_pDebugObject] PAGE:0058FE82 test byte ptr [edx+_DEBUG_OBJECT.Flags], 1 ; 1 = DEBUG_OBJECT_DELETE_PENDING PAGE:0058FE86 jnz short IS_NOT_DELETE_PENDING ;用来判断是否被调试 ; if ((Flages_LV=(Flages_Re& DEBUG_EVENT_NOWAIT)) == 0) PAGE:0058FE88 cmp [ebp+Flages_LV], edi ;完成添加Debug_Event到Debug_Object.EventList尾部操作 PAGE:0058FE8B lea eax, [edx+_DEBUG_OBJECT.EventList] ; eax = &Debug_Object->EventLsit PAGE:0058FE8E mov ecx, [eax+Debug_event.EventList.Blink] ; EventList(Flink)偏移4是她的第一个成员Blink ; debug_event->EventList.Flink = &Debug_object->EventList.Flink PAGE:0058FE91 mov [ebx+Debug_event.EventList.Flink], eax ;debug_Event->EventList.Blink = Debug_object->EventList.Blink PAGE:0058FE93 mov [ebx+Debug_event.EventList.Blink], ecx ; PAGE:0058FE96 mov [ecx+Debug_event.EventList.Flink], ebx ; PAGE:0058FE98 mov [eax+(_DEBUG_OBJECT.EventList.Blink-30h)], ebx PAGE:0058FE9B jnz short FLAGES_NOT_EQU_0 ; ; SetEvent()函数设置一个事件对象的信号状态去发信号, 并试图满足尽可能多的等待,之前的事件对象状态作为返回值返回 PAGE:0058FE9D push edi ; Wait = FALSE PAGE:0058FE9E push edi ; Increment = 0 PAGE:0058FE9F push edx ; Event pDebug_Object 这个没理解 PAGE:0058FEA0 call _KeSetEvent@12 ; KeSetEvent(x,x,x) PAGE:0058FEA5 FLAGES_NOT_EQU_0: ; thread_re = STATUS_SUCCESS = 0 这里又用函数参数作局部变量使用 PAGE:0058FEA5 mov [ebp+Thread_Re], edi PAGE:0058FEA8 jmp short REALSE_FASE_MUTEX IS_NOT_DELETE_PENDING: PAGE:0058FEAA mov [ebp+Thread_Re], 0C0000354h ; STATUS_DEBUGGER_INACTIVE = 354
2) 中断异常添加的异常信息
被调试进程产生异常时,内核异常处理对应接口KiTrap捕获异常进行处理。部分信息如下
X86异常及中断号
中断号 异常 对应函数
0 除零错误 _KiTrap00
1 调试陷阱 _KiTrap01
3 断点 _KiTrap03
………..
这里仅简述_kiTrap03主要处理过程
kiTrap03 : 为当前发生异常建立一个TrapFrame,保存寄存器, 当前的状态,发生异常时的处理模式,然后掉用CommonDispatchException
CommonDispatchException: mov esi, ecx ; Ethread mov edi, edx ; 调试端口 mov edx, eax mov ebx, [ebp+_KTRAP_FRAME._Eip] dec ebx ; ebx = EIP - 1 mov ecx, 3 mov eax, 80000003h ; Int3断点 call CommonDispatchException
CommonDispatchExceptioin函数主要完成Exception_Record结构体的赋值,然后调用KiDispatchException
CommonDispatchExceptioin: mov [esp+EXCEPTION_RECORD.ExceptionCode], eax ; 参数 eax = ExceptioinCode 创建异常代码 xor eax, eax mov [esp+EXCEPTION_RECORD.ExceptionFlags], eax ; 设置异常标志 mov [esp+EXCEPTION_RECORD.ExceptionRecord], eax ; 设置副异常代码 mov [esp+EXCEPTION_RECORD.ExceptionAddress], ebx ; 参数 ebx = EIP (TRAP3 EIP-1) 异常地址 mov [esp+EXCEPTION_RECORD.NumberParameters], ecx ; 参数 ecx ErNumberParameters ( TRAP3 = 3 ) cmp ecx, 0 ; 这里传入参数为3 jz short ParameNumber_EQU_0 lea ebx, [esp+EXCEPTION_RECORD.ExceptionInformation] ; Exception_Record.ExceptionInformation 是个数组 mov [ebx+(EXCEPTION_RECORD.ExceptionInformation-14h)], edx ; 外面eax的值,可能为0 mov [ebx+4], esi ; EThread 地址 ExcptionInfo[1] = Ethread mov [ebx+8], edi ; ExceptionInfo[2] = edi ParameNumber_EQU_0: mov ecx, esp test byte ptr [ebp+(_KTRAP_FRAME.EFlags+2)], 2 jz short NOT_VM mov eax, 0FFFFh jmp short de20 ; 处理模式 NOT_VM: mov eax, [ebp+_KTRAP_FRAME.SegCs] de20: and eax, 1 ; eax保存之前的模式 push 1 ; 是否第一次 push eax ; 之前的模式 push ebp ; TrapFrame push 0 ; ExceptionFrame X86不是用为NULL push ecx ; 异常记录(传入参数) call _KiDispatchException@20
KiDispatchException完成异常记录的信息格式 DbgkForwardException完成异常信息的添加,还是通过调用DbgkpSendApiMessage来完成。这里主要简述用户模式调试异常的分发。
mov eax, [esi+_EXCEPTION_RECORD.ExceptionCode] cmp eax, 80000003h jz short STATUS_BREAKPOINT ; 是断点就修改正EIP,即EIP = EIP - 1; cmp eax, 10000004h ; KI_EXCEPTION_ACCESS_VIOLATION (KI_EXCEPTION_INTERNAL | 0x4) jnz short END_EXCEPTION_BLOCK ; mov [esi+_EXCEPTION_RECORD.ExceptionCode], 0C0000005h ; 异常代码: 访问异常 cmp byte ptr [ebp+PreviousMode], 1 ; 判断是否UseMode jnz short END_EXCEPTION_BLOCK ; 不是用户模式就跳出 lea eax, [ebp+ContextFrame] push eax ; Context push esi ; ExceptionRecord call _KiCheckForAtlThunk@8 ; 决定一个访问异常被提起由于一个试图执行一个ATL thunk在非执行非栈区 ; 如果是这样,thunk将效仿继续执行 test al, al ; 返回值:TRUE => Context进行了更新,以反映效仿的ATL的thunk,恢复执行。 jnz Handled1 ; 返回值是TRUE,则跳到HANDLE1去 cmp byte ptr ds:0FFDF0280h, 1 ; SharedUserData->ProcessorFeatures[PF_NX_ENABLED] = TRUE jnz short END_EXCEPTION_BLOCK ; ExceptionInfo[0] = 8 = EXCEPTION_EXECUTE_FAULT cmp [esi+_EXCEPTION_RECORD.ExceptionInformation], 8 jnz short END_EXCEPTION_BLOCK ; 不是执行出错就跳出 mov eax, _KeFeatureBits ; KeFeatureBits = 0 test eax, 40000000h ; KF_GLOBAL_32BIT_EXECUTE = 40...全局32位可执行 jnz short SET_EXCPTIONINFO_EQU_0 ; mov ecx, large fs:_KPCR.PrcbData.CurrentThread mov ecx, [ecx+_KTHREAD.___u6.ApcState.Process] test [ecx+_EPROCESS.Pcb.___u25.Flags.field_0], 2 ; ExecuteEnable bit1位置,所以是否为0, jnz short SET_EXCPTIONINFO_EQU_0 test eax, eax ; eax&KF_GLOBAL_32BIT_NOEXECUTE js short END_EXCEPTION_BLOCK ; 符号位为负 eax&800 就看高位有没有了 mov eax, large fs:_KPCR.PrcbData.CurrentThread mov eax, [eax+_ETHREAD.Tcb.___u6.ApcState.Process] test [eax+_EPROCESS.Pcb.___u25.Flags.field_0], 1 ; ExecuteDisable bit0 jnz short END_EXCEPTION_BLOCK SET_EXCPTIONINFO_EQU_0: ; CODE XREF: KiDispatchException(x,x,x,x,x)+C6 j ; KiDispatchException(x,x,x,x,x)+D6 j and [esi+_EXCEPTION_RECORD.ExceptionInformation], 0 jmp short END_EXCEPTION_BLOCK STATUS_BREAKPOINT: ; CODE XREF: KiDispatchException(x,x,x,x,x)+83 j dec [ebp+ContextFrame._Eip] ; 断点的话就需要把EIP向前移一个字节 END_EXCEPTION_BLOCK: ; CODE XREF: KiDispatchException(x,x,x,x,x)+8A
根据是什么模式,调试接口是否NULL来决定是内核处理,还是用户方式处理, 这里仅列出了用户的处理流程
cmp byte ptr [ebp+PreviousMode], 0 ; 判断是否为KernelMode jnz short NOT_KERNEL_MODE ;判断是第几次处理,要使用DebugPort还是ExceptionProt,然就调用对应的下面三种方式; NOT_KERNEL_MODE: cmp [ebp+FirstChance], 1 ;是否是第一次机会 jnz NOT_FIRST_CHANCE_ ;不是则是去第二次分发地方处理 ;有异常调系统首先发给调试器处理(前提是有调试), 如果调试器没有处理,系统还有机会派发第二次给调试器处理。 ;第一次机会,调试端口 push 0 ; 不是第二次 push 1 ; DebugException push esi ; 异常记录 call _DbgkForwardException@12 ; DbgkForwardException(x,x,x) ;如果调试器第一次没有处理还会有第二次机会接受到系统派发的异常处理信息给调试器处理。 ;第二次机会,调试端口 push 1 ; 这里就是第二次机会了 push 1 ; 调试端口 push esi ; ExceptionRecord call _DbgkForwardException@12 ; DbgkForwardException(x,x,x) ;如果派发2次给调试器处理,都没处理的话就会发异常端口来处理异常。 ;第二次机会,异常端口 push 1 ; 第二次机会 push 0 ; 不是调试端口 push esi ; ExceptionRecord call _DbgkForwardException@12 ; 根据传入的DebugException来决定调用哪种接口处理信息
DbgkForwardException 根据传入参数来决定是第几次机会,发送什么端口及异常记录信息
DbgkForwardException( pExceptionRecord , 异常记录结构体指针 isDebugException, 是否是调试异常,是则发送调试端口 isSecondChance) 是否是第二次
mov eax, large fs:_KPCR.PrcbData.CurrentThread push ebx mov ebx, [ebp+DebugException] ; DebugPort = TRUE ExceptionPort = FALSE test bl, bl mov [ebp+ApiMsg], 78005Ch ; 这部分是格式化ApiMsg、 mov [ebp+ZeroInit], 8 mov eax, [eax+_ETHREAD.Tcb.___u6.ApcState.Process] jz short DebugException_EQU_FALSE ; DebugException = FALSE 则是使用ExceptionPort mov ecx, large fs:_KPCR.PrcbData.CurrentThread test byte ptr [ecx+_ETHREAD.CrossThreadFlags], 4 ; PS_CROSS_THREAD_FLAGS_HIDEFROMDBG jz short NOT_CROSS_THREAD_FLAGES xor eax, eax ; eax = NULL jmp short NOT_CROSS_THREAD_FLAGES_END_IfElse ; dl = FALSE NOT_CROSS_THREAD_FLAGES: mov eax, [eax+_EPROCESS.DebugPort] ; DebugException = TRUE 时使用DebugPort NOT_CROSS_THREAD_FLAGES_END_IfElse: xor dl, dl ; DL= FALSE jmp short debugException_EQU_FALSE_END_IfElse DebugException_EQU_FALSE: mov eax, [eax+_EPROCESS.ExceptionPort] ; DebugException = FALSE 则是使用ExceptionPort mov [ebp+ZeroInit], 7 ; LPC_EXCEPTION mov dl, 1 ; DL= TRUE debugException_EQU_FALSE_END_IfElse: test eax, eax ; 判断调试端口是否为NULL jz short END_FUN_SET_RETURN_VALUE push esi mov esi, [ebp+ExceptionRecord] push edi push 14h pop ecx ; ECX = 14H lea edi, [ebp+var_58] ; [ebp+var_58] = [ebp+ExceptioinRecord] 共复制14H个字节 rep movsd xor ecx, ecx ; ECX = 0 cmp [ebp+SecondChance], cl ; 判断是否是第一次机会 0是第一次,1是第二次 pop edi ; 恢复前面压栈保存的寄存器 setz cl ; Set byte if zero (ZF=1) 如果zf标志位1,设置一个1字节寄存器为1 test dl, dl ; dl 是个标志,主要看使用ExceptionPort还是DebugPort ; ExcepP =>dl = 1 DebugP => dl = 0 pop esi ; push ebx ; SuspendProcess BOOL值决定哪种端口使用哪种端口 mov [ebp+FirstChance], ecx ; ecx = !(SecondChance) ; 在SecondChance为0的情况下才会SetZ使cl=>1 ; 也就是SecondChance=0 =>[var_8]=1 SecondChance=1 =>[var_8]=0 jz short DEBUG_PORT push eax ; Port lea eax, [ebp+ApiMsg] push eax ; ApiMsg call _DbgkpSendApiMessageLpc@12 ; DbgkpSendApiMessageLpc(x,x,x) jmp short DEBUG_PORT_END_IfElse DEBUG_PORT: lea eax, [ebp+ApiMsg] push eax ; pApiMsg call _DbgkpSendApiMessage@8 ; DbgkpSendApiMessage(x,x) DEBUG_PORT_END_IfElse: test eax, eax ; 判断DbgkpSendApiMsg是否成功,返回值又是函数中处理信息的函数的返回值 jl short END_FUN_SET_RETURN_VALUE test bl, bl ; 决定使用哪种Port的标志值 jz short IS_DEUBG_PORT ;
说明:
这里只简单的分析了主要的模块,还有很多函数没分析。 又水平确实有限,不足和错误地方难免,还望各位不吝指出或帮忙补充。