迷霧退散:揭秘創建進程時ebx為什么指向peb的答案
一、背景
這篇文章的起因,是筆者之前在做樣本分析的時候,經常會遇到需要調試傀儡進程的情況,而其中有一種情景是將啟動的白進程PE文件整個掏空并用黑進程進行替換。
為了確保被替換后的進程能順利執行不崩潰,需要獲取原進程各種上下文,并修改被替換后的新進程上下文,其中在原進程被掛起還沒開始執行的時候,需要將eax指向新oep,而ebx指向新peb,而為什么這樣設置的原因卻很少有人提及。為此,在經過查閱了一定的資料與簡單的分析后,我們可以找到答案。
二、具體分析
先拋出結論,這里的eax與ebx屬于線程上下文信息,在一個PE文件開始被運行的過程中,主線程上下文初始化過程是在進程已經創建完成,而主線程還沒創建的階段發生的,下面是具體更詳細的分析:
首先我們需要對進程的創建有一個大概的認識,在ring3下創建進程API無非是CreateProcessA/W,但是無論調用哪一個,最終都會將相關參數轉化為Unicode字符串,并最終調用CreateProcessInternalW,因此以下將主要分析CreateProcessInternalW,而在xp和win7下,它具體實現又有一些不一樣的地方。
2.1 XP下執行流程
在xp下,它大概分為四個部分,分別是ring3下創建進程,ring0下創建進程,ring3下創建線程,ring0下創建線程,以NtCreateProcessEx為分界線,NtCreateProcessEx之前為ring3下創建進程主要流程。
2.1.1Ring3下創建進程
1. 判斷處理dwCreationFlag各種標志位,包括是否包含不合法標記組合,判斷優先級。


2. 判斷lpEnvironment是否為空,
不為空則調用RtlAnsiStringToUnicodeString將其轉為UniCode字符串。

3. 判斷lpApplicationName、lpCommandLine是否為空。

如果lpApplicationName不為空直接調用RtlDosPathNameToNtPathName_U函數。
將DOS路徑(C:\\WINDOWS\\XXX)轉換為NT路徑(\\Device\\HarddiskVolume1\\WINDOWS\\XXX),
為空則會解析lpCommandLine,主要按照’”’引號,’ ’空格,’\t’制表符作為分隔符進行解析并獲取相應的PE文件,然后將DOS路徑轉換為NT路徑。




4. 調用NtOpenFile得到文件句柄,調用了NtCreateSectiond函數得到內存區對象句柄。
5. 調用BasepIsProcessAllowed函數, 該函數用來判斷應用程序名是否在授權文件列表中。

6. 之后會經過一大段的函數進行各種校驗,再得到內存區對象句柄后調用NtQuerySection函數,
返回后得到節的基本信息(節基地址,大小,屬性),并判斷創建標志中是否包含DEBUG_PROCESS或者DEBUG_ONLY_THIS_PROCESS,
如果不包含該標志,則判斷PEB->ReadImageFileExecOptions域是否為0,
如果包含DEBUG_PROCESS或者DEBUG_ONLY_THIS_PROCESS,或者不包含該標志但ReadImageFileExecOptions域不為0,
調用LdrQueryImageFileExecutionOptions函數查詢該信息。

7. 檢查鏡像文件的部分信息的有效性,并調用函數BasepIsImageVersionOk判斷鏡像文件版本是否合法。
8. 加載advapi32.dll并獲得CreateProcessAsUserSecure函數的地址。

9. 調用BaseFormatObjectAttributes將安全屬性結構格式為NT對象屬性結構(得到了對象屬性),接著調用了_DbgUiConnectToDbg在實現通過調用NtCreateDebugObject函數來創建調試對象,
調用DbgUiGetThreadDebugObject來獲得調試對象(作為參數傳遞到0環)。
10. 最后調用NtCreateProcessEx函數。

2.1.2Ring0下創建進程
NtCreateProcessEx內為ring0下創建進程主要流程。
判斷父進程是否存在,不存在則退出,否則,調用PspCreateProcess。

在PspCreateProcess中,保存當前線程運行的前一個模式。通過KTHREAD->PreviousMode可以得到前一個模式。判斷創建標志是否包含除DEBUG_PROCESSDEBUG_ONLY_THIS_PROCESS,CREATE_SUSPENDED之外其它標志, 如果包含其他的標志,則報錯退出。

