Windows核心分析——核心除錯機制的實現(NtCreateDebugObject、DbgkpPostFakeProcessCreateMessages、DbgkpPostFakeThreadMessages分析)

Ox9A82發表於2016-03-31

本文主要分析核心中與除錯相關的幾個核心函式。

首先是NtCreateDebugObject函式,用於建立一個核心除錯物件,分析程式可知,其實只是一層對ObCreateObject的封裝,並初始化一些結構成員而已。

我後面會寫一些與window物件管理方面的筆記,會分析到物件的建立過程。

來自WRK1.2

NTSTATUS NtCreateDebugObject ( OUT PHANDLE DebugObjectHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes, IN ULONG Flags ) { NTSTATUS Status; HANDLE Handle; KPROCESSOR_MODE PreviousMode; PDEBUG_OBJECT DebugObject; //檢測這個宏所在位置的IRQL級別,相當於一個斷言,確定當前IRQL允許分頁 PAGED_CODE(); //獲取當前操作模式是核心還是使用者 PreviousMode = KeGetPreviousMode(); try { if (PreviousMode != KernelMode) { //驗證是否可讀寫(驗證引數正確性) ProbeForWriteHandle (DebugObjectHandle); } *DebugObjectHandle = NULL; } except (ExSystemExceptionFilter ()) { return GetExceptionCode (); } if (Flags & ~DEBUG_KILL_ON_CLOSE) { return STATUS_INVALID_PARAMETER; } // // 建立除錯物件 // Status = ObCreateObject (PreviousMode, DbgkDebugObjectType, ObjectAttributes, PreviousMode, NULL, sizeof (DEBUG_OBJECT), 0, 0, &DebugObject); if (!NT_SUCCESS (Status)) { return Status; } ExInitializeFastMutex (&DebugObject->Mutex); //初始化除錯核心物件中的除錯事件連結串列 InitializeListHead (&DebugObject->EventList); KeInitializeEvent (&DebugObject->EventsPresent, NotificationEvent, FALSE); if (Flags & DEBUG_KILL_ON_CLOSE) { DebugObject->Flags = DEBUG_OBJECT_KILL_ON_CLOSE; } else { DebugObject->Flags = 0; } // 除錯物件插入當前程式的控制程式碼表 Status = ObInsertObject (DebugObject, NULL, DesiredAccess, 0, NULL, &Handle/*返回一個控制程式碼*/); if (!NT_SUCCESS (Status)) { return Status; } //用異常處理進行安全複製 try { *DebugObjectHandle = Handle; } except(ExSystemExceptionFilter ()) { Status = GetExceptionCode (); } return Status; }

 29號寫的windows除錯機制學習筆記中分析了DebugActiveProcess()函式,這個函式會呼叫到ZwDebugActiveProcess(),而這個函式最後又會執行到NtDebugActiveProcess()

而這個函式里關鍵的呼叫就是DbgkpPostFakeProcessCreateMessages()和DbgkpSetProcessDebugObject()

NTSTATUS
NtDebugActiveProcess (
    IN HANDLE ProcessHandle,
    IN HANDLE DebugObjectHandle
    )
{
    NTSTATUS Status;
    KPROCESSOR_MODE PreviousMode;
    PDEBUG_OBJECT DebugObject;//返回撥試物件的值
    PEPROCESS Process;
    PETHREAD LastThread;
	//檢測這個宏所在位置的IRQL級別,相當於一個斷言,確定當前IRQL允許分頁
    PAGED_CODE ();

    PreviousMode = KeGetPreviousMode();
	//用控制程式碼使用程式核心物件
    Status = ObReferenceObjectByHandle (ProcessHandle,
                                        PROCESS_SET_PORT,
                                        PsProcessType,
                                        PreviousMode,
                                        &Process,
                                        NULL);
    if (!NT_SUCCESS (Status)) 
	{
        return Status;
    }

    // Don't let us debug ourselves or the system process.
    //驗證引數合法性
    if (Process == PsGetCurrentProcess () /*是否要除錯自己*/|| Process == PsInitialSystemProcess/*不能除錯system程式,pid為4*/) 
	{
        ObDereferenceObject (Process);//消除計數
        return STATUS_ACCESS_DENIED;
    }

	//用控制程式碼使用除錯核心物件
    Status = ObReferenceObjectByHandle (DebugObjectHandle,
                                        DEBUG_PROCESS_ASSIGN,
                                        DbgkDebugObjectType,
                                        PreviousMode,
                                        &DebugObject,
                                        NULL);

    if (NT_SUCCESS (Status))
	{
       
        // We will be touching process address space. Block process rundown.    
		//加鎖,防止程式在操作過程中退出
        if (ExAcquireRundownProtection (&Process->RundownProtect)) 
		{

            //這是個關鍵的函式,下面會針對分析
			//作用是傳送偽造的除錯資訊
			//根據字面意思是傳送偽造的程式建立除錯資訊
            Status = DbgkpPostFakeProcessCreateMessages (Process,
                                                         DebugObject,
                                                         &LastThread);


            // Set the debug port. If this fails it will remove any faked messages.
			//上面的是WRK自帶的註釋
            Status = DbgkpSetProcessDebugObject (Process,
                                                 DebugObject,
                                                 Status,
                                                 LastThread);
			//釋放鎖
            ExReleaseRundownProtection (&Process->RundownProtect);
        }
		else 
		{
            Status = STATUS_PROCESS_IS_TERMINATING;
        }

        ObDereferenceObject (DebugObject);
    }
    ObDereferenceObject (Process);

    return Status;
}

 注意DbgkpPostFakeProcessCreateMessages()是用於向偵錯程式發生偽造的除錯資訊的。為什麼要進行偽造除錯資訊呢?因為是為了獲取完整的除錯資訊。

想象這樣一個場景,你附加一個正在執行的程式。如果沒有偽造除錯資訊那麼偵錯程式是無法正常工作的,因為你附加時程式已經執行起來了,之前的除錯資訊你是收不到的,

偵錯程式也就無法正常工作了。偽造除錯資訊就是為了完整的把除錯資訊重新傳送一遍給偵錯程式而誕生的。

我們來看DbgkpPostFakeProcessCreateMessages()這個函式,我們可以看到這個其實也只是一層封裝而已。有作用的是DbgkpPostFakeThreadMessages和DbgkpPostFakeModuleMessages這兩個函式。

NTSTATUS
DbgkpPostFakeProcessCreateMessages (
    IN PEPROCESS Process,
    IN PDEBUG_OBJECT DebugObject,
    IN PETHREAD *pLastThread
    )
{
    NTSTATUS Status;
    KAPC_STATE ApcState;
    PETHREAD Thread;
    PETHREAD LastThread;

    PAGED_CODE ();

	//附加到目標程式上
    KeStackAttachProcess(&Process->Pcb, &ApcState);
	//傳送假的執行緒建立資訊(主要功能在這裡實現)
    Status = DbgkpPostFakeThreadMessages (Process,
                                          DebugObject,
                                          NULL,
                                          &Thread,
                                          &LastThread);

    if (NT_SUCCESS (Status)) 
	{	
		//傳送模組訊息
        Status = DbgkpPostFakeModuleMessages (Process, Thread, DebugObject);
        if (!NT_SUCCESS (Status)) 
		{
            ObDereferenceObject (LastThread);
            LastThread = NULL;
        }
        ObDereferenceObject (Thread);
    } 
	else 
	{
        LastThread = NULL;
    }
	//解除附加
    KeUnstackDetachProcess(&ApcState);

    *pLastThread = LastThread;

    return Status;
}

DbgkpPostFakeThreadMessages函式分析如下

1.傳送程式建立偽造除錯資訊

2.依次傳送每個執行緒的建立偽造除錯資訊

3.把每個資訊放入除錯物件的連結串列中的除錯事件中

這裡的偽造除錯資訊使用的是

DBGKM_APIMSG結構

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;

typedef struct _DBGKM_CREATE_PROCESS {
    ULONG SubSystemKey;
    HANDLE FileHandle;
    PVOID BaseOfImage;
    ULONG DebugInfoFileOffset;
    ULONG DebugInfoSize;
    DBGKM_CREATE_THREAD InitialThread;
} DBGKM_CREATE_PROCESS, *PDBGKM_CREATE_PROCESS;

注意這個結構最後被寫入到除錯事件中

 

NTSTATUS
DbgkpPostFakeThreadMessages (
    IN PEPROCESS Process,
    IN PDEBUG_OBJECT DebugObject,
    IN PETHREAD StartThread,
    OUT PETHREAD *pFirstThread,
    OUT PETHREAD *pLastThread
    )

{
    NTSTATUS Status;
    PETHREAD Thread, FirstThread, LastThread;
    DBGKM_APIMSG ApiMsg;
    BOOLEAN First = TRUE;
    BOOLEAN IsFirstThread;
    PIMAGE_NT_HEADERS NtHeaders;
    ULONG Flags;
    NTSTATUS Status1;
	//驗證IRQL
    PAGED_CODE ();

    LastThread = FirstThread = NULL;

    Status = STATUS_UNSUCCESSFUL;
   //注意,上面傳過來的就是NULL!!!
    if (StartThread != NULL) {
	//StartThread!=NULL說明當前執行緒有ID即當前執行緒不是初始執行緒
        First = FALSE;//不是第一個
        FirstThread = StartThread;
        ObReferenceObject (FirstThread);
    } 
	else 
	{
		//==0說明當前執行緒是初始執行緒。也說明是在建立程式。
        StartThread = PsGetNextProcessThread (Process, NULL);//這裡獲得的就是初始執行緒
        First = TRUE;//是第一個
    }

    for (Thread = StartThread;
         Thread != NULL;
		 //遍歷程式的每一個執行緒
         Thread = PsGetNextProcessThread (Process, Thread)) 
	{
		//設定除錯事件不等待
        Flags = DEBUG_EVENT_NOWAIT;
        if (LastThread != NULL) {
            ObDereferenceObject (LastThread);
        }
		//用來記錄最後一個執行緒
        LastThread = Thread;
        ObReferenceObject (LastThread);
        //鎖住執行緒,防止執行緒終止
        if (ExAcquireRundownProtection (&Thread->RundownProtect)) 
		{
            Flags |= DEBUG_EVENT_RELEASE;
			//判斷獲得的執行緒是否是系統的執行緒
            if (!IS_SYSTEM_THREAD (Thread)) 
			{	//暫停執行緒
                Status1 = PsSuspendThread (Thread, NULL);
                if (NT_SUCCESS (Status1)) 
				{
					//暫停成功,加一個暫停標記
                    Flags |= DEBUG_EVENT_SUSPEND;
                }
            }
        } 
		else 
		{       
			//獲取鎖失敗,加上標記
            Flags |= DEBUG_EVENT_PROTECT_FAILED;
        }
		//構造一個ApiMsg結構(DBGKM_APIMSG型別)
        RtlZeroMemory (&ApiMsg, sizeof (ApiMsg));
		
		//如果申請成功,並且這個執行緒是第一個執行緒
		//說明是程式建立
		//這裡會發生程式建立偽造訊息
        if (First && (Flags&DEBUG_EVENT_PROTECT_FAILED) == 0 &&
            !IS_SYSTEM_THREAD (Thread) && Thread->GrantedAccess != 0) 
		{
            IsFirstThread = TRUE;//說明是第一執行緒建立兼程式建立
        } 
		else 
		{
            IsFirstThread = FALSE;
        }

        if (IsFirstThread)
		{
			//這裡設定了程式建立偽造訊息的結構
            ApiMsg.ApiNumber = DbgKmCreateProcessApi;
            if (Process->SectionObject != NULL) //
			{ 
				//把程式主模組的檔案控制程式碼儲存在偽造訊息的結構中
                ApiMsg.u.CreateProcessInfo.FileHandle  = DbgkpSectionToFileHandle (Process->SectionObject);
            } 
			else 
			{
                ApiMsg.u.CreateProcessInfo.FileHandle = NULL;
            }
			
			//把程式主模組基址儲存在偽造資訊的結構中
            ApiMsg.u.CreateProcessInfo.BaseOfImage = Process->SectionBaseAddress;
			//用異常處理增強穩定性
            try 
			{
				//獲得PE結構的NT頭部
                NtHeaders = RtlImageNtHeader(Process->SectionBaseAddress);
                if (NtHeaders)
					{
                    ApiMsg.u.CreateProcessInfo.InitialThread.StartAddress = NULL; // Filling this in breaks MSDEV!
					//解析NT頭部中的除錯資訊,放入偽造資訊的結構中
                    ApiMsg.u.CreateProcessInfo.DebugInfoFileOffset = NtHeaders->FileHeader.PointerToSymbolTable;
                    ApiMsg.u.CreateProcessInfo.DebugInfoSize       = NtHeaders->FileHeader.NumberOfSymbols;
					}
            } 
			except (EXCEPTION_EXECUTE_HANDLER) 
			{
                ApiMsg.u.CreateProcessInfo.InitialThread.StartAddress = NULL;
                ApiMsg.u.CreateProcessInfo.DebugInfoFileOffset = 0;
                ApiMsg.u.CreateProcessInfo.DebugInfoSize = 0;
            }
        } 
		else 
		{	//不是第一個,說明是執行緒建立,設定一個執行緒建立偽造資訊結構
            ApiMsg.ApiNumber = DbgKmCreateThreadApi;
            ApiMsg.u.CreateThread.StartAddress = Thread->StartAddress;
        }
		
		//把上面構造的訊息包插入到佇列中
        Status = DbgkpQueueMessage (Process,
                                    Thread,
                                    &ApiMsg,
                                    Flags,
                                    DebugObject);
		//錯誤處理
        if (!NT_SUCCESS (Status)) 
		{
            if (Flags&DEBUG_EVENT_SUSPEND) 
			{
                PsResumeThread (Thread, NULL);
            }
            if (Flags&DEBUG_EVENT_RELEASE) 
			{
                ExReleaseRundownProtection (&Thread->RundownProtect);
            }
            if (ApiMsg.ApiNumber == DbgKmCreateProcessApi && ApiMsg.u.CreateProcessInfo.FileHandle != NULL) 
			{
                ObCloseHandle (ApiMsg.u.CreateProcessInfo.FileHandle, KernelMode);
            }
            PsQuitNextProcessThread (Thread);
            break;
        } 
		else if (IsFirstThread) {
            First = FALSE;//已經處理完第一次了
            ObReferenceObject (Thread);
            FirstThread = Thread;
        }
    }


    if (!NT_SUCCESS (Status)) {
        if (FirstThread) {
            ObDereferenceObject (FirstThread);
        }
        if (LastThread != NULL) {
            ObDereferenceObject (LastThread);
        }
    } else {
        if (FirstThread) {
            *pFirstThread = FirstThread;
            *pLastThread = LastThread;
        } else {
            Status = STATUS_UNSUCCESSFUL;
        }
    }
    return Status;
}

這個就是上面說的針對除錯物件的除錯事件連結串列的操作了

NTSTATUS
DbgkpQueueMessage (
    IN PEPROCESS Process,
    IN PETHREAD Thread,
    IN OUT PDBGKM_APIMSG ApiMsg,
    IN ULONG Flags,
    IN PDEBUG_OBJECT TargetDebugObject
    )
{
    PDEBUG_EVENT DebugEvent;//ApiMsg最後會封裝入這個結構內
    DEBUG_EVENT StaticDebugEvent;
    PDEBUG_OBJECT DebugObject;
    NTSTATUS Status;

    PAGED_CODE ();
	//判斷是同步事件還是非同步事件……
    if (Flags&DEBUG_EVENT_NOWAIT) 
	{	
		//非同步事件這樣處理
		//給除錯事件分配空間
        DebugEvent = ExAllocatePoolWithQuotaTag (NonPagedPool|POOL_QUOTA_FAIL_INSTEAD_OF_RAISE,
                                                 sizeof (*DebugEvent),
                                                 'EgbD');
        if (DebugEvent == NULL) 
		{
            return STATUS_INSUFFICIENT_RESOURCES;
        }
		//設定DEBUG_EVENT結構的資訊
        DebugEvent->Flags = Flags|DEBUG_EVENT_INACTIVE;
        ObReferenceObject (Process);
        ObReferenceObject (Thread);
        DebugEvent->BackoutThread = PsGetCurrentThread ();
        DebugObject = TargetDebugObject;
    } 
	else 
	{	
		//同步事件這樣處理
		//同步處理時沒有為結構開闢pool記憶體
        DebugEvent = &StaticDebugEvent;
		//直接給定一個區域性變數,因為在函式內就處理完成了,區域性變數就可以滿足條件
        DebugEvent->Flags = Flags;
		//獲取同步鎖
        ExAcquireFastMutex (&DbgkpProcessDebugPortMutex);

        DebugObject = Process->DebugPort;

        //
        // See if this create message has already been sent.
        //
        if (ApiMsg->ApiNumber == DbgKmCreateThreadApi ||
            ApiMsg->ApiNumber == DbgKmCreateProcessApi) 
			{
				if (Thread->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_SKIP_CREATION_MSG) 
				{
					DebugObject = NULL;
				}
			}

        //
        // See if this exit message is for a thread that never had a create
        //
        if (ApiMsg->ApiNumber == DbgKmExitThreadApi ||
            ApiMsg->ApiNumber == DbgKmExitProcessApi)
			{
            if (Thread->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_SKIP_TERMINATION_MSG) 
			{
                DebugObject = NULL;
            }
        }
    }
	
    KeInitializeEvent (&DebugEvent->ContinueEvent, SynchronizationEvent, FALSE);
	//填充DebugEvent
    DebugEvent->Process = Process;
    DebugEvent->Thread = Thread;
    DebugEvent->ApiMsg = *ApiMsg;
    DebugEvent->ClientId = Thread->Cid;

    if (DebugObject == NULL) {
        Status = STATUS_PORT_NOT_SET;
    } else {

        //
        // We must not use a debug port thats got no handles left.
        //
		//獲取一個除錯物件的鎖
        ExAcquireFastMutex (&DebugObject->Mutex);

        //
        // If the object is delete pending then don't use this object.
        //
        if ((DebugObject->Flags&DEBUG_OBJECT_DELETE_PENDING) == 0) 
		{
			//把除錯事件插入除錯物件中的連結串列裡
            InsertTailList (&DebugObject->EventList, &DebugEvent->EventList);
            //
            // Set the event to say there is an unread event in the object
            //
            if ((Flags&DEBUG_EVENT_NOWAIT) == 0) 
			{	
				//如果是同步的,通知偵錯程式去處理了
                KeSetEvent (&DebugObject->EventsPresent, 0, FALSE);
            }
            Status = STATUS_SUCCESS;
        } else 
		{
            Status = STATUS_DEBUGGER_INACTIVE;
        }

        ExReleaseFastMutex (&DebugObject->Mutex);
    }


    if ((Flags&DEBUG_EVENT_NOWAIT) == 0)
	{
        ExReleaseFastMutex (&DbgkpProcessDebugPortMutex);

        if (NT_SUCCESS (Status))
		{
			//等待偵錯程式返回資訊
            KeWaitForSingleObject (&DebugEvent->ContinueEvent,
                                   Executive,
                                   KernelMode,
                                   FALSE,
                                   NULL);

            Status = DebugEvent->Status;
            *ApiMsg = DebugEvent->ApiMsg;
        }
    } 
	else 
	{
        if (!NT_SUCCESS (Status)) {
            ObDereferenceObject (Process);
            ObDereferenceObject (Thread);
            ExFreePool (DebugEvent);
        }
    }

    return Status;
}

 

相關文章