繼續上一篇(2)未完成的研究,我們接下來學習 KPROCESS這個資料結構。
1. 相關閱讀材料
《深入理解計算機系統(原書第2版)》
二. KPROCESS
KPROCESS,也叫核心程式塊。我們在開始學習它的資料機構之前,首先要思考的一個問題是,它和EPROCESS名字感覺差不多,那它們之間是什麼關係呢?它們在核心區域中都位於那一層呢?
我們先來看一張圖:
windows核心中的執行體負責各種與管理和策略相關的的功能(在學習筆記(2)有相關的介紹)。而核心層(或微核心)實現了作業系統的"核心機制"。程式和執行緒在這"兩"層上都有對應的資料結構。甚至EPROCESS和KPROCESS中的某些成員域(例如TheradLstHead)是相同的東西存了兩份(EPROCESS中的TheradLstHead域的連結串列包含了各個子執行緒的ETHREAD結構中的TheradLstEntry節點,而KPROCESS中的TheradLstHead域的連結串列包含了各個子執行緒的KTHREAD結構中的TheradLstEntry節點)。
清除地區分核心中的確有這兩種不同層次的資料結構很重要。
那EPROCESS和KROCESS都儲存在哪裡呢?我們知道,我們可以通過
DWORD EProcess;
EProcess = (DWORD)PsGetCurrentProcess();
的方法來獲得EPROCESS在核心中的基址,然後回想我們之前學習的EPROCESS的結構,EPROCESS結構的第一個成員域: KPROCESS Pcb 就是我們今天要學習的KPROCESS。
ntdll!_EPROCESS +0x000 Pcb : _KPROCESS +0x000 Header : _DISPATCHER_HEADER +0x010 ProfileListHead : _LIST_ENTRY [ 0x82529db0 - 0x82529db0 ] +0x018 DirectoryTableBase : [2] 0xce401a0 +0x020 LdtDescriptor : _KGDTENTRY +0x028 Int21Descriptor : _KIDTENTRY +0x030 IopmOffset : 0x20ac +0x032 Iopl : 0 '' +0x033 Unused : 0 '' +0x034 ActiveProcessors : 0 +0x038 KernelTime : 6 +0x03c UserTime : 1 +0x040 ReadyListHead : _LIST_ENTRY [ 0x82529de0 - 0x82529de0 ] +0x048 SwapListEntry : _SINGLE_LIST_ENTRY +0x04c VdmTrapcHandler : (null) +0x050 ThreadListHead : _LIST_ENTRY [ 0x826cdd58 - 0x826cdd58 ] +0x058 ProcessLock : 0 +0x05c Affinity : 1 +0x060 StackCount : 1 +0x062 BasePriority : 8 '' +0x063 ThreadQuantum : 18 '' +0x064 AutoAlignment : 0 '' +0x065 State : 0 '' +0x066 ThreadSeed : 0 '' +0x067 DisableBoost : 0 '' +0x068 PowerState : 0 '' +0x069 DisableQuantum : 0 '' +0x06a IdealNode : 0 '' +0x06b Flags : _KEXECUTE_OPTIONS +0x06b ExecuteOptions : 0 ''
換句話說,這個Pcb(或者叫核心程式控制塊,或者叫KPROCESS,都是指同一個東西)是EPROCESS中的第一個"內嵌結構體"。結合結構體的記憶體中存放的知識,我們還知道一個程式的KPROCESS物件的地址和EPROCESS物件的地址是相同的。
每個KPROCESS物件都代表一個程式,反之每一個程式都有一個對應的KPROCESS。
首先給出KPROCESS的結構體定義,然後我們來一條一條地學習。 base\ntos\inc\ke.h (在(2)中有說明,這是windows的"開源"研究專案 WRK,從中我們可以找到很多標頭檔案檔案供我們學習之用)
typedef struct _KPROCESS { DISPATCHER_HEADER Header; LIST_ENTRY ProfileListHead; ULONG_PTR DirectoryTableBase[2]; KGDTENTRY LdtDescriptor; KIDTENTRY Int21Descriptor; USHORT IopmOffset; UCHAR Iopl; BOOLEAN Unused; volatile KAFFINITY ActiveProcessors; ULONG KernelTime; ULONG UserTime; LIST_ENTRY ReadyListHead; SINGLE_LIST_ENTRY SwapListEntry; PVOID VdmTrapcHandler; LIST_ENTRY ThreadListHead; KSPIN_LOCK ProcessLock; KAFFINITY Affinity; union { struct { LONG AutoAlignment : 1; LONG DisableBoost : 1; LONG DisableQuantum : 1; LONG ReservedFlags : 29; }; LONG ProcessFlags; }; SCHAR BasePriority; SCHAR QuantumReset; UCHAR State; UCHAR ThreadSeed; UCHAR PowerState; UCHAR IdealNode; BOOLEAN Visited; union { KEXECUTE_OPTIONS Flags; UCHAR ExecuteOptions; }; ULONG_PTR StackCount; LIST_ENTRY ProcessListEntry; } KPROCESS, *PKPROCESS, *PRKPROCESS;
1. DISPATCHER_HEADER Header
Header域表名KPROCESS物件也是一個分發器物件(dispatcher object),我們之前說過windows核心中和程式相關的資料結構有2套,EPROCESS更側重於管理方面的資訊,而KPROCESS更側重於CPU排程方面的資訊。
而這個"分發器物件"Header就涉及到: 基於執行緒排程的同步機制 相關的知識。
我們知道,當一個執行緒的控制流到達一個等待函式時(例如KeWaitForSingleObject),若等待的條件不滿足,則執行緒排程器會將處理器的執行權交給其他處於就緒狀態的執行緒。此後,當這個處於等待狀態的執行緒等待條件滿足時,系統會通過KiUnwaitThread與KiReadyThread函式使該執行緒變成"延遲的就緒狀態(加入排程佇列,並不能保證立刻獲得CPU排程)",從而使該執行緒可以繼續執行。
在此過程中,執行緒物件的一個等待塊連結串列,即KTHREAD的WaitBlockList域(我們在之後的學習筆記中會涉及到),記錄了該執行緒正在等待的那些物件。隨著這些物件的狀態發生變化,該執行緒的執行條件將有可能"被滿足"。所以,其他的執行緒可以通過改變這些物件的狀態來控制等待執行緒的執行與否(這裡所謂的被等待的物件我們最常見的就是: Mutex互斥體、Event事件、SPINLOCK自旋鎖)。要理解這些執行緒間同步的核心思想: 就是要獲得到一個"令牌"後才能"行動"。
這些被等待的物件可以用來協調執行緒之間的行為,它們被稱為"同步物件"或者"分發器物件"。在windows核心中,凡是頭部以DISPATCH_HEADER開始的物件都是"分發器物件"。
關於DISPATCHER_HEADER結構的定義見 base\ntos\inc\ntosdef.h
typedef struct _DISPATCHER_HEADER { union { struct { UCHAR Type; union { UCHAR Absolute; UCHAR NpxIrql; }; union { UCHAR Size; UCHAR Hand; }; union { UCHAR Inserted; BOOLEAN DebugActive; }; }; volatile LONG Lock; }; LONG SignalState; LIST_ENTRY WaitListHead; } DISPATCHER_HEADER;
我們將在學習KTHREAD的時候會詳細學習這個資料結構以及執行緒間同步的相關知識。我們目前只有記住幾點:
1. 這個DISPATCHER_HEADER 是在KPROCESS中的,即是一個程式擁有的物件 2. 這個DISPATCHER_HEADER 是被別的執行緒等待的,也就是別的執行緒會阻塞等待在這個程式上 3. 程式物件本身是可以"被等待"的,即程式物件是一個"可等待物件"。
2. LIST_ENTRY ProfileListHead
ProfileListHead域用於當該程式參與效能分析(profiling)時,作為一個節點加入到全域性的效能分析程式列表(核心全域性變數KiProfileListHead)中。
+0x010 ProfileListHead : _LIST_ENTRY [ 0x82529db0 - 0x82529db0 ]
3. ULONG_PTR DirectoryTableBase[2]
DirectoryTableBase是一個只有2項的陣列:
DirectoryTableBase[0]: 指向該程式的頁目錄表地址
DirectoryTableBase[1]: 指向該程式的超空間(Hyper Space)的頁目錄表地址
關於記憶體的頁式儲存、頁面的相關內容,在學習筆記(2)中已經給出相關介紹
4. 針對x86處理器的系統中斷呼叫
KGDTENTRY LdtDescriptor;
KIDTENTRY Int21Descriptor;
USHORT IopmOffset;
UCHAR Iopl;
LdtDescriptor: 指向該程式的LDT(區域性描述符表)的描述符(參考學習筆記(2))
Int21Descriptor: 為了相容DOS程式,允許它們通過int 21h指令來呼叫DOS系統功能
IopmOffset: 指定了IOPM(I/O許可權表、I/O Prilege Map)的位置,核心通過IOPM可以控制程式的使用者模式I/O訪問許可權。
Iopl: 定義了程式的I/O優先順序(I/O Privilege Level)
+0x020 LdtDescriptor : _KGDTENTRY +0x028 Int21Descriptor : _KIDTENTRY +0x030 IopmOffset : 0x20ac +0x032 Iopl : 0 ''
關於Int21Descriptor所指向的IDT(中斷描述符表),這裡擴充一下。
首先,我們要明白幾點,windows的技術是在不斷髮展的,所以我們現在再去學習windows的核心有時候會感到迷惑,同一個實現或同一個功能往往存在多種具體的實現技術,這是真實存在但卻正常的現象,一方面windows要做到"向下相容",另一方面windows要不斷的封裝出更好方便好用的新技術來"取代"原來的老的技術,對於我們來說,我們最好是都學,但心裡一定要區分這些技術的出現"先後順序(從歷史的角度來看)",要明白當前windows中主要使用的是哪些技術。
1) 中斷時一種機制,用來處理硬體需要向CPU輸入資訊的情況。 比如滑鼠,鍵盤等。觸發的本質是: 使CPU的執行暫時暫停並跳到"中斷處理函式"中,終端處理函式已經在記憶體中了 2) 這些中斷處理函式儲存在一個叫做IDT(中斷描述符)的表中,每個"中斷號"在這個表中都有一項 3) IDT是在DOS時代使用的"新"技術,在更早的時候我們知道是在記憶體的0~1024中存在"中斷處理例程"來完成中斷的操作。 4) 中斷可以由硬體產生(稱為外部中斷),也可以由軟體產生(稱為內部中斷),在程式中寫入int n指令可以產生n號中斷和異常(n從0-ffh) 5) "中斷"是一種CPU的硬體機制,由CPU來提供(回想CPU的EFLAG暫存器中有一個T標誌位標識是否單步中斷)。而中斷所需要的資料結構由作業系統來維護,並儲存在記憶體中 6) 早期的作業系統甚至是通過中斷來進行核心呼叫的。int指令是一種c從ring3 進入ring 0的方法。比如windows在xp版本之前使用的int 2e。在x86 CPU提供了sysenter指令後,
這種方式才被放棄 7) 每一種中斷對應一箇中斷號。CPU執行中斷指令時,會去IDT表中查詢對應的中斷服務程式(interrupt service routine,ISR)。ISR(為了表述方便後面用ISR n表示n號中斷的處理程式)
,x86CPU最大可以支援256種中斷 8) 中斷是CPU的機制,不管執行的是什麼作業系統,只有是執行於x86架構,IDT結構式必然存在的。IDT表中的ISRs應該有作業系統提供(這也體現一個基本的道理,不管是win還是linux,
可能它們使用的技術名稱不同,但是追其根本,核心的思想永遠是不變的,只不過在不同的作業系統上有不同的表現形式罷了)
那既然說是IDT(中斷描述符表),我們知道表一定是一個類似陣列的結構,這個陣列的基地址儲存在IDTR上(回想一下學習筆記(2)中的GDTR是不是也是類似的思路)。IDTR是一個暫存器,它的結構如下:
這個暫存器中含有IDT表32位的基地址和16位的長度(限長)值。IDT表基地址應該對齊在8位元組邊界上以提高處理器的訪問效率。結合GDTR的思想就很好理解了。建議拿起筆和紙畫一畫,再結合一些別的資料看一看,不要著急,一定要把這些基本的知識點領會了,這樣學些才有效果。
typedef struct P2C_IDTR_ { P2C_U16 limit; // 範圍 P2C_U32 base; // 基地址(就是IDT標的開始地址) } P2C_IDTR, *PP2C_IDTR; P2C_IDTR idtr; // 一句彙編讀取到IDT的位置。 _asm sidt idtr return (void *)idtr.base;
結合上面的圖片理解這段程式碼,我們可以獲取到IDT(中斷描述符表)的基地址(注意,是這個表的基地址)。那接下來我們就可以利用和我們平時索引陣列的方法來索引這個結構體陣列了,我們稱這個IDT表(陣列)中單獨的每一項為"中斷門(本質就是一個陣列項)"。所以接下來最後一個問題,這個陣列的每一項的結構是什麼樣的呢?
我們知道,IDT是一個最大為256項的表,每個表項為8位元組。稱為中斷門,中斷門對應的資料結構為 P2C_IDTENTRY
根據中斷號對應的異常型別不同(Faults/Traps/Aborts)8個位元組的意義也不同。
關於異常型別,請閱讀"《深入理解計算機系統(原書第2版)》" 8.1.2 異常的型別,可以獲得原理級的解釋。
(圖中每一個紅框都代表一個"中斷門"),它的資料結構定義如下:
typedef struct P2C_IDT_ENTRY_ { P2C_U16 offset_low; P2C_U16 selector; P2C_U8 reserved; P2C_U8 type:4; P2C_U8 always:1; P2C_U8 dpl:2; P2C_U8 present:1; P2C_U16 offset_high; } P2C_IDTENTRY, *PP2C_IDTENTRY;
(其中offset_low和offset_high組合起來就是一個完整的這個中斷例程的入口地址,我們在程式設計中可以使用一些巨集來進行 Bit Operation來進行拼接和拆分)
回到主線上來來,KPROCESS的Int21Descriptor中儲存的就是IDTR中儲存的一樣,即IDT的基址,我們可以利用這個成員域來對IDT進行定址。
5. volatile KAFFINITY ActiveProcessors
ActiveProcessors域記錄了當前程式正在哪些處理器上執行。這和程式建立時的CPU處理器的親和性有關。我們將在學習程式、執行緒建立過程的學習專題中再次學習它。
+0x034 ActiveProcessors : 0
6. 執行用時
ULONG KernelTime;
ULONG UserTime;
KernelTime: 記錄了一個程式物件在核心模式下所花的時間
UserTime: 記錄了一個程式在使用者模式下所花的時間
程式的KernelTime和UserTime時間值等於其所屬的執行緒的對應的KernelTime和UserTime值的和。
即: 程式(KernelTime + UserTime) = 執行緒1(KernelTime + UserTime)+ .. + 執行緒n(KernelTime + UserTime
但是要注意,由於僅當一個執行緒結束時才會更新其程式的這兩個時間值,所以,若一個程式中還沒有任何一個執行緒結束,則KRPCESS中的這兩個域為0,即程式和執行緒的執行時間並不是實時同步的。
+0x038 KernelTime : 6 +0x03c UserTime : 1
7. LIST_ENTRY ReadyListHead
ReadyListHead域是一個雙向連結串列的表頭,該連結串列記錄了這個程式中處於"就緒狀態"但尚未被加入"全域性就緒連結串列"的"執行緒",這個域的意義在於,當一個程式被換出記憶體以後,他所屬的執行緒一旦就緒,則被掛到此連結串列中,並要求換入該程式。以後,當該程式被換入記憶體,ReadyListHead中的所有執行緒被加入到系統全域性的"就緒執行緒連結串列"中。
注意,ReadyListHead所指向的連結串列中的每一項都是一個指向KTHREAD物件的WaitListEntry域的地址,所以,
從該連結串列中的每一項都可以定位到對應的執行緒物件(處於"就緒狀態"但尚未被加入"全域性就緒連結串列"的"執行緒")。從某種程式上來說,我們可以利用這個連結串列來進行執行緒列舉。
+0x040 ReadyListHead : _LIST_ENTRY [ 0x82529de0 - 0x82529de0 ]
8. SINGLE_LIST_ENTRY SwapListEntry
SwapListEntry域是一個"單連結串列項"(注意是一個項),當一個程式要被換出記憶體時,它通過此域定址到"KiProcessOutSwapListHead為鏈頭的單連結串列",並把當前程式加入到以KiProcessOutSwapListHead為鏈頭的單連結串列中。
+0x048 SwapListEntry : _SINGLE_LIST_ENTRY
9. LIST_ENTRY ThreadListHead
ThreadListHead域指向一個連結串列頭,此連結串列包含了該程式的所有當前執行緒。這個域非常重要,當一個執行緒被初始建立的時候,被加入到此連結串列中,線上程被終止的時候從連結串列中移除。由此我們可以看出程式和執行緒之間的從屬關係。
+0x050 ThreadListHead : _LIST_ENTRY [ 0x826cdd58 - 0x826cdd58 ]
10. KSPIN_LOCK ProcessLock
ProcessLock域是一個自旋鎖(spin lock)物件,它的用途是保護此程式中的資料成員。在核心程式碼中關於程式和執行緒各種操作的很多地方都可以看到對ProcessLock鎖的操作,這可以確保對程式資料結構的修改和訪問總是一致的。既不會產生"髒讀","幻讀"現象。
11. KAFFINITY Affinity
Affinity域指定了該程式可以在哪些處理器上執行,其型別為KAFFINITY。它採用了bit點陣圖表示法,二進位制表示的每一位分別對應於當前機器上的一個處理器(核)。
typedef ULONG_PTR KAFFINITY;
+0x05c Affinity : 1
13. 程式中的執行緒排程
LONG ProcessFlags
SCHAR BasePriority;
SCHAR QuantumReset;
1) ProcessFlags域包括了程式中的幾個標誌: AutoAlignment、DisableBoost、DisableQuantum。
AutoAlignment: 用於該程式中的記憶體訪問對齊設定,此標誌也會被傳遞到執行緒的資料結構中,當一個執行緒的對齊檢查開關開啟時,
該執行緒中的未對齊資料訪問將會導致對齊錯誤(alignment fault)
DisableBoost、DisableQuantum: 與執行緒排程過程中的優先順序提升和時限(Quantum)有關。
2) BasePriority域用於指定一個程式中的執行緒的"基本優先順序",所有的執行緒在啟動時都會"繼承"程式的BasePriority值
3) QuantumReset域用於指定一個程式中執行緒的"基本時限重置值",在現代windows版本中此值被設定為6.
請參考《windows 核心原理與實現》 3.5 windows中的執行緒排程
13. UCHAR State
State域用來說明一個程式是否在記憶體中,共有六種可能的狀態:
ProcessInMemory、ProcessOutOfMemory、ProcessInTransition、ProcessOutTransition、ProcessInSwap、ProcessOutSwap
所謂一個程式在記憶體中,後者已被換出,或者正在轉移當中,是指該程式的虛擬地址空間需要佔據足夠的實體記憶體,或者虛擬空間中的內容已被換出實體記憶體,或者正在換入或換出的過程中。即是否產生了虛擬記憶體到實體地址的對映。
14. UCHAR ThreadSeed
ThreadSeed域用於為該程式的執行緒選擇適當的"理想處理器(Ideal Processor)",在每個執行緒被初始化的時候,都指定此程式的ThreadSeed值作為這個新執行緒的"理想處理器",然後ThreadSeed域又被設定一個新的值,以便該程式的下一個執行緒使用。
這裡的理想處理器是指在多處理器的環境下,每個執行緒都有一個優先選擇的處理器。
15. UCHAR PowerState
PowerState域用於記錄電源狀態,這涉及到電源狀態管理的知識。請參考《windows 核心原理與實現》 6.4節
(我們會發現,學習核心資料結構是一個很好的學習途徑,在學習的過程中,它可以整個windows系統的所有知識都串起來,讓你有一個整體的聯動感)
16. UCHAR IdealNode
IdealNode域用於為一個"程式"選擇優先的處理器節點,這是在程式初始化時設定的。這裡所謂的節點是NUMA(非一致記憶體訪問)結構中的概念
17. BOOLEAN Visited
Visited域作用不詳,在WRK中也沒有使用到
18. UCHAR ExecuteOptions
ExecuteOptions域用於設定一個程式的記憶體執行選項,這是為了支援NX(No-Execute 記憶體不可執行)而引入到windows XP/Server 2003中的一個域。和我們在溢位中經常談到的DEP有很大的關係。
NX位(全名"No eXecute bit",即"禁止執行位"),是應用在CPU中的一種安全技術。 支援NX技術的系統會把記憶體中的區域分類為只供儲存處理器指令集與只供儲存資料使用的兩種。任何標記了NX位的區塊代表僅供儲存資料使用而不是儲存處理器的指令集,處理器將不會將此處的數
據作為程式碼執行,以此這種技術可防止大多數的快取溢位式攻擊(即一些惡意程式把自身的惡意指令集通過特殊手段放在其他程式的儲存區並被執行,從而攻擊甚至控制整臺電腦系統)。
19. ULONG_PTR StackCount
StackCount域記錄了當前"程式"中有多少"執行緒"的棧(執行緒棧)位於記憶體中
20. LIST_ENTRY ProcessListEntry
ProcessListEntry域用於將當前系統中的所有具有活動執行緒的程式串成一個連結串列,連結串列頭為KiProcessListHead。看到這裡,我們回想EPROCESS中也有一個類似的域成員ActiveProcessLinks。那這兩個域成員有什麼區別呢?
要注意:這一連結串列(KiProcessListHead)僅用於AMD64系統
至此,KPROCESS的資料結構也全部介紹完了。簡單總結一下:
1) KPROCESS物件中記錄的資訊主要包括兩類: 1.1) 一類和程式的記憶體環境相關,比如頁目錄表、交換狀態等 1.2) 另一類是與執行緒相關的一些屬性,比如執行緒列表以及執行緒所需要的優先順序、時限設定等 2) 系統中的KROCESS通過KiProcessListHead連結串列串起來,但這一連結串列僅用於AMD64系統。我們知道,系統的總程式連結串列是在執行體層(EPROCESS)管理的
二. PEB(Process Environment Block 程式環境塊)
我們接下來學習系統中和程式相關的最後一個資料結構: PEB(程式環境塊)。
在學習PEB的資料結構之前,我們先要搞清幾個問題:
1. 和EPROCESS和KPROCESS不一樣,PEB位於使用者模式地址空間中,PEB可以放在使用者模式空間的任何地方。只要我們能告知作業系統它所在的記憶體位置即可(GDT也是放在使用者空間任意地方)。
它包含了映像載入器、堆管理器和其他的windows系統DLL所需要的資訊,因為它們需要在使用者模式下修改PEB中的資訊 2. 有三種方式可以訪問(定址)到PEB,它們分別對應於不同的應用場景。 2.1 在核心中我們可以先PsGetCurrentProcess得到當前程式的EPROCESS,然後通過它的成員域PEB訪問到當前程式 的PEB(注意,PEB是針對每個程式而言的,每個程式都有一個PEB) 2.2 在核心中,我們可以通過KPRCB獲得當前執行緒的ETHREAD,然後通過它的成員域來訪問當TEB,然後再通過TEB的成員域來訪問PEB 2.3 在使用者模式中,我們可以通過FS暫存器來得到TEB,然後通過TEB的成員域訪問到當前程式的PEB。
下面看看PEB的資料結構定義:
typedef struct _PEB { BOOLEAN InheritedAddressSpace; BOOLEAN ReadImageFileExecOptions; BOOLEAN BeingDebugged; BOOLEAN Spare; HANDLE Mutant; PVOID ImageBaseAddress; PPEB_LDR_DATA LoaderData; PRTL_USER_PROCESS_PARAMETERS ProcessParameters; PVOID SubSystemData; PVOID ProcessHeap; PVOID FastPebLock; PPEBLOCKROUTINE FastPebLockRoutine; PPEBLOCKROUTINE FastPebUnlockRoutine; ULONG EnvironmentUpdateCount; PPVOID KernelCallbackTable; PVOID EventLogSection; PVOID EventLog; PPEB_FREE_BLOCK FreeList; ULONG TlsExpansionCounter; PVOID TlsBitmap; ULONG TlsBitmapBits[0x2]; PVOID ReadOnlySharedMemoryBase; PVOID ReadOnlySharedMemoryHeap; PPVOID ReadOnlyStaticServerData; PVOID AnsiCodePageData; PVOID OemCodePageData; PVOID UnicodeCaseTableData; ULONG NumberOfProcessors; ULONG NtGlobalFlag; BYTE Spare2[0x4]; LARGE_INTEGER CriticalSectionTimeout; ULONG HeapSegmentReserve; ULONG HeapSegmentCommit; ULONG HeapDeCommitTotalFreeThreshold; ULONG HeapDeCommitFreeBlockThreshold; ULONG NumberOfHeaps; ULONG MaximumNumberOfHeaps; PPVOID *ProcessHeaps; PVOID GdiSharedHandleTable; PVOID ProcessStarterHelper; PVOID GdiDCAttributeList; PVOID LoaderLock; ULONG OSMajorVersion; ULONG OSMinorVersion; ULONG OSBuildNumber; ULONG OSPlatformId; ULONG ImageSubSystem; ULONG ImageSubSystemMajorVersion; ULONG ImageSubSystemMinorVersion; ULONG GdiHandleBuffer[0x22]; ULONG PostProcessInitRoutine; ULONG TlsExpansionBitmap; BYTE TlsExpansionBitmapBits[0x80]; ULONG SessionId; } PEB, *PPEB;
可能是我水平的原因,我翻遍了google和baidu都沒有找到一篇詳細講PEB結構的文章。希望有知道的朋友能不吝賜教,分享一些這方面的好的資料。我就我手上有的資源盡我的能力來分析一下這個PEB結構吧。
http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/NT%20Objects/Process/PEB.html http://blog.csdn.net/imquestion/article/details/16421 《windows核心情景分析》P247頁,PEB結構
1. BOOLEAN InheritedAddressSpace
不詳..
2. BOOLEAN ReadImageFileExecOptions
不詳..
3. BOOLEAN BeingDebugged
BeingDebugged域指示了當前這個程式是否正在被除錯。有一種反除錯技術就是在程式載入的時候檢測當前PEB的BeingDebugged域成員,以此來判斷當前程式是否處於"被除錯"狀態。
//這個函式也可以被用來檢測當前程式是否處在除錯狀態 BOOL WINAPI CheckRemoteDebuggerPresent( _In_ HANDLE hProcess, _Inout_ PBOOL pbDebuggerPresent );
4. BOOLEAN Spare
不詳...
5. HANDLE Mutant
不詳..
6. PVOID ImageBaseAddress
ImageBaseAddress域儲存的是程式映像基址,就是PE中的IMAGE_OPTIONAL_HEADER->ImageBase對應的值。對於EXE來說,預設的ImageBase為0x400000;對於DLL來說,它是0x10000000
7. PPEB_LDR_DATA LoaderData(也有叫Ldr的)
LoaderData域是PEB中一個很重要的成員域,它是一個指向PEB_LDR_DATA結構體的指標。它由PE Loader(載入器)填充,也就說,在這個指標指向的結構中,可以找到很多在PE中包含的資訊。另外,我們在做Buffer OverFlow的時候經常會遇到這個資料結構,列舉使用者程式載入的模組就和它密切相關。我們擴充套件出去,詳細學習一下這個結構。
typedef struct _PEB_LDR_DATA { ULONG Length; BOOLEAN Initialized; PVOID SsHandle; LIST_ENTRY InLoadOrderModuleList; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; } PEB_LDR_DATA, *PPEB_LDR_DATA;
7.1) ULONG Length
//結構體大小 Size of structure, used by ntdll.dll as structure version ID.
7.2) BOOLEAN Initialized
//指示是當前程式時候已經被初始化過了 If set, loader data section for current process is initialized.
7.3) 載入模組連結串列,這三個雙連結串列的內容都一樣,但是順序不一樣。
LIST_ENTRY nLoadOrderModuleList //Doubly linked list containing pointers to LDR_MODULE structure for previous and next module in load order. LIST_ENTRY InMemoryOrderModuleList //As above, but in memory placement order. LIST_ENTRY InInitializationOrderModuleList //As InLoadOrderModuleList, but in initialization order.
nLoadOrderModuleList、InMemoryOrderModuleList、InInitializationOrderModuleList這三個域都是指向它們各自的雙連結串列中的下一個LDR_MODULE的LIST_ENTRY
typedef struct _LDR_MODULE { LIST_ENTRY InLoadOrderModuleList; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; PVOID BaseAddress; PVOID EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; ULONG Flags; SHORT LoadCount; SHORT TlsIndex; LIST_ENTRY HashTableEntry; ULONG TimeDateStamp; } LDR_MODULE, *PLDR_MODULE;
(感謝原作者的勞動,這兩張圖很清晰的表名了動態定址程式載入模組的線路圖)
LoaderData 是指向 PEB_LDR_DATA 的指標,通過 PEB_LDR_DATA ,我們可以找到程式載入的所有模組(kernel32.dll、ntdll.dll、user.dll....)
例如我們可以利用類似的程式碼來進行對kernel32.dll的動態定址:
find_kernel32: push esi xor ecx, ecx mov esi, [fs:ecx+0x30] //PEB = FS:0x30 mov esi, [esi + 0x0c] //LDR = PEB:0x0c mov esi, [esi + 0x1c] //FLink = LDR:0x1c next_module: mov eax, [esi + 0x8] //函式返回時eax儲存模組基址 mov edi,[esi+0x20] //指向BaseDllName mov esi ,[esi] //這裡是轉移到連結串列的下一個 cmp [edi+12*2],cx //kernel32.dll 12*2個位元組最後一位正好是00 jne next_module pop esi Ret
這裡還要注意一個問題,就是既然這三個連結串列都是一樣的,那我們在列舉程式載入模組的時候隨便選一條就可以了嗎? 並不是這樣的,在XP和win7下InitializationOrderModuleList中節點的順序是不同的。
看雪上的這篇文章很好的說明了這點: http://bbs.pediy.com/showthread.php?t=149527
InLoadOrderModuleList在所有系統中,按照順序:第一個是EXE模組本身的ImageBase,第二個是NTDLL.DLL,第三個是KERNEL32.DLL
好,繼續回到我們的主線PEB的資料結構的學習上來。
8. PRTL_USER_PROCESS_PARAMETERS ProcessParameters
ProcessParameters域是指向 RTL_USER_PROCESS_PARAMETERS 的指標,RTL_USER_PROCESS_PARAMETERS 中是一些程式的引數。
typedef struct _RTL_USER_PROCESS_PARAMETERS { ULONG MaximumLength; ULONG Length; ULONG Flags; ULONG DebugFlags; PVOID ConsoleHandle; ULONG ConsoleFlags; HANDLE StdInputHandle; HANDLE StdOutputHandle; HANDLE StdErrorHandle; UNICODE_STRING CurrentDirectoryPath; HANDLE CurrentDirectoryHandle; UNICODE_STRING DllPath; UNICODE_STRING ImagePathName; UNICODE_STRING CommandLine; PVOID Environment; ULONG StartingPositionLeft; ULONG StartingPositionTop; ULONG Width; ULONG Height; ULONG CharWidth; ULONG CharHeight; ULONG ConsoleTextAttributes; ULONG WindowFlags; ULONG ShowWindowFlags; UNICODE_STRING WindowTitle; UNICODE_STRING DesktopName; UNICODE_STRING ShellInfo; UNICODE_STRING RuntimeData; RTL_DRIVE_LETTER_CURDIR DLCurrentDirectory[0x20]; } RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;
這個資料結構包含了當前程式的路徑,包含的DLL路徑,命令列,當前檔案目錄(在windows程式設計中,我們經常會需要用到當前相對路徑,當前絕對路徑)等很多資訊。詳情請參考:
http://undocumented.ntinternals.net/UserMode/Structures/RTL_USER_PROCESS_PARAMETERS.html
9. PVOID ProcessHeap
ProcessHeap 域指向的是程式堆(預設的那個)的首地址,每個程式在新建的時都會由系統自動建立一個預設堆以供使用。這也就是為什麼我們在程式中可以直接使用malloc來動態申請堆記憶體的時候不需要指定使用的是哪個堆。
int*p; p=(int*)malloc(sizeof(int));
同時,除了程式的預設堆之外,我們也可以在程式執行中建立新的堆,並從我們建立的堆中來分配記憶體。
HANDLE hp; HLOCAL h1; //建立一個動態堆 hp = HeapCreate(0, 0, 0); //從我們建立的堆中申請一個byte的堆空間 h1 = HeapAlloc(hp, HEAP_ZERO_MEMORY, 16);
10. PVOID FastPebLock
FastPebLock域存放的是PEBLOCKROUTINE這個例程函式需要用到的引數。
11. PEB加鎖/解鎖回撥例程
PPEBLOCKROUTINE FastPebLockRoutine;
PPEBLOCKROUTINE FastPebUnlockRoutine;
typedef void (*PPEBLOCKROUTINE) ( PVOID PebLock );
PEB中的這兩個域指向的是兩個回撥函式的入口地址,從名字上看是上鎖和解鎖的回撥函式。我不太清楚它們存在的意義。
12. ULONG EnvironmentUpdateCount
//程式的環境變數更改的次數 Counter of process environment updates.
13. PPVOID KernelCallbackTable
KernelCallbackTable域用於從核心"回撥"使用者空間的函式。
14. 其他
PVOID EventLogSection;
PVOID EventLog;
PPEB_FREE_BLOCK FreeList;
ULONG TlsExpansionCounter;
不詳..
15. PVOID TlsBitmap
TlsBitmap域代表TLS點陣圖
16. 其他
ULONG TlsBitmapBits[0x2]; PVOID ReadOnlySharedMemoryBase; PVOID ReadOnlySharedMemoryHeap; PPVOID ReadOnlyStaticServerData; PVOID AnsiCodePageData; PVOID OemCodePageData; PVOID UnicodeCaseTableData; ULONG NumberOfProcessors; ULONG NtGlobalFlag; BYTE Spare2[0x4]; LARGE_INTEGER CriticalSectionTimeout; ULONG HeapSegmentReserve; ULONG HeapSegmentCommit; ULONG HeapDeCommitTotalFreeThreshold; ULONG HeapDeCommitFreeBlockThreshold; ULONG NumberOfHeaps; ULONG MaximumNumberOfHeaps; PPVOID *ProcessHeaps; PVOID GdiSharedHandleTable; PVOID ProcessStarterHelper; PVOID GdiDCAttributeList; PVOID LoaderLock; ULONG OSMajorVersion; ULONG OSMinorVersion; ULONG OSBuildNumber; ULONG OSPlatformId; ULONG ImageSubSystem; ULONG ImageSubSystemMajorVersion; ULONG ImageSubSystemMinorVersion; ULONG GdiHandleBuffer[0x22]; ULONG PostProcessInitRoutine; ULONG TlsExpansionBitmap; BYTE TlsExpansionBitmapBits[0x80]; ULONG SessionId;
不詳..
(原諒我全部這樣寫上不詳,我確實沒在網上和書上找到有關PEB的相關資料結構的資料,不過Ldr這塊的內容倒是挺多了,利用PEB來列舉程式DLL模組也是個很成熟的技術了,在shellcode的編寫中,再次希望看到這篇文章的牛牛如果有關於這方面的補充資料的,望分享之,不勝感激)
至此,EPROCESS/KPROCESS/PEB的資料結構學習就結束了。我們可以發現,通過學習這些基礎的資料結構,我們可以積累大量的作業系統的相關知識,我甚至覺得這些結構才是windows的集大成者,通過這些結構的學習,我們可以輻射出去擴充套件到windows的很多領域的知識,以後應多多從這方面去思考一些技術。