Windows內核開發-1-Windows內部概述2


Windows內部概述-2-

線程:

執行代碼的實體是線程。一個線程的包含在進程里面的,線程使用進程提供的資源來運行代碼。

一個線程擁有以下的內容:

1:明確的運行模式,用戶態或者內核態。

2:執行的環境,包括寄存器和執行狀態。

3:一個或兩個棧空間,用來存放變量和調用管理。

4:Thread Local Storage(TLS) 線程本地存儲數組,用來存儲線程專用數據和提供統一的訪問語法。

5:基本的優先級和當前(動態)的優先級。

6:和處理器關聯,用來指示允許線程在那個一個處理器上運行。

線程的狀態  
Running 正在當前邏輯處理器上執行代碼。
Ready 等待被列入運行隊列中,因為所有的相關處理機都是在運行或者不可用中。
Waiting 等待一些事件去觸發,一旦被觸發后就會進入Ready狀態

 

 

 

線程棧

每個線程當運行時有一個棧空間來存放變量、傳給函數的參數和函數調用前存儲返回地址的位置。

一個線程有至少一個棧堆留在系統的內核空間,而且空間非常小(默認為12KB 32位系統和64系統上24KB)。用戶態的線程有第二個堆棧在它的用戶進程地址空間范圍內是挺大的一個空間(默認情況下可以增長到1mb)。

 

 

當線程處於運行狀態或者就緒態時,內核堆棧總是停留在RAM內存中。而對於用戶態的堆棧來說就是另一方面了,可以被直接分頁交互,就像用戶模式的內存一樣。

就堆棧大小而言的話用戶模式下的堆棧和內核模式下的堆棧使用情況不同。用戶模式下的堆棧一開始會提交少量的內存(可以小到和一個頁面一樣大小),將剩下的內存地址空間作為保留內存,這段保留內存是沒有被分配的。這樣做的目的是在線程執行代碼的時候可以動態地增長堆棧空間。為了達到動態增長堆棧的目的,通常是在已經提交了的堆棧內存中往后設置一個被稱為PAGE_GUARD狀態的內存保護頁(有時是多個頁),如果線程需要更多的堆棧空間,它將會訪問保護頁面,這樣就會觸發一個由內存管理器處理的異常,然后內存管理器再刪除保護狀態並提交該保護狀態的頁面,再將下一個頁面標記為保護頁面,這樣讓堆棧根據需要來增長。

 

 

用戶態的線程堆棧大小由以下內容確定

1:可執行的文件在PE文件中有一個默認的提交和保留狀態堆棧大小,如果線程沒有修改就是默認的。

2:當在創建線程的時候,可以通過函數參數來指定所需堆棧大小,預先提交的大小或保留的大小。

線程小結:線程才是真正跑代碼的東西,線程利用的是進程提供的代碼和數據來運行,線程可以分為三個狀態來標識,同時線程也分用戶態和內核態,兩者的存儲堆棧大小不同,用戶態的堆棧也有三個狀態目的是為了動態的增長堆棧大小,線程的堆棧大小可以由PE文件結構和創建線程函數來規定。

系統服務(系統函數call)

一個應用程序會執行各自操作系統應該執行的操作,例如:分配內存,打開文件,創建線程等等。這些涉及操作系統的操作,最后都是由操作系統內核來運行代碼實現,那么用戶模式下如何進行這樣的操作呢?其實很簡單,肯定是內核提供了API接口給我們使用。

例如:打開Sublime新建一個文件,那么這里的Sublime的代碼通過調用CreateFile這個API來實現新建文件,CreateFile這個API是記錄在kernel32.dll這個動態鏈接庫里面的,kernel32.dll是Windows其中的一個子系統的dll。但是這函數仍然是在用戶太下運行的,所以它仍然是無法直接打開文件的。在進行錯誤檢查后,一個名叫在NTDLL.dll中的API函數NtCreateFile會被調用,該NTDLL.dll是一個基礎的dll所以這個NtCreateFile也被稱為本地的API函數,但是實際上它仍然屬於用戶模式的最低代碼層。該API有一個重要作用就是實現了向內核狀態的轉換,但在它轉換前它將一個稱為系統服務號碼的數字賦值給CPU的(inter/arm架構上的)eax寄存器,然后再 發出一個特殊的CPU指令,在跳轉到被稱為系統服務調度程序的預定義例程時也同時轉換到內核模式中。

