內核用戶模式調試支持(Dbgk)


簡介

將詳細分析Windows調試的內核模式接口。希望讀者對C和通用NT內核體系結構和語義有一些基本的了解。此外,這並不是介紹什么是調試或如何編寫調試器。它可以作為經驗豐富的調試器編寫人員或好奇的安全專家的參考。

內核用戶模式調試支持

最后一塊拼圖以內核模式存在,並提供了我們到目前為止看到的事件和結構,以便調試可以工作。Dbgk不依賴KD,它是一個完全不同的組件,因為Windows XP提供了自己的對象和系統調用來管理它。以前的Windows版本沒有這樣的對象,而是依賴於靜態數據結構,該結構將在內存中進行分析,然后用於通過Windows的本地過程調用(LPC)機制發送的各種通知。

這些系統調用的可用性和調試對象的存在的一個好處是內核模式驅動程序也可以參與用戶模式調試。雖然這可能不是這個新設計的目標之一,但它是一些人應該感興趣的特性。雖然沒有導出實際的Nt*調用,但是它們仍然可以被知道其系統調用ID的驅動程序訪問。即使這個數字在每個OS版本之間發生變化,在驅動程序中保留一個表還是相對容易的。通過將TDI接口添加到這樣的驅動程序中,可以開發一個高速遠程調試器驅動程序,它將完全沒有用戶模式組件,從而允許遠程調試機器上的每個進程。

我們要做的第一件事是查看實現用戶模式調試的實際對象,DEBUG_OBJECT:

//
// Debug Object
//
typedef struct _DEBUG_OBJECT
{
    KEVENT EventsPresent;
    FAST_MUTEX Mutex;
    LIST_ENTRY EventList;
    union
    {
        ULONG Flags;
        struct
        {
            UCHAR DebuggerInactive:1;
            UCHAR KillProcessOnExit:1;
        };
    };
} DEBUG_OBJECT, *PDEBUG_OBJECT;

如您所見,對象本身是用戶模式使用WaitForDebugEvent的實際事件、實際調試事件列表、鎖和與此調試會話相關的某些標志(例如調試器是否已連接,以及在斷開連接時是否應終止進程)的輕量級包裝器。因此,我們更感興趣的結構是調試事件結構:

//
// Debug Event
//
typedef struct _DEBUG_EVENT
{
    LIST_ENTRY EventList;
    KEVENT ContinueEvent;
    CLIENT_ID ClientId;
    PEPROCESS Process;
    PETHREAD Thread;
    NTSTATUS Status;
    ULONG Flags;
    PETHREAD BackoutThread;
    DBGKM_MSG ApiMsg;
} DEBUG_EVENT, *PDEBUG_EVENT;

這個結構包含與調試事件相關的所有數據。由於許多事件可以在調用者在用戶模式下執行WaitForDebugEvent之前排隊,因此調試事件必須與調試對象鏈接在一起,這就是事件列表的用途。
其他一些成員持有發出通知的事件的PID和TID,以及在內核模式下指向相應進程和線程對象的指針。此結構中的事件用於在對調試器消息的響應可用時在內部通知內核。此響應通常以來自Win32的continuedbugevent調用的形式出現,該調用將發出事件信號。
調試事件中包含的最終結構是正在發送的實際API消息,其中包含用戶模式將看到的數據,並且是DBGUI_WAIT_STATE_CHANGE結構的內核模式表示。沒錯,內核還有另一種表示調試事件的方式,它也需要稍后進行轉換,以便本機調試接口能夠理解它。
從下面的結構中可以看出,好的一面是大多數字段都保持不變,內核內部仍然使用DBGKM結構,DbgUi結構中已經顯示了DBGKM結構。但是,內核不使用DBG_STATE常量,而是使用另一種稱為API消息編號的常量,如下所示:

//
// Debug Message API Number
//
typedef enum _DBGKM_APINUMBER
{
    DbgKmExceptionApi = 0,
    DbgKmCreateThreadApi = 1,
    DbgKmCreateProcessApi = 2,
    DbgKmExitThreadApi = 3,
    DbgKmExitProcessApi = 4,
    DbgKmLoadDllApi = 5,
    DbgKmUnloadDllApi = 6,
    DbgKmErrorReportApi = 7,
    DbgKmMaxApiNumber = 8,
} DBGKM_APINUMBER;