3. 通過參數ParentProcess調用ObReferenceObjectByHandle函數得到父進程對象的指針。
4. 判斷參數 JobMemberLevel是否為0, 如果不為0,接著判斷父進程的EPROCESS->Job是否為0,如果JobMemberLevel不為為0且EPROCESS->Job為0,則返回無效參數錯誤后退出該函數;否則的話,將父進程對象中的屬性保存到局部變量中。

5. 調用ObCreateObject函數創建新進程對象并將對象內容初始化為0,然后從父進程繼承配額信息(PspInheritQuot)和設備位圖信息(ObInheritDeviceMap),將父進程對象中的部分域給新進程。

6. 判斷參數SectionHandle是否為0,若不為0,調用ObReferenceObjectByHandle函數得到區對象指針,然后將區對象指針賦值給新進程EPROCESS的相應域。

7. 接著就判斷參數DebugPort是否為0,若不為0,調用ObReferenceObjectByHandle函數通過調試對象句柄得到調試對象指針,否則調用DbgkCopyProcessDebugPort函數從父進程拷貝DebugPort給新進程。
8. 判斷參數ExceptionPort是否為0,若不為0,調用ObReferenceObjectByHandle函數通過異常端口對象句柄得到異常端口對象指針。
9. 接著調用PspInitializeProcessSecurity函數來設置新進程的安全屬性, 主要是設置新進程的安全令牌對象。該函數會調用SeSubProcessToken函數來設置新進程對象的令牌對象。


10. 接著調用MmCreateProcessAddressSpace為新進程創建地址空間,并構建頁目錄表、頁表及物理頁的關系。
11. 調用KeInitializeProcess函數初始化新進程對象中內核對象、優先級、親和性、頁目錄表物理地址幀號。
12. 調用ObInitProcess函數來初始化新進程對象的表。

13. 調用MmInitializeProcessAddressSpace函數初始化進程地址空間,該函數的實現中調用了KiAttachProcess函數來實現進程的切換(將當前線程掛靠到新進程中),以及初始化EPROCESS中的部分域和PFN、工作集列表等。
14. 調用PspMapSystemDll函數映射新進程對象的系統DLL(即NTDLL,映射第一個DLL),該函數會調用MmMapViewOfSection映射節區,而MmMapViewOfSection會調用MiMapViewOfImageSection函數將DLL作為鏡像映射。


15. 接著調用MmGetSessionId函數獲得指定進程的會話ID,然后調用SeSetSessionIdToken函數設置令牌的會話ID,
之后再調用ExCreateHandle函數在PspCidTable中添加一項(PID)。

16. 調用MmCreatePeb為新進程創建PEB,該函數首先通過調用KeAttachProcess函數將當前線程切換到新進程對象,然后通過MmMapViewOfSection函數將NLS節區映射到新進程的地址空間中,隨后調用MiCreatePebOrTeb創建PEB/TEB。


在MiCreatePebOrTeb函數中首先會通過ExAllocatePoolWithTag來申請0x34大小的空間,接著通過MiFindEmptyAddressRangeDownTree函數在VAD樹中查找一塊未被使用的地址空間范圍,并返回該范圍的起始地址,最后通過MiInsertVad函數將申請的地址空間插入到VAD樹中。


在創建PEB結構后,初始化PEB中部分域的值(鏡像基地址,操作系統編譯號等域),最后調用KeDetachProcess函數使線程回到原來的線程中。截止此步驟,PEB創建完成。

同時觀察也可以發現,這里也解析了包括Nt頭、擴展頭、擴展頭魔術字效驗等關鍵PE結構信息,聯想到之前分析流程也處理了一部分PE結構,可以猜測早期的PE文件結構逆向可能也是通過逆向進程創建過程,即逆向CreateProcess API來實現的。
17. 最后將新進程對象EPROCESS.ActiveProcessLinks更新為全局的活動進程鏈表(PsActiveProcessHead), 判斷父進程是否為系統進程,調用SeCreateAccessStateEx設置訪問狀態,
調用ObInsertObject函數將進程對象加入到進程對象的句柄表中,并通過KeQuerySystemTime(獲取當前系統時間)結束PspCreateProcess的調用,完成ring0下進程的創建。

接下來我們回到CreateProcessInternalW,以NtCreateThread為分界線,NtCreateProcessEx之后到NtCreateThread之前為ring3下創建線程流程,而NtCreateThread內則是ring0下創建線程流程,經過分析發現,我們所需要尋找的線程上下文設置其實就在ring3下創建線程流程內。