在學習筆記(1)中,我們學習了IRP的數據結構的相關知識,接下來我們繼續來學習內核中很重要的另一批數據結構: EPROCESS/KPROCESS/PEB。把它們放到一起是因為這三個數據結構及其外延和windows中進程的表示關系密切,我們在做進程隱藏和進程枚舉的時候一定會涉及到這3類數據結構,所以這里有必要及時做一下總結,做個學習筆記,理清思路。
1. 相關閱讀材料
《windows 內核原理與實現》 --- 潘愛民
《深入解析windows操作系統(第4版,中文版)》 --- 潘愛民
《Windows核心編程(第四版)》中文版
首先我們來看一張windows的內核分布圖,我們之后要講的執行體和內核層都在上面。

每個windows進程都是由一個執行體進程塊(EPROCESS)塊來表示的。"EPROCESS塊中除了包含許多與進程有關的屬性之外,還包含和指向許多其他的相關數據結構"(這也是為什么能利用EPROCESS進行進程枚舉的原因)。例如,每個進程都有一個或多個線程,這些線程由執行體線程塊(ETHREAD)來表示。EPROCESS塊和相關的數據結構位於系統空間中,不過,進程環境塊(PEB)是個例外。它位於進程地址空間中(因為它包含一些需要由用戶模式代碼來修改的信息)。
除了EPROCESS塊以外,windows子系統進程(Csrss.exe)為每個windows進程維護了一個類似的結構。而且,windows子系統的內核部分(Win32k.sys)有一個針對每個進程的數據結構(KPROCESS),當一個線程第一次調用windows的USER或GDI函數(它們在內核模式中實現的)時,此數據結構就會被創建。在學習的一開始,我們對這些數據結構在windows系統的層級位置和大致的作用有一個了解很關鍵。接下來,我們來詳細了解這些數據結構。
一 . EPROCESS
EPEOCESS(執行體進程塊,E是Execute的意思,注意和KPROCESS區分開來)位於內核層之上(KPROCESS就內核層,我們之后會學習到KPROCESS),它側重於提供各種管理策略,同時為上層應用程序提供基本的功能接口。所以,在執行體層的進程和線程數據結構中,有些成員直接對應於上層應用程序中所看到的功能實體。
我們使用winDbg來查看windows XP下的EPROCESS數據結構(winDbg雙機調試的方法在《寒江獨釣》第一章以及網上有很多成熟的使用說明)。
我們接下來的例子都以windows XP下的notepad.exe作為實驗材料。首先,啟動winDbg后,找到notepad.exe這個進程(你在虛擬機里要先啟動notepad.exe哦)
!process 0 0 //查看當前進程 .... PROCESS 823e5490 SessionId: 0 Cid: 0af0 Peb: 7ffd5000 ParentCid: 02b8 DirBase: 0f200340 ObjectTable: e283dc30 HandleCount: 106. Image: alg.exe PROCESS 824a7020 SessionId: 0 Cid: 00a0 Peb: 7ffda000 ParentCid: 0668 DirBase: 0f2001a0 ObjectTable: e292f898 HandleCount: 44. Image: notepad.exe ...
可以看到 PROCESS 824a7020 ,824a7020 就是notepad.exe的_EPROCESS結構地址(這個地址在notepad的生命周期中就不會變了)
dt _eprocess 824a7020 //查看notepad.exe的_EPROCESS kd> dt _eprocess 824a7020 ntdll!_EPROCESS +0x000 Pcb : _KPROCESS +0x06c ProcessLock : _EX_PUSH_LOCK +0x070 CreateTime : _LARGE_INTEGER 0x1ceee34`5d8ce8a2 +0x078 ExitTime : _LARGE_INTEGER 0x0 +0x080 RundownProtect : _EX_RUNDOWN_REF +0x084 UniqueProcessId : 0x000000a0 +0x088 ActiveProcessLinks : _LIST_ENTRY [ 0x8055b158 - 0x823e5518 ] +0x090 QuotaUsage : [3] 0xa50 +0x09c QuotaPeak : [3] 0xc48 +0x0a8 CommitCharge : 0x184 +0x0ac PeakVirtualSize : 0x244d000 +0x0b0 VirtualSize : 0x1f87000 +0x0b4 SessionProcessLinks : _LIST_ENTRY [ 0xf8b6a014 - 0x823e5544 ] +0x0bc DebugPort : (null) +0x0c0 ExceptionPort : 0xe1982848 +0x0c4 ObjectTable : 0xe292f898 _HANDLE_TABLE +0x0c8 Token : _EX_FAST_REF +0x0cc WorkingSetLock : _FAST_MUTEX +0x0ec WorkingSetPage : 0xc6af +0x0f0 AddressCreationLock : _FAST_MUTEX +0x110 HyperSpaceLock : 0 +0x114 ForkInProgress : (null) +0x118 HardwareTrigger : 0 +0x11c VadRoot : 0x82401480 +0x120 VadHint : 0x8256a530 +0x124 CloneRoot : (null) +0x128 NumberOfPrivatePages : 0xc0 +0x12c NumberOfLockedPages : 0 +0x130 Win32Process : 0xe109b210 +0x134 Job : (null) +0x138 SectionObject : 0xe19d2c18 +0x13c SectionBaseAddress : 0x01000000 +0x140 QuotaBlock : 0x82cf5db8 _EPROCESS_QUOTA_BLOCK +0x144 WorkingSetWatch : (null) +0x148 Win32WindowStation : 0x0000003c +0x14c InheritedFromUniqueProcessId : 0x00000668 +0x150 LdtInformation : (null) +0x154 VadFreeHint : (null) +0x158 VdmObjects : (null) +0x15c DeviceMap : 0xe21f5258 +0x160 PhysicalVadList : _LIST_ENTRY [ 0x824a7180 - 0x824a7180 ] +0x168 PageDirectoryPte : _HARDWARE_PTE_X86 +0x168 Filler : 0 +0x170 Session : 0xf8b6a000 +0x174 ImageFileName : [16] "notepad.exe" +0x184 JobLinks : _LIST_ENTRY [ 0x0 - 0x0 ] +0x18c LockedPagesList : (null) +0x190 ThreadListHead : _LIST_ENTRY [ 0x823c224c - 0x823c224c ] +0x198 SecurityPort : (null) +0x19c PaeTop : 0xf8cba1a0 +0x1a0 ActiveThreads : 1 +0x1a4 GrantedAccess : 0x1f0fff +0x1a8 DefaultHardErrorProcessing : 1 +0x1ac LastThreadExitStatus : 0 +0x1b0 Peb : 0x7ffda000 _PEB +0x1b4 PrefetchTrace : _EX_FAST_REF +0x1b8 ReadOperationCount : _LARGE_INTEGER 0x1 +0x1c0 WriteOperationCount : _LARGE_INTEGER 0x0 +0x1c8 OtherOperationCount : _LARGE_INTEGER 0x248 +0x1d0 ReadTransferCount : _LARGE_INTEGER 0x57ce +0x1d8 WriteTransferCount : _LARGE_INTEGER 0x0 +0x1e0 OtherTransferCount : _LARGE_INTEGER 0x18846 +0x1e8 CommitChargeLimit : 0 +0x1ec CommitChargePeak : 0x184 +0x1f0 AweInfo : (null) +0x1f4 SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO +0x1f8 Vm : _MMSUPPORT +0x238 LastFaultCount : 0 +0x23c ModifiedPageCount : 7 +0x240 NumberOfVads : 0x42 +0x244 JobStatus : 0 +0x248 Flags : 0xd0800 +0x248 CreateReported : 0y0 +0x248 NoDebugInherit : 0y0 +0x248 ProcessExiting : 0y0 +0x248 ProcessDelete : 0y0 +0x248 Wow64SplitPages : 0y0 +0x248 VmDeleted : 0y0 +0x248 OutswapEnabled : 0y0 +0x248 Outswapped : 0y0 +0x248 ForkFailed : 0y0 +0x248 HasPhysicalVad : 0y0 +0x248 AddressSpaceInitialized : 0y10 +0x248 SetTimerResolution : 0y0 +0x248 BreakOnTermination : 0y0 +0x248 SessionCreationUnderway : 0y0 +0x248 WriteWatch : 0y0 +0x248 ProcessInSession : 0y1 +0x248 OverrideAddressSpace : 0y0 +0x248 HasAddressSpace : 0y1 +0x248 LaunchPrefetched : 0y1 +0x248 InjectInpageErrors : 0y0 +0x248 VmTopDown : 0y0 +0x248 Unused3 : 0y0 +0x248 Unused4 : 0y0 +0x248 VdmAllowed : 0y0 +0x248 Unused : 0y00000 (0) +0x248 Unused1 : 0y0 +0x248 Unused2 : 0y0 +0x24c ExitStatus : 259 +0x250 NextPageColor : 0x8d96 +0x252 SubSystemMinorVersion : 0 '' +0x253 SubSystemMajorVersion : 0x4 '' +0x252 SubSystemVersion : 0x400 +0x254 PriorityClass : 0x2 '' +0x255 WorkingSetAcquiredUnsafe : 0 '' +0x258 Cookie : 0x5c77be6c
並結合windows的"開源"學習版本的源代碼: WRK。 在\base\ntos\inc\ps.h中,可以找到相關的數據結構的定義說明。
typedef struct _EPROCESS { KPROCESS Pcb; EX_PUSH_LOCK ProcessLock; LARGE_INTEGER CreateTime; LARGE_INTEGER ExitTime; EX_RUNDOWN_REF RundownProtect; HANDLE UniqueProcessId; LIST_ENTRY ActiveProcessLinks; SIZE_T QuotaUsage[PsQuotaTypes]; SIZE_T QuotaPeak[PsQuotaTypes]; SIZE_T CommitCharge; SIZE_T PeakVirtualSize; SIZE_T VirtualSize; LIST_ENTRY SessionProcessLinks; PVOID DebugPort; PVOID ExceptionPort; PHANDLE_TABLE ObjectTable; EX_FAST_REF Token; PFN_NUMBER WorkingSetPage; KGUARDED_MUTEX AddressCreationLock; KSPIN_LOCK HyperSpaceLock; struct _ETHREAD *ForkInProgress; ULONG_PTR HardwareTrigger; PMM_AVL_TABLE PhysicalVadRoot; PVOID CloneRoot; PFN_NUMBER NumberOfPrivatePages; PFN_NUMBER NumberOfLockedPages; PVOID Win32Process; struct _EJOB *Job; PVOID SectionObject; PVOID SectionBaseAddress; PEPROCESS_QUOTA_BLOCK QuotaBlock; PPAGEFAULT_HISTORY WorkingSetWatch; HANDLE Win32WindowStation; HANDLE InheritedFromUniqueProcessId; PVOID LdtInformation; PVOID VadFreeHint; PVOID VdmObjects; PVOID DeviceMap; PVOID Spare0[3]; union { HARDWARE_PTE PageDirectoryPte; ULONGLONG Filler; }; PVOID Session; UCHAR ImageFileName[ 16 ]; LIST_ENTRY JobLinks; PVOID LockedPagesList; LIST_ENTRY ThreadListHead; PVOID SecurityPort; PVOID PaeTop; ULONG ActiveThreads; ACCESS_MASK GrantedAccess; ULONG DefaultHardErrorProcessing; NTSTATUS LastThreadExitStatus; PPEB Peb; EX_FAST_REF PrefetchTrace; LARGE_INTEGER ReadOperationCount; LARGE_INTEGER WriteOperationCount; LARGE_INTEGER OtherOperationCount; LARGE_INTEGER ReadTransferCount; LARGE_INTEGER WriteTransferCount; LARGE_INTEGER OtherTransferCount; SIZE_T CommitChargeLimit; SIZE_T CommitChargePeak; PVOID AweInfo; SE_AUDIT_PROCESS_CREATION_INFO SeAuditProcessCreationInfo; MMSUPPORT Vm; LIST_ENTRY MmProcessLinks; ULONG ModifiedPageCount; ULONG JobStatus; union { ULONG Flags; struct { ULONG CreateReported : 1; ULONG NoDebugInherit : 1; ULONG ProcessExiting : 1; ULONG ProcessDelete : 1; ULONG Wow64SplitPages : 1; ULONG VmDeleted : 1; ULONG OutswapEnabled : 1; ULONG Outswapped : 1; ULONG ForkFailed : 1; ULONG Wow64VaSpace4Gb : 1; ULONG AddressSpaceInitialized : 2; ULONG SetTimerResolution : 1; ULONG BreakOnTermination : 1; ULONG SessionCreationUnderway : 1; ULONG WriteWatch : 1; ULONG ProcessInSession : 1; ULONG OverrideAddressSpace : 1; ULONG HasAddressSpace : 1; ULONG LaunchPrefetched : 1; ULONG InjectInpageErrors : 1; ULONG VmTopDown : 1; ULONG ImageNotifyDone : 1; ULONG PdeUpdateNeeded : 1; // NT32 only ULONG VdmAllowed : 1; ULONG SmapAllowed : 1; ULONG CreateFailed : 1; ULONG DefaultIoPriority : 3; ULONG Spare1 : 1; ULONG Spare2 : 1; }; }; NTSTATUS ExitStatus; USHORT NextPageColor; union { struct { UCHAR SubSystemMinorVersion; UCHAR SubSystemMajorVersion; }; USHORT SubSystemVersion; }; UCHAR PriorityClass; MM_AVL_TABLE VadRoot; ULONG Cookie; } EPROCESS, *PEPROCESS;
不好意思一下子復制了一大堆的代碼出來,也說明EPOCESS在windows內核中的地位,所以很復雜,我們現在來一條一條的學習這個EPROCESS的數據結構成員,和其他內核中數據結構的聯系以及相關的應用場景和外延。
1. KPROCESS Pcb
Pcb域即KPROCESS結構體,它們是同一種東西,只是兩種叫法而已,我們現在只要知道幾點,KRPOCESS的詳細細節我們放到后面講:
1) KPROCESS位於比EPROCESS更底層的內核層中 2) KPROCESS被內核用來進行線程調度使用
這里還要注意的是,因為Pcb域是EPROCESS結構的第一個成員,所以在系統內部,一個進程的KPROCESS對象的地址和EPROCESS對象的地址是相同的。這種情況和"TIB就是TEB結構的第一個成員,而EXCEPTION_REGISTRATION_RECORD又是TIB的第一個成員,又因為FS:[0x18] 總是指向當前線程的 TEB 。 所以導致用 FS:[0x18] 就直接可以尋址到SEH的鏈表了"。windows中的這種結構體的嵌套思想,應該予以領會。
2. EX_PUSH_LOCK ProcessLock
ProcessLock域是一個推鎖(push lock)對象,用於保護EPROCESS中的數據成員(回想IRP中也同樣有一種類似的鎖機制)。用來對可能產生的並行事件強制串行化。
typedef struct _EX_PUSH_LOCK { union { ULONG Locked: 1; ULONG Waiting: 1; ULONG Waking: 1; ULONG MultipleShared: 1; ULONG Shared: 28; ULONG Value; PVOID Ptr; }; } EX_PUSH_LOCK, *PEX_PUSH_LOCK;
3. LARGE_INTEGER CreateTime / LARGE_INTEGER ExitTime
這兩個域分別代表了進程的創建時間和退出時間,windows在內核中會記錄大量和進程周邊的相關信息,例如創建事件,運行時間,負載,使用內存數等,一方面對於上層應用來說,我們可以很方便地調用一些API去獲取到實時的系統運行狀態,另一方面操作系統本身也可以依據這些信息對進程進行合理的調度。
typedef union _LARGE_INTEGER { struct { DWORD LowPart; LONG HighPart; }; struct { DWORD LowPart; LONG HighPart; } u; LONGLONG QuadPart; } LARGE_INTEGER, *PLARGE_INTEGER;
+0x070 CreateTime : _LARGE_INTEGER 0x1ceee34`5d8ce8a2
4. EX_RUNDOWN_REF RundownProtect
RundownProtect域是進程的停止保護鎖,當一個進程到最后被銷毀時,它要等到所有其他進程和線程已經釋放了此鎖,才可以繼續進行,否則就會產生孤兒線程。加鎖機制也是windows中進程間或線程間同步的一個很經典的機制,進程只要設置阻塞等待在一個鎖對象上,等待擁有那個鎖的其他進程或線程釋放鎖對象,操作系統會發出signal信號,重新激活之前阻塞等待在那個鎖上的進程,這樣就完成了進程間,線程間的同步。
struct EX_RUNDOWN_REF typedef struct _EX_RUNDOWN_REF { union { ULONG Count; PVOID Ptr; }; } EX_RUNDOWN_REF, *PEX_RUNDOWN_REF;
5. HANDLE UniqueProcessId
UniqueProcessId域是進程的唯一編號,在進程創建時就設定好了,我們在"任務管理器"中看到的PID就是從這個域中獲取的值。
+0x084 UniqueProcessId : 0x000000a0

6. LIST_ENTRY ActiveProcessLinks
ActiveProcessLinks域是一個雙鏈表節點(注意是雙鏈表中的一個節點),在windows系統中,所有的活動進程都連接在一起,構成了一個鏈表。
表頭是全局變量PsActiveProcessHead。內部變量PsActiveProcessHead是一個LIST_ENTRY結構,它是一個雙鏈表的表頭,在windows的進程雙鏈表中指定了系統進 程列表的第一個成員(回想數據結構中雙鏈表需要一個和鏈表項相同的表頭結構來當作入口點)。 這里注意一下: PID=4的System的ActiveProcessLinks其實也就是PsActiveProcessHead。即系 統進程System的LIST_ENTRY結構充當這個進程雙鏈表的表頭。
當一個進程被創建時,其ActiveProcessLinks域將被作為"節點"加入到內核中的進程雙鏈表中,當進程被刪除時,則從鏈表中移除。如果我們需要枚舉所有的進程,直接操縱此鏈表即可。思路其實很清晰的,利用PsGetCurrentProcess()獲得當前進程的EPROCESS結構,在根據指定偏移找到Flink和Blink,后面的事情就是數據結構中的遍歷雙鏈表了。
typedef struct _LIST_ENTRY { struct _LIST_ENTRY *Flink; struct _LIST_ENTRY *Blink; } LIST_ENTRY, *PLIST_ENTRY;
+0x088 ActiveProcessLinks : _LIST_ENTRY [ 0x8055b158 - 0x823e5518 ]
我們暫且不表,將進程枚舉的知識放到后面的學習筆記中,我們集中精力學習EROCESS的數據結構。
7. 進程內存配額使用
ULONG QuotaUsage[3] ULONG QuotaPeak[3]
QuotaUsage和QuotaPeak域是指一個進程的內存使用量和尖峰使用量(作用我們之前說過,即為了實時地報告出當前進程運行的性能數據)。這兩個域是數組,其中的元素:
[0]: 非換頁內存池
[1]: 換頁內存池
[2]: 交換文件中的內存使用情況。
QuotaUsage[3]和QuotaPeak[3]這兩個數組是在PspChargeQuota函數中計算的。在WRK中 \base\ntos\ps\psquota.c 有具體的函數實現。
NTSTATUS FORCEINLINE PspChargeQuota (
IN PEPROCESS_QUOTA_BLOCK QuotaBlock,
IN PEPROCESS Process,
IN PS_QUOTA_TYPE QuotaType,
IN SIZE_T Amount)
{
..
}
+0x090 QuotaUsage : [3] 0xa50 +0x09c QuotaPeak : [3] 0xc48
8. 進程虛擬內存使用
ULONG CommitCharge
ULONG PeakVirtualSize
ULONG VirtualSize
CommitCharge域中存儲了一個進程的虛擬內存已提交的"頁面數量"。PeakVirtualSize域是指虛擬內存大小的尖峰值。VirtualSize域是指一個進程的虛擬內存大小。
+0x0a8 CommitCharge : 0x184 +0x0ac PeakVirtualSize : 0x244d000 +0x0b0 VirtualSize : 0x1f87000

(我們在任務管理器中看到的這些性能參數就是從這些域中計算總和得到的)
9. LIST_ENTRY SessionProcessLinks
SessionProcessLinks域是一個雙鏈表節點,當進程加入到一個系統會話中時,這個進程的SessionProcessLinks域將作為一個節點(LIST_ENTRY在內核中很常見)加入到該會話的進程鏈表中。
+0x0b4 SessionProcessLinks : _LIST_ENTRY [ 0xf8b6a014 - 0x823e5544 ]
10. 端口
PVOID DebugPort;
PVOID ExceptionPort;
DebugPort和ExceptionPort域是兩個句柄(指針),分別指向當前進程對應的調試端口和異常端口。當該進程的線程發生用戶模式的異常時(軟件中斷)時,內核的異常處理例程在處理異常過程中,將向
該進程的"異常端口"或"調試端口"發送消息,從而使這些端口的接收方(調試器或windows子系統)能夠處理該異常。這涉及到windows的異常分發過程。請參考《windows內核原理與實現》
5.2.7節: 異常分發。這里總結一下windows異常發生時的處理順序。
發生異常時系統的處理順序: 1.系統首先判斷異常是否應發送給目標程序的異常處理例程,如果決定應該發送,並且目標程序正在被調試,則系統 掛起程序並向調試器發送EXCEPTION_DEBUG_EVENT消息.注意,這也是用來探測調試器的存在的技術之一 2.如果你的程序沒有被調試或者調試器未能處理異常,系統就會繼續查找你是否安裝了線程相關的異常處理例程,如果 你安裝了線程相關的異常處理例程,系統就把異常發送給你的程序seh處理例程,交由其處理. 3.每個線程相關的異常處理例程可以處理或者不處理這個異常,如果他不處理並且安裝了多個線程相關的異常處理例程, 可交由鏈起來的其他例程處理. 4.如果這些例程均選擇不處理異常,如果程序處於被調試狀態,操作系統仍會再次掛起程序通知debugger. 5.如果程序未處於被調試狀態或者debugger沒有能夠處理,並且你調用SetUnhandledExceptionFilter安裝了最后異 常處理例程的話,系統轉向對它的調用. 6.如果你沒有安裝最后異常處理例程或者他沒有處理這個異常,系統會調用默認的系統處理程序,通常顯示一個對話框, 你可以選擇關閉或者最后將其附加到調試器上的調試按鈕.如果沒有調試器能被附加於其上或者調試器也處理不了,系統 就調用ExitProcess終結程序. 7.不過在終結之前,系統仍然對發生異常的線程異常處理句柄來一次展開,這是線程異常處理例程最后清理的機會.
+0x0bc DebugPort : (null) +0x0c0 ExceptionPort : 0xe1982848
11. PHANDLE_TABLE ObjectTable
ObjectTable域是當前進程的句柄表。句柄是一個抽象概念,代表了進程已打開的一個對象。句柄表包含了所有已經被該進程打開的那些"對象"的引用(這里對象的概念很大了,所有的內核對象都算對象)。
windows進程的句柄表是一個層次結構。
windows執行體實現了一套對象機制來管理各種資源或實體。每種對象都有一個"類型對象",類型對象定義了該類對象的一些特性和方法。對象管理器中的對象是執行體對象(在內核空間中),
它們位於系統空間中,在進程空間不能通過地址來引用它們。windows使用句柄(handle)來管理進程中的對象引用(類似我們進行文件操作時返回的文件對象句柄)。
當一個進程利用名稱來創建或打開一個對象時,將獲得一個句柄(這個句柄本質上一個指針,保存在當前進程的EPROCESS的句柄表中)。
該句柄指向所創建或打開的對象(同時,被創建或打開的內核對象的引用計數+1)。以后,該進程無須再次使用名稱來引用該對象,使用此句柄訪問即可。windows這樣做可以顯著提高引用對象的效率。
在windows中,句柄是進程范圍內的對象引用,換句話說,句柄僅在一個進程范圍內有效。一個進程中的句柄傳遞給另一個進程后(例如父進程創建了子進程時會自動復制父進程擁有的句柄表),
但是句柄值已經不一樣了,不同進程空間中可能引用的是同一個對象,但是它們的句柄值不同(聯想進程的空間獨立性)。
實際上,windows支持的句柄是一個"索引",指向該句柄所在進程的句柄表(handle table)中的一個表項。
base\ntos\inc\ex.h
typedef struct _HANDLE_TABLE { //指向句柄表的結構 ULONG TableCode; //句柄表的內存資源記錄在此進程中 PEPROCESS QuotaProcess; //創建進程的ID,用於回調函數 PVOID UniqueProcessId; //句柄表鎖,僅在句柄表擴展時使用 EX_PUSH_LOCK HandleLock; //所有的句柄表形成一個鏈表(這個成員域用來指向下一個句柄表節點的),鏈表頭為全局變量HandleTableListHead LIST_ENTRY HandleTableList; //若在訪問句柄時發生競爭,則在此推鎖上阻塞等待 EX_PUSH_LOCK HandleContentionEvent; //調試信息,僅當調試句柄時才有意義 PHANDLE_TRACE_DEBUG_INFO DebugInfo; //審計信息所占用的頁面數量 LONG ExtraInfoPages; //空閑鏈表表頭的句柄索引 LONG FirstFreeHandle; //最近被釋放的句柄索引,用於FIFO類型空閑鏈表 PHANDLE_TABLE_ENTRY LastFreeHandleEntry; //下一次句柄表擴展的起始句柄索引 ULONG NextHandleNeedingPool; //正在使用的句柄表項的數量 LONG HandleCount; union { //標志域 ULONG Flags; //是否使用FIFO風格的重用,即先釋放還是先重用 BOOLEAN StrictFIFO : 1; } } HANDLE_TABLE, *PHANDLE_TABLE;
HANDLE_TABLE結構中的第一個域成員TableCode域是一個指針,指向句柄表的最高層表項頁面(前面說過句柄表是一個多層的結構),它的低2bit的值代表了當前句柄表的層數。
1) 如果TableCode的最低2位為0(..00)。說明句柄表只有一層(從0開始計數),這種情況下該進程最多只能容納512個句柄(宏定義LOWLEVEL_THRESHOLD)。 2) 如果TableCode的最低2位為1(..01)。說明句柄表有兩層,這種情況下該進程可容納的句柄數是512*1024(宏定義MIDLEVEL_THRSHOLD)。 3) 如果TableCode的最低2位為2(..10)。說明句柄表有三層,這種情況下該進程可容納的句柄數是512*1024*1024(宏定義HIGHLEVEL_THRSHOLD)。
但windows執行體限定每個進程的句柄數不得超過2^24=16777216(宏定義MAX_HANDLES)。

看到這里,我們要理清一下思路:
1. 在EPROCESS中有一個 PHANDLE_TABLE ObjectTable 成員域指向的是一個HENDLE_TABLE數據結構,這個結構並不是真正保存句柄表真實數據的地址,我們可以把這個
結構理解為一個句柄表的meta元數據結構。在HANDLE_TABLE數據結構的第一個域成員TableCode才是指向"這個句柄表"對應的真實句柄數組數據。
2. TableCode指向的這個句柄數據的數據結構為: HANDLE_TABLE_ENTRY。即圖中的每個小格子為一個HANDLE_TABLE_ENTRY結構
typedef struct _HANDLE_TABLE_ENTRY { union { PVOID Object; ULONG ObAttributes; PHANDLE_TABLE_ENTRY_INFO InfoTable; ULONG_PTR Value; }; union { union { ACCESS_MASK GrantedAccess; struct { USHORT GrantedAccessIndex; USHORT CreatorBackTraceIndex; }; }; LONG NextFreeTableEntry; }; } HANDLE_TABLE_ENTRY, *PHANDLE_TABLE_ENTRY;
3. 每個HANDLE_TABLE_ENTRY結構體,即每個句柄的大小的為4byte,而windows執行體在分配句柄表內存時按頁面(32位系統下4KB大小)來申請內存。因此,才有了我們
之前看到了每層的句柄數1024。
4. 那我們在圖中看到的不同的句柄表(注意是表,一個表中有很多句柄項)之間的的鏈接是怎么回事呢?這是通過TABLE_HANDLE中的成員域HandleTableList來進行鏈接的,這個一個LIST_ENTRY結構,也即和進程雙鏈表一樣,這是一個句柄表雙鏈表。
總結一下:EPROCESS中的PHANDLE_TABLE ObjectTable 成員域指向的是一個HENDLE_TABLE數據結構,這個域的第一個成員的低2位很重要,它決定了這個進程中總 共有多少個這樣的PHANDLE_TABLE ObjectTable結構,例如(..10),那就有3個PHANDLE_TABLE ObjectTable結構,它們依靠PHANDLE_TABLE ObjectTable中 的HandleTableList來形成一個雙鏈表,依次形成層次關系,而PHANDLE_TABLE ObjectTable中的TableCode指向的才是指向真實的句柄數組,每個句柄以 HANDLE_TABLE_ENTRY節誒狗來標識,大小為4byte。所有的HANDLE_TABLE_ENTRY形成一個句柄數組組成一層這樣的"句柄數據組",總共有3排 (因為TableCode的低2位為..10)。 這樣,windows中一個進程的句柄表就被表現出來了。
+0x0c4 ObjectTable : 0xe292f898 _HANDLE_TABLE +0x000 TableCode : 0xe10fd000 +0x004 QuotaProcess : 0x824a7020 _EPROCESS +0x008 UniqueProcessId : 0x000000a0 +0x00c HandleTableLock : [4] _EX_PUSH_LOCK +0x01c HandleTableList : _LIST_ENTRY [ 0xe1090f64 - 0xe283dc4c ] +0x024 HandleContentionEvent : _EX_PUSH_LOCK +0x028 DebugInfo : (null) +0x02c ExtraInfoPages : 0 +0x030 FirstFree : 0xac +0x034 LastFree : 0 +0x038 NextHandleNeedingPool : 0x800 +0x03c HandleCount : 48 +0x040 Flags : 0 +0x040 StrictFIFO : 0y0
更多的關於句柄表的數據結構和句柄表隨着進程創建和運行過程中的擴展和收縮請參考《windows 內核原理與實現》3.4.1: windows進程的句柄表。我們這里就不繼續展開了。
12. EX_FAST_REF Token
Token域是一個快速引用,指向該進程的訪問令牌,用於該進程的安全訪問檢查。這是進程訪問令牌相關的知識,之前做西電線上賽的時候就遇到一題是要提升進程訪問令牌權限的要求。
BOOL WINAPI OpenProcessToken(
_In_ HANDLE ProcessHandle,
_In_ DWORD DesiredAccess,
_Out_ PHANDLE TokenHandle
);
+0x0c8 Token : _EX_FAST_REF +0x000 Object : 0xe10ea4bb +0x000 RefCnt : 0y011 +0x000 Value : 0xe10ea4bb
http://msdn.microsoft.com/library/aa379295(VS.85).aspx
相關的內容請參考《Windows核心編程(第四版)》中文版》4.5章,有關UAC的部分。這里提供一個提升進程Access Token權限的鏈接,小聶博客看到的,當時用的就是這個思路
http://xiaonieblog.com/?post=81
13. PFN_NUMBER WorkingSetPage
WorkingSetPage域是指包含進程工作集的頁面
工作集是指一個進程當前正在使用的物理頁面的集合。在windows中,有3種工作工作集。 1) 進程工作集 2) 系統工作集 系統工作集是指System進程(由全局變量PsInitialSystemProcess表示)的工作集,其中包括系統模塊的映像區(比如ntoskrnl.exe和設備驅動程序)、換頁內存池和系統緩存(cache) 3) 會話工作集 會話工作集是指一個會話所屬的代碼和數據區,包括windows子系統用到的與會話有關的數據結構、會話換頁內存池、會話的映射視圖,以及會話空間中的設備驅動程序。
每個進程都有一個專門的頁面用來存放它的"工作集鏈表",這是在創建進程地址空間時已經映射好的一個頁面,其地址位於全局變量MmWorkingSetList中,所以,在任何一個進程中,通過變量MmWokingSetList就可以訪問到它的"工作集鏈表"。windows通過MmWokingSetList來跟蹤一個進程所使用的物理頁面。 這個MmWokingSetList的類型為"MMWSL":
typedef struct _MMWSL { WSLE_NUMBER FirstFree; WSLE_NUMBER FirstDynamic; WSLE_NUMBER LastEntry; WSLE_NUMBER NextSlot; // The next slot to trim PMMWSLE Wsle; WSLE_NUMBER LastInitializedWsle; WSLE_NUMBER NonDirectCount; PMMWSLE_HASH HashTable; ULONG HashTableSize; ULONG NumberOfCommittedPageTables; PVOID HashTableStart; PVOID HighestPermittedHashAddress; ULONG NumberOfImageWaiters; ULONG VadBitMapHint; USHORT UsedPageTableEntries[MM_USER_PAGE_TABLE_PAGES]; ULONG CommittedPageTables[MM_USER_PAGE_TABLE_PAGES/(sizeof(ULONG)*8)]; } MMWSL, *PMMWSL;
更多的內容請學習《windows 內核原理與實現》 4.6章: 工作集管理的相關知識。因為windows內核的知識很多,要在一次的學習中把所有相關的知識點都學到,那就有點像無線遞歸的感覺了,一層又一層。我們把工作集的內容放到以后研究,但是有一點很重要,我們要記住的一點是: 工作集定了這個進程所擁有的所有內存頁,那進程的內存分配,使用,調度算法一定和工作集有關,如果以后我們遇到有關內存調度算法的題目,我們應該要能反應到一些相關的基礎知識點,然后到這些經典的書上去翻閱相關知識,這樣慢慢積累下來感覺會比較好。
14. KGUARDED_MUTEX AddressCreationLock
AddressCreationLock域是一個守護互斥體鎖(guard mutex),用於保護對地址空間的操作。當內核代碼需要對虛擬地址空間進行操作時(回想IRP中講到3種緩沖區時說過,UserBuffer方式,內核代碼直接訪問用戶空間的內存數據),它必須在AddressCreationLock上執行"獲取鎖"操作,完成以后再"釋放鎖"操作。
base\ntos\mm\mi.h中的宏可以簡化代碼:
#define LOCK_ADDRESS_SPACE(PROCESS) KeAcquireGuardedMutex (&((PROCESS)->AddressCreationLock)); #define UNLOCK_ADDRESS_SPACE(PROCESS) KeReleaseGuardedMutex (&((PROCESS)->AddressCreationLock));
15. KSPIN_LOCK HyperSpaceLock
HyperSpaceLock是一個自旋鎖,用於保護進程的超空間。
typedef struct { volatile unsigned int lock; } spinlock_t;
16. struct _ETHREAD *ForkInProgress
ForkInProgress指向正在復制地址空間的那個線程,僅當在地址空間復制過程中,此域才會被賦值,在其他情況下為NULL。
base\ntos\mm\forksup.c:
NTSTATUS
MiCloneProcessAddressSpace (
IN PEPROCESS ProcessToClone,
IN PEPROCESS ProcessToInitialize
)
17. ULONG_PTR HardwareTrigger
HardwareTrigger用於記錄硬件錯誤性能分析次數
18.進程的復制
PMM_AVL_TABLE PhysicalVadRoot: PhysicalVadRoot域指向進程的物理VAD的根。它並不總是存在,只有當確實需要映射物理內存時才會被創建。
PVOID CloneRoot: CloneRoot指向一個平衡樹的根,當進程地址空間復制時,此樹被創建,創建出來后,一直到進程退出的時候才被銷毀。CloneRoot域完全是為了支持fork語義而引入。
PFN_NUMBER NumberOfPrivatePages: 指進程私有頁面的數量
PFN_NUMBER NumberOfLockedPages: 指進程被鎖住的頁面的數量
+0x11c VadRoot : 0x82401480 +0x120 VadHint : 0x824392c8 +0x124 CloneRoot : (null) +0x128 NumberOfPrivatePages : 0xd9 +0x12c NumberOfLockedPages : 0
19. PVOID Win32Process / struct _EJOB *Job
Win32Process域是一個指針,指向由windows子系統管理的進程區域,如果此值不為NULL,說明這是一個windows子系統進程(GUI進程)。對於job域,只有當一個進程屬於一個job(作業)的時候,它才會指向一個_EJOB對象。
+0x130 Win32Process : 0xe109b210 +0x134 Job : (null)
20. 進程對應的內存區
PVOID SectionObject: SectionObject域也是一個指針,代表進程的內存去對象(進程的可執行映像文件的內存區對象)
PVOID SectionBaseAddress: SectionBaseAddress域為該內存區對象的基地址
+0x138 SectionObject : 0xe19d2c18 +0x13c SectionBaseAddress : 0x01000000
21. PEPROCESS_QUOTA_BLOCK QuotaBlock
QuotaBlock域指向進程的配額塊,進程的配額塊類型為: EPROCESS_QUOTA_BLOCK
typedef struct _EPROCESS_QUOTA_BLOCK { struct _EPROCESS_QUOTA_ENTRY QuotaEntry[3]; struct _LIST_ENTRY QuotaList; ULONG32 ReferenceCount; ULONG32 ProcessCount; }EPROCESS_QUOTA_BLOCK, *PEPROCESS_QUOTA_BLOCK;
注意到結構中有我們熟悉的LIST_ENTRY雙鏈表結構,心里基本猜到了。windows系統中的"配額塊"相互串起來構成了一個雙鏈表,每個配額塊都可以被多個進程共享,所以有一個引用計數指用來說明當前有多少個進程正在使用這一配額塊。配額塊中主要定義了非換頁內存池、換頁內存池、交換文件中的內存配額限制。
這里要注意的是,所有配額塊構成的雙鏈表的表頭為PspQuotaBlockList。系統的默認配額塊為PspDefaultQuotaBlock。
+0x140 QuotaBlock : 0x82cf5db8 _EPROCESS_QUOTA_BLOCK +0x000 QuotaEntry : [3] _EPROCESS_QUOTA_ENTRY +0x030 QuotaList : _LIST_ENTRY [ 0x82c4dea0 - 0x82ca7700 ] +0x038 ReferenceCount : 0x751 +0x03c ProcessCount : 7
22. PPAGEFAULT_HISTORY WorkingSetWatch
WorkingSetWatch域用於監視一個進程的頁面錯誤,一旦啟用了頁面錯誤監視功能(由全局變量PsWatchEnabled開關來控制),則每次發生頁面錯誤都會將該頁面錯誤記錄到WorkingSetWatch域的WatchInfo成員數組中,知道數組滿為止。
相關的處理函數為: PsWatchWorkingSet()。
typedef struct _PAGEFAULT_HISTORY { ULONG CurrentIndex; ULONG MapIndex; KSPIN_LOCK SpinLock; PVOID Reserved; PROCESS_WS_WATCH_INFORMATION WatchInfo[1]; } PAGEFAULT_HISTORY, *PPAGEFAULT_HISTORY;
23. HANDLE Win32WindowStation
Win32WindowStation域是一個進程所屬的窗口站的句柄。由於句柄的值是由每個進程的句柄表來決定的,所以,兩個進程即使同屬於一個窗口站,它們的Win32WindowStation也可能不同,但指向的窗口站對象是相同的。窗口站是由windows子系統來管理和控制的。
24. HANDLE InheritedFromUniqueProcessId
InheritedFromUniqueProcessId域說明了一個進程是從哪里繼承來的,即父進程的標識符。
25. PVOID LdtInformation
LdtInformation用來維護一個進程的LDT(局部描述符表)信息。
http://blog.csdn.net/billpig/article/details/5833980
http://hi.baidu.com/zeyu203/item/74192edfb16abd392b35c7c0
這兩篇文章對GDT和LDT解釋的很好了,這里總結一下我對GDT和LDT的理解。
1) 全局描述符表GDT(Global Descriptor Table)
除了使用虛擬地址來實現有效和靈活的內存管理以外,在計算機發展史上,另一種重要的內存管理方式是將物理內存划分成若干個段(segment),處理器在訪問一個內存單元時,通過"段基址+偏移"的方式算出實際的物理地址。每個段都可以有自己的訪問屬性,包括讀寫屬性、特權級別等。例如,在Intel x86處理器中,有專門的段寄存器,允許每條指令在訪問內存時指定在哪個段上進行。段的概念在Intel 8086/8088實模式中已經使用了,但當時段的用途是擴展地址范圍,將系統的內存從64KB擴展到1MB(CS:IP那種模式)。
在Intel x86中,邏輯地址的段部分(即原來的CS:IP中的CS部分)稱為"段選擇符(segment selector)",也有稱為段選擇子的。
這個"段選擇符"指定了段的索引以及要訪問的特權級別。段寄存器CS,SS,DS,ES,FS,GS專門用於指定一個地址的段選擇符。雖然只有這六個寄存器,但是軟件可以靈活地使用它們來完成各種功能。其中與有三個段寄存器有特殊的用途:
1) CS: 代碼段寄存器,指向一個包含指令的段,即代碼段
2) SS: 棧段寄存器,指向一個包含當前調用棧的段,即棧段
3) DS: 數據段寄存器,指向一個包含全局和靜態數據的段,即數據段
在整個系統中,全局描述符表GDT只有一張(一個處理器對應一個GDT),GDT可以被放在內存的任何位置,但CPU必須知道GDT的入口,也就是基地址放在哪里,Intel的設計者提供了一個寄存器GDTR用來存放GDT的入口地址,程序員將GDT設定在內存中某個位置之后,可以通過LGDT指令將GDT的入口地址裝入此積存器,從此以后,CPU就根據此寄存器中的內容作為GDT的入口來訪問GDT了。GDTR中存放的是GDT在內存中的基地址和其表長界限(也就是說,我們通過GDTR中的值可以找到這張全局描述表的真實數據結構,GDTR的作用也就僅僅這樣了,找到GDT就沒了)。

那借來來就有一個問題了,我們通過GDTR中保存的基地址找到了一個所謂的全局描述表GDT,並通過"段選擇子"索引到了GDT中的某一項(具體怎么索引我們后面會舉例子),我們這里暫時稱為某一項,因為我們目前還不知道GDT中的一項項是什么數據,那GDT中都保存了什么數據呢?這就要涉及到局部描述符表LDT了。
2) 局部描述符表LDT(Local Descriptor Table)
局部描述符表可以有若干張,每個任務可以有一張。我們可以這樣理解GDT和LDT:GDT為一級描述符表,LDT為二級描述符表。

我們知道,一個應用程序中有代碼段/數據段/TSS段/LDT段,而每一個段都對一個一個LDT,而這些LDT可以通過GDT尋址到。我們回想一下,GDTR是用來尋址GDT的,因為GDT只有一個,所以可以用GDTR+段選擇子的方法來尋址LDT,但是LDT不一樣了,LDT有很多個,所以不存在基址這一說,所以LDTR中保存的是當前的LDT,這個指是可以動態改變的。
LDT即局部描述符(或者叫段描述符),他記錄了這個段的一些屬性: 段的起始地址、有效范圍、和一些屬性。

每一個LDT中都這樣的數據結構,它很好地描述了這個段對應的信息。我們重點解釋其中幾個字段:
G: 當G位為0時,此長度單位為byte。當G為1時,此長度單位為4096byte。所以,段長度可達2^20 * 4096 = 4GB,即整個32位線性地址空間。 DPL(描述符特權級 Descriptor Privilege Level)是允許訪問此段的最低特權級(結合下面學習的"段選擇子"中有一個字段(RPL)是標識這個段選擇子也即這個內存訪問請求的 特權級),這樣是不是就把對應關系建立起來了,比如DPL為0的段只有當RPL=0時才能訪問,而DPL為3的段,可由任何RPL的代碼訪問。這樣就解釋了為什么ring3的內存空間 ring0的內核代碼可以任意訪問,而ring0的內存空間ring3不能訪問了。 TYPE(類型域): 指定了段的類型,包括代碼段、數據段、TSS段、LDT段。
3) 段選擇子(Selector)
我們之前還有一個疑問沒解決,"我們通過GDTR中保存的基地址找到了一個所謂的全局描述表GDT,並通過"段選擇子"索引到了GDT中的某一項"。那段選擇字是怎么來進行索引的呢?
段選擇子是一個16位的寄存器(同實模式下的段寄存器相同)

段選擇子包括三部分:描述符索引(index)、TI、請求特權級(RPL)。他的index(描述符索引)部分表示所需要的段的描述符(LDT)在描述符表的位置(編號),由這個位置再根據在GDTR中存儲的描述符表基址(GDTR的32位基地址用來尋址GDT的基地址)就可以找到相應的描述符(LDT)。然后用描述符表中的段基址加上邏輯地址(假如給出這樣的邏輯地址 SEL:OFFSET)的OFFSET就可以轉換成線性地址,段選擇子中的TI值只有一位0或1,0代表選擇子是在GDT選擇,1代表選擇子是在LDT選擇。請求特權級(RPL)則代表選擇子的特權級,共有4個特權級(0級、1級、2級、3級)(但是只有2中狀態被實際使用,0表示最高特權級,3表示最低特權級,CPU只能訪問同一特權級或級別較低特權級的段)。
例如給出邏輯地址:21h:12345678h轉換為線性地址 a. 選擇子SEL=21h=0000000000100 0 01b 他代表的意思是:選擇子的index=4即100b選擇GDT中的第4個描述符;TI=0代表選擇子是在GDT選擇;左后的01b代表特權級RPL=1 b. OFFSET=12345678h若此時GDT第四個描述符中描述的段基址(Base)為11111111h,則線性地址=11111111h+12345678h=23456789h
說了這么多,來總一下LDT和GDT:

1. 首先,要把一些概念和名詞弄清楚,有很多書和網上的文章中給了很多名詞,我們要能理解它們。段選擇符和段選擇子是一個概念,它們就相當於8080下實模式中的CS。 2. 我們給出的邏輯地址由段選擇子和偏移量組成: SEL + OFFSET. 3. GDTR中保存着GDT的基地址,通過GDTR我們可以尋址到GDT(你可以理解為尋址一個數組的基地址) 4. GDT這個"數組"中保存着很多LDT,它們每個都代表着一個段,我們需要通過SEL(段選擇子)來索引具體的LDT 5. LDTR中保存的是當前的LDT地址,也即一個段選擇子。 6. 每個LDT中保存了這個段的一些關鍵的信息,包括段基址,段的特權,段的大小等等。
更多細節請參考《windows 內核原理與實現》4.1.2 段式內存管理
說了這么多,回到我們的主線上來,LdtInformation域保存的就是一個進程的LDT。
26. PVOID VadFreeHint
VadFreeHint域指向一個提示VAD(虛擬地址描述符)節點,用於加速在VAD樹中執行查找操作。
27. PVOID VdmObjects
VdmObjects域指向當前進程的VDM數據區,其類型為VMD_PROCESS_OBJECTS,進程可通過NtVdmControl系統服務來初始化VDM。
28. PVOID DeviceMap
DeviceMap域指向進程使用的設備表,通常情況下同一個會話中的進程共享同樣的設備表。有關設備表的用法請參考《windows 內核原理與實現》 7章。請原諒我又采取這么馬虎的方式忽略過去了,因為我不想讓這篇文章的主線變得過於冗長,我會在之后的學習筆記中補充上之前說過的類似的參考之類的話,分專題進行學習筆記。
29. PVOID Spare0[3]
Spare0域是一個備用域。用法未知,WRK中也沒有使用。
30. 頁表項
union
{
HARDWARE_PTE PageDirectoryPte;
ULONGLONG Filler;
};
PageDirectoryPte域是頂級頁目錄頁面的頁表項。這涉及到windows中的頁式內存管理的知識,我們拓展出去。
讓進程使用虛擬地址,而虛擬地址和物理地址之間通過一個映射表來完成轉譯。頁式內存和我們之前學習的段式內存管理方式都是基於這樣一種思路的具體實現。
在頁式內存管理中,虛擬地址空間是按頁(page)來管理的(回想段式內存管理中,虛擬地址空間是靠"段+偏移"來管理的),對應於物理內存也按頁來管理(一般情況下虛擬內存和物理內存使用相同的頁面大小
4KB)。物理內存中的頁面有時稱為"頁幀(page frmae)",其大小與虛擬空間中的頁面相同。因此,映射的基本度量單位為"頁",在虛擬地址空間中連續的頁面對應於在物理內存中的頁面可以不連續。
通過維護這個虛擬地址空間與物理內存頁面之前的映射關系,物理頁面可以被"動態"地分配給特定的虛擬頁面,從而當只有真正有需要的時候才把物理頁面分配給虛擬頁面(其他時候,進程看到的都是一個
偽的平坦4GB內存空間)。

注意,在一個系統中,物理地址空間只有一個,但虛擬地址空間可以有多個。每個虛擬地址空間都必須有一個映射關系。
有了頁面划分的機制以后,每個虛擬地址(邏輯地址)32位信息中,其中一部分位信息指定了一個"頁索引",其余的位信息則指定了業內的偏移量。也就是說,虛擬地址分成了兩部分: 頁索引+頁內偏移。

既然有頁索引,就自然會有一個頁表。在windows中,我們稱之為"頁面映射表"。Intel x86采用了"分級頁表"的方式來管理這一映射關系。32位的虛擬地址中的"頁索引部分"又被分成"頁目錄索引(10位)"
和"頁表索引(10位)"這兩部分。

基於這樣的虛擬地址構成,每個虛擬地址(進程地址空間)對應有一個頁目錄(最頂層),其中包含2^10=1024個目錄項(PDE Page Directory Entry)。而頁目錄中的每一個目錄項又指向一張包含1024項的頁
表,而每個頁表中才保存的是頁面。
所以,Intel x86處理器在解析一個虛擬地址時,首先根據最高10位在"頁目錄"中定位到一個"頁目錄項(PDE)",這個"頁目錄項"指向一個"頁表",然后根據接下來的10位,在頁表中定位到一個"頁表項(
PTE Page Table Entry)"。這個"頁表項"就是一個"頁面",此頁表項指定了目標頁面的物理地址。最后在此物理地址的基礎上加上頁面偏移,即得到最終的物理地址。
(這段話可能有點繞,一定要結合圖片細心的理解,它其實就是一個多層的關系)
《windows 內核原理與實現》 4.1.1 節中有關於頁式內存管理的詳細細節。
回到我們的主線上來:
PageDirectoryPte域是頂級頁目錄頁面的頁表項
即PageDirectoryPte域是頁目錄中的第一項對應的那個頁表。
31. PVOID Session
Session指向進程所在的系統會話,實際上它是一個指向MM_SESSION_SPACE的指針。\base\ntos\mm\mi.h 中相關的結構體定義。
每個進程在初始化創建地址空間時會加入到當前的系統會話中。
+0x170 Session : 0xf8b6a000
32. UCHAR ImageFileName[ 16 ]
ImageFileName域包含了進程的映像文件名,僅包含最后一個路徑分隔符之后的字符串,不超過16字節。
+0x174 ImageFileName : [16] "notepad.exe"
33. LIST_ENTRY JobLinks
JobLinks域是一個雙鏈表節點,通過此節點,一個job中的所有進程構成了一個鏈表。在windows中,所有的job構成了一個雙鏈表,其鏈表頭為全局變量PspJobList。每個job中的進程又構成了一個雙鏈表。
(可以發現,在windows內核中,進程,job,線程都是可以通過雙鏈表遍歷的方法來進行枚舉的,但是遇到"斷鏈法"就不行了,我們之后會專題研究windows內核中進程枚舉的知識)
+0x184 JobLinks : _LIST_ENTRY [ 0x0 - 0x0 ] +0x000 Flink : (null) +0x004 Blink : (null)
34. PVOID LockedPagesList
LockedPagesList域是一個指向LOCK_HEADER結構的指針,該結構包含了一個鏈表頭,windows通過此鏈表來記錄哪些頁面已被鎖住(這里所謂的鎖住和Mdll中的映射機制有關,本質上就是把用戶空間下的內存地址鎖定到內核空間中以便訪問)。
base\ntos\mm\iosup.c中有一組函數用於管理此鏈表: MiAddMdlTracker、MiFreeMdlTracker、MiUpdateMdlTracker/
35. LIST_ENTRY ThreadListHead
ThreadListHead域是一個雙鏈表的"頭結點",該鏈表中包含了一個進程中的所有"線程"。即EPROCESS中的ThreadListHead域的鏈表中包含了各個子線程的ETHREAD結構中的ThreadListHead節點。
這里要注意看清除哦,EPROCESS中的ThreadListHead域包含的不是EHREEAD的基址,而是ETHREAD中的ThreadListHead節點的指針,即它們是通過這個雙鏈表來串起
來的,這個細節在我們枚舉進程或枚舉進程的時候會經常遇到,就是要處理一個偏移量的問題。
36. PVOID SecurityPort
SecurityPort域是一個安全端口,指向該進程域lsass.exe進程之間的跨進程通信端口。
37. PVOID PaeTop
PaeTop域用於支持PAE內存訪問機制。
+0x19c PaeTop : 0xf8cb21a0
38. ULONG ActiveThreads
ActiveThreads域記錄了當前進程有多少活動線程。當該值減為0時,所有的線程將退出,於是進程也退出。
+0x1a0 ActiveThreads : 1
(可以看到,當前記事本進程就一個線程: 主線程)
39. ACCESS_MASK GrantedAccess
GrantedAccess域包含了進程的訪問權限,訪問權限是一個"位組合"。 public\sdk\inc\ntpsapi.h 中的宏 PROCESS_XXX
... #define PROCESS_TERMINATE (0x0001) // winnt #define PROCESS_CREATE_THREAD (0x0002) // winnt #define PROCESS_SET_SESSIONID (0x0004) // winnt #define PROCESS_VM_OPERATION (0x0008) // winnt #define PROCESS_VM_READ (0x0010) // winnt #define PROCESS_VM_WRITE (0x0020) // winnt // begin_ntddk begin_wdm begin_ntifs #define PROCESS_DUP_HANDLE (0x0040) // winnt ...
+0x1a4 GrantedAccess : 0x1f0fff
40. ULONG DefaultHardErrorProcessing
DefaultHardErrorProcessing域指定了默認的硬件錯誤處理,默認為1
+0x1a8 DefaultHardErrorProcessing : 1
41. NTSTATUS LastThreadExitStatus
LastThreadExitStatus域記錄了剛才最后一個線程的退出狀態。當主線程的入口點函數(WinMain, wWinMain, main, wmain)返回時,會返回到C/C++"運行庫啟動代碼",后者將正確清理進程使用的全部C運行時資源。在《windows核心編程》這本書中的第4章: 進程。有對進程和線程的創建以及C/C++運行庫的啟動代碼的權威解釋。
+0x1ac LastThreadExitStatus : 0
42. PPEB Peb
Peb域是一個進程的"進程環境塊(PEB Process Environment Block)",這是一個位於"進程地址空間(即用戶模式空間)"的內存塊(為什么要放在用戶模式空間呢?因為這個結構需要在被用戶模式空間的代碼在運行中修改),其中包含了有關進程地址空間中的堆和系統模塊等信息。我們之后會詳細分析PEB。
43. EX_FAST_REF PrefetchTrace
PrefetchTrace域是一個快速引用,指向與該進程關聯的一個"預取痕跡結構",以支持該進程的預取。
+0x1b4 PrefetchTrace : _EX_FAST_REF +0x000 Object : 0x8252944e +0x000 RefCnt : 0y110 +0x000 Value : 0x8252944e
44. 進程中和IRP相關的內容
LARGE_INTEGER ReadOperationCount;
LARGE_INTEGER WriteOperationCount;
LARGE_INTEGER OtherOperationCount;
LARGE_INTEGER ReadTransferCount;
LARGE_INTEGER WriteTransferCount;
LARGE_INTEGER OtherTransferCount;
ReadOperationCount,WriteOperationCount記錄了當前進程NtReadFile和NtWriteFile系統服務被調用的次數,OtherOperationCount記錄了除讀寫操作以外的其他IO服務的次數(文件信息設置.)
ReadTransferCount,WriteTransferCount記錄了IO讀寫操作"完成"的次數,OtherTransferCount記錄了除讀寫操作以外操作完成的次數。
44. PVOID AweInfo
AweInfo域是一個指向AWEINFO結構的指針,其目的是支持AWE(Adress Windowing Extension 地址窗口擴展)
45. SE_AUDIT_PROCESS_CREATION_INFO SeAuditProcessCreationInfo
SeAuditProcessCreationInfo域包含了創建進程時指定的進程映像全路徑名,我們之前學過的ImageFileName域實際上就是從這里"提取"出來的。
+0x1f4 SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO +0x000 ImageFileName : 0x82851db0 _OBJECT_NAME_INFORMATION
46. MMSUPPORT Vm
Vm域是windows為每個進程管理虛擬內存的重要數據結構成員,其類型為MMSUPPORT, \base\ntos\inc\ps.h 中有相關定義。
typedef struct _MMSUPPORT { LIST_ENTRY WorkingSetExpansionLinks; LARGE_INTEGER LastTrimTime; MMSUPPORT_FLAGS Flags; ULONG PageFaultCount; WSLE_NUMBER PeakWorkingSetSize; WSLE_NUMBER GrowthSinceLastEstimate; WSLE_NUMBER MinimumWorkingSetSize; WSLE_NUMBER MaximumWorkingSetSize; struct _MMWSL *VmWorkingSetList; WSLE_NUMBER Claim; WSLE_NUMBER NextEstimationSlot; WSLE_NUMBER NextAgingSlot; WSLE_NUMBER EstimatedAvailable; WSLE_NUMBER WorkingSetSize; EX_PUSH_LOCK WorkingSetMutex; } MMSUPPORT, *PMMSUPPORT;
+0x1f8 Vm : _MMSUPPORT +0x000 LastTrimTime : _LARGE_INTEGER 0x1ceeef4`925cc6fc +0x008 Flags : _MMSUPPORT_FLAGS +0x00c PageFaultCount : 0x3c6 +0x010 PeakWorkingSetSize : 0x39b +0x014 WorkingSetSize : 0x399 +0x018 MinimumWorkingSetSize : 0x32 +0x01c MaximumWorkingSetSize : 0x159 +0x020 VmWorkingSetList : 0xc0883000 _MMWSL +0x024 WorkingSetExpansionLinks : _LIST_ENTRY [ 0x805595f0 - 0x828ec62c ] +0x02c Claim : 0 +0x030 NextEstimationSlot : 0 +0x034 NextAgingSlot : 0 +0x038 EstimatedAvailable : 0 +0x03c GrowthSinceLastEstimate : 0x3c6
(從它的數據結構中我們可以看到很多熟悉的字段,因為一定涉及到內存調度,所以有工作集字段,此外,包括峰值等..)
47. LIST_ENTRY MmProcessLinks
MmProcessLinks域代表一個雙鏈表節點,所有擁有自己地址空間的進程都將加入到一個雙鏈表中,鏈表頭是全局變量MmProcessList。當進程地址空間被初始創建時,MmProcessLinks節點會被加入到此全局鏈表中。當進程地址空間被銷毀時,該節點脫離此鏈表。此全局鏈表的存在使得windows系統共可以方便地執行一些全局的內存管理任務,同時也可以被我們用來進行進程枚舉。
48. ULONG ModifiedPageCount
ModifiedPageCount域記錄了該進程中已修改的頁面的數量,即"臟頁面數量",這和緩存的讀寫有關。
+0x23c ModifiedPageCount : 7
49. ULONG JobStatus
JobStatus域記錄了進程所屬job的狀態。
50. ULONG Flags
Flags域包含了進程的標志位,這些標志位反映了進程的當前狀態和配置。 \base\ntos\inc\ps.h 中的宏定義 PS_PROCESS_FLAGS_XXX
... #define PS_PROCESS_FLAGS_PROCESS_DELETE 0x00000008UL // Delete process has been issued #define PS_PROCESS_FLAGS_WOW64_SPLIT_PAGES 0x00000010UL // Wow64 split pages #define PS_PROCESS_FLAGS_VM_DELETED 0x00000020UL // VM is deleted #define PS_PROCESS_FLAGS_OUTSWAP_ENABLED 0x00000040UL // Outswap enabled #define PS_PROCESS_FLAGS_OUTSWAPPED 0x00000080UL // Outswapped #define PS_PROCESS_FLAGS_FORK_FAILED 0x00000100UL // Fork status #define PS_PROCESS_FLAGS_WOW64_4GB_VA_SPACE 0x00000200UL // Wow64 process with 4gb virtual address space #define PS_PROCESS_FLAGS_ADDRESS_SPACE1 0x00000400UL // Addr space state1 #define PS_PROCESS_FLAGS_ADDRESS_SPACE2 0x00000800UL // Addr space state2 #define PS_PROCESS_FLAGS_SET_TIMER_RESOLUTION 0x00001000UL // SetTimerResolution has been called ...
+0x248 Flags : 0xd0800
51. NTSTATUS ExitStatus
ExitStatus域包含了進程的退出狀態,從進程的退出狀態通常可以獲知進程非正常退出的大致原因。反映退出狀態的一些宏定義位於 public\sdk\inc\ntstatus.h
... #define STATUS_SEVERITY_SUCCESS 0x0 #define STATUS_SEVERITY_INFORMATIONAL 0x1 ...
52. USHORT NextPageColor
NextPageColor域用於物理頁面分配算法。
53. USHORT SubSystemVersion
SubSystemVersion域中的SubSystemMinorVersion和SubSystemMajorVersion分別記錄了一個"進程的子系統"的主板本號和此版本號,它們的值來源於進程映像文件PE的對應版本信息(PE的頭部包含了此信息)。在之前的PE結構探究中有關於這方面的內容。
http://www.cnblogs.com/LittleHann/archive/2013/06/14/3136111.html
+0x252 SubSystemMinorVersion : 0 '' +0x253 SubSystemMajorVersion : 0x4 ''
54. UCHAR PriorityClass
PriorityClass域是一個單字節值,它說明了一個進程的優先級程度。這和進程、線程優先級的知識有關。
1) windows支持6個進程優先級類(priority class): idle, below normal, normal, above normal, high, real-time。
normal是最常用的優先級類 real-time:此進程中的線程必須立即響應事件,執行實時任務。次進程中的線程還會搶占操作系統的組件的CPU時間。 high:此進程中的線程必須立即響應事件,執行實時任務。任務管理器運行在這一級,因此用戶可以通過它結束失控的進程 above normal: normal:此進程中的線程無需特殊的調度,大多數進程都是這一級別的 below normal: idle:此進程中的線程在系統空閑時運行。屏保,后台實時程序通常使用該優先級
2) 選擇了進程優先級后,我們應該轉而關心進程中線程的相對優先級
idle, lowest, below normal, normal, above normal, highest, time-critical
應用程序的開發人員無需處理優先級,而是由系統將進程的優先級類和線程的相對優先級整合起來映射到一個優先級值(組成一個二維表)。

