??
字號:
漫談兼容內核之十:Windows的進程創建和映像裝入
[align=center]毛德操[/align]
關于Windows的進程創建和映像裝入的過程,“Microsoft Windows Internals 4e”一書的第六章中有頗為詳細的說明。本文就以此為依據,夾譯、夾敘、夾議地作一介紹。書中說,創建進程的過程分成六個階段,發生于操作系統的三個部分中,那就是:Windows客戶端即某個應用進程的包括Kernel32.dll在內的動態連接庫,Windows的“執行體”、即內核(確切地說是內核的上層),以及Windows子系統的服務進程Csrss中。這六個階段是:
1. 打開目標映像文件。
2. 創建Windows的“執行體進程對象”,也就是內核中的“進程控制塊”數據結構。
3. 創建該進程的初始(第一個)線程,包括其堆棧、上下文、以及“執行體線程對象”,即內核中的“線程控制塊”數據結構。
4. 將新建進程通知Windows子系統。
5. 啟動初始線程地運行(除非因為參數中的CREATE_SUSPENDED標志位為1而一創建便被掛起)。
6. 在新進程和線程的上下文中完成用戶空間的初始化,包括裝入所需的DLL,然后開始目標程序的運行。
下面分階段敘述。
第一階段:打開目標映像文件
在Win32位API中,創建進程是由CreateProcess()完成的。這實際上是個宏定義,根據不同的情況定義成CreateProcessA()或CreateProcessW()之一,這兩個函數都在kernel32.dll中(可以用工具depends觀察)。兩個函數的區別僅在于字符串的表達,前者采用ASCII字符,而后者采用“寬字符”、即Unicode。實際上Windows的內部都采用寬字符,所以前者只是把字符串轉換成寬字符格式,然后調用后者。
可以在Windows上運行的可執行軟件有好幾類,處理的方法自然就不一樣:
● Windows的32位.exe映像,直接運行。
● Windows的16位.exe映像,啟動ntvdm.exe,以原有命令行作為參數。
● DOS的.exe、.com、或.pif映像,啟動ntvdm.exe,以原有命令行作為參數。
● DOS的.bat或.cmd批命令文件(腳本),啟動cmd.exe,以原有命令行作為參數。
● POSIX可執行映像,啟動posix.exe,以原有命令行作為參數。
● OS/2可執行映像,啟動os2.exe,以原有命令行作為參數。
這里面最重要的當然是32位的.exe映像,而最后兩類現在已經很少見了。從對于除32位.exe以外的各種映像的處理,讀者不妨對比一下Wine對.exe映像的處理,看看這里有著什么樣的相似性。
當然,我們在這里只關心32位.exe映像。對于這一類映像,CreateProcess()首先打開映像文件,再為其(分配)創建一個“Section”、即內存區間。創建內存區間的目的當然是要把映像文件影射到這個區間,不過此時還不忙著映射,還要看看。看什么呢?首先是看已經打開的目標文件是否一個合格的.exe映像(萬一是DLL映像?)。還要看的事就有點出乎讀者意外了,看的是在“注冊表”中的這個路徑:
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options
用depends可以看到,ntdll.dll中有個函數LdrQueryImageFileExecutionOption s(),就是專門干這個事的。
如果上述路徑下有以目標映像文件的文件名和擴展名為“鍵”的表項,例如“image.exe”,而表項中又有名為“Debugger”的值,那么這個值(一個字符串)就替換了原來的目標文件名,變成新的目標映像名,并重新執行上述的第一階段操作。這樣做的目的當然是為調試程序提供方便,但是我們不妨設想:如果黑客或某個木馬程序設法在注冊表中加上了一個表項?這時候用戶以為是啟動了程序A,而實際啟動的卻是B!。
第二階段:創建內核中的進程對象
我們知道,Linux上的每個進程(線程)都有一個“進程控制塊”、即task_struct數據結構,與具體進程/線程有關的絕大部分信息都集中存儲在這個數據結構中。而Windows則有所不同。首先,Windows的進程和線程各有不同的“對象”、即數據結構,從概念上把線程和進程分離開來。線程是具體的(執行)上下文,是CPU調度的單位和目標,而進程只是若干共享地址空間和特性(如調度優先級)的線程的集合。于是,進程有進程的數據結構,線程有線程的數據結構。這就好像是對一組task_struct數據結構“提取公因子”所形成的結果,這個舉措是很好理解的。進一步,Windows又把本可集中存儲的的進程數據結構也拆分成好幾個對象,有的在內核中,有的則在用戶空間。
內核中與進程有關的對象有:
● EPROCESS。即struct _EPROCESS,在“Internals”書中也稱為“Process Block”。它代表著Windows的一個進程,‘E’表示“Executive”,微軟把Windows內核中的上層稱為“Executive”、以區別于下層的設備驅動和內存管理等成分、一般翻譯成“執行體”。“Executive”也有“管理”、“運行”的意思(所以CEO就是“總裁”)。
● KPROCESS。這是EPROCESS內部的一個成分,其名稱就叫“Pcb”。
● W32PROCESS。下面將要講到,在用戶空間有個“Windows子系統”的服務進程csrss。這個服務進程為系統中的每個Windows應用進程都維持著一個數據結構,其中包含了一些與窗口和圖形界面有關的信息。而對于窗口和圖形界面的操作原來也是由csrss在“客戶”進程的請求下實現的。但是,為了提高效率,后來把這部分功能移到了內核中。與此相應,有關數據結構的一部分也需要移到內核中,就成了W32PROCESS。
既然KPROCESS是EPROCESS一部分,實際上內核中與進程有關的對象實際上只有兩種,就是EPROCESS和W32PROCESS。不過這里沒有包括“打開對象表”,那也是每個進程都有的(Linux內核中的“打開文件表”也在進程控制塊的外面)。
用戶空間與進程有關的對象有:
● 如上所述,把W32PROCESS數據結構移入內核以后,csrss仍需要為每個Windows進程保持一些別的信息,所以csrss內部仍有按進程的相應數據結構。
● PEB(Process Environment Block)、即“進程環境塊”。PEB中記錄著進程的運行參數、映像裝入地址等等信息。PEB在用戶空間中的位置是固定的,總是在0x7ffdf000。在Windows中,用戶空間和系統空間的分界線是2GB、即0x80000000,所以PEB在靠近用戶空間頂端的地方。
“Internals”書中并未給出有關數據結構的定義,但是通過Debug手段給出了EPROCESS的內部結構:
[code] +0x000 Pcb : _KPROCESS
+0x06c ProcessLock : _EX_PUSH_LOCK
+0x070 CreateTime : _LARGE_INTEGER
+0x078 ExitTime : _LARGE_INTEGER
+0x080 RundownProtect : _EX_RUNDOWN_REF
+0x084 UniqueProcessId : Ptr32Void
+0x088 ActiveProcessLinks : _LIST_ENTRY
+0x090 QuotaUsage : [3] Uint4B
+0x09c QuotaPeak : [3] Uint4B
+0x0a8 CommitCharge : Uint4B
+0x0ac PeakVirtualSize : Uint4B
+0x0b0 VirtualSize : Uint4B
+0x0b4 SessionProcessLinks : _LIST_ENTRY
+0x0bc DebugPort : Ptr32Void
+0x0c0 ExceptionPort : Ptr32Void
+0x0c4 ObjectTable : Ptr32_HANDLE_TABLE
+0x0c8 Token : _EX_FAST_REF
+0x0cc WorkingSetLock : _FAST_MUTEX
+0x0ec WorkingSetPage : Uint4B
+0x0f0 AddressCreationLock : _FAST_MUTEX
+0x110 HyperSpaceLock : Uint4B
+0x114 ForkInProgress : Ptr32_ETHREAD
+0x118 HardwareTrigger : Uint4B[/code]
可見,EPROCESS的第一個成分是Pcb,其類型是_KPROCESS、即KPROCESS,這是一個大小為0x6c的數據結構。書中也給出了它的內部結構。
“Undocumented Windows 2000 Secrets”一書也以Debug手段給出了這個數據結構的內部結構,但是列出的結構與此有很大的不同,也許是因為版本的關系。從所列的內容看,似乎“Secrets”一書倒是正確的,因為那里所列的EPROCESS結構中有關于虛存的成分Vm,是一個大小為0x50的數據結構,而這里沒有,但是虛存(地址空間)顯然是進程的主要資源,所以EPROCESS數據結構中理應有它的位置。由此看來,“Secrets”一書所述更接近于桌面和服務器系統的現實,而“Internals”書中所列可能更接近于不帶MMU的嵌入式系統。而且,“Secrets”一書還在附錄C中給出了通過逆向工程手段得到的EPROCESS和PEB等數據結構的定義(代碼),這當然是很有價值的。
那么,如果確有不同版本的EPROCESS,這會有什么影響呢?首先,用戶空間的應用程序不能直接訪問內核中的EPROCESS數據結構,所以具體的EPROCESS數據結構屬于內核的內部實現,只要內核中的各種成分、各個環節都配套成龍,“自圓其說”,就沒有什么問題,這跟Linux內核中一些條件編譯和裁剪的效果是類似的。可是,另一方面,對于可以動態裝入的.sys模塊,如果在模塊中需要訪問這些數據結構,那就可能有問題了,因為.sys模塊都是以二進制映像的形式提供的,不像在Linux中那樣可以由源代碼重新編譯。怎么辦呢?我們可以到Windows的DDK中去找找答案。
在DDK的.h文件中,有函數IoGetCurrentProcess()的申明:
[code]NTKERNELAPI
PEPROCESS
IoGetCurrentProcess(
VOID
);[/code]
這個函數是內核為.sys模塊提供的支撐函數,相當于由Linux內核導出的函數。其返回值類型是PEPROCESS,就是指向EPROCESS數據結構的指針。顯然,這跟Linux內核中的current相似,調用的目的是獲取當前進程的EPROCESS數據結構(指針)。但是,DDK的.h文件中卻并未給出EPROCESS數據結構的定義,所以調用這個函數所得到的僅僅是個指針,實際上與void*并無區別。這意味著在.sys模塊中是不允許直接訪問其內部成分的。那么,.sys模塊如何使用這個指針呢?下面就是一個例子,還是在DDK中:
[code]NTKERNELAPI
VOID
MmProbeAndLockProcessPages (
IN OUT PMDL MemoryDescriptorList,
IN PEPROCESS Process,
IN KPROCESSOR_MODE AccessMode,
IN LOCK_OPERATION Operation
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -