想要调试一个程序有两种方法,第一种是使用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;
以上,就是用户态调试流程的全部内容,能力有限,有些地方分析的不是很细致~