注意: 1. 表中線程優先級沒有0,因為0優先級保留給頁面清零線程了,系統不允許其他任何線程的優先級為0. 2. ring3應用程序無法獲得一下優先級:17,18,19,20,21,27,28,29,30。如果你編寫的是內核模式的驅動程序,那可以獲得這些優先級 3. real-time優先級類的線程,其優先級不能低於16.非real-time優先級線程的優先級不能高於1
在 public\sdk\inc\ntpsapi.h 中可以找到PROCESS_PRIORITY_CLASS_XX的宏定義
#define PROCESS_PRIORITY_CLASS_UNKNOWN 0 #define PROCESS_PRIORITY_CLASS_IDLE 1 #define PROCESS_PRIORITY_CLASS_NORMAL 2 #define PROCESS_PRIORITY_CLASS_HIGH 3 #define PROCESS_PRIORITY_CLASS_REALTIME 4 #define PROCESS_PRIORITY_CLASS_BELOW_NORMAL 5 #define PROCESS_PRIORITY_CLASS_ABOVE_NORMAL 6
PriorityClass域說明了一個進程的優先級程度
55. MM_AVL_TABLE VadRoot
VadRoot域指向一個平衡二叉樹的根,用於管理該進程的虛擬地址空間。
56. ULONG Cookie
Cookie域存放的是一個代表該進程的隨機值,當第一次通過NtQueryInformationProcess函數獲取此Cookie值的時候,系統會生成一個隨機值,以后就用此值代表此進程。
+0x258 Cookie : 0x936269b3
我們在編譯時使用的GS防御技術中用到的Cookie值指的就是這個,關於這個Cookie,我了解的不是很多,這里給出在程序中dunp下來的cookie創建流程吧。
大概思路是計算這么一個表達式的值 KPRCB->KeSystemCalls^KPRCB->InterruptTime^KeQuerySystemTime()返回值的高雙字^KeQuerySystemTime()返回值的低雙字 再用cmpxchg指令把這個隨機值賦給EPROCESS.COOKIE 805c395b 8d45c4 lea eax,[ebp-0x3c] 805c395e 50 push eax 805c395f e8c643f3ff call nt!KeQuerySystemTime (804f7d2a) 805c3964 3ea120f0dfff mov eax,ds:[ffdff020] ;eax=kpcr->Prcb 805c396a 8b8818050000 mov ecx,[eax+0x518] ;ecx=KPRCB->KeSystemCalls 805c3970 3388b8040000 xor ecx,[eax+0x4b8] ;ecx=KPRCB->KeSystemCalls ^ KPRCB->InterruptTime 805c3976 334dc8 xor ecx,[ebp-0x38] ;ecx= 805c3979 334dc4 xor ecx,[ebp-0x3c] 805c397c 898d34ffffff mov [ebp-0xcc],ecx ;ecx=KPRCB->KeSystemCalls ^ KPRCB->InterruptTime ^KeQuerySystemTime()
返回值的高雙字^KeQuerySystemTime()返回值的 低雙字 805c3982 89bd2cffffff mov [ebp-0xd4],edi ;edi=&eprocess->Cookie 805c3988 b800000000 mov eax,0x0 ;eax=0 805c398d 8b8d2cffffff mov ecx,[ebp-0xd4] ;ecx=&eprocess->Cookie 805c3993 8b9534ffffff mov edx,[ebp-0xcc] ;edx=KPRCB->KeSystemCalls ^ KPRCB->InterruptTime ^KeQuerySystemTime()
返回值的高雙字^KeQuerySystemTime()返回值的 低雙字 805c3999 0fb111 cmpxchg [ecx],edx
+0x258 Cookie : 0x936269b3
至此,我們已經把EPROCESS的全部結構都分析完畢了,本來是想把KPROCESS/PEB放到一起的,可以發現這樣篇幅有些過長了,所以決定把KPROCESS和PEB放到下一篇學習筆記一起解決。
EPROCESS KPROCESS PEB 《寒江獨釣》內核學習筆記(3)
