繼續上一篇(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的很多領域的知識,以后應多多從這方面去思考一些技術。