然后系統服務調度程序反過來將eax中的值作為系統服務調度表(SSDT)的索引,通過該表,代碼會跳轉到系統服務(系統調用)本身上,對於我們這里的創建文件,SSDT將指向I/O管理器的NtCreateFile函數來創建文件。(這里的NtCreateFile和NTDLL.dll的NTDLL.dll中的NtCreateFile名稱一樣,參數也一樣)。在系統服務執行完后,線程就會返回用戶模式繼續執行后面的命令。

1 CreateFile

2 NTDLL.dll中的NtCreateFile

3 mov eax.n 跳轉到系統調度程序,且進入內核態

4 系統調度程序根據eax的值從SSDT中找到要提供的服務

5 使用內核的NtCreateFile來創建文件

 

 

 

系統服務總結:當涉及到和OS相關的操作時通常都會調用內核的內容來實現,這就會通過要實現的功能進入內核,然后內核系統調度程序根據要實現的功能再在內核中實現后又返回到下一條指令中繼續執行。

通用系統架構:

 

 

 

   
   
User processes 基於文件的正常進程,如:QQ.EXE
Subsystem DLLs 子系統DLL里面包含了子系統提供的API。子系統就是對內核所公開的功能的某種視圖。(就是公開給大家知道讓大家調用的),從技術角度來說自Windows8.1之后只有一個子系統,就是Windows子系統。子系統dll包括著名的文件,如kernel32.dll、user32.dll、gdi32.dll、advapi32.dll、combase.dll等。這些主要包括官方記錄的窗口api。
NTDLL.DLL 一個系統范圍內的dll,實現了Windows內置的API,但是仍然是處於用戶模式中,雖然是用戶模式中最低的代碼層。它最大的作用就是實現了從用戶層向內核層的轉換來進行系統調用,NTDLL還提供了堆管理、文件加載和一部分的用戶線程池。
Service Processes 服務進程是用來對Service Control Manager進行管理和交互的一個普通進程。SCM就是Windows的服務控制管理,可以在cmd+d下輸入services.msc來查看。SCM可以啟動、停止、暫停、恢復並向其它和服務有關的發送消息。服務通常在其中一個特殊的窗口帳戶下執行——本地系統、網絡服務或本地服務。
Executive 執行程序是NtOskrnl.exe("內核")的上層,它承載了內核模式下的大部分代碼。主要包括各種"管理器",對象管理器、內存管理器、I/O管理器、即插即用管理器,電源管理器和配置管理器等等。它比大多的更底層的內核模塊都要大。
Kernel 內核層實現了操作系統內核模式中最基本和對時間最敏感部分的代碼。包含了線程調度、中斷和異常分發以及各自原語實現,例如:互斥體,信號體。一些內核代碼直接用特定CPU的機器語言編寫來提高效率並且可以直接訪問CPU里的一些細節。
Device Drivers 設備驅動程序是可以被加載的內核模塊,它們里面的代碼直接在內核狀態下執行而且有和內核一樣的權限和功能。
Win32k.sys Windows子系統的內核模式組件。從本質上來說,這是一個內核驅動模塊用來處理部分Windows的用戶態接口API和經典圖形設備接口(GDI)API。這也就意味着所有的Windows操作(如CreateWindowsEx,GetMessage等等)都是由這個組件來處理的。該組件幾乎與UI界面無關。
Hardware Abstraction Layer (HAL) 硬件抽象層HAL是最接近CPU硬件的一個抽象層,它允許設備驅動程序使用不需要了解中斷控制器等方面的詳細和具體知識的API,這一層對於編寫用於處理硬件設備的設備驅動程序非常有用,
System Processes 系統進程是一個總稱,用來描述操作系統內置的進程,是默認開機就會有的進程。他們是非常重要的,沒有它們系統無法正常運行。終止了一些系統進程可能會導致系統崩潰。一些系統進程是內置的本機進程,這意味着他們只使用內置的API(也就是NTDLL提供的API)。例如:Smss.exe,Services.exe等等。
Subsystem Process 子系統進程。運行Csrss.exe鏡像的Windows子系統進程可以看作是內核的助手,用於管理在Windows系統下運行的進程。這是一個關鍵的進程,如果關閉了它那么系統就會崩潰。通常每個會話都會有一個Csrss.exe的實例。所以在一個標准的系統里面有兩個實例是一定會存在的:一個用於會話0,另一個用於已登錄的用戶會話(通常為1)。
Hyper-V Hypervisor 微軟虛擬機:如果Hyper-V hypervisor是Win10以及更高版本之上的,那么就支持基於虛擬化的安全性Virtualization Based Security (VBS),基於虛擬化的安全性VBS提供了一個額外的安全層,其中實際機器實際上是由Hyper-V控制的虛擬機。

