身为一名逆向人员,ida,x64dbg几乎是我们无法离开的工具,这些调试器的背后的原理是什么?所谓各路五花八门的反调试,调试器检测,无痕hook,线程逃逸又是什么?接下来,我将带大家深入内核,探究windows内核里的调试体系。
一个简单的调试器
我们来看一个最简的调试器代码:
#include <windows.h>
#include <stdio.h>
intmain(int argc, char *argv[]) {
if (argc < 2) {
printf("Usage: %s <pid>\n", argv[0]);
return 1;
}
DWORD pid = (DWORD)atoi(argv[1]);
if (!DebugActiveProcess(pid)) {
printf("DebugActiveProcess failed: %lu\n", GetLastError());
return 1;
}
printf("Attached to PID %lu\n", pid);
DEBUG_EVENT de;
for (;;) {
WaitForDebugEvent(&de, INFINITE); /* debugwaitforsignal */
switch (de.dwDebugEventCode) {
case EXCEPTION_DEBUG_EVENT:
printf("[EXCEPTION] addr=%p code=%08lx\n",
de.u.Exception.ExceptionRecord.ExceptionAddress,
de.u.Exception.ExceptionRecord.ExceptionCode);
break;
case EXIT_PROCESS_DEBUG_EVENT:
printf("[EXIT_PROCESS]\n");
goto done;
}
ContinueDebugEvent(de.dwProcessId, de.dwThreadId, DBG_CONTINUE); /* debugcontinue */
}
done:
ContinueDebugEvent(de.dwProcessId, de.dwThreadId, DBG_CONTINUE);
DebugActiveProcessStop(pid);
return 0;
}这边就涉及3个重要函数:
DebugActiveProcess WaitForDebugEvent ContinueDebugEvent
接下来这些函数就是我的分析重点。我这里不卖关子,直接告诉你这几个函数的功能:
DebugActiveProcess
激活调试,将调试器与被调试进程关联WaitForDebugEvent
等待调试事件ContinueDebugEvent
预示着调试器处理完成调试事件,被调试进程继续执行
这里我们不难想到第一个问题,调试器是如何与被调试对象关联的呢?
调试对象
当我们执行DebugActiveProcess,就会将创建一个调试对象,他既挂在被调试进程上,也挂在调试器上。
什么是调试对象?我们直接从wrk里拿定义:
#define DEBUG_OBJECT_DELETE_PENDING (0x1) // Debug object is delete pending.
#define DEBUG_OBJECT_KILL_ON_CLOSE (0x2) // Kill all debugged processes on close
typedef struct _DEBUG_OBJECT {
//
// Event thats set when the EventList is populated.
//
KEVENT EventsPresent;
//
// Mutex to protect the structure
//
FAST_MUTEX Mutex;
//
// Queue of events waiting for debugger intervention
//
LIST_ENTRY EventList;
//
// Flags for the object
//
ULONG Flags;
} DEBUG_OBJECT, *PDEBUG_OBJECT;EventsPresent就是调试器监听的信号量,当调试事件来临,被调试进程就会给信号量置位,唤起调试器。
EventList就是时一个win内核最喜欢的双向循环链表头,他串联着整个调试事件。
每当发生调试事件,被调试进程就会进行如下操作:
1.检测是否被调试
2.创建调试事件(DEBUG_EVENT),并且挂入链表
3.置位调试对象的EventsPresent,调试器会被激活
4.开始死等ContinueEvent的信号量
5.收到来自调试器的ContinueEvent信号量,恢复执行,同时reset调试对象的EventsPresent(也有可能不会)
调试器流程:
1.我用WaitForDebugEvent死等
2.我的EventsPresent被激活,我检测EventList串起来的一大串链表是否有东西
3.发现有东西,开始处理,处理完成一个就发送ContinueEvent信号量,然后摘除这个节点
4.处理到链表为空,reset调试对象的EventsPresent
5.WaitForDebugEvent继续死等
DEBUG_EVENT定义:
#define DEBUG_EVENT_READ (0x01) // Event had been seen by win32 app
#define DEBUG_EVENT_NOWAIT (0x02) // No waiter one this. Just free the pool
#define DEBUG_EVENT_INACTIVE (0x04) // The message is in inactive. It may be activated or deleted later
#define DEBUG_EVENT_RELEASE (0x08) // Release rundown protection on this thread
#define DEBUG_EVENT_PROTECT_FAILED (0x10) // Rundown protection failed to be acquired on this thread
#define DEBUG_EVENT_SUSPEND (0x20) // Resume thread on continue
#define DBGKP_FIELD_FROM_IMAGE_OPTIONAL_HEADER(hdrs,field) \
((hdrs)->OptionalHeader.##field)
typedef struct _DEBUG_EVENT {
LIST_ENTRY EventList; // Queued to event object through this
KEVENT ContinueEvent;
CLIENT_ID ClientId;
PEPROCESS Process; // Waiting process
PETHREAD Thread; // Waiting thread
NTSTATUS Status; // Status of operation
ULONG Flags;
PETHREAD BackoutThread; // Backout key for faked messages
DBGKM_APIMSG ApiMsg; // Message being sent
} DEBUG_EVENT, *PDEBUG_EVENT;从图结合定义我们可用看到DEBUG_EVENT和DEBUG_OBJECT都有EventList,像一条链子一样把DEBUG_OBJECT和一堆DEBUG_EVENT串起来。
当然你可能现在云里雾里,我们结合代码分析。
DebugActiveProcess分析
DebugActiveProcess为kernelBase.dll的导出函数,在ida里是:
BOOL __stdcall DebugActiveProcess(DWORD dwProcessId){
NTSTATUS v2; // eax
__int64 v3; // rcx
HANDLE hProcess; // rax
HANDLE hProcess_copy; // rbx
NTSTATUS active; // edi
v2 = DbgUiConnectToDbg(); // 创建调试对象并且挂到上面去
if ( v2 < 0 )
{
v3 = (unsignedint)v2;
LABEL_3:
BaseSetLastNTError(v3);
return 0;
}
hProcess = ProcessIdToHandle(dwProcessId);
hProcess_copy = hProcess;
if ( !hProcess )
return 0;
active = DbgUiDebugActiveProcess(hProcess);
if ( active < 0 )
{
NtClose(hProcess_copy);
v3 = (unsignedint)active;
goto LABEL_3;
}
NtClose(hProcess_copy);
return 1;
}DbgUiConnectToDbg的功能就是初始化调试对象,并且先将调试对象挂到调试器上。
我们来到ntdll:
__int64 DbgUiConnectToDbg(){
unsignedint v0; // ecx
OBJECT_ATTRIBUTES ObjectAttributes; // [rsp+20h] [rbp-38h] BYREF
v0 = 0;
if ( !NtCurrentTeb()->DbgSsReserved[1] )
{
memset(&ObjectAttributes.RootDirectory, 0, 20);
*(_OWORD *)&ObjectAttributes.SecurityDescriptor = 0;// set ObjectAttributes
ObjectAttributes.Length = 48;
return (unsignedint)NtCreateDebugObject(&NtCurrentTeb()->DbgSsReserved[1], 0x1F000Fu, &ObjectAttributes, 1u);
}
return v0;
}emmm,很简单的中转一下直接进入内核。
注意看一下&NtCurrentTeb()->DbgSsReserved[1]的汇编:
mov rcx, gs:30h
add rcx, 16A8h ; DebugObjectHandle16A8的偏移应该刻进dna,这就是调试器该被挂入调试对象的地方,放的是个句柄。
NtCreateDebugObject分析
我们来看ntoskrnl:
NTSTATUS NtCreateDebugObject(
PHANDLE DebugObjectHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
ULONG Flags)
{
char Flags_copy; // si
KPROCESSOR_MODE PreviousMode; // r10
__int64 v8; // rcx
NTSTATUS result; // eax
PDEBUG_OBJECT pDebugObject_copy; // rbx
_EWOW64PROCESS *is_wow64; // rax
unsigned __int16 machine; // ax
char *v13; // [rsp+20h] [rbp-68h]
__int64 v14; // [rsp+20h] [rbp-68h]
PDEBUG_OBJECT pDebug_Object; // [rsp+58h] [rbp-30h] BYREF
void *handle; // [rsp+60h] [rbp-28h]
Flags_copy = Flags;
handle = 0;
pDebug_Object = 0;
PreviousMode = KeGetCurrentThread()->PreviousMode;
if ( PreviousMode )
{
v8 = 0x7FFFFFFF0000LL;
if ( (unsigned __int64)DebugObjectHandle < 0x7FFFFFFF0000LL )
v8 = (__int64)DebugObjectHandle;
*(_QWORD *)v8 = *(_QWORD *)v8;
}
*DebugObjectHandle = 0;
if ( (Flags & 0xFFFFFFFE) != 0 )
return 0xC000000D; // STATUS_INVALID_PARAMETER
result = ObCreateObjectEx(
PreviousMode,
DbgkDebugObjectType, // 创建调试对象
ObjectAttributes,
PreviousMode,
v13, // optional
0x68u,
0,
0,
(PVOID *)&pDebug_Object);
if ( result >= 0 )
{
pDebugObject_copy = pDebug_Object;
pDebug_Object->Mutex.Count = 1;
pDebugObject_copy->Mutex.Owner = 0;
pDebugObject_copy->Mutex.Contention = 0;
KeInitializeEvent(&pDebugObject_copy->Mutex.Event, SynchronizationEvent, 0);// 同步事件
pDebugObject_copy->EventList.Blink = &pDebugObject_copy->EventList;
pDebugObject_copy->EventList.Flink = &pDebugObject_copy->EventList;// 清空链表
KeInitializeEvent(&pDebugObject_copy->EventsPresent, NotificationEvent, 0);
if ( (Flags_copy & 1) != 0 )
pDebugObject_copy->Flags = 2; // #define DEBUG_OBJECT_KILL_ON_CLOSE (0x2) // Kill all debugged processes on close
else
pDebugObject_copy->Flags = 0;
is_wow64 = (_EWOW64PROCESS *)KeGetCurrentThread()->ApcState.Process[1].AffinityPadding[10];// 判断是否wow64
if ( is_wow64 )
{
machine = is_wow64->Machine;
if ( machine == 332 || machine == 452 )
pDebugObject_copy->Flags |= 4u;
}
LODWORD(v14) = 0;
result = ObInsertObjectEx(pDebug_Object, 0, DesiredAccess, 0, (PVOID *)v14, 0);// ObInsertObjectEx(pDebug_Object, 0, DesiredAccess, 0, (PVOID *)v14, 0,handle);
if ( result >= 0 )
*DebugObjectHandle = handle; // 调试对象挂上去了
}
return result;
}代码不难,主要就是创建调试对象,初始化一下,然后挂上去,注意时先挂调试器
当然里面的结构体我都是导入的,你们看起来肯定没这么好看,之后我会把我自己用的idb文件放重来供大家学习,创建好了就要激活。
DbgUiDebugActiveProcess分析
我们来到ntdll:
__int64 __fastcall DbgUiDebugActiveProcess(HANDLE hProcess){
int active; // ebx
active = NtDebugActiveProcess(hProcess, NtCurrentTeb()->DbgSsReserved[1]);
if ( active >= 0 )
{
active = DbgUiIssueRemoteBreakin(hProcess);
if ( active < 0 )
ZwRemoveProcessDebug(hProcess, NtCurrentTeb()->DbgSsReserved[1]);
}
return (unsignedint)active;
}NtDebugActiveProcess就是将调试对象与被调试进程关联的地方,在ntoskrnl里比较复杂,我们先看DbgUiIssueRemoteBreakin。
__int64 __fastcall DbgUiIssueRemoteBreakin(HANDLE hProcess){
int v1; // ebx
__int64 v3; // [rsp+30h] [rbp-48h]
__int128 v4; // [rsp+60h] [rbp-18h] BYREF
HANDLE Handle; // [rsp+88h] [rbp+10h] BYREF
v1 = RtlpCreateUserThreadEx((__int64)hProcess, 0, 2, 0, 0, 0x4000, v3, (__int64)DbgUiRemoteBreakin, 0, &Handle, &v4);
if ( v1 >= 0 )
NtClose(Handle);
return (unsignedint)v1;
}
void __noreturn DbgUiRemoteBreakin(){
if ( (NtCurrentPeb()->BeingDebugged || (MEMORY[0x7FFE02D4] & 2) != 0) && (NtCurrentTeb()->SameTebFlags & 0x20) == 0 )
{
if ( UseWOW64 )
{
if ( g_LdrpWow64PrepareForDebuggerAttach )
g_LdrpWow64PrepareForDebuggerAttach();
}
DbgBreakPoint();
}
RtlExitUserThread(0);
}嗯,很简单,就是创建一个远程线程,然后这个线程有个int3会把程序中断下来
解释了为什么我们附加会让程序暂停下来。
当然这也是一个反调试点,如果我们让DbgUiRemoteBreakin没有int3,不就断不下来了?
NtDebugActiveProcess
接下来时最复杂的:
active = NtDebugActiveProcess(hProcess, NtCurrentTeb()->DbgSsReserved[1]);NtCurrentTeb()->DbgSsReserved[1]依旧16A8
我们来到ntoskrnl:
//hProcess为被调试进程
NTSTATUS __fastcall NtDebugActiveProcess(HANDLE hProcess, HANDLE hDebugObject)
{
KPROCESSOR_MODE PreviousMode; // bp
NTSTATUS result; // eax
__int64 v5; // rcx
struct _KTHREAD *CurrentThread; // rax
PEPROCESS pProcess_copy; // rdi
PEPROCESS Process; // rsi
int ntstatus; // ebx
unsigned __int64 v10; // rax
__int16 v11; // cx
unsigned __int64 v12; // rax
__int16 v13; // cx
BOOLEAN v14; // al
struct _DEBUG_OBJECT *pDebugObject_copy; // rsi
NTSTATUS Messages; // eax
PETHREAD LastThread[5]; // [rsp+40h] [rbp-28h] BYREF
_EPROCESS *pProcess; // [rsp+80h] [rbp+18h] BYREF
_DEBUG_OBJECT *pDebugObject; // [rsp+88h] [rbp+20h] BYREF
pProcess = 0;
PreviousMode = KeGetCurrentThread()->PreviousMode;
LastThread[0] = 0;
result = ObReferenceObjectByHandleWithTag(
hProcess,
0x800u,
(POBJECT_TYPE)PsProcessType,
PreviousMode,
'OgbD',
(PVOID *)&pProcess,
0);
if ( result >= 0 )
{
CurrentThread = KeGetCurrentThread();
pProcess_copy = &pProcess->Pcb;
Process = CurrentThread->ApcState.Process;
if ( pProcess == (_EPROCESS *)Process || pProcess == (_EPROCESS *)PsInitialSystemProcess )// 这个就是system进程
{
ntstatus = 0xC0000022; // STATUS_ACCESS_DENIED
}
else
{
LOBYTE(v5) = PreviousMode;
if ( (unsigned __int8)PsTestProtectedProcessIncompatibility(v5, CurrentThread->ApcState.Process, pProcess) )
{
ntstatus = 0xC0000712;
}
else if ( (pProcess_copy->?.SecureHandle & 1) == 0
|| (ntstatus = PsRequestDebugSecureProcess(pProcess_copy), ntstatus >= 0) )
{
v10 = Process[1].AffinityPadding[10];
if ( !v10
|| (v11 = *(_WORD *)(v10 + 8), v11 != 332) && v11 != 452
|| (v12 = pProcess_copy[1].AffinityPadding[10]) != 0
&& ((v13 = *(_WORD *)(v12 + 8), v13 == 332) || v13 == 452) )
{
pDebugObject = 0;
ntstatus = ObReferenceObjectByHandle(
hDebugObject,
2u,
DbgkDebugObjectType,
PreviousMode,
(PVOID *)&pDebugObject,
0);
if ( ntstatus >= 0 )
{
v14 = ExAcquireRundownProtection((PEX_RUNDOWN_REF)&pProcess_copy[1].ProfileListHead.Blink);// 获取rundown保护锁,防止你在我们挂调试对象时退出
pDebugObject_copy = pDebugObject;
if ( v14 )
{
Messages = DbgkpPostFakeProcessCreateMessages(pProcess_copy, pDebugObject, LastThread);
ntstatus = DbgkpSetProcessDebugObject(pProcess_copy, pDebugObject_copy, Messages, LastThread[0]);
ExReleaseRundownProtection((PEX_RUNDOWN_REF)&pProcess_copy[1].ProfileListHead.Blink);
}
else
{
ntstatus = 0xC000010A; // STATUS_PROCESS_IS_TERMINATING
}
HalPutDmaAdapter((PADAPTER_OBJECT)pDebugObject_copy);
}
}
else
{
ntstatus = 0xC00000BB; // STATUS_NOT_SUPPORTED
}
}
}
ObfDereferenceObjectWithTag(pProcess_copy, 'OgbD');
return ntstatus;
}
return result;
}进行了前面一些无关紧要的操作,什么增加引用计数,system和psp保护的进程不让你调试,然后获取目标进程rundown保护锁,最关键两个操作是:
Messages = DbgkpPostFakeProcessCreateMessages(pProcess_copy, pDebugObject, LastThread);
ntstatus = DbgkpSetProcessDebugObject(pProcess_copy, pDebugObject_copy, Messages, LastThread[0]);DbgkpPostFakeProcessCreateMessages是发假消息,就是每次你用ida附加,不是底下会刷屏一大堆信息,什么创建了几个线程,加载了什么dll,exe镜像在哪,就是这个发的。
DbgkpSetProcessDebugObject就是将被调试进程也挂上调试对象的函数。
DbgkpPostFakeProcessCreateMessages
// Process -> 被调试进程对象
//
//
// NTSTATUS
// DbgkpPostFakeProcessCreateMessages (
// IN PEPROCESS Process,
// IN PDEBUG_OBJECT DebugObject,
// IN PETHREAD *pLastThread
// )
NTSTATUS DbgkpPostFakeProcessCreateMessages(PEPROCESS Process, PDEBUG_OBJECT DebugObject, PETHREAD *pLastThread){
struct _KTHREAD *v4; // rbx
NTSTATUS result; // eax
PETHREAD FirstThread; // [rsp+30h] [rbp-68h] BYREF
PETHREAD LastThread; // [rsp+38h] [rbp-60h] BYREF
_KAPC_STATE apc; // [rsp+40h] [rbp-58h] BYREF
v4 = 0;
FirstThread = 0;
memset(&apc, 0, sizeof(apc));
LastThread = 0;
result = DbgkpPostFakeThreadMessages(Process, DebugObject, 0, &FirstThread, &LastThread);
if ( result >= 0 )
{
KiStackAttachProcess((ULONG_PTR)Process);
DbgkpPostModuleMessages(Process, FirstThread, &DebugObject->EventsPresent);
KiUnstackDetachProcess(&apc, 0);
ObfDereferenceObjectWithTag(FirstThread, 0x4F676244u);
result = 0;
v4 = LastThread;
}
*pLastThread = v4;
return result;
}有发送线程假消息和和模块假消息,模块假消息不难,发的主要是一些加载了哪些dll啦之类的,但是我们要拿这些信息需要被调试进程的上下文,所以KiStackAttachProcess一下。
DbgkpPostModuleMessages不难,我们重点看DbgkpPostFakeThreadMessages:
NTSTATUS DbgkpPostFakeThreadMessages(
PEPROCESS Process,
PDEBUG_OBJECT DebugObject,
PETHREAD StartThread,
PETHREAD *pFirstThread,
PETHREAD *pLastThread)
{
PETHREAD CurrentProcessThread; // rbx
struct _ETHREAD *FirstThread; // r15
struct _ETHREAD *LastThread; // rdi
int status; // r12d
bool IsFirstThread; // r13
ULONG flag; // esi 这边的flag都是debug_event的flag
char IsProcessCreate; // r13
void *SectionObject; // rcx
PIMAGE_NT_HEADERS NT_HEADERS; // rax
bool NotFirstThread; // [rsp+30h] [rbp-1E8h]
struct _KTHREAD *CurrentThread; // [rsp+68h] [rbp-1B0h]
DBGKM_APIMSG DbgKM_ApiMSG; // [rsp+90h] [rbp-188h] BYREF
// typedef struct _DBGKM_APIMSG {
// PORT_MESSAGE h;
// DBGKM_APINUMBER ApiNumber;
// NTSTATUS ReturnedStatus;
// union {
// DBGKM_EXCEPTION Exception;
// DBGKM_CREATE_THREAD CreateThread;
// DBGKM_CREATE_PROCESS CreateProcessInfo;
// DBGKM_EXIT_THREAD ExitThread;
// DBGKM_EXIT_PROCESS ExitProcess;
// DBGKM_LOAD_DLL LoadDll;
// DBGKM_UNLOAD_DLL UnloadDll;
// } u;
// } DBGKM_APIMSG, *PDBGKM_APIMSG;
struct _KAPC_STATE ApcState; // [rsp+1A0h] [rbp-78h] BYREF
CurrentProcessThread = StartThread;
memset(&ApcState, 0, sizeof(ApcState));
memset(&DbgKM_ApiMSG, 0, 0x110u);
FirstThread = 0;
LastThread = 0;
CurrentThread = KeGetCurrentThread();
status = 0xC0000001; // STATUS_UNSUCCESSFUL
if ( CurrentProcessThread )
{
FirstThread = CurrentProcessThread;
ObfReferenceObjectWithTag(CurrentProcessThread, 'OgbD');
}
else
{
CurrentProcessThread = PsGetNextProcessThread(Process, 0);
}
IsFirstThread = StartThread == 0;
NotFirstThread = StartThread == 0;
while ( CurrentProcessThread )
{
if ( LastThread )
ObfDereferenceObjectWithTag(LastThread, 'OgbD');
LastThread = CurrentProcessThread;
ObfReferenceObjectWithTag(CurrentProcessThread, 'OgbD');
if ( (CurrentProcessThread->Tcb.MiscFlags & 0x400) == 0 )
{
if ( (CurrentProcessThread->CrossThreadFlags & 2) != 0
|| (PsSynchronizeWithThreadInsertion(
(__int64)CurrentProcessThread,
(__int64)CurrentThread), // 等待rundown保护锁
(CurrentProcessThread->CrossThreadFlags & 2) != 0) )
{
if ( ExAcquireRundownProtection(&CurrentProcessThread->RundownProtect) )//
// #define DEBUG_EVENT_READ (0x01) // Event had been seen by win32 app
// #define DEBUG_EVENT_NOWAIT (0x02) // No waiter one this. Just free the pool
// #define DEBUG_EVENT_INACTIVE (0x04) // The message is in inactive. It may be activated or deleted later
// #define DEBUG_EVENT_RELEASE (0x08) // Release rundown protection on this thread
// #define DEBUG_EVENT_PROTECT_FAILED (0x10) // Rundown protection failed to be acquired on this thread
// #define DEBUG_EVENT_SUSPEND (0x20) // Resume thread on continue
{
flag = 0xA; // A = 2+8
// DEBUG_EVENT_NOWAIT
// DEBUG_EVENT_RELEASE
if ( (int)PsSuspendThread(CurrentProcessThread, 0) >= 0 )
flag = 0x2A; // 多并一个DEBUG_EVENT_SUSPEND
}
else
{
flag = 0x12;
}
memset(&DbgKM_ApiMSG, 0, 0x110u);
if ( !IsFirstThread || (flag & 0x10) != 0 )
{
IsProcessCreate = 0;
DbgKM_ApiMSG.ApiNumber = DbgKmCreateThreadApi;
DbgKM_ApiMSG.u.CreateProcessInfo.FileHandle = CurrentProcessThread->Win32StartAddress;
}
else // 发送进程创建假消息
{
IsProcessCreate = 1;
DbgKM_ApiMSG.ApiNumber = DbgKmCreateProcessApi;
SectionObject = Process->SectionObject;
if ( SectionObject )
DbgKM_ApiMSG.u.CreateProcessInfo.FileHandle = DbgkpSectionToFileHandle(SectionObject);
else
DbgKM_ApiMSG.u.CreateProcessInfo.FileHandle = 0;
DbgKM_ApiMSG.u.CreateProcessInfo.BaseOfImage = Process->SectionBaseAddress;// exe基址
KeStackAttachProcess(&Process->Pcb, &ApcState);
NT_HEADERS = RtlImageNtHeader(Process->SectionBaseAddress);
if ( NT_HEADERS )
{
DbgKM_ApiMSG.u.Exception.ExceptionRecord.ExceptionInformation[1] = 0;
*(_QWORD *)&DbgKM_ApiMSG.u.CreateProcessInfo.DebugInfoFileOffset = *(_QWORD *)&NT_HEADERS->FileHeader.PointerToSymbolTable;// 拿pdb
}
KeUnstackDetachProcess(&ApcState);
}
status = DbgkpQueueMessage(Process, CurrentProcessThread, &DbgKM_ApiMSG, flag, DebugObject);// 将DBGKM_APIMSG转成DEBUG_EVENT并插入DEBUG_OBJECT的链表
if ( status < 0 )
{ // 发生错误,清理并跳出循环
if ( (flag & 0x20) != 0 )
PsResumeThread(CurrentProcessThread, 0);
if ( (flag & 8) != 0 )
ExReleaseRundownProtection(&CurrentProcessThread->RundownProtect);
if ( DbgKM_ApiMSG.ApiNumber == DbgKmCreateProcessApi
&& DbgKM_ApiMSG.u.Exception.ExceptionRecord.ExceptionRecord )
{
ObCloseHandle(DbgKM_ApiMSG.u.Exception.ExceptionRecord.ExceptionRecord, 0);
}
PsQuitNextProcessThread(CurrentProcessThread);
break;
}
if ( IsProcessCreate )
{
IsFirstThread = 0;
NotFirstThread = 0;
ObfReferenceObjectWithTag(CurrentProcessThread, 'OgbD');
FirstThread = CurrentProcessThread;
DbgkSendSystemDllMessages((char *)CurrentProcessThread, &DebugObject->EventsPresent, &DbgKM_ApiMSG);
}
else
{
IsFirstThread = NotFirstThread;
}
}
}
CurrentProcessThread = PsGetNextProcessThread(Process, CurrentProcessThread);
}
if ( status >= 0 )
{
if ( FirstThread )
{
*pFirstThread = FirstThread;
*pLastThread = LastThread;
}
else
{
if ( LastThread )
ObfDereferenceObjectWithTag(LastThread, 'OgbD');
return 0xC0000001;
}
}
else
{
if ( FirstThread )
ObfDereferenceObjectWithTag(FirstThread, 'OgbD');
if ( LastThread )
ObfDereferenceObjectWithTag(LastThread, 'OgbD');
}
return status;
}首先我们看到多了一个结构体也就是DBGKM_APIMSG,这个是debugevent的成员,记录着关键的调试信息。
我们可用看到有个大循环,一开始传进来的StartThread是0
然后一直PsGetNextProcessThread,得到每一个线程的一些关键信息,并且填入DBGKM_APIMSG
最后调用最关键的函数DbgkpQueueMessage
我们可用看到交叉应用,他被很多关键函数调用,比如DbgkpSendApiMessage,这就是异常啦之类的发送调试事件必须用到的函数。
这个函数的作用就是,创建一个调试事件,然后把收到的DBGKM_APIMSG写道调试事件的ApiMsg成员里,最后挂入调试对象的双向循环链表里,然后置位调试器信号量,激活调试器。
NTSTATUS DbgkpQueueMessage(
PEPROCESS Process,
PETHREAD Thread,
PDBGKM_APIMSG ApiMsg,
ULONG Flags,
PDEBUG_OBJECT TargetDebugObject)
{
__int64 v10; // r12
DEBUG_EVENT *alloc_debug_event; // rax
DEBUG_EVENT *p_debug_event; // r14
DBGKM_APINUMBER ApiNumber; // ecx
DBGKM_APIMSG *p_ApiMsg; // rbx
DBGKM_APIMSG *ApiMsg_From_Debug_Event; // rax
PDBGKM_APIMSG In_ApiMsg; // rcx
__int64 v18; // rdx
__int128 v19; // xmm1
NTSTATUS Status; // esi
struct _FAST_MUTEX *p_Mutex; // r12
struct _LIST_ENTRY *Blink; // rcx
__int128 v23; // xmm1
ULONG is_no_wait; // [rsp+30h] [rbp-1C8h]
DEBUG_EVENT debug_event; // [rsp+40h] [rbp-1B8h] BYREF
// #define DEBUG_EVENT_READ (0x01) // Event had been seen by win32 app
// #define DEBUG_EVENT_NOWAIT (0x02) // No waiter one this. Just free the pool
// #define DEBUG_EVENT_INACTIVE (0x04) // The message is in inactive. It may be activated or deleted later
// #define DEBUG_EVENT_RELEASE (0x08) // Release rundown protection on this thread
// #define DEBUG_EVENT_PROTECT_FAILED (0x10) // Rundown protection failed to be acquired on this thread
// #define DEBUG_EVENT_SUSPEND (0x20) // Resume thread on continue
//
//
memset(
&debug_event,
0,
0x168u);
v10 = 2;
is_no_wait = Flags & 2;
if ( (Flags & 2) == 0 ) // 需等待事件
{
debug_event.Flags = Flags;
p_debug_event = &debug_event;
ExAcquireFastMutex(&DbgkpProcessDebugPortMutex);
ApiNumber = ApiMsg->ApiNumber;
TargetDebugObject = (PDEBUG_OBJECT)Process->DebugPort;// 需等待事件必须有debug port
if ( (unsigned int)(ApiNumber - 1) <= 1 && (Thread->CrossThreadFlags & 0x40) != 0 )
TargetDebugObject = 0;
if ( ApiNumber == DbgKmLoadDllApi )
{
if ( ((unsigned __int8)Flags & Thread->CrossThreadFlags & 0x40) == 0 )
{
LABEL_14:
KeInitializeEvent(&debug_event.ContinueEvent, SynchronizationEvent, 0);
goto LABEL_15;
}
TargetDebugObject = 0;
}
if ( (unsigned int)(ApiNumber - 3) <= 1 && SLOBYTE(Thread->CrossThreadFlags) < 0 )
TargetDebugObject = 0;
goto LABEL_14;
}
alloc_debug_event = (DEBUG_EVENT *)ExAllocatePoolWithQuotaTag((POOL_TYPE)520, 0x168u, 'EgbD');
p_debug_event = alloc_debug_event;
if ( !alloc_debug_event )
return 0xC000009A; // STATUS_INSUFFICIENT_RESOURCES
alloc_debug_event->Flags = Flags | 4;
ObfReferenceObjectWithTag(Process, 0x4F676244u);
ObfReferenceObjectWithTag(Thread, 0x4F676244u);
p_debug_event->BackoutThread = (PETHREAD)KeGetCurrentThread();
LABEL_15:
p_ApiMsg = &p_debug_event->ApiMsg;
p_debug_event->Process = Process;
ApiMsg_From_Debug_Event = &p_debug_event->ApiMsg;
p_debug_event->Thread = Thread;
In_ApiMsg = ApiMsg;
v18 = 2;
do // 复制
{
*(_OWORD *)&ApiMsg_From_Debug_Event->h.u1.s1.DataLength = *(_OWORD *)&In_ApiMsg->h.u1.s1.DataLength;
*(union _PORT_MESSAGE::$BD8137A324A8476723CC573F97133CB1 *)((char *)&ApiMsg_From_Debug_Event->h.8 + 8) = *(union _PORT_MESSAGE::$BD8137A324A8476723CC573F97133CB1 *)((char *)&In_ApiMsg->h.8 + 8);
*(_OWORD *)&ApiMsg_From_Debug_Event->h.ClientViewSize = *(_OWORD *)&In_ApiMsg->h.ClientViewSize;
*(_OWORD *)&ApiMsg_From_Debug_Event->u.Exception.ExceptionRecord.ExceptionCode = *(_OWORD *)&In_ApiMsg->u.Exception.ExceptionRecord.ExceptionCode;
*((_OWORD *)&ApiMsg_From_Debug_Event->u.UnloadDll + 1) = *((_OWORD *)&In_ApiMsg->u.UnloadDll + 1);
*((_OWORD *)&ApiMsg_From_Debug_Event->u.UnloadDll + 2) = *((_OWORD *)&In_ApiMsg->u.UnloadDll + 2);
*((_OWORD *)&ApiMsg_From_Debug_Event->u.UnloadDll + 3) = *((_OWORD *)&In_ApiMsg->u.UnloadDll + 3);
ApiMsg_From_Debug_Event = (DBGKM_APIMSG *)((char *)ApiMsg_From_Debug_Event + 128);
v19 = *((_OWORD *)&In_ApiMsg->u.UnloadDll + 4);
In_ApiMsg = (PDBGKM_APIMSG)((char *)In_ApiMsg + 128);
*((_OWORD *)&ApiMsg_From_Debug_Event[-1].u.UnloadDll + 9) = v19;
--v18;
}
while ( v18 );
*(_OWORD *)&ApiMsg_From_Debug_Event->h.u1.s1.DataLength = *(_OWORD *)&In_ApiMsg->h.u1.s1.DataLength;
p_debug_event->ClientId = Thread->Cid;
if ( TargetDebugObject )
{
p_Mutex = &TargetDebugObject->Mutex;
ExAcquireFastMutex(&TargetDebugObject->Mutex);
if ( (TargetDebugObject->Flags & 1) != 0 )
{
Status = 0xC0000354;
}
else
{
Blink = TargetDebugObject->EventList.Blink;
if ( Blink->Flink != &TargetDebugObject->EventList )
__fastfail(3u); // 检查完整性
p_debug_event->EventList.Flink = &TargetDebugObject->EventList;
p_debug_event->EventList.Blink = Blink;
Blink->Flink = &p_debug_event->EventList; // 尾插法
TargetDebugObject->EventList.Blink = &p_debug_event->EventList;
if ( !is_no_wait ) // 需等待,假消息不走这
KeSetEvent(&TargetDebugObject->EventsPresent, 0, 0);
Status = 0;
}
KeReleaseGuardedMutex(p_Mutex);
v10 = 2;
}
else
{
Status = 0xC0000353; // STATUS_PORT_NOT_SET
}
if ( is_no_wait )
{
if ( Status < 0 )
{
ObfDereferenceObjectWithTag(Process, 0x4F676244u);
ObfDereferenceObjectWithTag(Thread, 0x4F676244u);
ExFreePoolWithTag(p_debug_event, 0);
}
}
else
{
KeReleaseGuardedMutex(&DbgkpProcessDebugPortMutex);
if ( Status >= 0 )
{
KeWaitForSingleObject(&p_debug_event->ContinueEvent, Executive, 0, 0, 0);
Status = p_debug_event->Status;
do
{
*(_OWORD *)&ApiMsg->h.u1.s1.DataLength = *(_OWORD *)&p_ApiMsg->h.u1.s1.DataLength;
*(union _PORT_MESSAGE::$BD8137A324A8476723CC573F97133CB1 *)((char *)&ApiMsg->h.8 + 8) = *(union _PORT_MESSAGE::$BD8137A324A8476723CC573F97133CB1 *)((char *)&p_ApiMsg->h.8 + 8);
*(_OWORD *)&ApiMsg->h.ClientViewSize = *(_OWORD *)&p_ApiMsg->h.ClientViewSize;
*(_OWORD *)&ApiMsg->u.Exception.ExceptionRecord.ExceptionCode = *(_OWORD *)&p_ApiMsg->u.Exception.ExceptionRecord.ExceptionCode;
*((_OWORD *)&ApiMsg->u.UnloadDll + 1) = *((_OWORD *)&p_ApiMsg->u.UnloadDll + 1);
*((_OWORD *)&ApiMsg->u.UnloadDll + 2) = *((_OWORD *)&p_ApiMsg->u.UnloadDll + 2);
*((_OWORD *)&ApiMsg->u.UnloadDll + 3) = *((_OWORD *)&p_ApiMsg->u.UnloadDll + 3);
ApiMsg = (PDBGKM_APIMSG)((char *)ApiMsg + 128);
v23 = *((_OWORD *)&p_ApiMsg->u.UnloadDll + 4);
p_ApiMsg = (DBGKM_APIMSG *)((char *)p_ApiMsg + 128);
*((_OWORD *)&ApiMsg[-1].u.UnloadDll + 9) = v23;
--v10;
}
while ( v10 );
*(_OWORD *)&ApiMsg->h.u1.s1.DataLength = *(_OWORD *)&p_ApiMsg->h.u1.s1.DataLength;
}
}
return Status;
}当然他的功能很多,我们发假消息用的flag是:
// #define DEBUG_EVENT_READ (0x01) // Event had been seen by win32 app
// #define DEBUG_EVENT_NOWAIT (0x02) // No waiter one this. Just free the pool
// #define DEBUG_EVENT_INACTIVE (0x04) // The message is in inactive. It may be activated or deleted later
// #define DEBUG_EVENT_RELEASE (0x08) // Release rundown protection on this thread
// #define DEBUG_EVENT_PROTECT_FAILED (0x10) // Rundown protection failed to be acquired on this thread
// #define DEBUG_EVENT_SUSPEND (0x20) // Resume thread on continue
flag = 0xA; // A = 2+8
// DEBUG_EVENT_NOWAIT
// DEBUG_EVENT_RELEASE也就是走的NOWAIT这条路,只会干复制apimsg并且挂到循环链表上面去的活:
p_ApiMsg = &p_debug_event->ApiMsg;
p_debug_event->Process = Process;
ApiMsg_From_Debug_Event = &p_debug_event->ApiMsg;
p_debug_event->Thread = Thread;
In_ApiMsg = ApiMsg;
v18 = 2;
do // 复制
{
*(_OWORD *)&ApiMsg_From_Debug_Event->h.u1.s1.DataLength = *(_OWORD *)&In_ApiMsg->h.u1.s1.DataLength;
*(union _PORT_MESSAGE::$BD8137A324A8476723CC573F97133CB1 *)((char *)&ApiMsg_From_Debug_Event->h.8 + 8) = *(union _PORT_MESSAGE::$BD8137A324A8476723CC573F97133CB1 *)((char *)&In_ApiMsg->h.8 + 8);
*(_OWORD *)&ApiMsg_From_Debug_Event->h.ClientViewSize = *(_OWORD *)&In_ApiMsg->h.ClientViewSize;
*(_OWORD *)&ApiMsg_From_Debug_Event->u.Exception.ExceptionRecord.ExceptionCode = *(_OWORD *)&In_ApiMsg->u.Exception.ExceptionRecord.ExceptionCode;
*((_OWORD *)&ApiMsg_From_Debug_Event->u.UnloadDll + 1) = *((_OWORD *)&In_ApiMsg->u.UnloadDll + 1);
*((_OWORD *)&ApiMsg_From_Debug_Event->u.UnloadDll + 2) = *((_OWORD *)&In_ApiMsg->u.UnloadDll + 2);
*((_OWORD *)&ApiMsg_From_Debug_Event->u.UnloadDll + 3) = *((_OWORD *)&In_ApiMsg->u.UnloadDll + 3);
ApiMsg_From_Debug_Event = (DBGKM_APIMSG *)((char *)ApiMsg_From_Debug_Event + 128);
v19 = *((_OWORD *)&In_ApiMsg->u.UnloadDll + 4);
In_ApiMsg = (PDBGKM_APIMSG)((char *)In_ApiMsg + 128);
*((_OWORD *)&ApiMsg_From_Debug_Event[-1].u.UnloadDll + 9) = v19;
...
// 挂循环链表上去
else
{
Blink = TargetDebugObject->EventList.Blink;
if ( Blink->Flink != &TargetDebugObject->EventList )
__fastfail(3u); // 检查完整性
p_debug_event->EventList.Flink = &TargetDebugObject->EventList;
p_debug_event->EventList.Blink = Blink;
Blink->Flink = &p_debug_event->EventList; // 尾插法
TargetDebugObject->EventList.Blink = &p_debug_event->EventList;
if ( !is_no_wait ) // 需等待,假消息不走这
KeSetEvent(&TargetDebugObject->EventsPresent, 0, 0);
Status = 0;
}
KeReleaseGuardedMutex(p_Mutex);最后DbgkpPostFakeThreadMessages还会传出处理后的最后一个线程,接下来有用:
*pFirstThread = FirstThread;
*pLastThread = LastThread;DbgkpSetProcessDebugObject
show code:
NTSTATUS DbgkpSetProcessDebugObject(
PEPROCESS Process,
PDEBUG_OBJECT DebugObject,
NTSTATUS MsgStatus,
PETHREAD LastThread)
{
struct _ETHREAD *CurrentThread; // r13
int v5; // edi
PETHREAD pLastThread_copy; // rbx
struct _ETHREAD *NextProcessThread; // r14
struct _LIST_ENTRY *Entry; // r14
PDEBUG_EVENT DebugEvent; // rbx
ULONG Flags; // eax
PETHREAD Thread; // r13
struct _LIST_ENTRY *Flink; // rcx
struct _LIST_ENTRY *v15; // rax
struct _LIST_ENTRY *Blink; // rax
struct _LIST_ENTRY *p_EventList; // rax
ULONG v18; // eax
PVOID v19; // rcx
__int64 v20; // rax
PVOID Object; // [rsp+30h] [rbp-30h] BYREF
struct _ETHREAD *v23; // [rsp+38h] [rbp-28h]
PKGUARDED_MUTEX Mutex; // [rsp+40h] [rbp-20h]
PVOID clear_link; // [rsp+48h] [rbp-18h] BYREF
PDEBUG_EVENT p_clear_link; // [rsp+50h] [rbp-10h]
bool first; // [rsp+A8h] [rbp+48h]
char v28; // [rsp+B0h] [rbp+50h]
PETHREAD pLastThread; // [rsp+B8h] [rbp+58h] BYREF
pLastThread = LastThread;
CurrentThread = (struct _ETHREAD *)KeGetCurrentThread();
Object = 0;
p_clear_link = (PDEBUG_EVENT)&clear_link;
clear_link = &clear_link;
v5 = MsgStatus;
v23 = CurrentThread;
first = 1;
v28 = 0;
if ( MsgStatus >= 0 )
{
pLastThread_copy = pLastThread;
v5 = 0;
}
else
{
pLastThread_copy = 0;
pLastThread = 0;
}
if ( v5 >= 0 )
{
ExAcquireFastMutex(&DbgkpProcessDebugPortMutex);
while ( 1 )
{
if ( Process->DebugPort )
{
v5 = 0xC0000048; // STATUS_PORT_ALREADY_SET
v28 = 1;
goto LABEL_11;
}
Process->DebugPort = DebugObject; // 设置被调试进程debugport
ObfReferenceObjectWithTag(pLastThread_copy, 'OgbD');
v28 = 1;
NextProcessThread = PsGetNextProcessThread(Process, pLastThread_copy);
if ( !NextProcessThread )
goto LABEL_11;
Process->DebugPort = 0; // 说明又创建了一个线程,再去发假消息
KeReleaseGuardedMutex(&DbgkpProcessDebugPortMutex);
v28 = 0;
ObfDereferenceObjectWithTag(pLastThread_copy, 'OgbD');
v5 = DbgkpPostFakeThreadMessages(Process, DebugObject, NextProcessThread, (PETHREAD *)&Object, &pLastThread);
if ( v5 < 0 )
break;
ObfDereferenceObjectWithTag(Object, 0x4F676244u);
ExAcquireFastMutex(&DbgkpProcessDebugPortMutex);
pLastThread_copy = pLastThread;
}
pLastThread_copy = 0;
pLastThread = 0;
}
LABEL_11:
Mutex = &DebugObject->Mutex;
ExAcquireFastMutex(&DebugObject->Mutex);
if ( v5 >= 0 )
{
if ( (DebugObject->Flags & 1) != 0 ) //
// #define DEBUG_OBJECT_DELETE_PENDING (0x1) // Debug object is delete pending.
// #define DEBUG_OBJECT_KILL_ON_CLOSE (0x2) // Kill all debugged processes on close
{
Process->DebugPort = 0;
v5 = 0xC0000354; // STATUS_DEBUGGER_INACTIVE
}
else
{
_InterlockedOr((volatile signed __int32 *)&Process->___u6, 3u);
ObfReferenceObject(DebugObject);
pLastThread_copy = pLastThread;
}
}
Entry = DebugObject->EventList.Flink;
if ( Entry == &DebugObject->EventList ) // 空链表
goto empty_link;
do
{
DebugEvent = (PDEBUG_EVENT)Entry;
Entry = Entry->Flink;
Flags = DebugEvent->Flags;
if ( (Flags & 4) == 0 || DebugEvent->BackoutThread != CurrentThread )
continue;
Thread = DebugEvent->Thread;
if ( v5 < 0 )
{
if ( (PDEBUG_EVENT)Entry->Blink != DebugEvent
|| (Blink = DebugEvent->EventList.Blink, (PDEBUG_EVENT)Blink->Flink != DebugEvent) )// 链表完整性判断
{
check_fail:
__fastfail(3u);
}
Blink->Flink = Entry;
Entry->Blink = Blink;
goto LABEL_30;
}
if ( (Flags & 0x10) != 0 )
{
_InterlockedOr((volatile signed __int32 *)&Thread->___u21, 0x80u);
Flink = DebugEvent->EventList.Flink;
if ( (PDEBUG_EVENT)DebugEvent->EventList.Flink->Blink != DebugEvent )
goto check_fail;
v15 = DebugEvent->EventList.Blink;
if ( (PDEBUG_EVENT)v15->Flink != DebugEvent )
goto check_fail;
v15->Flink = Flink;
Flink->Blink = v15;
LABEL_30:
p_EventList = &p_clear_link->EventList;
if ( (PVOID *)p_clear_link->EventList.Flink != &clear_link )
goto check_fail;
DebugEvent->EventList.Flink = (struct _LIST_ENTRY *)&clear_link;
DebugEvent->EventList.Blink = p_EventList;
p_EventList->Flink = &DebugEvent->EventList;
p_clear_link = DebugEvent;
goto LABEL_32;
}
if ( first )
{
DebugEvent->Flags = Flags & 0xFFFFFFFB;
KeSetEvent(&DebugObject->EventsPresent, 0, 0);// 激活调试器
first = 0;
}
DebugEvent->BackoutThread = 0;
_InterlockedOr((volatile signed __int32 *)&Thread->___u21, 0x40u);
LABEL_32:
v18 = DebugEvent->Flags;
if ( (v18 & 8) != 0 )
{
DebugEvent->Flags = v18 & 0xFFFFFFF7;
ExReleaseRundownProtection(&Thread->RundownProtect);
}
CurrentThread = v23;
}
while ( Entry != &DebugObject->EventList );
pLastThread_copy = pLastThread;
empty_link:
KeReleaseGuardedMutex(Mutex);
if ( v28 )
KeReleaseGuardedMutex(&DbgkpProcessDebugPortMutex);
if ( pLastThread_copy )
ObfDereferenceObjectWithTag(pLastThread_copy, 0x4F676244u);
while ( 1 )
{
v19 = clear_link;
if ( clear_link == &clear_link )
break;
if ( *((PVOID **)clear_link + 1) != &clear_link )
goto check_fail;
v20 = *(_QWORD *)clear_link;
if ( *(PVOID *)(*(_QWORD *)clear_link + 8LL) != clear_link )
goto check_fail;
clear_link = *(PVOID *)clear_link;
*(_QWORD *)(v20 + 8) = &clear_link;
DbgkpWakeTarget(v19);
}
if ( v5 >= 0 )
DbgkpMarkProcessPeb((ULONG_PTR)Process);
return v5;
}注意看我们传入的LastThread,他又拿去PsGetNextProcessThread,如果拿到了,那说明这期间你又偷偷创建线程了,拿去重新发假消息。
NextProcessThread = PsGetNextProcessThread(Process, pLastThread_copy);
if ( !NextProcessThread )
goto LABEL_11;
Process->DebugPort = 0; // 说明又创建了一个线程,再去发假消息
KeReleaseGuardedMutex(&DbgkpProcessDebugPortMutex);
v28 = 0;
ObfDereferenceObjectWithTag(pLastThread_copy, 'OgbD');
v5 = DbgkpPostFakeThreadMessages(Process, DebugObject, NextProcessThread, (PETHREAD *)&Object, &pLastThread);
if ( v5 < 0 )
break;
ObfDereferenceObjectWithTag(Object, 0x4F676244u);
ExAcquireFastMutex(&DbgkpProcessDebugPortMutex);
pLastThread_copy = pLastThread;终于在这一步,我们将调试对象与被调试进程关联了:
Process->DebugPort = DebugObject; // 设置被调试进程debugport经过一些杂七杂八的检查,比如检查链表完整性。然后就会激活调试器,让他去收那些假消息:
KeSetEvent(&DebugObject->EventsPresent, 0, 0);// 激活调试器然后轮询链表,摘下那些没用的,已读的事件,放入clear_link,让DbgkpWakeTarget处理:
void DbgkpWakeTarget(PDEBUG_EVENT DebugEvent)
{
ULONG Flags; // eax
PETHREAD Thread; // rdi
Flags = DebugEvent->Flags;
Thread = DebugEvent->Thread;
if ( (Flags & 0x20) != 0 )
{
PsResumeThread(DebugEvent->Thread, 0);
Flags = DebugEvent->Flags;
}
if ( (Flags & 8) != 0 )
{
ExReleaseRundownProtection(&Thread->RundownProtect);
Flags = DebugEvent->Flags;
}
if ( (Flags & 2) != 0 )
DbgkpFreeDebugEvent(DebugEvent);
else
KeSetEvent(&DebugEvent->ContinueEvent, 0, 0);
}我们来看WaitForDebugEvent和ContinueDebugEvent
WaitForDebugEvent
BOOL __stdcall WaitForDebugEvent(LPDEBUG_EVENT lpDebugEvent, DWORD dwMilliseconds)
{
return WaitForDebugEventWorker(lpDebugEvent);
}
// r3的DEBUG_EVENT
//
// typedef struct _DEBUG_EVENT {
// DWORD dwDebugEventCode; // 调试事件类型
// DWORD dwProcessId; // 发生事件的进程ID
// DWORD dwThreadId; // 发生事件的线程ID
// union {
// EXCEPTION_DEBUG_INFO Exception;
// CREATE_THREAD_DEBUG_INFO CreateThread;
// CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;
// EXIT_THREAD_DEBUG_INFO ExitThread;
// EXIT_PROCESS_DEBUG_INFO ExitProcess;
// LOAD_DLL_DEBUG_INFO LoadDll;
// UNLOAD_DLL_DEBUG_INFO UnloadDll;
// OUTPUT_DEBUG_STRING_INFO DebugString;
// RIP_INFO RipInfo;
// } u;
// } DEBUG_EVENT, *LPDEBUG_EVENT;
//
__int64 __fastcall WaitForDebugEventWorker(LPDEBUG_EVENT DebugEvent, DWORD WaitTime, char ZeroBit)
{
union _LARGE_INTEGER *time; // rsi
NTSTATUS status; // eax
DWORD dwDebugEventCode; // eax
_BYTE *i; // rcx
HANDLE hThread; // r8
_BYTE WaitTime_1[16]; // [rsp+20h] [rbp-E8h] BYREF
struct _DBGUI_WAIT_STATE_CHANGE DbgUiWaitStateCange; // [rsp+30h] [rbp-D8h] BYREF
time = (union _LARGE_INTEGER *)BaseFormatTimeOut(WaitTime_1);
do
{
do
status = DbgUiWaitStateChange(&DbgUiWaitStateCange, time);
while ( status == 0x101 ); // 00000101 STATUS_ALERTED
}
while ( status == 0xC0 ); // 000000C0 STATUS_USER_APC
if ( status < 0 )
goto Failed;
if ( status == 0x102 ) // 00000102 STATUS_TIMEOUT
{
RtlSetLastWin32Error(0x79u);
return 0;
}
status = ZeroBit
? DbgUiConvertStateChangeStructureEx(&DbgUiWaitStateCange, DebugEvent)
: DbgUiConvertStateChangeStructure(&DbgUiWaitStateCange, DebugEvent);
if ( status < 0 )
{
Failed:
BaseSetLastNTError((unsigned int)status);
return 0;
}
dwDebugEventCode = DebugEvent->dwDebugEventCode;
if ( DebugEvent->dwDebugEventCode != 1 )
{
if ( dwDebugEventCode == 2 )
{
hThread = DebugEvent->u.CreateThread.hThread;
}
else
{
if ( dwDebugEventCode != 3 )
{
if ( dwDebugEventCode == 4 )
{
MarkThreadHandle(DebugEvent->dwThreadId);
}
else if ( dwDebugEventCode == 5 )
{
MarkThreadHandle(DebugEvent->dwThreadId);
for ( i = NtCurrentTeb()->DbgSsReserved[0]; i; i = *(_BYTE **)i )
{
if ( *((_DWORD *)i + 6) == DebugEvent->dwProcessId && !*((_DWORD *)i + 7) )
{
i[32] = 1;
return 1;
}
}
}
else if ( dwDebugEventCode != 6 && dwDebugEventCode != 7 && (dwDebugEventCode <= 7 || dwDebugEventCode > 9) )
{
return 0;
}
return 1;
}
SaveProcessHandle(DebugEvent->dwProcessId, DebugEvent->u.Exception.ExceptionRecord.ExceptionRecord);
hThread = DebugEvent->u.Exception.ExceptionRecord.ExceptionAddress;
}
SaveThreadHandle(DebugEvent->dwProcessId, DebugEvent->dwThreadId, hThread);
}
return 1;
}我们回忆一下r3也有一个debugevent,两个是不一样的,你总不能和r0一样一堆r0的地址吧,
代码不难,关键点是:
status = DbgUiWaitStateChange(&DbgUiWaitStateCange, time);
...
DbgUiConvertStateChangeStructureEx(&DbgUiWaitStateCange, DebugEvent)死等,然后拿到了个DbgUiWaitStateCange,然后转换为r3的DebugEvent,最后返回。
__int64 __fastcall DbgUiWaitStateChange(__int64 a1, __int64 a2)
{
__int64 v2; // r8
v2 = a2;
LOBYTE(a2) = 1;
return ZwWaitForDebugEvent(NtCurrentTeb()->DbgSsReserved[1], a2, v2, a1);
}
// from ntoskrnl
NTSTATUS NtWaitForDebugEvent(
HANDLE DebugObjectHandle,
BOOLEAN Alertable,
PLARGE_INTEGER Timeout,
PDBGUI_WAIT_STATE_CHANGE WaitStateChange)
{
char is_got_event; // r14
KPROCESSOR_MODE IsPreviousMode; // r15
__int64 v9; // rcx
NTSTATUS status; // eax
BOOLEAN v11; // r9
PDEBUG_OBJECT DebugObject; // rdi
struct _DEBUG_EVENT *p_EventListHead; // rdx
PDEBUG_EVENT entry; // rax
PDEBUG_EVENT debug_event; // rbx
ULONG Flags; // r8d
PDEBUG_EVENT Flink; // rcx
int status1; // ebx
bool v19; // sf
unsigned __int64 *v20; // rsi
LONGLONG QuadPart; // [rsp+38h] [rbp-150h] BYREF
PLARGE_INTEGER Timeouta; // [rsp+40h] [rbp-148h]
PVOID Object; // [rsp+48h] [rbp-140h] BYREF
__int64 v25; // [rsp+50h] [rbp-138h]
PVOID Process; // [rsp+58h] [rbp-130h]
PVOID Thread; // [rsp+60h] [rbp-128h]
_OWORD v28[12]; // [rsp+80h] [rbp-108h] BYREF
Timeouta = Timeout;
is_got_event = 0;
QuadPart = 0;
v25 = 0;
IsPreviousMode = KeGetCurrentThread()->PreviousMode;
memset(v28, 0, 0xB8u);
if ( Timeouta )
{
QuadPart = Timeouta->QuadPart;
Timeouta = (PLARGE_INTEGER)&QuadPart;
v25 = MEMORY[0xFFFFF78000000014];
}
if ( IsPreviousMode )
{
v9 = (__int64)WaitStateChange;
if ( (unsigned __int64)WaitStateChange >= 0x7FFFFFFF0000LL )
v9 = 0x7FFFFFFF0000LL; // 判断越界
*(_BYTE *)v9 = *(_BYTE *)v9;
*(_BYTE *)(v9 + 183) = *(_BYTE *)(v9 + 183);
}
Object = 0;
status = ObReferenceObjectByHandle(DebugObjectHandle, 1u, DbgkDebugObjectType, IsPreviousMode, &Object, 0);
if ( status >= 0 )
{
Process = 0;
Thread = 0;
v11 = Alertable;
DebugObject = (PDEBUG_OBJECT)Object;
while ( 1 )
{
status1 = KeWaitForSingleObject(DebugObject, Executive, IsPreviousMode, v11, Timeouta);
if ( status1 < 0 || status1 == 0xC0 || (unsigned int)(status1 - 0x101) <= 1 )
break;
ExAcquireFastMutex(&DebugObject->Mutex);
if ( (DebugObject->Flags & 1) != 0 ) // #define DEBUG_OBJECT_DELETE_PENDING (0x1) // Debug object is delete pending.
// #define DEBUG_OBJECT_KILL_ON_CLOSE (0x2) // Kill all debugged processes on close
{
status1 = 0xC0000354; // STATUS_DEBUGGER_INACTIVE
}
else
{
p_EventListHead = (struct _DEBUG_EVENT *)&DebugObject->EventList;
for ( entry = (PDEBUG_EVENT)DebugObject->EventList.Flink; ; entry = (PDEBUG_EVENT)entry->EventList.Flink )// 遍历debug_event
{
if ( entry == p_EventListHead )
{
KeResetEvent(&DebugObject->EventsPresent);
goto LABEL_24;
}
debug_event = entry;
Flags = entry->Flags;
if ( (Flags & 5) == 0 ) // #define DEBUG_EVENT_READ (0x01) // Event had been seen by win32 app
// #define DEBUG_EVENT_NOWAIT (0x02) // No waiter one this. Just free the pool
// #define DEBUG_EVENT_INACTIVE (0x04) // The message is in inactive. It may be activated or deleted later
// #define DEBUG_EVENT_RELEASE (0x08) // Release rundown protection on this thread
// #define DEBUG_EVENT_PROTECT_FAILED (0x10) // Rundown protection failed to be acquired on this thread
// #define DEBUG_EVENT_SUSPEND (0x20) // Resume thread on continue
//
// 5 = 1 + 4
{
is_got_event = 1;
Flink = (PDEBUG_EVENT)p_EventListHead->EventList.Flink;
if ( (PDEBUG_EVENT)p_EventListHead->EventList.Flink != entry )
{
while ( entry->ClientId.UniqueProcess != Flink->ClientId.UniqueProcess )
{
Flink = (PDEBUG_EVENT)Flink->EventList.Flink;
if ( Flink == entry )
goto LABEL_19;
}
entry->Flags = Flags | 4;
entry->BackoutThread = 0;
is_got_event = 0;
}
LABEL_19:
if ( is_got_event )
break;
}
}
Process = entry->Process;
Thread = entry->Thread;
ObfReferenceObjectWithTag(Thread, 0x4F676244u);
ObfReferenceObjectWithTag(Process, 0x4F676244u);
DbgkpConvertKernelToUserStateChange(v28, debug_event);
debug_event->Flags |= 1u; // 标记已读
LABEL_24:
status1 = 0;
}
KeReleaseGuardedMutex(&DebugObject->Mutex);
if ( status1 < 0 )
break;
if ( is_got_event )
{
DbgkpOpenHandles(v28, Process, Thread);
ObfDereferenceObjectWithTag(Thread, 0x4F676244u);
ObfDereferenceObjectWithTag(Process, 0x4F676244u);
break;
}
is_got_event = 0;
if ( QuadPart < 0 )
{
v19 = MEMORY[0xFFFFF78000000014] - v25 + QuadPart < 0;
QuadPart += MEMORY[0xFFFFF78000000014] - v25;
v25 = MEMORY[0xFFFFF78000000014];
DebugObject = (PDEBUG_OBJECT)Object;
if ( !v19 )
{
status1 = 258;
break;
}
}
v11 = Alertable;
}
HalPutDmaAdapter((PADAPTER_OBJECT)DebugObject);
*(_OWORD *)&WaitStateChange->NewState = v28[0];
*(_OWORD *)&WaitStateChange->AppClientId.UniqueThread = v28[1];
*(_OWORD *)(&WaitStateChange->StateInfo.UnloadDll + 1) = v28[2];
*(_OWORD *)(&WaitStateChange->StateInfo.UnloadDll + 3) = v28[3];
*(_OWORD *)(&WaitStateChange->StateInfo.UnloadDll + 5) = v28[4];
*(_OWORD *)(&WaitStateChange->StateInfo.UnloadDll + 7) = v28[5];
*(_OWORD *)(&WaitStateChange->StateInfo.UnloadDll + 9) = v28[6];
v20 = &WaitStateChange->StateInfo.Exception.ExceptionRecord.ExceptionInformation[9];
*((_OWORD *)v20 - 1) = v28[7];
*(_OWORD *)v20 = v28[8];
*((_OWORD *)v20 + 1) = v28[9];
*((_OWORD *)v20 + 2) = v28[10];
v20[6] = *(_QWORD *)&v28[11];
return status1;
}
return status;
}大概流程是:
//一个大循环死等DebugObject的信号量被置位
status1 = KeWaitForSingleObject(DebugObject, Executive, IsPreviousMode, v11, Timeouta);
// 特别注意,KeWaitForSingleObject如果status == true就会reset信号位
//拿一个就跑
p_EventListHead = (struct _DEBUG_EVENT *)&DebugObject->EventList;
...
is_got_event = 1;
...
if ( is_got_event )
break;
//填写WaitStateChange,然后返回
*(_OWORD *)&WaitStateChange->NewState = v28[0];
*(_OWORD *)&WaitStateChange->AppClientId.UniqueThread = v28[1];
*(_OWORD *)(&WaitStateChange->StateInfo.UnloadDll + 1) = v28[2];
*(_OWORD *)(&WaitStateChange->StateInfo.UnloadDll + 3) = v28[3];
*(_OWORD *)(&WaitStateChange->StateInfo.UnloadDll + 5) = v28[4];
*(_OWORD *)(&WaitStateChange->StateInfo.UnloadDll + 7) = v28[5];
*(_OWORD *)(&WaitStateChange->StateInfo.UnloadDll + 9) = v28[6];
v20 = &WaitStateChange->StateInfo.Exception.ExceptionRecord.ExceptionInformation[9];
*((_OWORD *)v20 - 1) = v28[7];
*(_OWORD *)v20 = v28[8];
*((_OWORD *)v20 + 1) = v28[9];
*((_OWORD *)v20 + 2) = v28[10];
v20[6] = *(_QWORD *)&v28[11];
return status1;然后转换一下,r3调试器就高高兴兴的跑去处理调试事件了。
ContinueDebugEvent
// from kernelbase
BOOL __stdcall ContinueDebugEvent(DWORD dwProcessId, DWORD dwThreadId, DWORD dwContinueStatus)
{
NTSTATUS v5; // eax
struct _CLIENT_ID v7; // [rsp+20h] [rbp-18h] BYREF
v7.UniqueProcess = (HANDLE)(int)dwProcessId;
v7.UniqueThread = (HANDLE)(int)dwThreadId;
v5 = DbgUiContinue(&v7, dwContinueStatus);
if ( v5 >= 0 )
{
RemoveHandles(dwThreadId, dwProcessId);
return 1;
}
else
{
BaseSetLastNTError((unsigned int)v5);
return 0;
}
}
// from ntdll
__int64 __fastcall DbgUiContinue(__int64 a1, unsigned int a2)
{
return NtDebugContinue(NtCurrentTeb()->DbgSsReserved[1], a1, a2);
}
// from ntoskrnl
NTSTATUS NtDebugContinue(HANDLE DebugObjectHandle, PCLIENT_ID ClientId, NTSTATUS ContinueStatus)
{
KPROCESSOR_MODE PreviousMode; // r9
NTSTATUS status; // eax
NTSTATUS status1; // edi
char find; // r15
PDEBUG_EVENT v8; // rsi
PDEBUG_OBJECT DebugObject; // r14
PDEBUG_EVENT DebugEvent; // rcx
PDEBUG_EVENT Flink; // rdx
PDEBUG_EVENT Blink; // rax
CLIENT_ID ClientId_copy; // [rsp+40h] [rbp-28h]
PVOID Object; // [rsp+88h] [rbp+20h] BYREF
PreviousMode = KeGetCurrentThread()->PreviousMode;
ClientId_copy = *ClientId;
if ( ContinueStatus != 0x80010001
&& (ContinueStatus <= 0x10000
|| ContinueStatus > 0x10002
&& ContinueStatus != 0x40010001
&& (ContinueStatus <= 0x40010002 || ContinueStatus > 0x40010004)) )
{
return 0xC000000D; // STATUS_INVALID_PARAMETER
}
Object = 0;
status = ObReferenceObjectByHandle(DebugObjectHandle, 1u, DbgkDebugObjectType, PreviousMode, &Object, 0);
status1 = status;
if ( status >= 0 )
{
find = 0;
v8 = 0;
DebugObject = (PDEBUG_OBJECT)Object;
ExAcquireFastMutex((PFAST_MUTEX)((char *)Object + 24));
DebugEvent = (PDEBUG_EVENT)DebugObject->EventList.Flink;
if ( DebugEvent == (PDEBUG_EVENT)&DebugObject->EventList )
goto empty_link;
while ( 1 )
{
if ( DebugEvent->ClientId.UniqueProcess == ClientId_copy.UniqueProcess )
{
if ( find )
{
DebugEvent->Flags &= ~4u;
KeSetEvent(&DebugObject->EventsPresent, 0, 0);
empty_link:
KeReleaseGuardedMutex(&DebugObject->Mutex);
HalPutDmaAdapter((PADAPTER_OBJECT)DebugObject);
if ( !find )
return -1073741811; // STATUS_INVALID_PARAMETER
if ( (PerfGlobalGroupMask & 0x400000) != 0 )
EtwTraceDebuggerEvent(v8->Process, v8->Thread, 2);
v8->ApiMsg.ReturnedStatus = ContinueStatus;
v8->Status = 0;
DbgkpWakeTarget((char *)v8); // 放行被调试进程
return status1;
}
if ( DebugEvent->ClientId.UniqueThread == ClientId_copy.UniqueThread && (DebugEvent->Flags & 1) != 0 )// 必须读过
// #define DEBUG_EVENT_READ (0x01) // Event had been seen by win32 app
// #define DEBUG_EVENT_NOWAIT (0x02) // No waiter one this. Just free the pool
// #define DEBUG_EVENT_INACTIVE (0x04) // The message is in inactive. It may be activated or deleted later
// #define DEBUG_EVENT_RELEASE (0x08) // Release rundown protection on this thread
// #define DEBUG_EVENT_PROTECT_FAILED (0x10) // Rundown protection failed to be acquired on this thread
// #define DEBUG_EVENT_SUSPEND (0x20) // Resume thread on continue
{
Flink = (PDEBUG_EVENT)DebugEvent->EventList.Flink;
Blink = (PDEBUG_EVENT)DebugEvent->EventList.Blink;
if ( (PDEBUG_EVENT)DebugEvent->EventList.Flink->Blink != DebugEvent
|| (PDEBUG_EVENT)Blink->EventList.Flink != DebugEvent )
{
__fastfail(3u);
}
Blink->EventList.Flink = &Flink->EventList;// 双向循环链表特有的去掉节点方法
Flink->EventList.Blink = &Blink->EventList;
v8 = DebugEvent;
find = 1;
}
}
DebugEvent = (PDEBUG_EVENT)DebugEvent->EventList.Flink;
if ( DebugEvent == (PDEBUG_EVENT)&DebugObject->EventList )
goto empty_link;
}
}
return status;
}他会遍历链表摘除读过的事件,如果还有就恢复被前面KeWaitForSingleObject清空的信号量,给通过DebugEvent的信号置位通行,DbgkpWakeTarget放行线程然后返回。
DebugEvent->Flags &= ~4u;
KeSetEvent(&DebugObject->EventsPresent, 0, 0);
empty_link:
KeReleaseGuardedMutex(&DebugObject->Mutex);
HalPutDmaAdapter((PADAPTER_OBJECT)DebugObject);
if ( !find )
return -1073741811; // STATUS_INVALID_PARAMETER
if ( (PerfGlobalGroupMask & 0x400000) != 0 )
EtwTraceDebuggerEvent(v8->Process, v8->Thread, 2);
v8->ApiMsg.ReturnedStatus = ContinueStatus;
v8->Status = 0;
DbgkpWakeTarget((char *)v8); // 放行被调试进程
return status1;如果判断调试事件链表是否为空,
DebugEvent = (PDEBUG_EVENT)DebugObject->EventList.Flink;
if ( DebugEvent == (PDEBUG_EVENT)&DebugObject->EventList )
goto empty_link;对于双向循环链表,前一个等于后一个等于自己就意味着空。如果为空就不置位debugobject的信号,直接放行:
DbgkpWakeTarget((char *)v8); // 放行被调试进程至此,整体框架构建完成。这次我们来看被调试进程是如何发送调试事件的,以及送大家一个有趣反调试。拿异常举例,当发生异常,如果检测到有调试器,会先发给调试器,调用这个函数。
DbgkForwardException 分析
BOOLEAN DbgkForwardException(PEXCEPTION_RECORD ExceptionRecord, BOOLEAN DebugException, BOOLEAN SecondChance){
struct _ETHREAD *CurrentThread; // rax
struct _EPROCESS *Process; // rsi
struct _DMA_ADAPTER *DebugPort; // rbx
char v9; // r14
int v11; // esi
NTSTATUS ReturnedStatus; // eax
int info[4]; // [rsp+20h] [rbp-E0h] BYREF
struct _DBGKM_APIMSG v14; // [rsp+30h] [rbp-D0h] BYREF
*(_QWORD *)info = 0;
memset(&v14, 0, 0x110u);
if ( SecondChance )
{
info[0] = 1;
PsSetProcessFaultInformation((ULONG_PTR)KeGetCurrentThread()->ApcState.Process, info);
}
v14.ApiNumber = DbgKmExceptionApi;
v14.h.u1.Length = 13631656;
v14.h.u2.ZeroInit = 8;
CurrentThread = (struct _ETHREAD *)KeGetCurrentThread();
Process = (struct _EPROCESS *)CurrentThread->Tcb.ApcState.Process;
if ( DebugException )
{
if ( (*(_DWORD *)(&KeGetCurrentThread()[1].SwapListEntry + 1) & 4) != 0 )
DebugPort = 0;
else
DebugPort = (struct _DMA_ADAPTER *)Process->DebugPort;
v9 = 0;
}
else
{
DebugPort = (struct _DMA_ADAPTER *)PsCaptureExceptionPort(CurrentThread->Tcb.ApcState.Process);
v14.h.u2.ZeroInit = 7;
v9 = 1;
}
if ( !DebugPort && DebugException )
return 0;
KeCopyExceptionRecord(&v14.u, ExceptionRecord);
v14.u.Exception.FirstChance = SecondChance == 0;
if ( v9 )
{
if ( DebugPort )
{
v11 = DbgkpSendApiMessageLpc((__int64)&v14, (int)DebugPort, DebugException);
HalPutDmaAdapter(DebugPort);
}
else
{
v11 = 0;
v14.ReturnedStatus = -2147418111;
}
}
else
{
v11 = DbgkpSendApiMessage(Process, DebugException != 0, &v14);
}
if ( v11 < 0 )
return 0;
ReturnedStatus = v14.ReturnedStatus;
if ( v14.ReturnedStatus == -2147418111 )
{
if ( DebugException )
return 0;
ReturnedStatus = DbgkpSendErrorMessage(ExceptionRecord, 2, &v14);
}
return ReturnedStatus >= 0;
}这里填写了根据异常apimsg的类型啦,一些异常记录,然后主动调用DbgkpSendApiMessage
__int64 __fastcall DbgkpSendApiMessage(struct _EPROCESS *Object, char a2, struct _DBGKM_APIMSG *a3){
int v6; // ebp
NTSTATUS v7; // esi
if ( (PerfGlobalGroupMask & 0x400000) != 0 )
EtwTraceDebuggerEvent(KeGetCurrentThread()->ApcState.Process, KeGetCurrentThread(), 1);
do
{
v6 = 0;
if ( Object == (struct _EPROCESS *)KeGetCurrentThread()->ApcState.Process && (a2 & 1) != 0 )
v6 = (unsigned __int8)DbgkpSuspendProcess(Object);
a3->ReturnedStatus = 259;
v7 = DbgkpQueueMessage(Object, (PETHREAD)KeGetCurrentThread(), a3, 32 * (a2 & 2), 0);
if ( v6 )
{
PsThawProcess(Object, 0);
KeLeaveCriticalRegion();
}
}
while ( v7 >= 0 && a3->ReturnedStatus == 1073807361 );
return (unsignedint)v7;
}可以看到调用了DbgkpQueueMessage,我们前面分析过了。
NTSTATUS DbgkpQueueMessage(
PEPROCESS Process,
PETHREAD Thread,
PDBGKM_APIMSG ApiMsg,
ULONG Flags,
PDEBUG_OBJECT TargetDebugObject)
{
__int64 v10; // r12
DEBUG_EVENT *alloc_debug_event; // rax
DEBUG_EVENT *p_debug_event; // r14
DBGKM_APINUMBER ApiNumber; // ecx
DBGKM_APIMSG *p_ApiMsg; // rbx
DBGKM_APIMSG *ApiMsg_From_Debug_Event; // rax
PDBGKM_APIMSG In_ApiMsg; // rcx
__int64 v18; // rdx
__int128 v19; // xmm1
NTSTATUS Status; // esi
struct _FAST_MUTEX *p_Mutex; // r12
struct _LIST_ENTRY *Blink; // rcx
__int128 v23; // xmm1
ULONG is_no_wait; // [rsp+30h] [rbp-1C8h]
DEBUG_EVENT debug_event; // [rsp+40h] [rbp-1B8h] BYREF
// #define DEBUG_EVENT_READ (0x01) // Event had been seen by win32 app
// #define DEBUG_EVENT_NOWAIT (0x02) // No waiter one this. Just free the pool
// #define DEBUG_EVENT_INACTIVE (0x04) // The message is in inactive. It may be activated or deleted later
// #define DEBUG_EVENT_RELEASE (0x08) // Release rundown protection on this thread
// #define DEBUG_EVENT_PROTECT_FAILED (0x10) // Rundown protection failed to be acquired on this thread
// #define DEBUG_EVENT_SUSPEND (0x20) // Resume thread on continue
//
//
memset(
&debug_event,
0,
0x168u);
v10 = 2;
is_no_wait = Flags & 2;
if ( (Flags & 2) == 0 ) // 需等待事件
{
debug_event.Flags = Flags;
p_debug_event = &debug_event;
ExAcquireFastMutex(&DbgkpProcessDebugPortMutex);
ApiNumber = ApiMsg->ApiNumber;
TargetDebugObject = (PDEBUG_OBJECT)Process->DebugPort;// 需等待事件必须有debug port
if ( (unsigned int)(ApiNumber - 1) <= 1 && (Thread->CrossThreadFlags & 0x40) != 0 )
TargetDebugObject = 0;
if ( ApiNumber == DbgKmLoadDllApi )
{
if ( ((unsigned __int8)Flags & Thread->CrossThreadFlags & 0x40) == 0 )
{
LABEL_14:
KeInitializeEvent(&debug_event.ContinueEvent, SynchronizationEvent, 0);
goto LABEL_15;
}
TargetDebugObject = 0;
}
if ( (unsigned int)(ApiNumber - 3) <= 1 && SLOBYTE(Thread->CrossThreadFlags) < 0 )
TargetDebugObject = 0;
goto LABEL_14;
}
alloc_debug_event = (DEBUG_EVENT *)ExAllocatePoolWithQuotaTag((POOL_TYPE)520, 0x168u, 'EgbD');
p_debug_event = alloc_debug_event;
if ( !alloc_debug_event )
return 0xC000009A; // STATUS_INSUFFICIENT_RESOURCES
alloc_debug_event->Flags = Flags | 4;
ObfReferenceObjectWithTag(Process, 0x4F676244u);
ObfReferenceObjectWithTag(Thread, 0x4F676244u);
p_debug_event->BackoutThread = (PETHREAD)KeGetCurrentThread();
LABEL_15:
p_ApiMsg = &p_debug_event->ApiMsg;
p_debug_event->Process = Process;
ApiMsg_From_Debug_Event = &p_debug_event->ApiMsg;
p_debug_event->Thread = Thread;
In_ApiMsg = ApiMsg;
v18 = 2;
do // 复制
{
*(_OWORD *)&ApiMsg_From_Debug_Event->h.u1.s1.DataLength = *(_OWORD *)&In_ApiMsg->h.u1.s1.DataLength;
*(union _PORT_MESSAGE::$BD8137A324A8476723CC573F97133CB1 *)((char *)&ApiMsg_From_Debug_Event->h.8 + 8) = *(union _PORT_MESSAGE::$BD8137A324A8476723CC573F97133CB1 *)((char *)&In_ApiMsg->h.8 + 8);
*(_OWORD *)&ApiMsg_From_Debug_Event->h.ClientViewSize = *(_OWORD *)&In_ApiMsg->h.ClientViewSize;
*(_OWORD *)&ApiMsg_From_Debug_Event->u.Exception.ExceptionRecord.ExceptionCode = *(_OWORD *)&In_ApiMsg->u.Exception.ExceptionRecord.ExceptionCode;
*((_OWORD *)&ApiMsg_From_Debug_Event->u.UnloadDll + 1) = *((_OWORD *)&In_ApiMsg->u.UnloadDll + 1);
*((_OWORD *)&ApiMsg_From_Debug_Event->u.UnloadDll + 2) = *((_OWORD *)&In_ApiMsg->u.UnloadDll + 2);
*((_OWORD *)&ApiMsg_From_Debug_Event->u.UnloadDll + 3) = *((_OWORD *)&In_ApiMsg->u.UnloadDll + 3);
ApiMsg_From_Debug_Event = (DBGKM_APIMSG *)((char *)ApiMsg_From_Debug_Event + 128);
v19 = *((_OWORD *)&In_ApiMsg->u.UnloadDll + 4);
In_ApiMsg = (PDBGKM_APIMSG)((char *)In_ApiMsg + 128);
*((_OWORD *)&ApiMsg_From_Debug_Event[-1].u.UnloadDll + 9) = v19;
--v18;
}
while ( v18 );
*(_OWORD *)&ApiMsg_From_Debug_Event->h.u1.s1.DataLength = *(_OWORD *)&In_ApiMsg->h.u1.s1.DataLength;
p_debug_event->ClientId = Thread->Cid;
if ( TargetDebugObject )
{
p_Mutex = &TargetDebugObject->Mutex;
ExAcquireFastMutex(&TargetDebugObject->Mutex);
if ( (TargetDebugObject->Flags & 1) != 0 )
{
Status = 0xC0000354;
}
else
{
Blink = TargetDebugObject->EventList.Blink;
if ( Blink->Flink != &TargetDebugObject->EventList )
__fastfail(3u); // 检查完整性
p_debug_event->EventList.Flink = &TargetDebugObject->EventList;
p_debug_event->EventList.Blink = Blink;
Blink->Flink = &p_debug_event->EventList; // 尾插法
TargetDebugObject->EventList.Blink = &p_debug_event->EventList;
if ( !is_no_wait ) // 需等待,假消息不走这
KeSetEvent(&TargetDebugObject->EventsPresent, 0, 0);
Status = 0;
}
KeReleaseGuardedMutex(p_Mutex);
v10 = 2;
}
else
{
Status = 0xC0000353; // STATUS_PORT_NOT_SET
}
if ( is_no_wait )
{
if ( Status < 0 )
{
ObfDereferenceObjectWithTag(Process, 0x4F676244u);
ObfDereferenceObjectWithTag(Thread, 0x4F676244u);
ExFreePoolWithTag(p_debug_event, 0);
}
}
else
{
KeReleaseGuardedMutex(&DbgkpProcessDebugPortMutex);
if ( Status >= 0 )
{
KeWaitForSingleObject(&p_debug_event->ContinueEvent, Executive, 0, 0, 0);
Status = p_debug_event->Status;
do
{
*(_OWORD *)&ApiMsg->h.u1.s1.DataLength = *(_OWORD *)&p_ApiMsg->h.u1.s1.DataLength;
*(union _PORT_MESSAGE::$BD8137A324A8476723CC573F97133CB1 *)((char *)&ApiMsg->h.8 + 8) = *(union _PORT_MESSAGE::$BD8137A324A8476723CC573F97133CB1 *)((char *)&p_ApiMsg->h.8 + 8);
*(_OWORD *)&ApiMsg->h.ClientViewSize = *(_OWORD *)&p_ApiMsg->h.ClientViewSize;
*(_OWORD *)&ApiMsg->u.Exception.ExceptionRecord.ExceptionCode = *(_OWORD *)&p_ApiMsg->u.Exception.ExceptionRecord.ExceptionCode;
*((_OWORD *)&ApiMsg->u.UnloadDll + 1) = *((_OWORD *)&p_ApiMsg->u.UnloadDll + 1);
*((_OWORD *)&ApiMsg->u.UnloadDll + 2) = *((_OWORD *)&p_ApiMsg->u.UnloadDll + 2);
*((_OWORD *)&ApiMsg->u.UnloadDll + 3) = *((_OWORD *)&p_ApiMsg->u.UnloadDll + 3);
ApiMsg = (PDBGKM_APIMSG)((char *)ApiMsg + 128);
v23 = *((_OWORD *)&p_ApiMsg->u.UnloadDll + 4);
p_ApiMsg = (DBGKM_APIMSG *)((char *)p_ApiMsg + 128);
*((_OWORD *)&ApiMsg[-1].u.UnloadDll + 9) = v23;
--v10;
}
while ( v10 );
*(_OWORD *)&ApiMsg->h.u1.s1.DataLength = *(_OWORD *)&p_ApiMsg->h.u1.s1.DataLength;
}
}
return Status;
}计算最后传入DbgkpQueueMessage的flag是0,也就是要wait的需等待事件,与nowait走一些不一样的路,最终会走到这里。
KeWaitForSingleObject(&p_debug_event->ContinueEvent, Executive, 0, 0, 0);这时候这个线程就开死等直到调试器处理完成,然后还会传出ApiMsg供外面的函数分析使用。
有趣的反调试
我们看DbgkForwardException的这里:
if ( (*(_DWORD *)(&KeGetCurrentThread()[1].SwapListEntry + 1) & 4) != 0 )
DebugPort = 0;汇编准确一点:
movrax, gs:610o
movecx, [rax+_ETHREAD.___u21.CrossThreadFlags]
这个是CrossThreadFlags的ThreadHideFromDebugger位,如果该位置位,直接就是DebugPort = 0,啥都发不过去了。
这里给出利用代码:
#include <windows.h>
#include <winternl.h>
#include <stdio.h>
// 未公开的 ThreadInformationClass 常量
#define ThreadHideFromDebugger 0x11
// NtSetInformationThread 函数原型
typedef NTSTATUS (NTAPI *pNtSetInformationThread)(
HANDLE ThreadHandle,
THREADINFOCLASS ThreadInformationClass,
PVOID ThreadInformation,
ULONG ThreadInformationLength
);
// 隐藏当前线程,使调试器无法接收该线程的异常
voidHideCurrentThreadFromDebugger() {
// 1. 动态获取 ntdll!NtSetInformationThread 地址
HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll");
if (!hNtdll) {
printf("[!] 无法加载 ntdll.dll\n");
return;
}
auto NtSetInformationThread = (pNtSetInformationThread)GetProcAddress(hNtdll, "NtSetInformationThread");
if (!NtSetInformationThread) {
printf("[!] 无法获取 NtSetInformationThread 地址\n");
return;
}
// 2. 调用未公开功能,隐藏当前线程
NTSTATUS status = NtSetInformationThread(
GetCurrentThread(), // 当前线程句柄
(THREADINFOCLASS)ThreadHideFromDebugger, // 0x11
nullptr, // 无额外数据
0 // 长度0
);
if (status == 0) {
printf("[+] 当前线程已成功从调试器隐藏\n");
} else {
printf("[!] 隐藏失败,NTSTATUS = 0x%08X\n", status);
}
}
intmain() {
// 执行反调试:隐藏自身线程
HideCurrentThreadFromDebugger();
// 正常业务代码(示例:每隔一秒打印一次)
printf("[*] 程序正常运行,请尝试用调试器附加并触发异常...\n");
while (true) {
Sleep(1000);
// 可以取消下面注释来触发一个异常,验证调试器是否还能捕获
// __try { *(int*)0 = 0; } __except(EXCEPTION_EXECUTE_HANDLER) {}
// 如果线程已隐藏,调试器将收不到异常,程序会自己处理。
}
return 0;
}我们直接使用NtSetInformationThread这个导出函数就可以设置线程teb的某些值。
看雪ID:Qmeimei10086
https://bbs.kanxue.com/user-home-1055387.htm

# 往期推荐
当高频观测不再经过异常路径:Shadow Cave 与常驻式插桩架构
D3CTF 2026 d3llvm.apk 反调试定位与加密 SO的Dump


球分享

球点赞

球在看

点击阅读原文查看更多