這些API編號是自解釋的,仍然保留以與舊的LPC機制兼容。內核將把它們轉換為DbgUi所期望的實際調試狀態。現在讓我們看看調試消息結構本身,它與以前版本的windows中使用的LPC機制上的相同消息相匹配:

//
// LPC Debug Message
//
typedef struct _DBGKM_MSG
{
    PORT_MESSAGE h;
    DBGKM_APINUMBER ApiNumber;
    ULONG ReturnedStatus;
    union
    {
        DBGKM_EXCEPTION Exception;
        DBGKM_CREATE_THREAD CreateThread;
        DBGKM_CREATE_PROCESS CreateProcess;
        DBGKM_EXIT_THREAD ExitThread;
        DBGKM_EXIT_PROCESS ExitProcess;
        DBGKM_LOAD_DLL LoadDll;
        DBGKM_UNLOAD_DLL UnloadDll;
    };
} DBGKM_MSG, *PDBGKM_MSG;

當然,出於我們的目的,我們可以忽略結構中的PORT_MESSAGE部分,因為我們將不會關注DbgSs(處理LPC消息的組件和在Windows XP之前包裝DbgUi的層)。
現在我們知道了結構的含義,我們可以開始研究一些Native API函數(系統調用),這些函數將調試對象包裝成另一個對象,如事件或信號量。
第一個系統調用是必需的,我們在第2部分的DbgUi中看到的是NtCreateDebugObject,它將返回一個調試對象的句柄,可以在稍后附加和等待。實現相當簡單:

