分析"伪调试"原理-对抗

Debugger

Baklib
狐白 最后一次编辑 3 年多前
282
当使用Window调试机制去调试一个程序的时候 会在程序对象里设置DebugPort 从而让调试器接收调试事件,这也导致进程也可以判断DebugPort来检测自身是否被调试
/* Check if we already have a port */ if (Process->DebugPort) { /* Set failure */ Status = STATUS_PORT_ALREADY_SET; } else { /* Otherwise, set the port and reference the thread */ Process->DebugPort = DebugObject; ObReferenceObject(LastThread);
但是,伪调试机制去调试一个程序时,并不会去设置DebugPort,这也导致无法用DebugPort来检测调试,从而避免一些问题
那么它是怎么做到的呢?中断 异常 都是调试器核心.
KiDispatchException(IN PEXCEPTION_RECORD ExceptionRecord, IN PKEXCEPTION_FRAME ExceptionFrame, IN PKTRAP_FRAME TrapFrame, IN KPROCESSOR_MODE PreviousMode, IN BOOLEAN FirstChance) { CONTEXT Context; /* Increase number of Exception Dispatches */ KeGetCurrentPrcb()->KeExceptionDispatchCount++; /* Set the context flags */ Context.ContextFlags = CONTEXT_FULL; /* Check if User Mode or if the kernel debugger is enabled */ if ((PreviousMode == UserMode) || (KeGetPcr()->KdVersionBlock)) { /* FIXME-V6: VFP Support */ } /* Get a Context */ KeTrapFrameToContext(TrapFrame, ExceptionFrame, &Context); /* Look at our exception code */ switch (ExceptionRecord->ExceptionCode) { /* Breakpoint */ case STATUS_BREAKPOINT: /* Decrement PC by four */ Context.Pc -= sizeof(ULONG); break; /* Internal exception */ case KI_EXCEPTION_ACCESS_VIOLATION: /* Set correct code */ ExceptionRecord->ExceptionCode = STATUS_ACCESS_VIOLATION; if (PreviousMode == UserMode) { /* FIXME: Handle no execute */ } break; } /* Handle kernel-mode first, it's simpler */ if (PreviousMode == KernelMode) { /* Check if this is a first-chance exception */ if (FirstChance != FALSE) { /* Break into the debugger for the first time */ if (KiDebugRoutine(TrapFrame, ExceptionFrame, ExceptionRecord, &Context, PreviousMode, FALSE)) { /* Exception was handled */ goto Handled; } /* If the Debugger couldn't handle it, dispatch the exception */ if (RtlDispatchException(ExceptionRecord, &Context)) goto Handled; } /* This is a second-chance exception, only for the debugger */ if (KiDebugRoutine(TrapFrame, ExceptionFrame, ExceptionRecord, &Context, PreviousMode, TRUE)) { /* Exception was handled */ goto Handled; } /* Third strike; you're out */ KeBugCheckEx(KMODE_EXCEPTION_NOT_HANDLED, ExceptionRecord->ExceptionCode, (ULONG_PTR)ExceptionRecord->ExceptionAddress, (ULONG_PTR)TrapFrame, 0); } else { /* FIXME: TODO */ /* 3rd strike, kill the process */ DPRINT1("Kill %.16s, ExceptionCode: %lx, ExceptionAddress: %lx\n", PsGetCurrentProcess()->ImageFileName, ExceptionRecord->ExceptionCode, ExceptionRecord->ExceptionAddress); ZwTerminateProcess(NtCurrentProcess(), ExceptionRecord->ExceptionCode); KeBugCheckEx(KMODE_EXCEPTION_NOT_HANDLED, ExceptionRecord->ExceptionCode, (ULONG_PTR)ExceptionRecord->ExceptionAddress, (ULONG_PTR)TrapFrame, 0); } Handled: /* Convert the context back into Trap/Exception Frames */ KeContextToTrapFrame(&Context, ExceptionFrame, TrapFrame, Context.ContextFlags, PreviousMode); return; }DbgkForwardException(IN PEXCEPTION_RECORD ExceptionRecord, IN BOOLEAN DebugPort, IN BOOLEAN SecondChance) { DBGKM_MSG ApiMessage; PDBGKM_EXCEPTION DbgKmException = &ApiMessage.Exception; NTSTATUS Status; PEPROCESS Process = PsGetCurrentProcess(); PVOID Port; BOOLEAN UseLpc = FALSE; PAGED_CODE(); DBGKTRACE(DBGK_EXCEPTION_DEBUG, "ExceptionRecord: %p Port: %u\n", ExceptionRecord, DebugPort); /* Setup the API Message */ ApiMessage.h.u1.Length = sizeof(DBGKM_MSG) << 16 | (8 + sizeof(DBGKM_EXCEPTION)); ApiMessage.h.u2.ZeroInit = 0; ApiMessage.h.u2.s2.Type = LPC_DEBUG_EVENT; ApiMessage.ApiNumber = DbgKmExceptionApi; /* Check if this is to be sent on the debug port */ if (DebugPort) { /* Use the debug port, unless the thread is being hidden */ Port = PsGetCurrentThread()->HideFromDebugger ? NULL : Process->DebugPort; } else { /* Otherwise, use the exception port */ Port = Process->ExceptionPort; ApiMessage.h.u2.ZeroInit = 0; ApiMessage.h.u2.s2.Type = LPC_EXCEPTION; UseLpc = TRUE; } /* Break out if there's no port */ if (!Port) return FALSE; /* Fill out the exception information */ DbgKmException->ExceptionRecord = *ExceptionRecord; DbgKmException->FirstChance = !SecondChance; /* Check if we should use LPC */ if (UseLpc) { /* Send the message on the LPC Port */ Status = DbgkpSendApiMessageLpc(&ApiMessage, Port, DebugPort); } else { /* Use native debug object */ Status = DbgkpSendApiMessage(&ApiMessage, DebugPort); } /* Check if we failed, and for a debug port, also check the return status */ if (!(NT_SUCCESS(Status)) || ((DebugPort) && (!(NT_SUCCESS(ApiMessage.ReturnedStatus)) || (ApiMessage.ReturnedStatus == DBG_EXCEPTION_NOT_HANDLED)))) { /* Fail */ return FALSE; } /* Otherwise, we're ok */ return TRUE; }
那么异常分发 的流程就是
->先通知内核调试器 如果不处理
则发送给子调试系统 如果不处理
则调用 应用层 KiUserExceptionDispatcher ->RtlDispatchException查找VEH链表->RtlDispatchException SEH链表
如果其中一个处理异常 那么就结束分发,直到程序弹出崩溃窗口
回归正题 伪调试想要有调试事件 那么肯定需要一个能捕捉异常分发的接口 也就是 KiUserExceptionDispatcher 或者 VEH,这样可以抢在程序前处理异常,比如INT3中断 硬件断点 
到此伪调试 核心内容就是以上这些
最后检测伪调试   Hook KiUserExceptionDispatcher岂不美哉 所有的异常都经过自身程序处理 这样就轮不到VEH处理