在學習筆記(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)