系統架構小結:這個概念屬實有點多了,慢慢再消化把。

句柄和內核對象

Windows公開了各種類型的系統對象,共用戶模式進程和內核驅動使用。這些對象的實例設計是在系統空間中的結構體,由用戶或內核模式的執行代碼向對象管理器請求時創建的。對這些對象的引用將會被計數,只有當最后一個引用的對象被釋放后才會銷毀內存並從內存中釋放資源。

因為這些實例對象是位於系統空間中,所以不能在用戶態被訪問。用戶態如果要訪問這些系統提供的對象必須采用間接訪問機制,將其稱為句柄handle。句柄是對基於進程維護的表中條目的索引,所有的句柄是針對進程的,每個進程中的句柄不能共用,它在邏輯上指向駐留在系統空間中的內核對象,可以想象成表,其中的句柄和內核對象的關系可以多對一,一對一但是不能一對多。由各種的Create和Open開頭的函數來創建或獲取一個對象和獲取這些對象對應的句柄。例如:CreateMutex這個用戶態的API會創建一個Mutex對象,如果成功,函數將返回該對象的句柄。返回值為零,表示一個無效的句柄(函數調用失敗),同樣如果調用OpenMutex函數來試圖打開一個互斥體的句柄,如果存在就返回句柄,不存在就失敗返回NULL(0)。

內核(驅動程序)可以使用句柄或者直接指向對象的指針來操作。這樣通常是為了想要調用基於Windows對象的API。在某些情況下,由用戶模式提供句柄給驅動必須通過ObReferenceObjectByHandle這個API函數轉換為指針的形式。

在Windows中有一個全局的系統句柄:這個句柄表存放在system進程里面,系統的句柄表只有一個,這個句柄表就是都可以用不局限於進程里了,系統句柄表里的句柄也被稱為內核句柄

句柄都是4的倍數,第一個有效的句柄是4,0永遠不是有效句柄值。

在內核模式情況下可以使用句柄也可以直接使用指針,到底是指針還是句柄通常看函數需要的參數。內核代碼可以使用ObReferenceObjectByHandle函數來將指針的句柄變成指針。如果對內核對象的引用成功會導致它的計數增加,所以就是如果用戶模式的代碼關閉了句柄這並不危險並不會導致空指針,因為要所有的引用都釋放了才會銷毀該內存。無論通過句柄做了什么,該句柄對應的對象都可以安全地訪問,直到內核代碼調用ObDerefenceObject函數,以減少對象的引用計數;如果內核代碼錯過此調用,則是資源泄漏,只能在下次系統啟動時解決(也就是重啟)。

所有的系統對象都會進行引用計數。每個內核對象包含兩個計數,句柄計數和指針計數

句柄計數是指:應用層對內核對象的句柄使用計數。

指針計數是指:內核驅動對內核對象的指針引用次數。

對象管理器維護着句柄計數的值和內核對象引用的次數值。一旦一個內核對象不再被需要,使用或者創建它的客戶端需要對其進行關閉句柄(如果使用的是句柄來訪問對象)或者是取消引用對象(如果用的是內核指針)。取消或者關閉后句柄/指針就沒有效果了,如果一個內核對象的引用計數達到零,則對象管理器就會銷毀該對象,釋放空間。

每個內核對象都對應着不同的屬性內容,根據結構體的定義而不一樣。

 

內核對象名稱

有一些內核對象有名字,這些有名稱的內核對象可以通過名字被對應的Open函數來打開對象。並不是所有的內核對象都有名字,比如:進程和線程就沒有名字,他們只有ID。這就是為什么OpenProcess和OpenThread函數需要線程/進程的ID而不是名字了。還有比較奇怪的一種情況:沒有名稱的對象是一個文件。文件名不是和對象名一一對應,兩者是不同的概念。

在用戶模式的情況下,如果具有該名稱的對象不存在就會調用構建函數來依據對象的名稱來創建一個對象;如果存在就打開該對應的名稱的對象。

提供給創建函數Create*的對象名稱並不是該對象的最終名稱,名稱由\Sessions\x\BaseNamedObjects\來命名對象,其中x是調用者的會話ID,如果會話為0,則由\BaseNamedObjects\來命名。如果調用者碰巧在應用程序容器(通常是通用Windows平台進程)中運行,則前置字符串將更為復雜,並且由唯一的應用程序容器SID:\Sessions\x\AppContainerNamedObjects{AppContainerSID}組成

