想要调试一个程序有两种方法,第一种是使用CreateProcess函数创建进程并调试它,另一种是使用DebugActiveProcess函数附加到目标进程调试。这两个函数内部都会创建一个调试对象,将调试对象的句柄保存在调试线程的TEB中,将调试对象保存在目标进程的debugport中,这样两个进程就建立起了联系。当发生调试事件的时候,操作系统会将调试事件结构体挂入调试对象的事件链表中,调试器通过获取调试事件数据来达到调试的目的。
至此,我们已经与目标进程建立起了调试关系,接下来,我们主要来分析操作系统是怎么处理用户态调试事件的。
系统:Win10 1511 Build:10586.
0x0:IDT表
在保护模式下,当有中断或异常发生时,CPU是通过中断描述符表(Interrupt Descriptor Table,简称IDT)来寻找处理函数的。因此,可以说IDT表是CPU(硬件)与操作系统(软件)交接中断和异常的关口(Gate)。
简单来说,IDT表是一张位于物理内存中的线性表,共有256个表项。在IA-32E(64位)模式下,每个IDT表项长度是16个字节,IDT表的总长的是4096字节(4KB)。在32位模式下,每个IDT表项长度是8个字节,IDT表的总长度是2048字节(2KB)。32位与64位主要差异在于地址长度的变化。
IDT表的每个表项是一个所谓的门描述符(Gate Descriptor)结构。之所以这样称呼,是因为IDT表项的基本用途就是引领CPU从一个空间到另一个空间去执行,每个表项好像一个从一个空间到另一个空间的大门(Gate)。在穿越这扇门的时候,CPU会做必要的安全检查和准备工作。
IDT表可以包含以下3种描述符。
任务门(Task-gate)描述符:用于任务切换,里面包含用于选择任务状态段(TSS)的段选择子。可以使用JMP或CALL指令通过任务门来切换到任务门所指向的任务,当CPU因为中断或异常转移到任务门的时候,也会切换到指定的任务。
中断门(interrupt-gate)描述符:用于描述中断处理例程的入口。
陷阱们(trap-gate)描述符:用于描述异常处理例程的入口。
下图描述了3种门描述符内容布局:
0x1:调试的本质
调试的本质其实就是异常的处理,当异常发生时,会调用IDT表中对应的异常处理函数。
日常调试中主要用的有三种异常,硬件断点(设置调试寄存器产生的断点),软件断点(int3断点),内存断点(内存异常产生的断点)。不同类型的异常调用的处理程序略有不同,但是差别不大。
不同的异常(或中断)有不同的中断向量号。
当异常发生时,处理器会根据向量号去IDT表中查找对应的门描述符并且调用其处理程序。调用处理程序时,会根据门描述符的 TYPE位来确定要压栈的参数,根据CPL和DPL来判断需不需要切换栈。
当处理器调用异常处理程序时,如果CPL小于DPL,则会发生栈切换,也就是从用户栈切换到内核栈,内核栈的SS,ESP从TSS中获取。有关这方面的细节,请参考保护模式相关知识,这里不再详细说明。
本篇文章的重点在于异常发生后,操作系统的处理流程是什么样的,下面以int3断点的处理流程进行分析。
0x2:异常处理的起点
当代码运行到Int3时,会引发一个int3中断,CPU会去IDT找到3号中断所对应的IDT表项,执行对应的处理代码:
sub rsp, 8 push rbp sub rsp, 158h lea rbp, [rsp+80h] mov byte ptr [rbp-_KTRAP_FRAME.ExceptionActive], 1 mov [rbp-_KTRAP_FRAME.rax], rax mov [rbp-_KTRAP_FRAME.rcx], rcx mov [rbp-_KTRAP_FRAME.rdx], rdx mov [rbp-_KTRAP_FRAME.r8], r8 mov [rbp-_KTRAP_FRAME.r9], r9 mov [rbp-_KTRAP_FRAME.r10], r10 mov [rbp-_KTRAP_FRAME.r11], r11 test byte ptr [rbp+_KTRAP_FRAME.SegCs], 1 ; 判断CPL确定当前特权级别,如果是内核模式就跳转 jz short loc_14014A3E1 swapgs ; 切换GS寄存器,相当于x32的FS寄存器,在应用层指向的是TEB,在内核模式下应指向KPCR mov r10, gs:_KPCR.Prcb.CurrentThread test byte ptr [r10+CurrentThread.Header.DebugActive], 80h ; 判断UmsPrimary是否置位 jz short loc_14014A3CC mov ecx, 0C0000102h rdmsr shl rdx, 20h or rax, rdx cmp [r10+CurrentThread.Teb], rax jz short loc_14014A3CC cmp [r10+CurrentThread.TebMappedLowVa], rax jz short loc_14014A3CC mov rdx, [r10+CurrentThread.Ucb] bts dword ptr [r10+.CurrentThread.SystemAffinityActive], 8 dec word ptr [r10+CurrentThread.SpecialApcDisable] mov [rdx+80h], rax loc_14014A3CC: ; CODE XREF: KiBreakpointTrap+4E↑j ; KiBreakpointTrap+65↑j ... test byte ptr [r10+CurrentThread.Header.DebugActive], 3 ; 判断ActiveDR7或Instrumented是否置位 mov word ptr [rbp+_KTRAP_FRAME.ErrorCode], 0 jz short loc_14014A3E1 ; 如果ActiveDR7和Instrumented都没置位,则不保存调试寄存器 call KiSaveDebugRegisterState loc_14014A3E1: ; CODE XREF: KiBreakpointTrap+3B↑j 内核模式 ; KiBreakpointTrap+9A↑j cld ; stmxcsr dword ptr [rbp-_KTRAP_FRAME.MxCsr] ldmxcsr dword ptr gs:_KPCR.Prcb movaps xmmword ptr [rbp-_KTRAP_FRAME.Xmm0], xmm0 movaps xmmword ptr [rbp+_KTRAP_FRAME.xmm1], xmm1 movaps xmmword ptr [rbp+_KTRAP_FRAME.xmm2], xmm2 movaps xmmword ptr [rbp+_KTRAP_FRAME.xmm3], xmm3 movaps xmmword ptr [rbp+_KTRAP_FRAME.xmm4], xmm4 movaps xmmword ptr [rbp+_KTRAP_FRAME.xmm5], xmm5 test dword ptr [rbp+_KTRAP_FRAME.EFlags], 200h ; 判断Eflags寄存器的IF位,是否响应可屏蔽中断,置位则响应 jz short loc_14014A414 sti ; 将Eflags寄存器的IF位置位 loc_14014A414: ; CODE XREF: KiBreakpointTrap+D1↑j mov ecx, 80000003h ; 参数1:异常代码 mov edx, 1 参数2 mov r8, [rbp+0E8h] ; 触发异常的下一条指令 dec r8 参数3:重新指向Int3那条指令 mov r9d, 0 参数4 <strong>call KiExceptionDispatch</strong> nop retn</span>
可以看到,处理函数的主要工作就是构建了一个_KTRAP_FRAME结构体,操作系统用这个结构体保存异常处的执行环境,当异常处理完成的时候操作系统要根据这个结构体返回三环。
第一个sub rsp,8是因为要空出来一个ErrorCode的空间,因为处理器调用中断处理程序时会自动像栈中压入一些参数,int3中断压入的参数是SS,RSP,Eflags,CS,RIP,但是有的中断会在此之上在压入一个ErrorCode,所以Int3的处理程序为了统一,需要在入口处sub Rsp,8空出来一个ErrorCode的位置。
然后将rbp压栈,继续提升栈顶开始构建_KTRAP_FRAME结构体,保存好一些通用寄存器后,判断CPL是否为内核态,如果不是内核态则切换GS寄存器。在X64中,由GS寄存器替代了FS寄存器的作用,在应用层GS指向TEB,到内核层GS指向KPCR,所以如果不是应用层的话就需要切换GS寄存器。
然后根据CurrentThread.Header.DebugActive的ActiveDR7和Instrumented来判断是否要保存调试寄存器,当结构体构建完成后,就准备调用KiExceptionDispatch函数进行异常分发。
0x3:KiExceptionDispatch
需要指出的是nt!KiExceptionDispatch (和nt!KiBreakpointTrap一样)是用手工汇编写成。它假设ecx包含异常代码,edx是异常参数的数量(最多3个),r8包含异常发生的地址,r9是第一个异常参数(如果有存在),r10是第二个异常参数(如果存在),r11是第三个异常参数(如果存在),rbp指向_KTRAP_FRAME结构体中的一个段(位于偏移+0x80处)
sub rsp, 1D8h ; 提升栈顶,构建_KEXCEPTION_FRAME和_EXCEPTION_RECORD结构体 lea rax, [rsp+100h] movaps xmmword ptr [rsp+_KEXCEPTION_FRAME.xmm6], xmm6 movaps xmmword ptr [rsp+_KEXCEPTION_FRAME.xmm7], xmm7 movaps xmmword ptr [rsp+_KEXCEPTION_FRAME.xmm8], xmm8 movaps xmmword ptr [rsp+_KEXCEPTION_FRAME.xmm9], xmm9 movaps xmmword ptr [rsp+_KEXCEPTION_FRAME.xmm10], xmm10 movaps xmmword ptr [rax-_KEXCEPTION_FRAME.xmm11], xmm11 movaps xmmword ptr [rax-_KEXCEPTION_FRAME.xmm12], xmm12 movaps xmmword ptr [rax-_KEXCEPTION_FRAME.xmm13], xmm13 movaps xmmword ptr [rax-_KEXCEPTION_FRAME.xmm14], xmm14 movaps xmmword ptr [rax-_KEXCEPTION_FRAME.xmm15], xmm15 mov [rax+_KEXCEPTION_FRAME.rbx], rbx mov [rax+_KEXCEPTION_FRAME.rdi], rdi mov [rax+_KEXCEPTION_FRAME.rsi], rsi mov [rax+_KEXCEPTION_FRAME.r12], r12 mov [rax+_KEXCEPTION_FRAME.r13], r13 mov [rax+_KEXCEPTION_FRAME.r14], r14 mov [rax+_KEXCEPTION_FRAME.r15], r15 mov rax, gs:_KPCR.Prcb.CurrentThread bt dword ptr [rax+CurrentThread.SystemAffinityActive], 8 jnb short loc_14014CB7D test byte ptr [rbp+_KTRAP_FRAME.SegCs], 1 ; 通过CPL判断Previous mode,如果是内核模式则跳转 jz short loc_14014CB7D call KiUmsExceptionEntry ; ums线程相关 loc_14014CB7D: ; CODE XREF: KiExceptionDispatch+6D↑j ; KiExceptionDispatch+76↑j lea rax, [rsp+138h] ; rax指向_EXCEPTION_RECORD结构体 mov [rax+_EXCEPTION_RECORD.ExceptionCode], ecx xor ecx, ecx mov [rax+_EXCEPTION_RECORD.ExceptionFlags], ecx mov [rax+_EXCEPTION_RECORD.ExceptionRecord], rcx mov [rax+_EXCEPTION_RECORD.ExceptionAddress], r8 mov [rax+_EXCEPTION_RECORD.NumberParameters], edx mov [rax+_EXCEPTION_RECORD.ExceptionInformation[0]], r9 mov [rax+_EXCEPTION_RECORD.ExceptionInformation[1]], r10 mov [rax+_EXCEPTION_RECORD.ExceptionInformation[2]], r11 mov r9b, [rbp+_KTRAP_FRAME.SegCs] and r9b, 1 ; PreviousMode mov byte ptr [rsp+20h], 1 ; BOOLEAN FirstChance lea r8, [rbp-80h] ; PKTRAP_FRAME TrapFrame。Rbp指向的还是IDT函数中封装的TrapFrame结构体的腰部,RBP-80指向的就是TrapFrame结构体头部 mov rdx, rsp ; PKEXCEPTION_FRAME ExceptionFrame mov rcx, rax ; PEXCEPTION_RECORD ExceptionRecord call KiDispatchException 异常分发主函数
这个函数的逻辑很简单,函数入口首先就是提升栈顶,留出两个结构体的空间,分别是_KEXCEPTION_FRAME结构体和_EXCEPTION_RECORD结构体。构建完结构体后就调用了KiDispatchException函数用来进行异常分发。
0x4:KiDispatchException
KiDispatchException是异常分发的主函数,函数代码也比较复杂,汇编层次不清晰,所以直接放IDA伪代码了。
__fastcall KiDispatchException(_EXCEPTION_RECORD *ExceptionRecord, KEXCEPTION_FRAME *ExceptionFrame, KTRAP_FRAME *TrapFrame, char PreviousMode, char FirstChance) { char PreviousMode_1; // r15 KTRAP_FRAME *TrapFrame_1; // rsi KEXCEPTION_FRAME *ExceptionFrame_1; // r12 _EXCEPTION_RECORD *ExceptionRecord_1; // rbx signed int Contextflag; // er13 unsigned __int64 v10; // rcx signed __int64 v11; // rcx void *v12; // rsp void *v13; // rsp int v14; // edx unsigned __int64 result; // rax __int64 v16; // r8 bool DebugService; // al __int64 user.rsp; // r12 unsigned __int64 user.rsp_1; // rdx _BYTE *user_context; // r15 SIZE_T size; // rcx _QWORD *v22; // rdx _OWORD *v23; // rcx signed __int64 v24; // rcx __int64 v25; // rbx __int64 CurrentProcess_2; // r14 __int64 v27; // r8 ULONG_PTR PreviousMode_2; // [rsp+20h] [rbp-10h] __int64 SecondChance; // [rsp+28h] [rbp-8h] CONTEXT *Context; // [rsp+30h] [rbp+0h] __int64 CurrentProcess; // [rsp+38h] [rbp+8h] int Contextflag_1; // [rsp+40h] [rbp+10h] __int64 context.P4Home; // [rsp+48h] [rbp+18h] int v35; // [rsp+50h] [rbp+20h] unsigned int ContextLength; // [rsp+54h] [rbp+24h] __int64 ExceptionRecord_2; // [rsp+60h] [rbp+30h] __int64 context.SegCs; // [rsp+68h] [rbp+38h] __int64 ContextEx; // [rsp+70h] [rbp+40h] unsigned __int64 user.rsp_2; // [rsp+78h] [rbp+48h] __int64 TrapFrame_2; // [rsp+88h] [rbp+58h] _BYTE *user_context_1; // [rsp+90h] [rbp+60h] CONTEXT **v43; // [rsp+98h] [rbp+68h] __int64 CurrentProcess_1; // [rsp+A0h] [rbp+70h] MACHINE_FRAME *MachineFrame; // [rsp+A8h] [rbp+78h] __int64 ExceptionFrame_2; // [rsp+B0h] [rbp+80h] __int64 pDesBuffer; // [rsp+C0h] [rbp+90h] _OWORD *context.Rsp; // [rsp+C8h] [rbp+98h] int v49; // [rsp+E0h] [rbp+B0h] __int64 Context.rip; // [rsp+128h] [rbp+F8h] __int64 a3[2]; // [rsp+180h] [rbp+150h] __int64 v52; // [rsp+190h] [rbp+160h] PreviousMode_1 = PreviousMode; TrapFrame_1 = TrapFrame; ExceptionFrame_1 = ExceptionFrame; ExceptionRecord_1 = ExceptionRecord; ExceptionRecord_2 = (__int64)ExceptionRecord; ExceptionFrame_2 = (__int64)ExceptionFrame; TrapFrame_2 = (__int64)TrapFrame; LOBYTE(Context) = PreviousMode; CurrentProcess = *(_QWORD *)(__readgsqword(0x188u) + 0xB8);// CurrentThread.ApcState.Process CurrentProcess_1 = CurrentProcess; __incgsdword(0x5CB4u); // kpcr.kprcb.KeExceptionDispatchCount++ Contextflag = 0x10001F; Contextflag_1 = 0x10001F; if ( PreviousMode ) { if ( KeFeatureBits & 0x800000 ) Contextflag = 0x10005F; Contextflag_1 = Contextflag; } RtlGetExtendedContextLength(Contextflag); // 获取ExtendContext长度 v10 = ContextLength + 15i64; if ( v10 <= ContextLength ) v10 = 0xFFFFFFFFFFFFFF0i64; v11 = v10 & 0xFFFFFFFFFFFFFFF0ui64; v12 = alloca(v11); // 分配CONTEXT_EX内存 v13 = alloca(v11); v43 = &Context; v35 = RtlInitializeExtendedContext((__int64)&Context, Contextflag, (int **)&ContextEx); KeContextFromKframes(TrapFrame_1, ExceptionFrame_1, &Context);// 根据ExceptionFrame和TrapFrame结构体构造Context结构体 if ( ExceptionRecord_1->ExceptionCode == 0x80000003 ) --Context.rip; if ( PreviousMode_1 && *(_QWORD *)(CurrentProcess + 0x6F8) )// CurrentProcess.PicoContext 可能用于windows下的linux子系统 { LOBYTE(result) = PspPicoProviderRoutines_0(ExceptionRecord_1, ExceptionFrame_1, TrapFrame_1, 0i64, PreviousMode_1);// PspPicoProviderRoutines函数 if ( (_BYTE)result ) return result; // 如果异常解决了,就将Context转换成KFrames并返回 } else if ( (unsigned __int8)KiPreprocessFault(ExceptionRecord_1) )// 如果异常是内部通用保护错误、无效操作码或整数除以0,则尝试在不实际引发异常的情况下解决问题。 { LABEL_39: LOBYTE(result) = KeContextToKframes((__int64)TrapFrame_1); return result; } if ( !PreviousMode_1 ) // 判断PreviousMode,正式开始异常分发。 { if ( !FirstChance || (LOBYTE(SecondChance) = 0, LOBYTE(PreviousMode_2) = 0, !(unsigned __int8)KiDebugRoutine( // 第一次分发先交给调试器 TrapFrame_1, ExceptionFrame_1, ExceptionRecord_1, &Context, PreviousMode_2, SecondChance)) && !(unsigned __int8)RtlDispatchException(ExceptionRecord_1, &Context) )// 如果调试器不出来开始调用异常处理函数处理异常 { // 二次分发执行块 LOBYTE(SecondChance) = 1; LOBYTE(PreviousMode_2) = 0; if ( !(unsigned __int8)KiDebugRoutine( TrapFrame_1, ExceptionFrame_1, ExceptionRecord_1, &Context, PreviousMode_2, SecondChance) ) KeBugCheckEx( // 二次分发还没被调试器处理则蓝屏 0x1Eu, ExceptionRecord_1->ExceptionCode, (ULONG_PTR)ExceptionRecord_1->ExceptionAddress, ExceptionRecord_1->ExceptionInformation[0], ExceptionRecord_1->ExceptionInformation[1]); } goto LABEL_39; } v14 = (signed int)context.Rsp; context.P4Home = (__int64)context.Rsp; context.SegCs = (__int64)context.Rsp; // 这里IDA有错误,动态调试是存储在其他变量中的 if ( !(*(_DWORD *)(CurrentProcess + 0x6B4) & 1) )// CurrentProcess.Flags3.Minimal { result = __readgsqword(0x188u); if ( *(_QWORD *)(*(_QWORD *)(result + 0xB8) + 0x428i64)// CurrentThread.Apcstate.Process.WoW64Process && ExceptionRecord_1->ExceptionCode == 0x80000002 && TrapFrame_1->EFlags & 0x40000 ) { _disable(); TrapFrame_1->EFlags &= 0xFFFBFFFF; _enable(); return result; } if ( (context.SegCs & 0xFFF8) == 0x20 ) // 如果在执行32位代码时发生异常,则将异常转换为wow64异常 { if ( ExceptionRecord_1->ExceptionCode == 0x80000003 ) { ExceptionRecord_1->ExceptionCode = 0x4000001F; } else if ( ExceptionRecord_1->ExceptionCode == 0x80000004 ) { ExceptionRecord_1->ExceptionCode = 0x4000001E; } context.P4Home = v14 & 0xFFFFFFF0; context.SegCs = v14 & 0xFFFFFFF0; } } memset( (char *)ExceptionRecord_1 + 8 * (ExceptionRecord_1->NumberParameters + 4i64), 0, -8 * (ExceptionRecord_1->NumberParameters + 4i64) + 0x98);// 将exceptionrecord参数后面的内存清零,为了KdIsThisAKdTrap做准备 if ( FirstChance ) // 用户态第一次分发 { DebugService = KdIsThisAKdTrap(ExceptionRecord_1); if ( !*(_QWORD *)(*(_QWORD *)(__readgsqword(0x188u) + 0xB8) + 0x420i64) && !KdIgnoreUmExceptions || DebugService )// 如果当前进程没有被调试,并且没有设置忽略用户态异常,或者这是一个调试服务则执行下面代码 { LOBYTE(SecondChance) = 0; LOBYTE(PreviousMode_2) = PreviousMode_1; if ( (unsigned __int8)KiDebugRoutine( // 如果异常被调试器处理则返回 TrapFrame_1, ExceptionFrame_1, ExceptionRecord_1, &Context, PreviousMode_2, SecondChance) ) goto LABEL_39; } LOBYTE(result) = DbgkForwardException(ExceptionRecord_1, 1, 0i64); if ( !(_BYTE)result ) // 如果异常没被处理 { if ( !*(_QWORD *)(CurrentProcess + 0x6F8) // CurrentProcess.PicoContext || (LOBYTE(PreviousMode_2) = PreviousMode_1, LOBYTE(result) = PspPicoProviderRoutines_0(// PspPicoProviderRoutines ExceptionRecord_1, ExceptionFrame_1, TrapFrame_1, 1i64, PreviousMode_2), // CurrentProcess.PicoContext如果为零或者PspPicoProviderRoutines返回True执行 !(_BYTE)result) ) { _disable(); TrapFrame_1->EFlags &= 0xFFFFFEFF; // 清空TF标志位,调试标志位 _enable(); v49 = 0xC0000005; user.rsp = context.P4Home; user.rsp_1 = context.P4Home; user.rsp_2 = context.P4Home; if ( (Contextflag & 0x100040) == 0x100040 ) { user.rsp_1 = (context.P4Home - *(unsigned int *)(ContextEx + 0x14)) & 0xFFFFFFFFFFFFFFC0ui64; user.rsp_2 = (context.P4Home - *(unsigned int *)(ContextEx + 0x14)) & 0xFFFFFFFFFFFFFFC0ui64; } CurrentProcess = (user.rsp_1 - 0x28) & 0xFFFFFFFFFFFFFFF0ui64; MachineFrame = (MACHINE_FRAME *)((user.rsp_1 - 0x28) & 0xFFFFFFFFFFFFFFF0ui64);// 这里是开辟用户栈空间 -0x28是_MACHINE_FRAME大小 context.P4Home = CurrentProcess - 0xA0; // MachineFrame - EXCEPTION_RECORD_LENGTH;EXCEPTION_RECORD_LENGTH=sizeof(EXCEPTION_RECORD) + STACK_ROUND) & ~STACK_ROUND context.Rsp = (_OWORD *)(CurrentProcess - 0xA0); pDesBuffer = CurrentProcess - 0xC0; user_context = (_BYTE *)(CurrentProcess - 0x590);// context user_context_1 = (_BYTE *)(CurrentProcess - 0x590); LODWORD(a3[0]) = 0xFFFFFB30; size = user.rsp - (CurrentProcess - 0x590);// 计算出一共栈顶提升了多少字节 HIDWORD(a3[0]) = user.rsp - (CurrentProcess - 0x590); a3[1] = 0x4D0FFFFFB30i64; LODWORD(v52) = user.rsp_1 - (CurrentProcess - 0xC0); HIDWORD(v52) = user.rsp - user.rsp_1; if ( (unsigned __int64)(user.rsp - (CurrentProcess - 0x590) - 1) > 0xFFE ) { ProbeForWrite(user_context, size, 0x10u);// 检测内存地址是否正常,可写,并且不越界 v23 = (_OWORD *)context.P4Home; v22 = (_QWORD *)CurrentProcess; } else { if ( ((_BYTE)CurrentProcess + 0x70) & 0xF ) ExRaiseDatatypeMisalignment(); if ( user_context >= MmUserProbeAddress ) user_context = MmUserProbeAddress; *user_context = *user_context; user_context[size - 1] = user_context[size - 1]; v22 = &MachineFrame->Rip; v23 = context.Rsp; user_context = user_context_1; } v22[3] = user.rsp; *v22 = Context.rip; *v23 = *(_OWORD *)&ExceptionRecord_1->ExceptionCode; v23[1] = *(_OWORD *)&ExceptionRecord_1->ExceptionAddress; v23[2] = *(_OWORD *)ExceptionRecord_1->ExceptionInformation; v23[3] = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[2]; v23[4] = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[4]; v23[5] = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[6]; v23[6] = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[8]; v24 = (signed __int64)(v23 + 8); *(_OWORD *)(v24 - 16) = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[10]; *(_OWORD *)v24 = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[12]; *(_QWORD *)(v24 + 16) = ExceptionRecord_1->ExceptionInformation[14]; v25 = pDesBuffer; v35 = RtlpCopyExtendedContext(1, pDesBuffer, (__int64)a3, Contextflag, ContextEx, 0i64); *(_OWORD *)v25 = *(_OWORD *)a3; *(_QWORD *)(v25 + 16) = v52; _disable(); TrapFrame_1->Rsp = (ULONG64)user_context;// TrapFrame中的RSP设置为新的栈顶 TrapFrame_1->SegCs = 0x33; // 新的CS寄存器 TrapFrame_1->Rip = KeUserExceptionDispatcher;// 返回三环的落脚点,KeUserExceptionDispatcher调用三环的异常处理函数 LOBYTE(result) = KiSetupForInstrumentationReturn((__int64)TrapFrame_1); _enable(); } } } else // 用户态第二次分发 { CurrentProcess_2 = CurrentProcess; LOBYTE(v16) = 1; // SecondChance LOBYTE(result) = DbgkForwardException(ExceptionRecord_1, 1, v16);// 将SecondChance设置为True再次发送给调试器 if ( !(_BYTE)result ) // 如果还是没被处理,判断进程是不是一个Pico进程,如果是的话调用PspPicoProviderRoutines函数 { if ( !*(_QWORD *)(CurrentProcess_2 + 0x6F8)// CurrentProcess.PicoContext || (LOBYTE(PreviousMode_2) = PreviousMode_1, LOBYTE(result) = PspPicoProviderRoutines_0( ExceptionRecord_1, ExceptionFrame_1, TrapFrame_1, 2i64, PreviousMode_2), !(_BYTE)result) ) { LOBYTE(v27) = 1; LOBYTE(result) = DbgkForwardException(ExceptionRecord_1, 0, v27);// 发送给异常端口 if ( !(_BYTE)result ) LOBYTE(result) = ZwTerminateProcess(-1i64, (unsigned int)ExceptionRecord_1->ExceptionCode); } } } return result; }
前面说过,当异常处理完成时,系统要根据TrapFrame结构返回三环,所以该函数首先要备份一下TrapFrame和ExceptionFrame结构体到Context结构体。
if ( PreviousMode ) { if ( KeFeatureBits & 0x800000 ) Contextflag = 0x10005F; Contextflag_1 = Contextflag; } RtlGetExtendedContextLength(Contextflag); // 获取ExtendContext长度 v10 = ContextLength + 15i64; if ( v10 <= ContextLength ) v10 = 0xFFFFFFFFFFFFFF0i64; v11 = v10 & 0xFFFFFFFFFFFFFFF0ui64; v12 = alloca(v11); // 分配CONTEXT_EX内存 v13 = alloca(v11); v43 = &Context; v35 = RtlInitializeExtendedContext((__int64)&Context, Contextflag, (int **)&ContextEx); KeContextFromKframes(TrapFrame_1, ExceptionFrame_1, &Context);// 根据ExceptionFrame和TrapFrame结构体构造Context结构体
如果是int3异常呢,操作系统默认int3指令为cc,占1字节,所以将RIP减一指向触发异常的指令地址。但是其实INT3并不是只有1字节,也有2字节的。
if ( ExceptionRecord_1->ExceptionCode == 0x80000003 ) --Context.rip;
然后判断一下该进程是不是Pico进程,经过查阅资料得知,Pico应该是windows下的linux子系统的进程。PspPicoProviderRoutines可能是负责解决pico进程异常的函数,这是一个函数指针,在windbg中看当前指针为空,可能是由于调试系统并没有安装linux子系统组件的缘故。
如果不是pico进程,则尝试调用KiPreprocessFault处理异常,如果异常是内部通用保护错误,无效操作码或者除零错误,则尝试在不实际引发异常的情况下解决问题。
if ( PreviousMode_1 && *(_QWORD *)(CurrentProcess + 0x6F8) )// CurrentProcess.PicoContext 可能用于windows下的linux子系统 { LOBYTE(result) = PspPicoProviderRoutines_0(ExceptionRecord_1, ExceptionFrame_1, TrapFrame_1, 0i64, PreviousMode_1);// PspPicoProviderRoutines函数 if ( (_BYTE)result ) return result; // 如果异常解决了,就将Context转换成KFrames并返回 } else if ( (unsigned __int8)KiPreprocessFault(ExceptionRecord_1) )// 如果异常是内部通用保护错误、无效操作码或整数除以0,则尝试在不实际引发异常的情况下解决问题。 { LABEL_39: LOBYTE(result) = KeContextToKframes((__int64)TrapFrame_1); return result; }
之后就开始进行正式的异常分发,如果先前模式为内核模式,则调用KiDebugRoutine尝试将异常发给调试器。KiDebugRoutine这是内核的全局变量函数指针,当系统在被调试的状态下指向KdpTrap函数,非调试下指向KdpStub函数。KdpStub函数非常简单,只是做了一些简单的处理就返回了False。KdpTrap函数会将异常发送给内核调试器,如果返回True,代表调试器处理了这个异常,如果返回False,则代表没有处理这个异常。
在第一次分发的时候,首先把异常发给调试器,如果调试器处理了异常则直接返回。如果调试器没有处理异常,则调用RtlDispatchException执行内核异常处理函数。RtlDispatchException的返回值和KiDebugRoutine一样,如果返回True代表异常处理函数处理了异常,如果返回False则代表异常没被处理。
如果异常没被处理,操作系统会将SecondChance设置为True,并且再一次调用KiDebugRoutine,一般调试器在收到SecondChance为True的异常时都会中断,哪怕这个异常不是用户设置的异常。
因为当第二次分发KiDebugRoutine依旧返回False的时候,系统会直接调用KeBugCheckEx引发BSOD!也就是蓝屏~
if ( !PreviousMode_1 ) // 判断PreviousMode,正式开始异常分发。 { if ( !FirstChance || (LOBYTE(SecondChance) = 0, LOBYTE(PreviousMode_2) = 0, !(unsigned __int8)KiDebugRoutine( // 第一次分发先交给调试器 TrapFrame_1, ExceptionFrame_1, ExceptionRecord_1, &Context, PreviousMode_2, SecondChance)) && !(unsigned __int8)RtlDispatchException(ExceptionRecord_1, &Context) )// 如果调试器不出来开始调用异常处理函数处理异常 { // 二次分发执行块 LOBYTE(SecondChance) = 1; LOBYTE(PreviousMode_2) = 0; if ( !(unsigned __int8)KiDebugRoutine( TrapFrame_1, ExceptionFrame_1, ExceptionRecord_1, &Context, PreviousMode_2, SecondChance) ) KeBugCheckEx( // 二次分发还没被调试器处理则蓝屏 0x1Eu, ExceptionRecord_1->ExceptionCode, (ULONG_PTR)ExceptionRecord_1->ExceptionAddress, ExceptionRecord_1->ExceptionInformation[0], ExceptionRecord_1->ExceptionInformation[1]); } goto LABEL_39; }
当先前模式不是内核模式的时候,就要开始进行应用层的异常分发
首先判断了一下CurrentProcess.Flags3.Minimal,这个标志为零才会运行代码块里的内容,不知道这个标志代表什么。
然后判断如果进程是Wow64进程,也就是x32进程的话,并且异常代码是0x80000002,并且Eflag寄存器AC被置位,则这个异常可能是一个对齐异常,将Eflag的AC位清空然后返回。
然后判断通过CS判断如果是一个x32异常,则将异常转换为Wow64的异常代码。
if ( !(*(_DWORD *)(CurrentProcess + 0x6B4) & 1) )// CurrentProcess.Flags3.Minimal { result = __readgsqword(0x188u); if ( *(_QWORD *)(*(_QWORD *)(result + 0xB8) + 0x428i64)// CurrentThread.Apcstate.Process.WoW64Process && ExceptionRecord_1->ExceptionCode == 0x80000002 && TrapFrame_1->EFlags & 0x40000 ) { _disable(); TrapFrame_1->EFlags &= 0xFFFBFFFF; _enable(); return result; } if ( (context.SegCs & 0xFFF8) == 0x20 ) // 如果在执行32位代码时发生异常,则将异常转换为wow64异常 { if ( ExceptionRecord_1->ExceptionCode == 0x80000003 ) { ExceptionRecord_1->ExceptionCode = 0x4000001F; } else if ( ExceptionRecord_1->ExceptionCode == 0x80000004 ) { ExceptionRecord_1->ExceptionCode = 0x4000001E; } context.P4Home = v14 & 0xFFFFFFF0; context.SegCs = v14 & 0xFFFFFFF0; } }
然后将ExceptionRecord没有数据的内存清零,为了后面调用KdIsThisAKdTrap做准备。
调用KdIsThisAKdTrap判断这个异常是不是一个调试服务,当需要打印调试,征求用户输入,报告模块加载卸载时,系统会触发一个异常,编号为0x2D。通常把这个异常称之为调试服务(DebugService)异常。观察IDT表,可以看到这个异常的处理函数是KiDebugService,这个函数做了一些预处理工作后就跳转到了Int3中断处理函数,我们知道int3是断点异常的处理函数,因此,从KiDebugService跳转到int3后,要传递的调试信息会被当作断点异常的参数传给异常分发函数。ExceptionRecord结构中的ExceptionInfomation[0]标识了这个异常是一个真正的断点异常,还是一个调试服务异常,它可以是以下几个值之一:
BREAKPOINT_BREAK(0):真正的断点异常。
BREAKPOINT_PRINT(1):打印调试信息,ExceptionInformation[1]为要打印的字符串。
BREAKPOINT_PROMPT(2):提示并要求用户输入,ExceptionInformation[1]指向提示字符串,ExceptionInformation[2]用来指向存放用户输入的缓冲区。
BREAKPOINT_LOAD_SYMBOLS(3):加载符号文件,ExceptionInformation[1]指向模块文件名称,ExceptionInformation[2]是模块的基地址。
BREAKPOINT_UNLOAD_SYMBOLS(4):卸载符号文件:ExceptionInformation[1]指向模块文件名称,ExceptionInformation[2]是模块的基地址。
如果当前进程没有被调试,并且系统没有设置忽略用户态异常,或者这是一个调试服务则把异常发送给内核调试器,如果异常被内核调试器处理则直接返回。
memset( (char *)ExceptionRecord_1 + 8 * (ExceptionRecord_1->NumberParameters + 4i64), 0, -8 * (ExceptionRecord_1->NumberParameters + 4i64) + 0x98);// 将exceptionrecord参数后面的内存清零,为了KdIsThisAKdTrap做准备 if ( FirstChance ) // 用户态第一次分发 { DebugService = KdIsThisAKdTrap(ExceptionRecord_1); if ( !*(_QWORD *)(*(_QWORD *)(__readgsqword(0x188) + 0xB8) + 0x420) && !KdIgnoreUmExceptions || DebugService )// 如果当前进程没有被调试,并且没有设置忽略用户态异常,或者这是一个调试服务则执行下面代码 { LOBYTE(SecondChance) = 0; LOBYTE(PreviousMode_2) = PreviousMode_1; if ( (unsigned __int8)KiDebugRoutine( // 如果异常被调试器处理则返回 TrapFrame_1, ExceptionFrame_1, ExceptionRecord_1, &Context, PreviousMode_2, SecondChance) ) goto LABEL_39; }
如果没被内核调试器处理则开始调用DbgkForwardException尝试将异常发送给三环调试器,如果调试器返回False则代表异常没有被处理。
当调试器没有处理异常的时候,还是判断当前进程是不是pico进程,如果是调用PspPicoProviderRoutines处理异常,或者DbgkForwardException返回False的之后向下执行。
if代码块中代码主要任务就是将异常信息拷贝到三环栈中,然后返回三环去执行异常处理函数。
首先要清空TrapFrame_1中Eflag寄存器的调试标志位,避免回到三环的时候循环出发异常
由于Context多了一个扩展结构,这个结构的定义不太清楚,所以不能分析的太详细,能力有限。
数据拷贝完成后设置三环的落脚点为KeUserExceptionDispatcher,也就是三环的异常分发函数。
值得一提的是KiSetupForInstrumentationReturn函数将_kprocess结构体中的InstrumentationCallback设置成了RIP的地址,然后将原本的RIP保存在了R10寄存器中,百度查询说这个字段指向一个回调函数,每次从内核层返回用户层的时候会调用。
LOBYTE(result) = DbgkForwardException(ExceptionRecord_1, 1, 0); if ( !(_BYTE)result ) // 如果异常没被处理 { if ( !*(_QWORD *)(CurrentProcess + 0x6F8) // CurrentProcess.PicoContext || (LOBYTE(PreviousMode_2) = PreviousMode_1, LOBYTE(result) = PspPicoProviderRoutines_0(// PspPicoProviderRoutines ExceptionRecord_1, ExceptionFrame_1, TrapFrame_1, 1i64, PreviousMode_2), // CurrentProcess.PicoContext如果为零或者PspPicoProviderRoutines返回True执行 !(_BYTE)result) ) { _disable(); TrapFrame_1->EFlags &= 0xFFFFFEFF; // 清空TF标志位,调试标志位 _enable(); v49 = 0xC0000005; user.rsp = context.P4Home; user.rsp_1 = context.P4Home; user.rsp_2 = context.P4Home; if ( (Contextflag & 0x100040) == 0x100040 ) { user.rsp_1 = (context.P4Home - *(unsigned int *)(ContextEx + 0x14)) & 0xFFFFFFFFFFFFFFC0ui64; user.rsp_2 = (context.P4Home - *(unsigned int *)(ContextEx + 0x14)) & 0xFFFFFFFFFFFFFFC0ui64; } CurrentProcess = (user.rsp_1 - 0x28) & 0xFFFFFFFFFFFFFFF0ui64; MachineFrame = (MACHINE_FRAME *)((user.rsp_1 - 0x28) & 0xFFFFFFFFFFFFFFF0ui64);// 这里是开辟用户栈空间 -0x28是_MACHINE_FRAME大小 context.P4Home = CurrentProcess - 0xA0; // MachineFrame - EXCEPTION_RECORD_LENGTH;EXCEPTION_RECORD_LENGTH=sizeof(EXCEPTION_RECORD) + STACK_ROUND) & ~STACK_ROUND context.Rsp = (_OWORD *)(CurrentProcess - 0xA0); pDesBuffer = CurrentProcess - 0xC0; user_context = (_BYTE *)(CurrentProcess - 0x590);// context user_context_1 = (_BYTE *)(CurrentProcess - 0x590); LODWORD(a3[0]) = 0xFFFFFB30; size = user.rsp - (CurrentProcess - 0x590);// 计算出一共栈顶提升了多少字节 HIDWORD(a3[0]) = user.rsp - (CurrentProcess - 0x590); a3[1] = 0x4D0FFFFFB30i64; LODWORD(v52) = user.rsp_1 - (CurrentProcess - 0xC0); HIDWORD(v52) = user.rsp - user.rsp_1; if ( (unsigned __int64)(user.rsp - (CurrentProcess - 0x590) - 1) > 0xFFE ) { ProbeForWrite(user_context, size, 0x10u);// 检测内存地址是否正常,可写,并且不越界 v23 = (_OWORD *)context.P4Home; v22 = (_QWORD *)CurrentProcess; } else { if ( ((_BYTE)CurrentProcess + 0x70) & 0xF ) ExRaiseDatatypeMisalignment(); if ( user_context >= MmUserProbeAddress ) user_context = MmUserProbeAddress; *user_context = *user_context; user_context[size - 1] = user_context[size - 1]; v22 = &MachineFrame->Rip; v23 = context.Rsp; user_context = user_context_1; } v22[3] = user.rsp; *v22 = Context.rip; *v23 = *(_OWORD *)&ExceptionRecord_1->ExceptionCode; v23[1] = *(_OWORD *)&ExceptionRecord_1->ExceptionAddress; v23[2] = *(_OWORD *)ExceptionRecord_1->ExceptionInformation; v23[3] = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[2]; v23[4] = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[4]; v23[5] = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[6]; v23[6] = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[8]; v24 = (signed __int64)(v23 + 8); *(_OWORD *)(v24 - 16) = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[10]; *(_OWORD *)v24 = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[12]; *(_QWORD *)(v24 + 16) = ExceptionRecord_1->ExceptionInformation[14]; v25 = pDesBuffer; v35 = RtlpCopyExtendedContext(1, pDesBuffer, (__int64)a3, Contextflag, ContextEx, 0i64); *(_OWORD *)v25 = *(_OWORD *)a3; *(_QWORD *)(v25 + 16) = v52; _disable(); TrapFrame_1->Rsp = (ULONG64)user_context;// TrapFrame中的RSP设置为新的栈顶 TrapFrame_1->SegCs = 0x33; // 新的CS寄存器 TrapFrame_1->Rip = KeUserExceptionDispatcher;// 返回三环的落脚点,KeUserExceptionDispatcher调用三环的异常处理函数 LOBYTE(result) = KiSetupForInstrumentationReturn((__int64)TrapFrame_1); _enable(); }
用户层的第一次异常分发到这里就结束了。
下面就是用户层的第二次异常分发。
第二次用户层异常分发先是将SecondChance设置为True,然后调用DbgkForwardException尝试将异常发送给调试器,跟内核调试器一样,应用层调试器一般也都会处理SecondChance为True的异常。
如果DbgkForwardException还是返回False,那么系统会再次调用DbgkForwardException将这个异常发送给异常端口。
如果还是返回False的话,直接调用ZwTerminateProcess结束进程!
else // 用户态第二次分发 { CurrentProcess_2 = CurrentProcess; LOBYTE(v16) = 1; // SecondChance LOBYTE(result) = DbgkForwardException(ExceptionRecord_1, 1, v16);// 将SecondChance设置为True再次发送给调试器 if ( !(_BYTE)result ) // 如果还是没被处理,判断进程是不是一个Pico进程,如果是的话调用PspPicoProviderRoutines函数 { if ( !*(_QWORD *)(CurrentProcess_2 + 0x6F8)// CurrentProcess.PicoContext || (LOBYTE(PreviousMode_2) = PreviousMode_1, LOBYTE(result) = PspPicoProviderRoutines_0( ExceptionRecord_1, ExceptionFrame_1, TrapFrame_1, 2i64, PreviousMode_2), !(_BYTE)result) ) { LOBYTE(v27) = 1; LOBYTE(result) = DbgkForwardException(ExceptionRecord_1, 0, v27);// 发送给异常端口 if ( !(_BYTE)result ) LOBYTE(result) = ZwTerminateProcess(-1i64, (unsigned int)ExceptionRecord_1->ExceptionCode); } } } return result; }
0x5:DbgkForwardException
在经历KiDispatchException的异常分发后,用户态异常终于来到了DbgkForwardException函数,这个函数主要的任务是将异常结构转换为DBGKM_MSG结构,然后根据不同的参数选择发送给异常端口还是调试端口,废话不多说,直接上代码:
bool __fastcall DbgkForwardException(EXCEPTION_RECORD *ExceptionRecord, char DebugPort, __int64 SecondChance) { char SecondChance_1; // r15 char DebugPort_1; // di EXCEPTION_RECORD *ExceptionRecord_1; // r12 char UseLpc; // r14 unsigned __int64 CurrentThrad; // rax ULONG_PTR CurrentProcess; // rsi void *DebugObject; // rbx __int128 v11; // xmm1 ULONG_PTR v12; // rax __int128 v13; // xmm0 __int128 v14; // xmm1 __int128 v15; // xmm0 __int128 v16; // xmm1 __int128 v17; // xmm0 __int128 v18; // xmm1 int result; // esi signed int ReturnedStatus; // eax __int64 v21; // [rsp+20h] [rbp-E0h] DBGKM_MSG ApiMessage; // [rsp+30h] [rbp-D0h] SecondChance_1 = SecondChance; DebugPort_1 = DebugPort; ExceptionRecord_1 = ExceptionRecord; UseLpc = 1; if ( (_BYTE)SecondChance ) { v21 = 1i64; PsSetProcessFaultInformation(*(_QWORD *)(__readgsqword(0x188u) + 0xB8), &v21);// 如果是第二次分发,调用PsSetProcessFaultInformation设置进程故障信息 } ApiMessage.ApiNumber = 0; ApiMessage.h.u1.Length = 0xD000A8; ApiMessage.h.u2.ZeroInit = 8; CurrentThrad = __readgsqword(0x188u); CurrentProcess = *(_QWORD *)(CurrentThrad + 0xB8); if ( DebugPort_1 ) // 是否发送给调试端口 { if ( *(_DWORD *)(__readgsqword(0x188u) + 0x6BC) & 4 )// 判断是否设置了CrossThreadFlags.HideFromDebugger,如果设置了HideFromDebugger标志位,则将调试对象清零 DebugObject = 0i64; else DebugObject = *(void **)(CurrentProcess + 0x420);// 如果没有设置HideFromDebugger位则获取调试对象 UseLpc = 0; } else { DebugObject = (void *)PsCaptureExceptionPort(*(_QWORD *)(CurrentThrad + 0xB8));// 获取异常端口对象 ApiMessage.h.u2.ZeroInit = 7; } if ( !DebugObject && DebugPort_1 ) // 如果DebugObject为空则返回False return 0; v11 = *(_OWORD *)&ExceptionRecord_1->ExceptionAddress;// 开始构建ApiMessage结构 *(_OWORD *)&ApiMessage.u.Exception.ExceptionRecord.ExceptionCode = *(_OWORD *)&ExceptionRecord_1->ExceptionCode; v12 = ExceptionRecord_1->ExceptionInformation[14]; v13 = *(_OWORD *)ExceptionRecord_1->ExceptionInformation; *((_OWORD *)&ApiMessage.u.UnloadDll + 1) = v11; v14 = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[2]; *((_OWORD *)&ApiMessage.u.UnloadDll + 2) = v13; v15 = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[4]; *((_OWORD *)&ApiMessage.u.UnloadDll + 3) = v14; v16 = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[6]; *((_OWORD *)&ApiMessage.u.UnloadDll + 4) = v15; v17 = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[8]; *((_OWORD *)&ApiMessage.u.UnloadDll + 5) = v16; v18 = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[12]; *((_OWORD *)&ApiMessage.u.UnloadDll + 6) = v17; *((_OWORD *)&ApiMessage.u.UnloadDll + 7) = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[10]; *((_OWORD *)&ApiMessage.u.UnloadDll + 8) = v18; ApiMessage.u.Exception.ExceptionRecord.ExceptionInformation[14] = v12; ApiMessage.u.Exception.FirstChance = SecondChance_1 == 0; if ( !UseLpc ) { result = DbgkpSendApiMessage(CurrentProcess, DebugPort_1 != 0, (__int64)&ApiMessage);// 调用DbgkpSendApiMessage发送刚才构建的结构体,如果是发送给调试端口,则挂起进程 goto LABEL_15; } if ( DebugObject ) { // 如果执行到这里,代表是发送给异常端口的 LOBYTE(SecondChance) = DebugPort_1; result = DbgkpSendApiMessageLpc(&ApiMessage, DebugObject, SecondChance); ObfDereferenceObject(DebugObject); LABEL_15: ReturnedStatus = ApiMessage.ReturnedStatus; // 获取ApiMessage中的ReturnStatus,判断异常是否处理。 goto LABEL_16; } ReturnedStatus = 0x80010001; result = 0; ApiMessage.ReturnedStatus = 0x80010001; LABEL_16: if ( result < 0 ) // 返回值是NTSTATUS类型,如果小于0则代表失败,直接返回0代表异常未处理。 return 0; if ( ReturnedStatus == 0x80010001 ) // 如果返回值不小于零,代表函数没有出错,这种情况下通过ApiMessage的ReturnStatus判断异常是否被处理,如果等于0x80010001代表异常没被处理。 { if ( !DebugPort_1 ) // 如果Debugport为0,代表这是要发送给异常端口的异常,对于这种异常,调用DbgkpSendErrorMessage发送错误消息。 { ReturnedStatus = DbgkpSendErrorMessage(ExceptionRecord_1, 2i64, &ApiMessage); return ReturnedStatus >= 0; } return 0; } return ReturnedStatus >= 0; // 如果DbgkpSendApiMessage返回值正常,并且ApiMessage.ReturnStatus的值也不等于0x80010001,这代表异常被成功处理,直接返回True. }
函数首先显示判断了是否是第二次分发,如果是第二次分发则设置进程故障信息,毕竟如果这次异常还是没被处理的话进程就直接被干掉了~
if ( (_BYTE)SecondChance ) { v21 = 1i64; PsSetProcessFaultInformation(*(_QWORD *)(__readgsqword(0x188u) + 0xB8), &v21);// 如果是第二次分发,调用PsSetProcessFaultInformation设置进程故障信息 }
接下来就开始简单的初始化DBGKM_MSG结构了,然后根据是是发送给异常端口还是调试端口,开始获取对应的对象。
如果是要发送给调试端口,则首先要检测一下CurrentThread.CrossThreadFlags.HideFromDebugger标志位是否被置位,如果这个标志被置位则直接将存储调试对象的变量置为零。这个标志位可以让这个线程的异常不走调试器,走正常的异常分发,这也是一个反调试的手段。
这个标志位可以调用ZwSetInformationThread函数来设置,这个函数是windows未公开的一个函数,在NTDLL模块导出,可以使用GetProcAddress来获取,这里不详细说明,有兴趣的可以去查资料。
如果HideFromDebugger没被置零,则通过进程结构体获取调试对象(CurrentProcess.DebugPort).
如果不是发送给调试端口的,则调用PsCaptureExceptionPort获取异常端口对象。
ApiMessage.ApiNumber = 0; ApiMessage.h.u1.Length = 0xD000A8; ApiMessage.h.u2.ZeroInit = 8; CurrentThrad = __readgsqword(0x188u); CurrentProcess = *(_QWORD *)(CurrentThrad + 0xB8); if ( DebugPort_1 ) // 是否发送给调试端口 { if ( *(_DWORD *)(__readgsqword(0x188u) + 0x6BC) & 4 )// 判断是否设置了CrossThreadFlags.HideFromDebugger,如果设置了HideFromDebugger标志位,则将调试对象清零 DebugObject = 0i64; else DebugObject = *(void **)(CurrentProcess + 0x420);// 如果没有设置HideFromDebugger位则获取调试对象 UseLpc = 0; } else { DebugObject = (void *)PsCaptureExceptionPort(*(_QWORD *)(CurrentThrad + 0xB8));// 获取异常端口对象 ApiMessage.h.u2.ZeroInit = 7; }</span> 然后开始判断调试对象是否获取成功,如果调试对象为零,并且参数是要发送给调试对象,则直接返回False. 然后开始根据ExceptionRecord开始构建DBGKM_MSG结构体。 <span style="font-family:Courier New,Courier,monospace">if ( !DebugObject && DebugPort_1 ) // 如果DebugObject为空则返回False return 0; v11 = *(_OWORD *)&ExceptionRecord_1->ExceptionAddress;// 开始构建ApiMessage结构 *(_OWORD *)&ApiMessage.u.Exception.ExceptionRecord.ExceptionCode = *(_OWORD *)&ExceptionRecord_1->ExceptionCode; v12 = ExceptionRecord_1->ExceptionInformation[14]; v13 = *(_OWORD *)ExceptionRecord_1->ExceptionInformation; *((_OWORD *)&ApiMessage.u.UnloadDll + 1) = v11; v14 = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[2]; *((_OWORD *)&ApiMessage.u.UnloadDll + 2) = v13; v15 = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[4]; *((_OWORD *)&ApiMessage.u.UnloadDll + 3) = v14; v16 = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[6]; *((_OWORD *)&ApiMessage.u.UnloadDll + 4) = v15; v17 = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[8]; *((_OWORD *)&ApiMessage.u.UnloadDll + 5) = v16; v18 = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[12]; *((_OWORD *)&ApiMessage.u.UnloadDll + 6) = v17; *((_OWORD *)&ApiMessage.u.UnloadDll + 7) = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[10]; *((_OWORD *)&ApiMessage.u.UnloadDll + 8) = v18; ApiMessage.u.Exception.ExceptionRecord.ExceptionInformation[14] = v12; ApiMessage.u.Exception.FirstChance = SecondChance_1 == 0;
然后就开始调用函数发送刚才构建的结构体了。
这里判断是否使用LPC,这个UseLPC变量是一个内部变量,初始化赋值为True,在获取调试对象时候赋值为False。
如果不使用LPC则调用DbgkpSendApiMessage函数发送结构体,函数中参数并没有调试对象,需要在函数内部获取。函数的第二个参数代表是否挂起进程。
如果不是发送给调试端口则判断DebugObject是否为零,这个变量中存储的不一定是调试对象,如果是发送给异常端口的话这个变量中存储的就是异常端口对象。
函数返回后首先判断函数返回值是否正常,如果函数返回值不正常则直接返回False.如果函数返回值正常,则判断ApiMessage.ReturnedStatus是否等于0x80010001,这个成员代表异常是否被调试器处理,如果等于0x80010001代表调试器未处理。
如果调试器 未处理异常,判断是否是发送给异常端口的,如果发送给异常端口则调用DbgkpSendErrorMessage发送错误消息,如果DbgkpSendErrorMessage返回值大于等于0,则返回True,否则返回False.
if ( !UseLpc ) { result = DbgkpSendApiMessage(CurrentProcess, DebugPort_1 != 0, (__int64)&ApiMessage);// 调用DbgkpSendApiMessage发送刚才构建的结构体,如果是发送给调试端口,则挂起进程 goto LABEL_15; } if ( DebugObject ) { // 如果执行到这里,代表是发送给异常端口的 LOBYTE(SecondChance) = DebugPort_1; result = DbgkpSendApiMessageLpc(&ApiMessage, DebugObject, SecondChance); ObfDereferenceObject(DebugObject); LABEL_15: ReturnedStatus = ApiMessage.ReturnedStatus; // 获取ApiMessage中的ReturnStatus,判断异常是否处理。 goto LABEL_16; } ReturnedStatus = 0x80010001; result = 0; ApiMessage.ReturnedStatus = 0x80010001; LABEL_16: if ( result < 0 ) // 返回值是NTSTATUS类型,如果小于0则代表失败,直接返回0代表异常未处理。 return 0; if ( ReturnedStatus == 0x80010001 ) // 如果返回值不小于零,代表函数没有出错,这种情况下通过ApiMessage的ReturnStatus判断异常是否被处理,如果等于0x80010001代表异常没被处理。 { if ( !DebugPort_1 ) // 如果Debugport为0,代表这是要发送给异常端口的异常,对于这种异常,调用DbgkpSendErrorMessage发送错误消息。 { ReturnedStatus = DbgkpSendErrorMessage(ExceptionRecord_1, 2i64, &ApiMessage); return ReturnedStatus >= 0; } return 0; } return ReturnedStatus >= 0; // 如果DbgkpSendApiMessage返回值正常,并且ApiMessage.ReturnStatus的值也不等于0x80010001,这代表异常被成功处理,直接返回True. }
0x6:DbgkpSendApiMessage
DbgkForwardException函数根据异常结构体构造了一个DBGKM_MSG结构体后,通过DbgkpSendApiMessage函数来发送调试事件。
函数首先判断PerfGlobalGroupMask是否开启了调试事件追踪,如果是就调用EtwTraceDebuggerEvent。
判断如果当前进程是指定进程,并且指定要挂起进程,则调用DbgkpSuspendProcess挂起进程,然后调用DbgkpQueueMessage将调试消息插入队列。
然后调用ZwFlushInstructionCache刷新指令缓存,这个函数的意思不太了解,百度说是刷新指令缓存。
然后恢复进程运行,判断DbgkpQueueMessage返回值和ApiMessage返回值,如果都正常就返回了,不正常在循环调用一次。
这个函数的工作主要就是挂起进程,主要的工作都是调用DbgkpQueueMessage来做的。
__int64 __fastcall DbgkpSendApiMessage(ULONG_PTR CurrentProcess, char SuspendProcess, DBGKM_MSG *ApiMessage) { DBGKM_MSG *ApiMessage_1; // r15 char SuspendProcess_1; // bp ULONG_PTR CurrentProcess_1; // rbx int v6; // er14 unsigned int v7; // esi ApiMessage_1 = ApiMessage; SuspendProcess_1 = SuspendProcess; CurrentProcess_1 = CurrentProcess; if ( PerfGlobalGroupMask & 0x400000 ) EtwTraceDebuggerEvent(*(_QWORD *)(__readgsqword(0x188u) + 0xB8), __readgsqword(0x188u), 1); do { v6 = 0; if ( CurrentProcess_1 == *(_QWORD *)(__readgsqword(0x188u) + 0xB8) && SuspendProcess_1 & 1 )// 如果参数指定的进程等于当前进程,并且设置挂起进程,则调用DbgkpSuspendProcess挂起进程。 v6 = (unsigned __int8)DbgkpSuspendProcess(CurrentProcess_1); ApiMessage_1->ReturnedStatus = 0x103; v7 = DbgkpQueueMessage( CurrentProcess_1, __readgsqword(0x188u), ApiMessage_1, (SuspendProcess_1 & 2) != 0 ? 0x40 : 0, 0i64); ZwFlushInstructionCache(); // 刷新指令缓存 if ( v6 ) // 根据是否冻结进程判定是否解冻进程 { PsThawProcess(CurrentProcess_1, 0i64); KeLeaveCriticalRegion(); } } while ( (v7 & 0x80000000) == 0 && ApiMessage_1->ReturnedStatus == 0x40010001 );// 如果返回值没出错,并且ApiMessage的返回状态为0x40010001,则创新调用DbgkpQueueMessage函数 return v7; }
0x7:DbgkpQueueMessage
这个函数的主要任务就是根据DBGKM_MSG构建一个调试事件,并且将这个事件挂入事件链表中,这也是用户态调试的最后一个函数。
__int64 __usercall DbgkpQueueMessage@<rax>(ULONG_PTR CurrentProcess@<rcx>, ULONG_PTR CurrentThrad@<rdx>, DBGKM_MSG *ApiMessage@<r8>, int Flags@<r9d>, PRKEVENT a5) { _DEBUG_OBJECT *DebugObject; // rbx signed __int64 v6; // r12 int Flags_1; // esi DBGKM_MSG *ApiMessage_1; // rbp ULONG_PTR CurrentThrad_1; // r15 ULONG_PTR CurrentProcess_1; // r13 _DEBUG_EVENT *DebugEvent; // rax _DEBUG_EVENT *DebugEvent_1; // r14 DBGKM_APINUMBER ApiNumber; // ecx DBGKM_MSG *v15; // rsi DBGKM_MSG *v16; // rax DBGKM_MSG *v17; // rcx signed __int64 v18; // rdx __int128 v19; // xmm1 signed int result; // ebx struct _FAST_MUTEX *debug_Mutex; // r12 _LIST_ENTRY *v22; // rcx __int128 v23; // xmm1 int NOWAIT; // [rsp+30h] [rbp-1C8h] _DEBUG_EVENT v25; // [rsp+40h] [rbp-1B8h] DebugObject = (_DEBUG_OBJECT *)a5; v6 = 2i64; Flags_1 = Flags; ApiMessage_1 = ApiMessage; NOWAIT = Flags & 2; // NOWAIT CurrentThrad_1 = CurrentThrad; CurrentProcess_1 = CurrentProcess; if ( Flags & 2 ) // DEBUG_EVENT_NOWAIT,检查Flag是否设置了NOWAIT标志,如果没有设置,函数需要等待调试器回复,如果设置了这个标志,则不需要等待调试器 { DebugEvent = (_DEBUG_EVENT *)ExAllocatePoolWithQuotaTag((POOL_TYPE)0x208, 0x168ui64, 0x45676244u);// 如果不需要等待调试器回复,则不能使用栈中的内存构建DEBUG_EVENT结构体,需要分配堆内存 DebugEvent_1 = DebugEvent; if ( !DebugEvent ) return 0xC000009Ai64; DebugEvent->Flags = Flags_1 | 4; ObfReferenceObjectWithTag(CurrentProcess_1, 1332175428i64); ObfReferenceObjectWithTag(CurrentThrad_1, 1332175428i64); DebugEvent_1->BackoutThread = (PETHREAD)__readgsqword(0x188u); } else { // 等待调试器回复 v25.Flags = Flags; DebugEvent_1 = &v25; ExAcquireFastMutex(&DbgkpProcessDebugPortMutex); ApiNumber = ApiMessage_1->ApiNumber; DebugObject = *(_DEBUG_OBJECT **)(CurrentProcess_1 + 0x420); if ( (unsigned int)(ApiNumber - 1) <= 1 && *(_BYTE *)(CurrentThrad_1 + 0x6BC) & 0x40 )// 判断这个事件如果是线程进程创建,并且线程设置了SkipCreationMsg,则将DebugObject设置为零 DebugObject = 0i64; if ( ApiNumber == 5 && (unsigned __int8)Flags_1 & *(_BYTE *)(CurrentThrad_1 + 0x6BC) & 0x40 )// 判断这个事件如果是模块加载,并且线程设置了SkipCreationMsg,则将DebugObject设置为零 DebugObject = 0i64; if ( (unsigned int)(ApiNumber - 3) <= 1 && *(_BYTE *)(CurrentThrad_1 + 0x6BC) < 0 )// 判断这个事件如果是线程进程退出,并且线程设置了SkipTerminationMsg,则将DebugObject设置为零,这里F5有错误,实际上后面会&0x80 DebugObject = 0i64; KeInitializeEvent(&v25.ContinueEvent, SynchronizationEvent, 0);// 初始化一个同步对象,为了等待调试器回复 } v15 = &DebugEvent_1->ApiMsg; DebugEvent_1->Process = (PEPROCESS)CurrentProcess_1; v16 = &DebugEvent_1->ApiMsg; DebugEvent_1->Thread = (PETHREAD)CurrentThrad_1; v17 = ApiMessage_1; v18 = 2i64; do { *(_OWORD *)&v16->h.u1.s1.DataLength = *(_OWORD *)&v17->h.u1.s1.DataLength; *(union _PORT_MESSAGE::$C6BC508B531A81D74C33985719C23F49 *)((char *)&v16->h.8 + 8) = *(union _PORT_MESSAGE::$C6BC508B531A81D74C33985719C23F49 *)((char *)&v17->h.8 + 8); *(_OWORD *)&v16->h.ClientViewSize = *(_OWORD *)&v17->h.ClientViewSize; *(_OWORD *)&v16->u.Exception.ExceptionRecord.ExceptionCode = *(_OWORD *)&v17->u.Exception.ExceptionRecord.ExceptionCode; *((_OWORD *)&v16->u.UnloadDll + 1) = *((_OWORD *)&v17->u.UnloadDll + 1); *((_OWORD *)&v16->u.UnloadDll + 2) = *((_OWORD *)&v17->u.UnloadDll + 2); *((_OWORD *)&v16->u.UnloadDll + 3) = *((_OWORD *)&v17->u.UnloadDll + 3); v16 = (DBGKM_MSG *)((char *)v16 + 128); v19 = *((_OWORD *)&v17->u.UnloadDll + 4); v17 = (DBGKM_MSG *)((char *)v17 + 128); *((_OWORD *)&v16[-1].u.UnloadDll + 9) = v19; --v18; } while ( v18 ); *(_OWORD *)&v16->h.u1.s1.DataLength = *(_OWORD *)&v17->h.u1.s1.DataLength; _mm_storeu_si128((__m128i *)&DebugEvent_1->ClientId, *(__m128i *)(CurrentThrad_1 + 0x628)); if ( DebugObject ) // 判断获取调试对象是否正常 { debug_Mutex = &DebugObject->Mutex; ExAcquireFastMutex(&DebugObject->Mutex); // 获取调试对象的互斥体 if ( DebugObject->Flags & 1 ) // 判断调试器是否处于活跃状态,如果不是则返回c0000354 { result = 0xC0000354; } else { v22 = DebugObject->EventList.Blink; // 将调试事件挂入到调试对象的事件链表中 DebugEvent_1->EventList.Flink = &DebugObject->EventList; DebugEvent_1->EventList.Blink = v22; if ( v22->Flink != &DebugObject->EventList ) __fastfail(3u); v22->Flink = &DebugEvent_1->EventList; DebugObject->EventList.Blink = &DebugEvent_1->EventList; if ( !NOWAIT ) KeSetEvent(&DebugObject->EventsPresent, 0, 0);// 如果不是NOWAIT状态,触发EventsPresent这个事件,通知调试器来处理调试事件。 result = 0; } KeReleaseGuardedMutex(debug_Mutex); // 释放调试对象的互斥体 v6 = 2i64; } else { // 如果调试对象获取失败,则返回c00000353 result = 0xC0000353; } if ( NOWAIT ) { if ( result < 0 ) // 如果返回值出错,则释放引用的进程线程对象,释放分配的DebugEvent内存 { ObfDereferenceObjectWithTag(CurrentProcess_1); ObfDereferenceObjectWithTag(CurrentThrad_1); ExFreePoolWithTag(DebugEvent_1, 0); } } else { KeReleaseGuardedMutex(&DbgkpProcessDebugPortMutex);// 释放DbgkpProcessDebugPortMutex互斥锁 if ( result >= 0 ) { KeWaitForSingleObject(&DebugEvent_1->ContinueEvent, 0, 0, 0, 0i64);// 等待调试事件的ContinueEvent,当事件被调试器处理完成的时候这个对象会被触发,当前线程也会被唤醒。 result = DebugEvent_1->Status; do // 通过调试事件的状态来设置ApiMessage,因为上一个函数需要通过ApiMessage的返回值决定下一步的行为。 { *(_OWORD *)&ApiMessage_1->h.u1.s1.DataLength = *(_OWORD *)&v15->h.u1.s1.DataLength; *(union _PORT_MESSAGE::$C6BC508B531A81D74C33985719C23F49 *)((char *)&ApiMessage_1->h.8 + 8) = *(union _PORT_MESSAGE::$C6BC508B531A81D74C33985719C23F49 *)((char *)&v15->h.8 + 8); *(_OWORD *)&ApiMessage_1->h.ClientViewSize = *(_OWORD *)&v15->h.ClientViewSize; *(_OWORD *)&ApiMessage_1->u.Exception.ExceptionRecord.ExceptionCode = *(_OWORD *)&v15->u.Exception.ExceptionRecord.ExceptionCode; *((_OWORD *)&ApiMessage_1->u.UnloadDll + 1) = *((_OWORD *)&v15->u.UnloadDll + 1); *((_OWORD *)&ApiMessage_1->u.UnloadDll + 2) = *((_OWORD *)&v15->u.UnloadDll + 2); *((_OWORD *)&ApiMessage_1->u.UnloadDll + 3) = *((_OWORD *)&v15->u.UnloadDll + 3); ApiMessage_1 = (DBGKM_MSG *)((char *)ApiMessage_1 + 128); v23 = *((_OWORD *)&v15->u.UnloadDll + 4); v15 = (DBGKM_MSG *)((char *)v15 + 128); *((_OWORD *)&ApiMessage_1[-1].u.UnloadDll + 9) = v23; --v6; } while ( v6 ); *(_OWORD *)&ApiMessage_1->h.u1.s1.DataLength = *(_OWORD *)&v15->h.u1.s1.DataLength; } } return (unsigned int)result; }
首先判断了参数中Flags是否指定了NOWAIT标志,如果指定了NOWAIT标志代表不需要等待调试器处理完直接返回。
如果指定了NOWAIT标志,就调用ExAllocatePoolWithQuotaTag分配DebugEvent的内存,因为如果不等待直接返回就不能用栈内存。
分配完内存后检查是否分配成功,如果未分配成功则直接返回。
if ( Flags & 2 ) // DEBUG_EVENT_NOWAIT,检查Flag是否设置了NOWAIT标志,如果没有设置,函数需要等待调试器回复,如果设置了这个标志,则不需要等待调试器 { DebugEvent = (_DEBUG_EVENT *)ExAllocatePoolWithQuotaTag((POOL_TYPE)0x208, 0x168ui64, 0x45676244u);// 如果不需要等待调试器回复,则不能使用栈中的内存构建DEBUG_EVENT结构体,需要分配堆内存 DebugEvent_1 = DebugEvent; if ( !DebugEvent ) return 0xC000009Ai64; DebugEvent->Flags = Flags_1 | 4; ObfReferenceObjectWithTag(CurrentProcess_1, 1332175428i64); ObfReferenceObjectWithTag(CurrentThrad_1, 1332175428i64); DebugEvent_1->BackoutThread = (PETHREAD)__readgsqword(0x188u); }
如果没有指定NOWAIT标志,则首先获取DbgkpProcessDebugPortMutex,这是一个全局互斥锁,来保证将调试事件挂入链表的时候不会发生多线程问题。
然后通过进程结构体获取调试对象,并且判断这个事件是否被设置了跳过,如果是的话则将调试对象设置为零。
经历这些判断后,调用KeInitializeEvent初始化一个事件,用于等待调试器回复用。
else { // 等待调试器回复 v25.Flags = Flags; DebugEvent_1 = &v25; ExAcquireFastMutex(&DbgkpProcessDebugPortMutex); ApiNumber = ApiMessage_1->ApiNumber; DebugObject = *(_DEBUG_OBJECT **)(CurrentProcess_1 + 0x420); if ( (unsigned int)(ApiNumber - 1) <= 1 && *(_BYTE *)(CurrentThrad_1 + 0x6BC) & 0x40 )// 判断这个事件如果是线程进程创建,并且线程设置了SkipCreationMsg,则将DebugObject设置为零 DebugObject = 0i64; if ( ApiNumber == 5 && (unsigned __int8)Flags_1 & *(_BYTE *)(CurrentThrad_1 + 0x6BC) & 0x40 )// 判断这个事件如果是模块加载,并且线程设置了SkipCreationMsg,则将DebugObject设置为零 DebugObject = 0i64; if ( (unsigned int)(ApiNumber - 3) <= 1 && *(_BYTE *)(CurrentThrad_1 + 0x6BC) < 0 )// 判断这个事件如果是线程进程退出,并且线程设置了SkipTerminationMsg,则将DebugObject设置为零,这里F5有错误,实际上后面会&0x80 DebugObject = 0i64; KeInitializeEvent(&v25.ContinueEvent, SynchronizationEvent, 0);// 初始化一个同步对象,为了等待调试器回复 }
然后就开始通过ApiMessage来构造DebugEvent结构体。
构建完结构体之后,检查调试对象是否不为零,如果为零直接返回错误,如果调试对象正常,则检查调试对象是否是活跃的。
然后获取调试对象的互斥锁,并且将调试事件挂入到调试对象的事件列表中。(其实这里有一点疑惑,如果只是为了在挂入链表时保证多线程安全的话,获取一个调试对象的互斥锁就可以了,为什么还要获取DbgkpProcessDebugPortMutex)
然后把调试事件挂入到事件链表中,挂入完成后,如果没指定NOWAIT标志,则调用KeSetEvent设置DebugObject->EventsPresent事件,用来通知调试器处理调试事件,然后释放调试对象的互斥锁。
v15 = &DebugEvent_1->ApiMsg; DebugEvent_1->Process = (PEPROCESS)CurrentProcess_1; v16 = &DebugEvent_1->ApiMsg; DebugEvent_1->Thread = (PETHREAD)CurrentThrad_1; v17 = ApiMessage_1; v18 = 2i64; do { *(_OWORD *)&v16->h.u1.s1.DataLength = *(_OWORD *)&v17->h.u1.s1.DataLength; *(union _PORT_MESSAGE::$C6BC508B531A81D74C33985719C23F49 *)((char *)&v16->h.8 + 8) = *(union _PORT_MESSAGE::$C6BC508B531A81D74C33985719C23F49 *)((char *)&v17->h.8 + 8); *(_OWORD *)&v16->h.ClientViewSize = *(_OWORD *)&v17->h.ClientViewSize; *(_OWORD *)&v16->u.Exception.ExceptionRecord.ExceptionCode = *(_OWORD *)&v17->u.Exception.ExceptionRecord.ExceptionCode; *((_OWORD *)&v16->u.UnloadDll + 1) = *((_OWORD *)&v17->u.UnloadDll + 1); *((_OWORD *)&v16->u.UnloadDll + 2) = *((_OWORD *)&v17->u.UnloadDll + 2); *((_OWORD *)&v16->u.UnloadDll + 3) = *((_OWORD *)&v17->u.UnloadDll + 3); v16 = (DBGKM_MSG *)((char *)v16 + 128); v19 = *((_OWORD *)&v17->u.UnloadDll + 4); v17 = (DBGKM_MSG *)((char *)v17 + 128); *((_OWORD *)&v16[-1].u.UnloadDll + 9) = v19; --v18; } while ( v18 ); *(_OWORD *)&v16->h.u1.s1.DataLength = *(_OWORD *)&v17->h.u1.s1.DataLength; _mm_storeu_si128((__m128i *)&DebugEvent_1->ClientId, *(__m128i *)(CurrentThrad_1 + 0x628)); if ( DebugObject ) // 判断获取调试对象是否正常 { debug_Mutex = &DebugObject->Mutex; ExAcquireFastMutex(&DebugObject->Mutex); // 获取调试对象的互斥体 if ( DebugObject->Flags & 1 ) // 判断调试器是否处于活跃状态,如果不是则返回c0000354 { result = 0xC0000354; } else { v22 = DebugObject->EventList.Blink; // 将调试事件挂入到调试对象的事件链表中 DebugEvent_1->EventList.Flink = &DebugObject->EventList; DebugEvent_1->EventList.Blink = v22; if ( v22->Flink != &DebugObject->EventList ) __fastfail(3u); v22->Flink = &DebugEvent_1->EventList; DebugObject->EventList.Blink = &DebugEvent_1->EventList; if ( !NOWAIT ) KeSetEvent(&DebugObject->EventsPresent, 0, 0);// 如果不是NOWAIT状态,触发EventsPresent这个事件,通知调试器来处理调试事件。 result = 0; } KeReleaseGuardedMutex(debug_Mutex); // 释放调试对象的互斥体 v6 = 2i64; } else { // 如果调试对象获取失败,则返回c00000353 result = 0xC0000353; }
继续判断,如果指定了NOWAIT标志,并且返回值小于零,也就是代表出现错误,则释放申请的内存,并且将引用的对象解引用。
如果没指定NOWAIT标志,则释放DbgkpProcessDebugPortMutex全局互斥锁,然后调用KeWaitForSingleObject等待DebugEvent_1->ContinueEvent,当事件被调试器处理完成后会触发这个事件,当前线程会被唤醒继续执行。
然后通过调试事件的信息来填充ApiMessage,用于通知上一层函数事件处理的结果。
if ( NOWAIT ) { if ( result < 0 ) // 如果返回值出错,则释放引用的进程线程对象,释放分配的DebugEvent内存 { ObfDereferenceObjectWithTag(CurrentProcess_1); ObfDereferenceObjectWithTag(CurrentThrad_1); ExFreePoolWithTag(DebugEvent_1, 0); } } else { KeReleaseGuardedMutex(&DbgkpProcessDebugPortMutex);// 释放DbgkpProcessDebugPortMutex互斥锁 if ( result >= 0 ) { KeWaitForSingleObject(&DebugEvent_1->ContinueEvent, 0, 0, 0, 0i64);// 等待调试事件的ContinueEvent,当事件被调试器处理完成的时候这个对象会被触发,当前线程也会被唤醒。 result = DebugEvent_1->Status; do // 通过调试事件的状态来设置ApiMessage,因为上一个函数需要通过ApiMessage的返回值决定下一步的行为。 { *(_OWORD *)&ApiMessage_1->h.u1.s1.DataLength = *(_OWORD *)&v15->h.u1.s1.DataLength; *(union _PORT_MESSAGE::$C6BC508B531A81D74C33985719C23F49 *)((char *)&ApiMessage_1->h.8 + 8) = *(union _PORT_MESSAGE::$C6BC508B531A81D74C33985719C23F49 *)((char *)&v15->h.8 + 8); *(_OWORD *)&ApiMessage_1->h.ClientViewSize = *(_OWORD *)&v15->h.ClientViewSize; *(_OWORD *)&ApiMessage_1->u.Exception.ExceptionRecord.ExceptionCode = *(_OWORD *)&v15->u.Exception.ExceptionRecord.ExceptionCode; *((_OWORD *)&ApiMessage_1->u.UnloadDll + 1) = *((_OWORD *)&v15->u.UnloadDll + 1); *((_OWORD *)&ApiMessage_1->u.UnloadDll + 2) = *((_OWORD *)&v15->u.UnloadDll + 2); *((_OWORD *)&ApiMessage_1->u.UnloadDll + 3) = *((_OWORD *)&v15->u.UnloadDll + 3); ApiMessage_1 = (DBGKM_MSG *)((char *)ApiMessage_1 + 128); v23 = *((_OWORD *)&v15->u.UnloadDll + 4); v15 = (DBGKM_MSG *)((char *)v15 + 128); *((_OWORD *)&ApiMessage_1[-1].u.UnloadDll + 9) = v23; --v6; } while ( v6 ); *(_OWORD *)&ApiMessage_1->h.u1.s1.DataLength = *(_OWORD *)&v15->h.u1.s1.DataLength; } } return (unsigned int)result;
以上,就是用户态调试流程的全部内容,能力有限,有些地方分析的不是很细致~