Windows核心分析——核心除錯機制的實現(NtCreateDebugObject、DbgkpPostFakeProcessCreateMessages、DbgkpPostFakeThreadMessages分析)
本文主要分析核心中與除錯相關的幾個核心函式。
首先是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; }
相關文章
- epoll核心實現分析2017-02-19
- JVMTI Attach機制與核心原始碼分析2018-05-29JVM原始碼
- Kafka核心中的分散式機制實現2019-10-06Kafka分散式
- Windows windbg kernel debug 雙機核心除錯 - USB3.0 除錯 USB除錯 除錯線2021-01-02Windows除錯
- 使用GDB與QEMU除錯核心時的問題分析(轉)2007-08-16除錯
- Linux程式排程核心實現分析2017-01-08Linux
- Linux核心分析--系統呼叫實現程式碼分析(轉)2007-08-17Linux
- 核心頁表除錯2024-03-03除錯
- Linux 核心通知鏈機制的原理及實現2016-10-22Linux
- Dubbo的微核心機制2018-09-11
- PHP 核心分析:Zend 虛擬機器2017-03-01PHP虛擬機
- Binder機制分析(3)—— 實現自己的Service2016-12-27
- Android 的 Handler 機制實現原理分析2016-08-10Android
- 核心必須懂(六): 使用kgdb除錯核心2019-04-17除錯
- Laravel 中介軟體處理的核心機制 Pipeline 關鍵分析2019-10-11Laravel
- 驅動除錯——挫敗 QQ.EXE 的核心模式保護機制(part I)2018-05-29除錯模式
- toa 核心模組分析2022-03-14
- Android核心分析2014-07-13Android
- Java核心反射機制2021-07-08Java反射
- 核心同步機制 RCU2013-11-24
- 分析Windows的死亡藍屏(BSOD)機制2016-06-06Windows
- windows核心原理分析之DPC函式的執行(1)2015-06-04Windows函式
- linux核心檔案IO的系統呼叫實現分析(open)2016-03-30Linux
- 核心引數導致的備庫當機分析2015-11-19
- JAVA的反射機制==>用反射分析類的實現2017-05-15Java反射
- btcpool礦池原始碼分析(2)-核心機制總結及優化思考2018-05-20TCP原始碼優化
- YARN 核心原始碼分析2022-09-21Yarn原始碼
- mmap核心原始碼分析2017-02-27原始碼
- Linux核心分析。32016-07-30Linux
- Linux核心分析。42016-07-30Linux
- Linux核心分析。52016-07-30Linux
- LINUX核心分析。62016-07-30Linux
- LINUX核心分析。72016-07-30Linux
- LINUX核心分析。82016-07-30Linux
- php核心分析(五)-zval2018-01-07PHP
- ETCD核心機制解析2020-11-04
- 概述javascript部分核心機制2018-06-10JavaScript
- 概述nodejs核心機制2018-06-11NodeJS