三: 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 j
根据是什么模式,调试接口是否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 ;
说明:
这里只简单的分析了主要的模块,还有很多函数没分析。 又水平确实有限,不足和错误地方难免,还望各位不吝指出或帮忙补充。