NTSTATUS
NTAPI
NtCreateDebugObject(OUT PHANDLE DebugHandle,
                    IN ACCESS_MASK DesiredAccess,
                    IN POBJECT_ATTRIBUTES ObjectAttributes,
                    IN BOOLEAN KillProcessOnExit)
{
    KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
    PDEBUG_OBJECT DebugObject;
    HANDLE hDebug;
    NTSTATUS Status = STATUS_SUCCESS;
    PAGED_CODE();

    /* Check if we were called from user mode*/
    if (PreviousMode != KernelMode)
    {
        /* Enter SEH for probing */
        _SEH_TRY
        {
            /* Probe the handle */
            ProbeForWriteHandle(DebugHandle);
        }
        _SEH_HANDLE
        {
            /* Get exception error */
            Status = _SEH_GetExceptionCode();
        } _SEH_END;
        if (!NT_SUCCESS(Status)) return Status;
    }

    /* Create the Object */
    Status = ObCreateObject(PreviousMode,
                            DbgkDebugObjectType,
                            ObjectAttributes,
                            PreviousMode,
                            NULL,
                            sizeof(DEBUG_OBJECT),
                            0,
                            0,
                            (PVOID*)&DebugObject);
    if (NT_SUCCESS(Status))
    {
        /* Initialize the Debug Object's Fast Mutex */
        ExInitializeFastMutex(&DebugObject->Mutex);

        /* Initialize the State Event List */
        InitializeListHead(&DebugObject->EventList);

        /* Initialize the Debug Object's Wait Event */
        KeInitializeEvent(&DebugObject->EventsPresent,
                          NotificationEvent,
                          FALSE);

        /* Set the Flags */
        DebugObject->KillProcessOnExit = KillProcessOnExit;

        /* Insert it */
        Status = ObInsertObject((PVOID)DebugObject,
                                 NULL,
                                 DesiredAccess,
                                 0,
                                 NULL,
                                 &hDebug);
        if (NT_SUCCESS(Status))
        {
            _SEH_TRY
            {
                *DebugHandle = hDebug;
            }
            _SEH_HANDLE
            {
                Status = _SEH_GetExceptionCode();
            } _SEH_END;
        }
    }

    /* Return Status */
    DBGKTRACE(DBGK_OBJECT_DEBUG, "Handle: %p DebugObject: %p\n",
              hDebug, DebugObject);
    return Status;

直接從用戶模式使用這個API的一個有趣之處是,它允許命名調試對象,以便可以將其插入到對象目錄名稱空間中。不幸的是,沒有退出NtOpendoGoBbDebug調用,因此該名稱不能用於查找,但這可以在內部存儲,或者可以將對象插入到樹中,例如可以稍后枚舉的\Debug GObjObjts。
再加上DbgUi層可以被跳過,這意味着調試對象句柄不需要存儲在TEB中,這使調試器編寫器能夠編寫一個調試器,該調試器同時調試多個進程,在調試對象之間無縫切換,並通過使用WaitForMultipleObjects,它可以從多個進程接收調試事件。
處理來自所有這些進程的消息和句柄可能有點困難,但是使用類似於kernel32如何在TEB中存儲每個線程數據的模型,可以對其進行建模,以另外存儲每個進程的數據。最終的結果將是一個強大的調試器一個其他人還不支持的創新。
現在讓我們看看進程的實際附件,它是由NtDebugActiveProcess完成的:

NTSTATUS
NTAPI
NtDebugActiveProcess(IN HANDLE ProcessHandle,
                     IN HANDLE DebugHandle)
{
    PEPROCESS Process;
    PDEBUG_OBJECT DebugObject;
    KPROCESSOR_MODE PreviousMode = KeGetPreviousMode();
    PETHREAD LastThread;
    NTSTATUS Status;
    PAGED_CODE();
    DBGKTRACE(DBGK_PROCESS_DEBUG, "Process: %p Handle: %p\n",
              ProcessHandle, DebugHandle);

    /* Reference the process */
    Status = ObReferenceObjectByHandle(ProcessHandle,
                                       PROCESS_SUSPEND_RESUME,
                                       PsProcessType,
                                       PreviousMode,
                                       (PVOID*)&Process,
                                       NULL);
    if (!NT_SUCCESS(Status)) return Status;

    /* Don't allow debugging the initial system process */
    if (Process == PsInitialSystemProcess) return STATUS_ACCESS_DENIED;

    /* Reference the debug object */
    Status = ObReferenceObjectByHandle(DebugHandle,
                                       DEBUG_OBJECT_ADD_REMOVE_PROCESS,
                                       DbgkDebugObjectType,
                                       PreviousMode,
                                       (PVOID*)&DebugObject,
                                       NULL);
    if (!NT_SUCCESS(Status))
    {
        /* Dereference the process and exit */
        ObDereferenceObject(Process);
        return Status;
    }

    /* Acquire process rundown protection */
    if (!ExAcquireRundownProtection(&Process->RundownProtect))
    {
        /* Dereference the process and debug object and exit */
        ObDereferenceObject(Process);
        ObDereferenceObject(DebugObject);
        return STATUS_PROCESS_IS_TERMINATING;
    }

    /* Send fake create messages for debuggers to have a consistent state */
    Status = DbgkpPostFakeProcessCreateMessages(Process,
                                                DebugObject,
                                                &LastThread);
    Status = DbgkpSetProcessDebugObject(Process,
                                        DebugObject,
                                        Status,
                                        LastThread);

    /* Release rundown protection */
    ExReleaseRundownProtection(&Process->RundownProtect);

    /* Dereference the process and debug object and return status */
    ObDereferenceObject(Process);
    ObDereferenceObject(DebugObject);
    return Status;
}

這個API也很簡單,並且依賴於更大的內部例程來執行大部分工作。首先,附加的一個問題是進程可能已經創建了多個新線程,以及加載了各種dll。系統無法預測哪個進程將被調試,因此它不會在任何地方將這些調試事件排隊,相反,Dbgk模塊必須掃描每個線程和模塊,並向調試器發送適當的“ake”事件消息。例如,當附加到進程時,這將生成大量的DLL加載事件,以便調試器能夠知道發生了什么。
不需要進入Dbgkp調用的內部,DLL加載消息的發送方式是通過PEB->Ldr循環PEB中包含的加載程序列表。有一個硬編碼最大的500個DLL,因此列表不會無限期地循環。但是,使用名為MmGetFileNameForAddress的內部API,它將查找DLL基址的VAD,並使用它獲取與其關聯的SECTION_OBJECT,而不是使用相應LDR_DATA_TABLE_ENTRY結構中包含的DLL名稱。內存管理器可以從這個SECTION_OBJECT中找到FILEOBJECT,然后使用ObQueryNameString查詢DLL的全名,該全名可用於打開用戶模式將接收的句柄。
請注意,雖然加載DLL結構的NamePointer參數很容易被填充,但它並沒有被填充。為了循環新創建的線程,使用了助手PsGetNextProcessThread API,它將循環每個線程。對於第一個線程,這將生成一個Create Process debug事件,而每個后續線程將生成Create thread消息。對於進程,事件數據是從SectionBaseAddress指針中檢索的,該指針具有基本圖像指針。對於線程,返回的唯一數據是已保存在ETHREAD中的起始地址。
最后,DbgkpSetProcessDebugObject完成了將對象與進程關聯的復雜工作。首先,存在一種可能性,即在DbgpPostFakeThreadMessages中解析初始列表之后,甚至創建新的線程。因此,這個例程將實際獲取調試端口互斥量並再次調用DbgpPostFakeThreadMessages,以捕獲任何丟失的線程。從邏輯上講,這可能會導致同一消息被發送兩次,但ETHREAD標志之一起作用:CreateThreadReported。DbgpPostFakeThreadMessages將在發送消息之前檢查此標志,從而避免重復(EPROCESS也是如此)。
DbgkpSetProcessDebugObject的第二部分將解析已經與該對象關聯的任何調試事件。這意味着我們剛發的那些假消息。這將意味着獲取每個線程的運行保護,以及檢查各種爭用條件或可能已被拾取的未完全插入的線程。最后,修改PEB以啟用BeingDebugged標志。例程完成后,調試對象將與目標完全關聯。
現在讓我們看看實現了類似的例程NtRemoveProcessDebug,它允許從活動調試的進程分離。

NTSTATUS
NTAPI
NtRemoveProcessDebug(IN HANDLE ProcessHandle,
                     IN HANDLE DebugHandle)
{
    PEPROCESS Process;
    PDEBUG_OBJECT DebugObject;
    KPROCESSOR_MODE PreviousMode = KeGetPreviousMode();
    NTSTATUS Status;
    PAGED_CODE();
    DBGKTRACE(DBGK_PROCESS_DEBUG, "Process: %p Handle: %p\n",
              ProcessHandle, DebugHandle);

    /* Reference the process */
    Status = ObReferenceObjectByHandle(ProcessHandle,
                                       PROCESS_SUSPEND_RESUME,
                                       PsProcessType,
                                       PreviousMode,
                                       (PVOID*)&Process,
                                       NULL);
    if (!NT_SUCCESS(Status)) return Status;

    /* Reference the debug object */
    Status = ObReferenceObjectByHandle(DebugHandle,
                                       DEBUG_OBJECT_ADD_REMOVE_PROCESS,
                                       DbgkDebugObjectType,
                                       PreviousMode,
                                       (PVOID*)&DebugObject,
                                       NULL);
    if (!NT_SUCCESS(Status))
    {
        /* Dereference the process and exit */
        ObDereferenceObject(Process);
        return Status;
    }

    /* Remove the debug object */
    Status = DbgkClearProcessDebugObject(Process, DebugObject);

    /* Dereference the process and debug object and return status */
    ObDereferenceObject(Process);
    ObDereferenceObject(DebugObject);
    return Status;
}

與大多數富NT對象一樣,Dbgk提供了調試對象的接口,並允許查詢或修改其某些設置。目前,只實現了set例程,並且它支持一個標志,即分離是否會終止進程。這是通過NTSETATIONDebug的系統調用來支持相應的Win32 API(Debug GestPosikIyOnEnter):

NTSTATUS
NTAPI
NtSetInformationDebugObject(IN HANDLE DebugHandle,
                            IN DEBUGOBJECTINFOCLASS DebugObjectInformationClass,
                            IN PVOID DebugInformation,
                            IN ULONG DebugInformationLength,
                            OUT PULONG ReturnLength OPTIONAL)
{
    PDEBUG_OBJECT DebugObject;
    KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
    NTSTATUS Status = STATUS_SUCCESS;
    PDEBUG_OBJECT_KILL_PROCESS_ON_EXIT_INFORMATION DebugInfo = DebugInformation;
    PAGED_CODE();

    /* Check buffers and parameters */
    Status = DefaultSetInfoBufferCheck(DebugObjectInformationClass,
                                       DbgkpDebugObjectInfoClass,
                                       sizeof(DbgkpDebugObjectInfoClass) /
                                       sizeof(DbgkpDebugObjectInfoClass[0]),
                                       DebugInformation,
                                       DebugInformationLength,
                                       PreviousMode);
    /* Check if the caller wanted the return length */
    if (ReturnLength)
    {
        /* Enter SEH for probe */
        _SEH_TRY
        {
            /* Return required length to user-mode */
            ProbeForWriteUlong(ReturnLength);
            *ReturnLength = sizeof(*DebugInfo);
        }
        _SEH_EXCEPT(_SEH_ExSystemExceptionFilter)
        {
            /* Get SEH Exception code */
            Status = _SEH_GetExceptionCode();
        }
        _SEH_END;
    }
    if (!NT_SUCCESS(Status)) return Status;

    /* Open the Object */
    Status = ObReferenceObjectByHandle(DebugHandle,
                                       DEBUG_OBJECT_WAIT_STATE_CHANGE,
                                       DbgkDebugObjectType,
                                       PreviousMode,
                                       (PVOID*)&DebugObject,
                                       NULL);
    if (NT_SUCCESS(Status))
    {
        /* Acquire the object */
        ExAcquireFastMutex(&DebugObject->Mutex);

        /* Set the proper flag */
        if (DebugInfo->KillProcessOnExit)
        {
            /* Enable killing the process */
            DebugObject->KillProcessOnExit = TRUE;
        }
        else
        {
            /* Disable */
            DebugObject->KillProcessOnExit = FALSE;
        }

        /* Release the mutex */
        ExReleaseFastMutex(&DebugObject->Mutex);

        /* Release the Object */
        ObDereferenceObject(DebugObject);
    }

    /* Return Status */
    return Status;
}

最后,debug對象提供的最后一個功能是wait-and-continue調用,它實現了雙向通道,通過該通道,調試器可以接收事件、修改目標狀態或自己的內部數據,然后恢復執行。由於涉及到固有的同步問題,這些調用更為復雜。首先,讓我們探索一下NtWaitForDebugEvent:

NTSTATUS
NTAPI
NtWaitForDebugEvent(IN HANDLE DebugHandle,
                    IN BOOLEAN Alertable,
                    IN PLARGE_INTEGER Timeout OPTIONAL,
                    OUT PDBGUI_WAIT_STATE_CHANGE StateChange)
{
    KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
    LARGE_INTEGER SafeTimeOut;
    PEPROCESS Process;
    LARGE_INTEGER StartTime;
    PETHREAD Thread;
    BOOLEAN GotEvent;
    LARGE_INTEGER NewTime;
    PDEBUG_OBJECT DebugObject;
    DBGUI_WAIT_STATE_CHANGE WaitStateChange;
    NTSTATUS Status = STATUS_SUCCESS;
    PDEBUG_EVENT DebugEvent, DebugEvent2;
    PLIST_ENTRY ListHead, NextEntry, NextEntry2;
    PAGED_CODE();
    DBGKTRACE(DBGK_OBJECT_DEBUG, "Handle: %p\n", DebugHandle);

    /* Clear the initial wait state change structure */
    RtlZeroMemory(&WaitStateChange, sizeof(WaitStateChange));

    /* Protect probe in SEH */
    _SEH_TRY
    {
        /* Check if we came with a timeout */
        if (Timeout)
        {
            /* Check if the call was from user mode */
            if (PreviousMode != KernelMode)
            {
                /* Probe it */
                ProbeForReadLargeInteger(Timeout);
            }

            /* Make a local copy */
            SafeTimeOut = *Timeout;
            Timeout = &SafeTimeOut;

            /* Query the current time */
            KeQuerySystemTime(&StartTime);
        }

        /* Check if the call was from user mode */
        if (PreviousMode != KernelMode)
        {
            /* Probe the state change structure */
            ProbeForWrite(StateChange, sizeof(*StateChange), sizeof(ULONG));
        }
    }
    _SEH_HANDLE
    {
        /* Get the exception code */
        Status = _SEH_GetExceptionCode();
    }
    _SEH_END;
    if (!NT_SUCCESS(Status)) return Status;

    /* Get the debug object */
    Status = ObReferenceObjectByHandle(DebugHandle,
                                       DEBUG_OBJECT_WAIT_STATE_CHANGE,
                                       DbgkDebugObjectType,
                                       PreviousMode,
                                       (PVOID*)&DebugObject,
                                       NULL);
    if (!NT_SUCCESS(Status)) return Status;

    /* Clear process and thread */
    Process = NULL;
    Thread = NULL;

    /* Start wait loop */
    while (TRUE)
    {
        /* Wait on the debug object given to us */
        Status = KeWaitForSingleObject(DebugObject,
                                       Executive,
                                       PreviousMode,
                                       Alertable,
                                       Timeout);
        if (!NT_SUCCESS(Status) ||
            (Status == STATUS_TIMEOUT) ||
            (Status == STATUS_ALERTED) ||
            (Status == STATUS_USER_APC))
        {
            /* Break out the wait */
            break;
        }

        /* Lock the object */
        GotEvent = FALSE;
        ExAcquireFastMutex(&DebugObject->Mutex);

        /* Check if a debugger is connected */
        if (DebugObject->DebuggerInactive)
        {
            /* Not connected */
            Status = STATUS_DEBUGGER_INACTIVE;
        }
        else
        {
            /* Loop the events */
            ListHead = &DebugObject->EventList;
            NextEntry =  ListHead->Flink;
            while (ListHead != NextEntry)
            {
                /* Get the debug event */
                DebugEvent = CONTAINING_RECORD(NextEntry,
                                               DEBUG_EVENT,
                                               EventList);
                DBGKTRACE(DBGK_PROCESS_DEBUG, "DebugEvent: %p Flags: %lx\n",
                          DebugEvent, DebugEvent->Flags);

                /* Check flags */
                if (!(DebugEvent->Flags &
                     (DEBUG_EVENT_FLAGS_USED | DEBUG_EVENT_FLAGS_INACTIVE)))
                {
                    /* We got an event */
                    GotEvent = TRUE;

                    /* Loop the list internally */
                    NextEntry2 = DebugObject->EventList.Flink;
                    while (NextEntry2 != NextEntry)
                    {
                        /* Get the debug event */
                        DebugEvent2 = CONTAINING_RECORD(NextEntry2,
                                                        DEBUG_EVENT,
                                                        EventList);

                        /* Try to match process IDs */
                        if (DebugEvent2->ClientId.UniqueProcess ==
                            DebugEvent->ClientId.UniqueProcess)
                        {
                            /* Found it, break out */
                            DebugEvent->Flags |= DEBUG_EVENT_FLAGS_USED;
                            DebugEvent->BackoutThread = NULL;
                            GotEvent = FALSE;
                            break;
                        }

                        /* Move to the next entry */
                        NextEntry2 = NextEntry2->Flink;
                    }

                    /* Check if we still have a valid event */
                    if (GotEvent) break;
                }

                /* Move to the next entry */
                NextEntry = NextEntry->Flink;
            }

            /* Check if we have an event */
            if (GotEvent)
            {
                /* Save and reference the process and thread */
                Process = DebugEvent->Process;
                Thread = DebugEvent->Thread;
                ObReferenceObject(Process);
                ObReferenceObject(Thread);

                /* Convert to user-mode structure */
                DbgkpConvertKernelToUserStateChange(&WaitStateChange,
                                                    DebugEvent);

                /* Set flag */
                DebugEvent->Flags |= DEBUG_EVENT_FLAGS_INACTIVE;
            }
            else
            {
                /* Unsignal the event */
                KeClearEvent(&DebugObject->EventsPresent);
            }

            /* Set success */
            Status = STATUS_SUCCESS;
        }

        /* Release the mutex */
        ExReleaseFastMutex(&DebugObject->Mutex);
        if (!NT_SUCCESS(Status)) break;

        /* Check if we got an event */
        if (!GotEvent)
        {
            /* Check if we can wait again */
            if (SafeTimeOut.QuadPart < 0)
            {
                /* Query the new time */
                KeQuerySystemTime(&NewTime);

                /* Substract times */
                SafeTimeOut.QuadPart += (NewTime.QuadPart - StartTime.QuadPart);
                StartTime = NewTime;

                /* Check if we've timed out */
                if (SafeTimeOut.QuadPart > 0)
                {
                    /* We have, break out of the loop */
                    Status = STATUS_TIMEOUT;
                    break;
                }
            }
        }
        else
        {
            /* Open the handles and dereference the objects */
            DbgkpOpenHandles(&WaitStateChange, Process, Thread);
            ObDereferenceObject(Process);
            ObDereferenceObject(Thread);
            break;
        }
    }

    /* We're done, dereference the object */
    ObDereferenceObject(DebugObject);

    /* Protect write with SEH */
    _SEH_TRY
    {
        /* Return our wait state change structure */
        RtlCopyMemory(StateChange,
                      &WaitStateChange,
                      sizeof(DBGUI_WAIT_STATE_CHANGE));
    }
    _SEH_EXCEPT(_SEH_ExSystemExceptionFilter)
    {
        /* Get SEH Exception code */
        Status = _SEH_GetExceptionCode();
    }
    _SEH_END;

    /* Return status */
    return Status;
}

發生的第一件事是對調試對象進行等待,或者更具體地說,對EventsPresent事件進行等待。當這個等待得到滿足時,對象被鎖定,API首先確保它在獲取鎖之前沒有變為非活動狀態。確認后,將分析當前調試事件,並且系統調用將確保調試事件尚未使用(處理)並且未處於非活動狀態。如果這些標志簽出,則再次分析列表,以確保同一進程沒有任何其他事件。如果找到任何事件,則此事件標記為不活動,並且不發送任何內容。如果多個事件用於同一個進程,則這似乎會阻止它們被發送。獲得調試事件后,將引用進程和線程,並將結構轉換為DbgUi等待狀態更改結構,並將事件標記為已使用。釋放調試對象鎖后,在找到調試事件時,在成功的情況下調用DbgkOpenHandles,這將打開用戶模式在DbgUi結構中期望的正確句柄,之后將刪除對進程和線程的額外引用。等待完成后,DbgUi結構被復制回調用方。最后一個API NtDebugContinue允許從調試事件繼續,其實現用於刪除發送的調試事件並喚醒目標。它的實現方式如下:

NTSTATUS
NTAPI
NtDebugContinue(IN HANDLE DebugHandle,
                IN PCLIENT_ID AppClientId,
                IN NTSTATUS ContinueStatus)
{
    KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
    PDEBUG_OBJECT DebugObject;
    NTSTATUS Status = STATUS_SUCCESS;
    PDEBUG_EVENT DebugEvent = NULL, DebugEventToWake = NULL;
    PLIST_ENTRY ListHead, NextEntry;
    BOOLEAN NeedsWake = FALSE;
    CLIENT_ID ClientId;
    PAGED_CODE();
    DBGKTRACE(DBGK_OBJECT_DEBUG, "Handle: %p Status: %p\n",
              DebugHandle, ContinueStatus);

    /* Check if we were called from user mode*/
    if (PreviousMode != KernelMode)
    {
        /* Enter SEH for probing */
        _SEH_TRY
        {
            /* Probe the handle */
            ProbeForRead(AppClientId, sizeof(CLIENT_ID), sizeof(ULONG));
            ClientId = *AppClientId;
            AppClientId = &ClientId;
        }
        _SEH_HANDLE
        {
            /* Get exception error */
            Status = _SEH_GetExceptionCode();
        } _SEH_END;
        if (!NT_SUCCESS(Status)) return Status;
    }

    /* Make sure that the status is valid */
    if ((ContinueStatus != DBG_CONTINUE) &&
        (ContinueStatus != DBG_EXCEPTION_HANDLED) &&
        (ContinueStatus != DBG_EXCEPTION_NOT_HANDLED) &&
        (ContinueStatus != DBG_TERMINATE_THREAD) &&
        (ContinueStatus != DBG_TERMINATE_PROCESS))
    {
        /* Invalid status */
        Status = STATUS_INVALID_PARAMETER;
    }
    else
    {
        /* Get the debug object */
        Status = ObReferenceObjectByHandle(DebugHandle,
                                           DEBUG_OBJECT_WAIT_STATE_CHANGE,
                                           DbgkDebugObjectType,
                                           PreviousMode,
                                           (PVOID*)&DebugObject,
                                           NULL);
        if (NT_SUCCESS(Status))
        {
            /* Acquire the mutex */
            ExAcquireFastMutex(&DebugObject->Mutex);

            /* Loop the state list */
            ListHead = &DebugObject->EventList;
            NextEntry = ListHead->Flink;
            while (ListHead != NextEntry)
            {
                /* Get the current debug event */
                DebugEvent = CONTAINING_RECORD(NextEntry,
                                               DEBUG_EVENT,
                                               EventList);

                /* Compare process ID */
                if (DebugEvent->ClientId.UniqueProcess ==
                    AppClientId->UniqueProcess)
                {
                    /* Check if we already found a match */
                    if (NeedsWake)
                    {
                        /* Wake it up and break out */
                        DebugEvent->Flags &= ~DEBUG_EVENT_FLAGS_USED;
                        KeSetEvent(&DebugEvent->ContinueEvent,
                                   IO_NO_INCREMENT,
                                   FALSE);
                        break;
                    }

                    /* Compare thread ID and flag */
                    if ((DebugEvent->ClientId.UniqueThread ==
                        AppClientId->UniqueThread) &&
                        (DebugEvent->Flags & DEBUG_EVENT_FLAGS_INACTIVE))
                    {
                        /* Remove the event from the list */
                        RemoveEntryList(NextEntry);

                        /* Remember who to wake */
                        NeedsWake = TRUE;
                        DebugEventToWake = DebugEvent;
                    }
                }

                /* Go to the next entry */
                NextEntry = NextEntry->Flink;
            }

            /* Release the mutex */
            ExReleaseFastMutex(&DebugObject->Mutex);

            /* Dereference the object */
            ObDereferenceObject(DebugObject);

            /* Check if need a wait */
            if (NeedsWake)
            {
                /* Set the continue status */
                DebugEvent->ApiMsg.ReturnedStatus = Status;
                DebugEvent->Status = STATUS_SUCCESS;

                /* Wake the target */
                DbgkpWakeTarget(DebugEvent);
            }
            else
            {
                /* Fail */
                Status = STATUS_INVALID_PARAMETER;
            }
        }
    }

    /* Return status */
    return Status;
}

首先,它負責將使用的延續狀態驗證為有限數量的已識別狀態代碼。然后,循環每個調試事件。如果進程和線程id匹配,那么調試事件將從列表中刪除,並且例程將記住找到了事件。如果找到同一進程的任何其他事件,則取消活動標志。回想一下,這個標志是由wait例程添加的,用於禁止同一進程同時發送多個事件。一旦所有的調試事件都被解析,那么目標就會被喚醒,這基本上意味着如果消息是一個假的線程創建消息,則恢復線程,釋放它的運行保護,然后釋放調試事件或通知等待它的人(取決於它是如何創建的)。由於這一節主要討論內核模式,讓我們看看我們從這一節中學到了什么:

  • Dbgk是內核中處理調試功能的所有支持代碼的組件。
  • 該實現通過一個名為DEBUG_Object的NT對象公開,並提供各種系統調用來訪問它。
  • 可以為用戶模式應用程序編寫內核模式調試器。
  • 只要跳過DbgUi層並手動使用系統調用重新實現,就可以編寫一個可以同時調試多個應用程序的調試器。
  • 內核使用自己版本的wait state change結構,封裝在DEBUG_EVENT結構中。
  • 內核仍然支持基於LPC的DbgSs調試。
  • 內核打開事件結構中存在的所有句柄,用戶模式負責關閉它們。
  • 編寫內核時,一次只發送同一進程的一個事件。
  • 內核需要解析PEB加載程序數據以獲得加載的dll列表,並且有500個循環迭代的硬編碼限制。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM