当使用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处理