以上的內容都表明對象名稱是和會話相關的(如果是AppContainer應用程序容器則是和包相關)。如果必須在會話之間來共享對象則可以在會話0時使用Global預設計對象名來創建對象,例如,使用名為Global的CreateMutex函數創建互斥體將在\BaseNamed對象下創建它。

 

 

圖所示的視圖是對象管理器命名空間,由命名對象的層次結構組成。將整個結構保存在內存中,並根據需要由對象管理器(執行人員的一部分)進行操作。請注意,未命名的對象不是此結構的一部分,這意味着在WinObj中看到的對象不包含所有現有對象,而是使用名稱創建的所有對象。

每個進程都由一個私有的針對內核對象的私有句柄表,它可以通過Process Explorer 或者Handles Sysinternals tools來查看:

 

 

 

默認的情況是只能看句柄類型和名字,但是可以在view中選擇顯示未命名的句柄,還可以右鍵選中這個地方來增加查看句柄的內容:

 

 

句柄視圖中的各個列為每個句柄提供了更多信息。 顯示了很多真實的對象名稱:Mutexes (Mutants), Semaphores, Events, Sections, ALPC Ports, Jobs, Timers等等還有一些用的比較少的對象類型。然而,有一些顯示的名字並不是他根本的名字:

對象 原因
進程和線程 由唯一的ID來標識,顯示的是ID
文件對象 它顯示該文件對象指向的文件名(或者設備名)。它和對象的名稱是不同的,因為文件名,所以無法獲取對文件對象的句柄--只能創建一個新的文件對象來訪問相同的基礎文件或設備。
注冊表 關鍵對象名稱隨注冊表項的路徑顯示。但是這不是一個名稱,它與文件對象的推理相同。
目錄對象顯示的路徑 目錄對象顯示的路徑,而不是真正的對象名稱。目錄不是文件系統對象,而是對象管理器目錄——可以使用系統內部WinObj工具輕松查看這些目錄
令牌(token) 令牌(token)對象名稱與存儲在令牌中的用戶名一起顯示

訪問現有的對象

進程資源管理器的句柄視圖中的“訪問”列顯示用於打開或創建句柄的訪問掩碼。此訪問掩碼是允許執行的操作的關鍵具有一個特定的句柄。例如,如果客戶端代碼想要終止一個進程,它必須首先調用Open進程函數,以獲得對所需進程的句柄,其訪問掩碼為(至少)PROCESS_TERMINATE,否則就沒有辦法用該句柄終止該進程。如果調用成功,則終止進程將成功。

下面是一個在給定進程ID時終止進程的用戶模式示例:

bool KillProcess(DWORD pid) {
// open a powerful-enough handle to the process
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
if (!hProcess)
return false;
// now kill it with some arbitrary exit code
BOOL success = TerminateProcess(hProcess, 1);
// close the handle
CloseHandle(hProcess);
return success != FALSE;
}

“訪問掩碼”列提供了訪問掩碼的文本說明(對於某些對象類型),使它更容易識別對特定句柄所允許的精確訪問。雙擊句柄條目將顯示該對象的一些屬性。

圖中的屬性包括對象的名稱(如果有)、其類型、描述及其地址:

 

在內核內存中,可以獲取打開的句柄的數目,以及一些特定的對象信息,例如:所顯示的事件對象的狀態和類型。請注意,所顯示的參考文獻並沒有表示實際的對該對象的未完成的引用數。一種查看實際參考計數的正確方法該對象是使用內核調試器的!trueref命令,如下所示:

1 lkd> !object 0xFFFFA08F948AC0B0
2 Object: ffffa08f948ac0b0 Type: (ffffa08f684df140) Event
3 ObjectHeader: ffffa08f948ac080 (new version)
4 HandleCount: 2 PointerCount: 65535
5 Directory Object: ffff90839b63a700 Name: ShellDesktopSwitchEvent
6 lkd> !trueref ffffa08f948ac0b0
7 ffffa08f948ac0b0: HandleCount: 2 PointerCount: 65535 RealPointerCount: 3

 

 

 

小結:windows系統公開了自己在內核中的對象和類,內核中的對象和類一一對應,只有唯一一個,在用戶態中可以使用句柄來調用內核對象,內核中可以用句柄也可以用指針,內核對象被引用后會計數,如果為0就會銷毀它。每個進程有一個表來專門記錄該進程對內核對象的引用。還有一個系統進程system記錄的是系統句柄表。

 

Windows內部概述總結:

進程

虛擬內存

線程

系統服務

系統架構

以上就是WIndows的內部五大版塊,每一個都能弄很久,所以總結起來實在是不太行,只有個大概的概念就好了,后面深入了就明白了。


免責聲明!

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



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