转载:Windows的进程创建和映像装入
生活随笔
收集整理的這篇文章主要介紹了
转载:Windows的进程创建和映像装入
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
原文地址:http://blog.chinaunix.net/uid-20476365-id-1942481.html? 關(guān)于Windows的進(jìn)程創(chuàng)建和映像裝入的過程,“Microsoft Windows Internals 4e”一書的第六章中有頗為詳細(xì)的說明。本文就以此為依據(jù),夾譯、夾敘、夾議地作一介紹。書中說,創(chuàng)建進(jìn)程的過程分成六個階段,發(fā)生于操作系統(tǒng)的三個部分中,那就是:Windows客戶端即某個應(yīng)用進(jìn)程的包括Kernel32.dll在內(nèi)的動態(tài)連接庫,Windows的“執(zhí)行體”、即內(nèi)核(確切地說是內(nèi)核的上層),以及Windows子系統(tǒng)的服務(wù)進(jìn)程Csrss中。這六個階段是: 1. 打開目標(biāo)映像文件。 2. 創(chuàng)建Windows的“執(zhí)行體進(jìn)程對象”,也就是內(nèi)核中的“進(jìn)程控制塊”數(shù)據(jù)結(jié)構(gòu)。 3. 創(chuàng)建該進(jìn)程的初始(第一個)線程,包括其堆棧、上下文、以及“執(zhí)行體線程對象”,即內(nèi)核中的“線程控制塊”數(shù)據(jù)結(jié)構(gòu)。 4. 將新建進(jìn)程通知Windows子系統(tǒng)。 5. 啟動初始線程地運(yùn)行(除非因?yàn)閰?shù)中的CREATE_SUSPENDED標(biāo)志位為1而一創(chuàng)建便被掛起)。 6. 在新進(jìn)程和線程的上下文中完成用戶空間的初始化,包括裝入所需的DLL,然后開始目標(biāo)程序的運(yùn)行。 下面分階段敘述。
第一階段:打開目標(biāo)映像文件 在Win32位API中,創(chuàng)建進(jìn)程是由CreateProcess()完成的。這實(shí)際上是個宏定義,根據(jù)不同的情況定義成CreateProcessA ()或CreateProcessW()之一,這兩個函數(shù)都在kernel32.dll中(可以用工具depends觀察)。兩個函數(shù)的區(qū)別僅在于字符串的表達(dá),前者采用ASCII字符,而后者采用“寬字符”、即Unicode。實(shí)際上Windows的內(nèi)部都采用寬字符,所以前者只是把字符串轉(zhuǎn)換成寬字符格式,然后調(diào)用后者。 可以在Windows上運(yùn)行的可執(zhí)行軟件有好幾類,處理的方法自然就不一樣: ● Windows的32位.exe映像,直接運(yùn)行。 ● Windows的16位.exe映像,啟動ntvdm.exe,以原有命令行作為參數(shù)。 ● DOS的.exe、.com、或.pif映像,啟動ntvdm.exe,以原有命令行作為參數(shù)。 ● DOS的.bat或.cmd批命令文件(腳本),啟動cmd.exe,以原有命令行作為參數(shù)。 ● POSIX可執(zhí)行映像,啟動posix.exe,以原有命令行作為參數(shù)。 ● OS/2可執(zhí)行映像,啟動os2.exe,以原有命令行作為參數(shù)。 這里面最重要的當(dāng)然是32位的.exe映像,而最后兩類現(xiàn)在已經(jīng)很少見了。從對于除32位.exe以外的各種映像的處理,讀者不妨對比一下Wine對.exe映像的處理,看看這里有著什么樣的相似性。 當(dāng)然,我們在這里只關(guān)心32位.exe映像。對于這一類映像,CreateProcess()首先打開映像文件,再為其(分配)創(chuàng)建一個 “Section”、即內(nèi)存區(qū)間。創(chuàng)建內(nèi)存區(qū)間的目的當(dāng)然是要把映像文件影射到這個區(qū)間,不過此時還不忙著映射,還要看看。看什么呢?首先是看已經(jīng)打開的目標(biāo)文件是否一個合格的.exe映像(萬一是DLL映像?)。還要看的事就有點(diǎn)出乎讀者意外了,看的是在“注冊表”中的這個路徑: HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options
用depends可以看到,ntdll.dll中有個函數(shù)LdrQueryImageFileExecutionOption s(),就是專門干這個事的。 如果上述路徑下有以目標(biāo)映像文件的文件名和擴(kuò)展名為“鍵”的表項(xiàng),例如“image.exe”,而表項(xiàng)中又有名為“Debugger”的值,那么這個值 (一個字符串)就替換了原來的目標(biāo)文件名,變成新的目標(biāo)映像名,并重新執(zhí)行上述的第一階段操作。這樣做的目的當(dāng)然是為調(diào)試程序提供方便,但是我們不妨設(shè)想:如果黑客或某個木馬程序設(shè)法在注冊表中加上了一個表項(xiàng)?這時候用戶以為是啟動了程序A,而實(shí)際啟動的卻是B!。
第二階段:創(chuàng)建內(nèi)核中的進(jìn)程對象 我們知道,Linux上的每個進(jìn)程(線程)都有一個“進(jìn)程控制塊”、即task_struct數(shù)據(jù)結(jié)構(gòu),與具體進(jìn)程/線程有關(guān)的絕大部分信息都集中存儲在這個數(shù)據(jù)結(jié)構(gòu)中。而Windows則有所不同。首先,Windows的進(jìn)程和線程各有不同的“對象”、即數(shù)據(jù)結(jié)構(gòu),從概念上把線程和進(jìn)程分離開來。線程是具體的(執(zhí)行)上下文,是CPU調(diào)度的單位和目標(biāo),而進(jìn)程只是若干共享地址空間和特性(如調(diào)度優(yōu)先級)的線程的集合。于是,進(jìn)程有進(jìn)程的數(shù)據(jù)結(jié)構(gòu),線程有線程的數(shù)據(jù)結(jié)構(gòu)。這就好像是對一組task_struct數(shù)據(jù)結(jié)構(gòu)“提取公因子”所形成的結(jié)果,這個舉措是很好理解的。進(jìn)一步,Windows又把本可集中存儲的的進(jìn)程數(shù)據(jù)結(jié)構(gòu)也拆分成好幾個對象,有的在內(nèi)核中,有的則在用戶空間。 內(nèi)核中與進(jìn)程有關(guān)的對象有: ● EPROCESS。即struct _EPROCESS,在“Internals”書中也稱為“Process Block”。它代表著Windows的一個進(jìn)程,‘E’表示“Executive”,微軟把Windows內(nèi)核中的上層稱為“Executive”、以區(qū)別于下層的設(shè)備驅(qū)動和內(nèi)存管理等成分、一般翻譯成“執(zhí)行體”。“Executive”也有“管理”、“運(yùn)行”的意思(所以CEO就是“總裁”)。 ● KPROCESS。這是EPROCESS內(nèi)部的一個成分,其名稱就叫“Pcb”。 ● W32PROCESS。下面將要講到,在用戶空間有個“Windows子系統(tǒng)”的服務(wù)進(jìn)程csrss。這個服務(wù)進(jìn)程為系統(tǒng)中的每個Windows應(yīng)用進(jìn)程都維持著一個數(shù)據(jù)結(jié)構(gòu),其中包含了一些與窗口和圖形界面有關(guān)的信息。而對于窗口和圖形界面的操作原來也是由csrss在“客戶”進(jìn)程的請求下實(shí)現(xiàn)的。但是,為了提高效率,后來把這部分功能移到了內(nèi)核中。與此相應(yīng),有關(guān)數(shù)據(jù)結(jié)構(gòu)的一部分也需要移到內(nèi)核中,就成了W32PROCESS。 既然KPROCESS是EPROCESS一部分,實(shí)際上內(nèi)核中與進(jìn)程有關(guān)的對象實(shí)際上只有兩種,就是EPROCESS和W32PROCESS。不過這里沒有包括“打開對象表”,那也是每個進(jìn)程都有的(Linux內(nèi)核中的“打開文件表”也在進(jìn)程控制塊的外面)。 用戶空間與進(jìn)程有關(guān)的對象有: ● 如上所述,把W32PROCESS數(shù)據(jù)結(jié)構(gòu)移入內(nèi)核以后,csrss仍需要為每個Windows進(jìn)程保持一些別的信息,所以csrss內(nèi)部仍有按進(jìn)程的相應(yīng)數(shù)據(jù)結(jié)構(gòu)。 ● PEB(Process Environment Block)、即“進(jìn)程環(huán)境塊”。PEB中記錄著進(jìn)程的運(yùn)行參數(shù)、映像裝入地址等等信息。PEB在用戶空間中的位置是固定的,總是在 0x7ffdf000。在Windows中,用戶空間和系統(tǒng)空間的分界線是2GB、即0x80000000,所以PEB在靠近用戶空間頂端的地方。
“Internals”書中并未給出有關(guān)數(shù)據(jù)結(jié)構(gòu)的定義,但是通過Debug手段給出了EPROCESS的內(nèi)部結(jié)構(gòu): +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
可見,EPROCESS的第一個成分是Pcb,其類型是_KPROCESS、即KPROCESS,這是一個大小為0x6c的數(shù)據(jù)結(jié)構(gòu)。書中也給出了它的內(nèi)部結(jié)構(gòu)。 “Undocumented Windows 2000 Secrets”一書也以Debug手段給出了這個數(shù)據(jù)結(jié)構(gòu)的內(nèi)部結(jié)構(gòu),但是列出的結(jié)構(gòu)與此有很大的不同,也許是因?yàn)榘姹镜年P(guān)系。從所列的內(nèi)容看,似乎 “Secrets”一書倒是正確的,因?yàn)槟抢锼械腅PROCESS結(jié)構(gòu)中有關(guān)于虛存的成分Vm,是一個大小為0x50的數(shù)據(jù)結(jié)構(gòu),而這里沒有,但是虛存 (地址空間)顯然是進(jìn)程的主要資源,所以EPROCESS數(shù)據(jù)結(jié)構(gòu)中理應(yīng)有它的位置。由此看來,“Secrets”一書所述更接近于桌面和服務(wù)器系統(tǒng)的現(xiàn)實(shí),而“Internals”書中所列可能更接近于不帶MMU的嵌入式系統(tǒng)。而且,“Secrets”一書還在附錄C中給出了通過逆向工程手段得到的 EPROCESS和PEB等數(shù)據(jù)結(jié)構(gòu)的定義(代碼),這當(dāng)然是很有價值的。 那么,如果確有不同版本的EPROCESS,這會有什么影響呢?首先,用戶空間的應(yīng)用程序不能直接訪問內(nèi)核中的EPROCESS數(shù)據(jù)結(jié)構(gòu),所以具體的 EPROCESS數(shù)據(jù)結(jié)構(gòu)屬于內(nèi)核的內(nèi)部實(shí)現(xiàn),只要內(nèi)核中的各種成分、各個環(huán)節(jié)都配套成龍,“自圓其說”,就沒有什么問題,這跟Linux內(nèi)核中一些條件編譯和裁剪的效果是類似的。可是,另一方面,對于可以動態(tài)裝入的.sys模塊,如果在模塊中需要訪問這些數(shù)據(jù)結(jié)構(gòu),那就可能有問題了,因?yàn)?sys模塊都是以二進(jìn)制映像的形式提供的,不像在Linux中那樣可以由源代碼重新編譯。怎么辦呢?我們可以到Windows的DDK中去找找答案。 在DDK的.h文件中,有函數(shù)IoGetCurrentProcess()的申明:
NTKERNELAPI PEPROCESS IoGetCurrentProcess( VOID );
這個函數(shù)是內(nèi)核為.sys模塊提供的支撐函數(shù),相當(dāng)于由Linux內(nèi)核導(dǎo)出的函數(shù)。其返回值類型是PEPROCESS,就是指向EPROCESS數(shù)據(jù)結(jié)構(gòu)的指針。顯然,這跟Linux內(nèi)核中的current相似,調(diào)用的目的是獲取當(dāng)前進(jìn)程的EPROCESS數(shù)據(jù)結(jié)構(gòu)(指針)。但是,DDK的.h文件中卻并未給出EPROCESS數(shù)據(jù)結(jié)構(gòu)的定義,所以調(diào)用這個函數(shù)所得到的僅僅是個指針,實(shí)際上與void*并無區(qū)別。這意味著在.sys模塊中是不允許直接訪問其內(nèi)部成分的。那么,.sys模塊如何使用這個指針呢?下面就是一個例子,還是在DDK中:
NTKERNELAPI VOID MmProbeAndLockProcessPages ( IN OUT PMDL MemoryDescriptorList, IN PEPROCESS Process, IN KPROCESSOR_MODE AccessMode, IN LOCK_OPERATION Operation );
這個函數(shù)的作用是鎖定某個進(jìn)程的某些存儲頁面(不讓換出),其輸入?yún)?shù)之一就是指向該進(jìn)程的EPROCESS結(jié)構(gòu)的指針。當(dāng)然,這個函數(shù)也是由內(nèi)核提供的 (屬于我們所說的設(shè)備驅(qū)動界面)。所以指針的提供者和使用者都是內(nèi)核,只要這二者配套即可,.sys模塊在這里只不過是傳遞了一下,所以也不會有問題。 假定proc是指向進(jìn)程控制塊的指針,并且進(jìn)程控制塊中有個成份X,是個整數(shù),那么在Linux的動態(tài)安裝模塊中可以直接用“proc->X”訪問這個成分,但是在Windows的.sys模塊中則只能通過類似于get_X()、set_X()一類的支撐函數(shù)訪問這個成分。將數(shù)據(jù)結(jié)構(gòu)的內(nèi)容跟對于這些內(nèi)容的操作(method)相分離,正是“對象”與“數(shù)據(jù)結(jié)構(gòu)”的區(qū)別所在。而將數(shù)據(jù)結(jié)構(gòu)的內(nèi)容“封裝”起來,也正是微軟所需要的,因?yàn)樗辉敢夤_這些數(shù)據(jù)結(jié)構(gòu)。 對于兼容內(nèi)核的開發(fā),這意味著我們不必拘泥于采用與Windows完全一致的EPROCESS數(shù)據(jù)結(jié)構(gòu)(盡管“Secrets”的附錄C已經(jīng)給出了它的定義),一些內(nèi)部的操作和處理也不必完全與之相同,而只要與DDK所規(guī)定的界面相符就可以了。
了解了有關(guān)的進(jìn)程對象,我們可以言歸正傳了。 所謂創(chuàng)建內(nèi)核中的進(jìn)程對象,實(shí)際上就是創(chuàng)建以EPROCESS為核心、為基礎(chǔ)的相關(guān)數(shù)據(jù)結(jié)構(gòu),這就是系統(tǒng)調(diào)用NtCreateProcess()要做的事情,主要包括: ● 分配并設(shè)置EPROCESS數(shù)據(jù)結(jié)構(gòu)。 ● 其他相關(guān)的數(shù)據(jù)結(jié)構(gòu)的設(shè)置,例如“打開對象表”。 ● 為目標(biāo)進(jìn)程創(chuàng)建初始的地址空間。 ● 對目標(biāo)進(jìn)程的“內(nèi)核進(jìn)程塊”KPROCESS進(jìn)行初始化。 ● 將系統(tǒng)DLL的映像映射到目標(biāo)進(jìn)程的(用戶)地址空間。 ● 將目標(biāo)進(jìn)程的映像映射到其自身的用戶空間。 ● 設(shè)置好目標(biāo)進(jìn)程的“進(jìn)程環(huán)境塊”PEB。 ● 映射其他需要映射到用戶空間的數(shù)據(jù)結(jié)構(gòu),例如與“當(dāng)?shù)卣Z言支持”、即NLS有關(guān)的數(shù)據(jù)結(jié)構(gòu)。 ● 完成EPROCESS創(chuàng)建,將其掛入進(jìn)程隊列(注意受調(diào)度的是線程隊列而不是進(jìn)程隊列)。 這里將系統(tǒng)DLL、實(shí)際上是ntdll.dll、映射到目標(biāo)進(jìn)程的用戶空間是很關(guān)鍵的。這是因?yàn)?#xff0c;除別的、主流的功能和作用外,ntdll.dll同時也起著相當(dāng)于Linux中ELF“解釋器”的作用,也擔(dān)負(fù)著為目標(biāo)映像建立動態(tài)連接的任務(wù)。
值得注意的是,NtCreateProcess()與CreateProcess()不同。CreateProcess()創(chuàng)建一個進(jìn)程并使其(初始線程)運(yùn)行,除非在創(chuàng)建時就指定要將其掛起。而NtCreateProcess(),則只是在內(nèi)核中創(chuàng)建該進(jìn)程的EPROCESS數(shù)據(jù)結(jié)構(gòu)并為其建立起地址空間。這只是個空殼架子,因?yàn)闆]有線程就談不上運(yùn)行,調(diào)度的目標(biāo)是線程而不是線程。而且,對NtCreateProcess()的調(diào)用還有個條件,那就是目標(biāo)映像已經(jīng)被映射到一個存儲區(qū)間(Section)中。
第三階段:創(chuàng)建初始線程 如上所述,進(jìn)程只是個空架子,實(shí)際的運(yùn)行實(shí)體是里面的線程。所以下一步就是創(chuàng)建目標(biāo)進(jìn)程的初始線程,即其第一個線程。 與EPROCESS相對應(yīng),線程的數(shù)據(jù)結(jié)構(gòu)是ETHREAD,并且其第一個成分是數(shù)據(jù)結(jié)構(gòu)KTHREAD,稱為TCB。同樣,“Internals”和 “Secrets”兩本書中所列的ETHREAD內(nèi)部結(jié)構(gòu)有所不同,后者的附錄C中給出了通過逆向工程得到的ETHREAD數(shù)據(jù)結(jié)構(gòu)定義。 同樣,從Windows DDK中申明的一些函數(shù)也可以看出,.sys模塊只是傳遞ETHREAD指針或KTHREAD指針(由于KTHREAD是ETHREAD中的第一個成分,二者實(shí)際上是一回事),而不會直接訪問它的具體成分。
PKTHREAD NTAPI KeGetCurrentThread();
NTKERNELAPI KPRIORITY KeQueryPriorityThread (IN PKTHREAD Thread);
NTKERNELAPI LONG KeSetBasePriorityThread (IN PKTHREAD Thread, IN LONG Increment);
NTKERNELAPI PDEVICE_OBJECT IoGetDeviceToVerify(IN PETHREAD Thread);
此外,就像進(jìn)程有“進(jìn)程環(huán)境塊”PEB一樣,線程也有“線程環(huán)境塊”TEB,KTHREAD結(jié)構(gòu)中有個指針指向其存在于用戶空間的TEB。前面講過, PEB在用戶空間的位置是固定的,PEB下方就是TEB,進(jìn)程中有幾個線程就有幾個TEB,每個TEB占一個4KB的頁面。
這個階段的操作是通過系統(tǒng)調(diào)用NtCreateThread()完成的,主要包括: ● 創(chuàng)建和設(shè)置目標(biāo)線程的ETHREAD數(shù)據(jù)結(jié)構(gòu),并處理好與EPROCESS的關(guān)系(例如進(jìn)程塊中的線程計數(shù)等等)。 ● 在目標(biāo)進(jìn)程的用戶空間創(chuàng)建并設(shè)置目標(biāo)線程的TEB。 ● 將目標(biāo)線程在用戶空間的起始地址設(shè)置成指向Kernel32.dll中的BaseProcessStart()或BaseThreadStart(),前者用于進(jìn)程中的第一個線程,后者用于隨后的線程。用戶程序在調(diào)用NtCreateThread()時也要提供一個用戶級的起始函數(shù)(地址), BaseProcessStart()和BaseThreadStart()在完成初始化時會調(diào)用這個起始函數(shù)。ETHREAD數(shù)據(jù)結(jié)構(gòu)中有兩個成份,分別用來存放這兩個地址。 ● 設(shè)置目標(biāo)線程的KTHREAD數(shù)據(jù)結(jié)構(gòu)并為其分配堆棧。特別地,將其上下文中的斷點(diǎn)(返回點(diǎn))設(shè)置成指向內(nèi)核中的一段程序KiThreadStartup,使得該線程一旦被調(diào)度運(yùn)行時就從這里開始執(zhí)行。 ● 系統(tǒng)中可能登記了一些每當(dāng)創(chuàng)建線程時就應(yīng)加以調(diào)用的“通知”函數(shù),調(diào)用這些函數(shù)。
第四階段:通知Windows子系統(tǒng) Windows、確切地說是Windows NT、當(dāng)初的設(shè)計目標(biāo)是支持三種不同系統(tǒng)的應(yīng)用軟件。第一種是Windows本身的應(yīng)用軟件,即所謂“Native”Windows軟件,這是微軟開發(fā) Windows NT的真正目的。第二種是OS/2的應(yīng)用軟件,這是因?yàn)楫?dāng)時微軟與IBM還有合作關(guān)系。第三種是與Unix應(yīng)用軟件相似、符合POSIX標(biāo)準(zhǔn)的軟件,那是因?yàn)楫?dāng)時美國的軍方采購有這樣的要求。不過實(shí)際上微軟對后兩種應(yīng)用的支持從一開始就是半心半意的,后來翅膀長硬了,就更不必勉為其難了。但是,盡管如此,當(dāng)初在設(shè)計的時候還是考慮了對不同“平臺”的支持,即在同一個內(nèi)核的基礎(chǔ)上配以不同的外圍軟件,形成不同的應(yīng)用軟件運(yùn)行環(huán)境,微軟稱之為“子系統(tǒng)(Subsystem)”。于是,在Windows內(nèi)核上就有了所謂“Windows子系統(tǒng)”、“OS/2子系統(tǒng)”、和“POSIX子系統(tǒng)”。當(dāng)然,時至今日,實(shí)際上只剩下Windows子系統(tǒng)了。 那么,所謂子系統(tǒng)是怎樣構(gòu)成的呢?“Internals”書中闡明了Windows子系統(tǒng)的構(gòu)成,說這是由下列幾個要素構(gòu)成的。 一、子系統(tǒng)進(jìn)程csrss.exe。包括了對下列成分和功能的支持: ● 控制臺(字符型)窗口的操作。面向控制臺/終端的應(yīng)用本身不支持窗口操作(例如窗口的移動、大化/小化、遮蓋等等),但是又需要在窗口中運(yùn)行,所以需要有額外的支持。 ● 進(jìn)程和線程的管理。例如彈出一個對話窗,說某個進(jìn)程沒有響應(yīng),讓使用者選擇是否結(jié)束該進(jìn)程的運(yùn)行,等等。每個Windows進(jìn)程/線程再創(chuàng)建/退出時都要向csrss.exe進(jìn)程發(fā)出通知。 ● DOS軟件和16位Windows軟件在(32位)Windows上的運(yùn)行。 ● 其它。包括對當(dāng)?shù)卣Z言(輸入法)的支持。 這個進(jìn)程之所以叫csrss,是“C/S Run-time SubSystem”的意思,csrss是Windows子系統(tǒng)的服務(wù)進(jìn)程。其實(shí)三個子系統(tǒng)都是C/S結(jié)構(gòu),但是OS/2子系統(tǒng)的服務(wù)進(jìn)程稱為 os2ss,POSIX子系統(tǒng)的服務(wù)進(jìn)程稱為Psxss。之所以如此,據(jù)“Internals”說,是因?yàn)樽畛鯐r三個子系統(tǒng)的服務(wù)進(jìn)程是合在一起的,就叫 csrss,后來才把那兩個子系統(tǒng)移了出來另立門戶,但剩下的還繼續(xù)叫csrss。 二、內(nèi)核中的圖形設(shè)備驅(qū)動、即Win32k.sys模塊。其功能包括: ● 視窗管理,控制著窗口的顯示和各種屏幕輸出(例如光標(biāo)),還擔(dān)負(fù)著從鍵盤、鼠標(biāo)等設(shè)備接收輸入并將它們分發(fā)給各個具體應(yīng)用的任務(wù)。 ● 為應(yīng)用軟件提供一個圖形函數(shù)庫。 三、若干“系統(tǒng)DLL”,如Kernel32.dll、Advapi32.dll、User32.dll、以及Gdi32.dll。
上述的第二個要素Win32k.sys原先也是和csrss.exe合在一起的,這部分功能也由服務(wù)進(jìn)程在用戶空間提供。應(yīng)用進(jìn)程通過進(jìn)程間通信向 csrss發(fā)出圖形操作請求,由csrss完成有關(guān)的圖形操作。但是后來發(fā)現(xiàn)頻繁的進(jìn)程間通信和調(diào)度成了瓶頸,所以就把這一部分功能剝離出來,移進(jìn)了內(nèi)核,這就是Win32k.sys。這一來,對于一般的32位Windows應(yīng)用而言,留給csrss、或者說必須要通過csrss辦的事就很少了。但是,盡管如此,在創(chuàng)建WIndows進(jìn)程時還是要通知csrss,因?yàn)樗鼡?dān)負(fù)著對所有WIndows進(jìn)程的管理。另一方面,csrss在接到通知以后就會在屏幕上顯示那個沙漏狀的光標(biāo),如果這是個有窗口的進(jìn)程的話。 注意這里向csrss發(fā)出通知的是父進(jìn)程、即調(diào)用CreateProcess()的進(jìn)程,而不是新創(chuàng)建出來的進(jìn)程,它還沒有開始運(yùn)行。
至此CreateProcess()的操作已經(jīng)完成,從CreateProcess()返回就退出了kernel32.dll,回到了應(yīng)用程序或更高層的 DLL中。這四個階段都是立足于父進(jìn)程的用戶空間,在整個過程中進(jìn)行了多次系統(tǒng)調(diào)用,每次系統(tǒng)調(diào)用完成后都回到用戶空間中。例如,在第二階段中就調(diào)用了 NtCreateProcess(),第三階段中就調(diào)用了NtCreateThread(),而整個創(chuàng)建進(jìn)程的過程包括了許多次系統(tǒng)調(diào)用(有些系統(tǒng)調(diào)用屬于細(xì)節(jié),所以上面并未提及)。 其實(shí)Linux的進(jìn)程創(chuàng)建也不是一次系統(tǒng)調(diào)用就可完成的,典型的過程就包括fock()、execve()等系統(tǒng)調(diào)用,但是在Windows上就更多了。這跟Windows的整個系統(tǒng)調(diào)用界面的設(shè)計有關(guān)。以用戶空間的內(nèi)存分配為例,Linux的系統(tǒng)調(diào)用brk()只有一個參數(shù),那就是區(qū)間的長度,但是 Windows的系統(tǒng)調(diào)用NtAllocateVirtualMemory()卻有6個參數(shù),其第一個參數(shù)是ProcessHandle,這是標(biāo)志著一個已打開進(jìn)程對象的Handle。這說明什么呢?這說明Linux進(jìn)程只能為自己分配空間,而Windows進(jìn)程卻可以為別的進(jìn)程分配空間。或者說,在存儲空間的分配上Linux進(jìn)程是“自力更生”的,而Windows進(jìn)程卻可以“包辦代替”。 這對于系統(tǒng)設(shè)計的影響可能遠(yuǎn)超讀者的想像。就拿為子進(jìn)程的第一個線程分配用戶空間堆棧而言,既然Linux進(jìn)程(線程)只能為自己分配空間,而用戶空間堆棧又必須在進(jìn)入用戶空間運(yùn)行之前就已存在,那就只好在內(nèi)核中完成用戶空間堆棧的分配。相比之下,Windows進(jìn)程可以為別的進(jìn)程分配空間,于是就可以由父進(jìn)程在用戶空間中為子進(jìn)程完成這些操作。這樣,有些事情Linux只能在內(nèi)核中做,而Windows可以在用戶空間做。有些人稱Windows為“微內(nèi)核”,這或許也是個原因。而Windows的CreateProcess()中包含著更多的系統(tǒng)調(diào)用,也就很好理解了。 現(xiàn)在,雖然父進(jìn)程已經(jīng)從庫函數(shù)CreateProcess()中返回了,子進(jìn)程的運(yùn)行卻還未開始,它的運(yùn)行還要經(jīng)歷下面的第五和第六兩個階段。
第五階段:啟動初始線程 新創(chuàng)建的線程未必是可以被立即調(diào)度運(yùn)行的,因?yàn)橛脩艨赡茉趧?chuàng)建時把標(biāo)志位CREATE_ SUSPENDED設(shè)成了1。如果那樣的話,就需要等待別的進(jìn)程通過系統(tǒng)調(diào)用恢復(fù)其運(yùn)行資格以后才可以被調(diào)度運(yùn)行。否則現(xiàn)在已經(jīng)可以被調(diào)度運(yùn)行了。至于什么時候才會被調(diào)度運(yùn)行,則就要看優(yōu)先級等等條件了。而一旦受調(diào)度運(yùn)行,那就是以新建進(jìn)程的身份在運(yùn)行、與CreateProcess()的調(diào)用者無關(guān)了。 如前所述,當(dāng)進(jìn)程的第一個線程首次受調(diào)度運(yùn)行時,由于線程(系統(tǒng)空間)堆棧的設(shè)置,首先執(zhí)行的是KiThreadStartup。這段程序把目標(biāo)線程的IRQL從DPC級降低到APC級,然后調(diào)用內(nèi)核函數(shù)PspUserThreadStartup()。 最后,PspUserThreadStartup()將用戶空間ntdll.dll中的函數(shù)LdrInitializeThunk()作為APC函數(shù)掛入 APC隊列,再企圖“返回到”用戶空間。Windows的APC跟Linux的signal機(jī)制頗為相似,相當(dāng)于用戶空間的“中斷服務(wù)”。所以,在返回用戶空間的前夕,就會檢查APC函數(shù)的存在并加以執(zhí)行(如果存在的話)。 于是,此時的CPU將兩次進(jìn)入用戶空間。第一次是因?yàn)锳PC請求的存在而進(jìn)入用戶空間,執(zhí)行APC函數(shù)LdrInitializeThunk(),執(zhí)行完畢以后仍回到系統(tǒng)空間。然后,第二次進(jìn)入用戶空間才是“返回”用戶空間。返回到用戶空間的什么地方呢?前面已經(jīng)講到,返回到Kernel32.dll中的 BaseProcessStart()或BaseThreadStart(),對于進(jìn)程中的第一個線程是BaseProcessStart()。至于用戶程序所提供的(線程)入口,則是作為參數(shù)(函數(shù)指針)提供給BaseProcessStart()或BaseThreadStart()的,這兩個函數(shù)都會使用這指針調(diào)用由用戶提供的入口函數(shù)。
第六階段:用戶空間的初始化和DLL的連接 用戶空間的初始化和DLL的連接是由LdrInitializeThunk()作為APC函數(shù)的執(zhí)行來完成的。 在應(yīng)用軟件與動態(tài)連接庫的連接這一點(diǎn)上,我們已經(jīng)看到,不管是Linux、Windows、還是Wine,都是一致的,那就是在用戶空間完成: ● Linux的.so模塊連接由“解釋器”在用戶空間完成。“解釋器”相當(dāng)于一個不需要事先連接的動態(tài)庫,因?yàn)樗娜肟谑枪潭ǖ摹!敖忉屍鳌钡挠诚袷怯蓛?nèi)核裝入用戶空間的。 ● Windows的DLL連接由ntdll.dll中的LdrInitializeThunk()在用戶空間完成。在此之前ntdll.dll與應(yīng)用軟件尚未連接,但是已經(jīng)被映射到了用戶空間。函數(shù)LdrInitializeThunk()在映像中的位置是系統(tǒng)初始化時就預(yù)先確定并記錄在案的,所以在進(jìn)入這個函數(shù)之前也不需要連接。 ● Wine的動態(tài)庫連接分兩種情況。一種是ELF格式的.so模塊,另一種是PE格式的DLL。二者的連接都是在用戶空間完成的,前者仍由ELF解釋器ld-linux.so.2完成,后者則由工具軟件wine-kthread完成。后者的具體調(diào)用路徑是: main() > wine_init() > __wine_process_init() > __wine_kernel_init() > wine_switch_to_stack() > start_process() > LdrInitializeThunk() 這在“Wine的二進(jìn)制映像裝入和啟動”那篇漫談中已經(jīng)講到過了。注意這里最終完成DLL連接的函數(shù)也叫LdrInitializeThunk(),顯然Wine的作者對于Windows的這一套是清楚的。
通過以上的敘述,我們可以看到Windows的進(jìn)程創(chuàng)建過程與Linux有較大的不同,但是裝入PE映像和實(shí)現(xiàn)DLL連接的過程卻與Linux的對應(yīng)過程相似,只是把“解釋器”集成到了“系統(tǒng)DLL”里面,并且是作為APC函數(shù)執(zhí)行的,其他就沒有太大的區(qū)別了。但是,如果跟Wine的PE映像裝入過程相比,則顯然Wine的過程(見“Wine的二進(jìn)制映像裝入和啟動”)是比較復(fù)雜、效率也比較低的。
第一階段:打開目標(biāo)映像文件 在Win32位API中,創(chuàng)建進(jìn)程是由CreateProcess()完成的。這實(shí)際上是個宏定義,根據(jù)不同的情況定義成CreateProcessA ()或CreateProcessW()之一,這兩個函數(shù)都在kernel32.dll中(可以用工具depends觀察)。兩個函數(shù)的區(qū)別僅在于字符串的表達(dá),前者采用ASCII字符,而后者采用“寬字符”、即Unicode。實(shí)際上Windows的內(nèi)部都采用寬字符,所以前者只是把字符串轉(zhuǎn)換成寬字符格式,然后調(diào)用后者。 可以在Windows上運(yùn)行的可執(zhí)行軟件有好幾類,處理的方法自然就不一樣: ● Windows的32位.exe映像,直接運(yùn)行。 ● Windows的16位.exe映像,啟動ntvdm.exe,以原有命令行作為參數(shù)。 ● DOS的.exe、.com、或.pif映像,啟動ntvdm.exe,以原有命令行作為參數(shù)。 ● DOS的.bat或.cmd批命令文件(腳本),啟動cmd.exe,以原有命令行作為參數(shù)。 ● POSIX可執(zhí)行映像,啟動posix.exe,以原有命令行作為參數(shù)。 ● OS/2可執(zhí)行映像,啟動os2.exe,以原有命令行作為參數(shù)。 這里面最重要的當(dāng)然是32位的.exe映像,而最后兩類現(xiàn)在已經(jīng)很少見了。從對于除32位.exe以外的各種映像的處理,讀者不妨對比一下Wine對.exe映像的處理,看看這里有著什么樣的相似性。 當(dāng)然,我們在這里只關(guān)心32位.exe映像。對于這一類映像,CreateProcess()首先打開映像文件,再為其(分配)創(chuàng)建一個 “Section”、即內(nèi)存區(qū)間。創(chuàng)建內(nèi)存區(qū)間的目的當(dāng)然是要把映像文件影射到這個區(qū)間,不過此時還不忙著映射,還要看看。看什么呢?首先是看已經(jīng)打開的目標(biāo)文件是否一個合格的.exe映像(萬一是DLL映像?)。還要看的事就有點(diǎn)出乎讀者意外了,看的是在“注冊表”中的這個路徑: HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options
用depends可以看到,ntdll.dll中有個函數(shù)LdrQueryImageFileExecutionOption s(),就是專門干這個事的。 如果上述路徑下有以目標(biāo)映像文件的文件名和擴(kuò)展名為“鍵”的表項(xiàng),例如“image.exe”,而表項(xiàng)中又有名為“Debugger”的值,那么這個值 (一個字符串)就替換了原來的目標(biāo)文件名,變成新的目標(biāo)映像名,并重新執(zhí)行上述的第一階段操作。這樣做的目的當(dāng)然是為調(diào)試程序提供方便,但是我們不妨設(shè)想:如果黑客或某個木馬程序設(shè)法在注冊表中加上了一個表項(xiàng)?這時候用戶以為是啟動了程序A,而實(shí)際啟動的卻是B!。
第二階段:創(chuàng)建內(nèi)核中的進(jìn)程對象 我們知道,Linux上的每個進(jìn)程(線程)都有一個“進(jìn)程控制塊”、即task_struct數(shù)據(jù)結(jié)構(gòu),與具體進(jìn)程/線程有關(guān)的絕大部分信息都集中存儲在這個數(shù)據(jù)結(jié)構(gòu)中。而Windows則有所不同。首先,Windows的進(jìn)程和線程各有不同的“對象”、即數(shù)據(jù)結(jié)構(gòu),從概念上把線程和進(jìn)程分離開來。線程是具體的(執(zhí)行)上下文,是CPU調(diào)度的單位和目標(biāo),而進(jìn)程只是若干共享地址空間和特性(如調(diào)度優(yōu)先級)的線程的集合。于是,進(jìn)程有進(jìn)程的數(shù)據(jù)結(jié)構(gòu),線程有線程的數(shù)據(jù)結(jié)構(gòu)。這就好像是對一組task_struct數(shù)據(jù)結(jié)構(gòu)“提取公因子”所形成的結(jié)果,這個舉措是很好理解的。進(jìn)一步,Windows又把本可集中存儲的的進(jìn)程數(shù)據(jù)結(jié)構(gòu)也拆分成好幾個對象,有的在內(nèi)核中,有的則在用戶空間。 內(nèi)核中與進(jìn)程有關(guān)的對象有: ● EPROCESS。即struct _EPROCESS,在“Internals”書中也稱為“Process Block”。它代表著Windows的一個進(jìn)程,‘E’表示“Executive”,微軟把Windows內(nèi)核中的上層稱為“Executive”、以區(qū)別于下層的設(shè)備驅(qū)動和內(nèi)存管理等成分、一般翻譯成“執(zhí)行體”。“Executive”也有“管理”、“運(yùn)行”的意思(所以CEO就是“總裁”)。 ● KPROCESS。這是EPROCESS內(nèi)部的一個成分,其名稱就叫“Pcb”。 ● W32PROCESS。下面將要講到,在用戶空間有個“Windows子系統(tǒng)”的服務(wù)進(jìn)程csrss。這個服務(wù)進(jìn)程為系統(tǒng)中的每個Windows應(yīng)用進(jìn)程都維持著一個數(shù)據(jù)結(jié)構(gòu),其中包含了一些與窗口和圖形界面有關(guān)的信息。而對于窗口和圖形界面的操作原來也是由csrss在“客戶”進(jìn)程的請求下實(shí)現(xiàn)的。但是,為了提高效率,后來把這部分功能移到了內(nèi)核中。與此相應(yīng),有關(guān)數(shù)據(jù)結(jié)構(gòu)的一部分也需要移到內(nèi)核中,就成了W32PROCESS。 既然KPROCESS是EPROCESS一部分,實(shí)際上內(nèi)核中與進(jìn)程有關(guān)的對象實(shí)際上只有兩種,就是EPROCESS和W32PROCESS。不過這里沒有包括“打開對象表”,那也是每個進(jìn)程都有的(Linux內(nèi)核中的“打開文件表”也在進(jìn)程控制塊的外面)。 用戶空間與進(jìn)程有關(guān)的對象有: ● 如上所述,把W32PROCESS數(shù)據(jù)結(jié)構(gòu)移入內(nèi)核以后,csrss仍需要為每個Windows進(jìn)程保持一些別的信息,所以csrss內(nèi)部仍有按進(jìn)程的相應(yīng)數(shù)據(jù)結(jié)構(gòu)。 ● PEB(Process Environment Block)、即“進(jìn)程環(huán)境塊”。PEB中記錄著進(jìn)程的運(yùn)行參數(shù)、映像裝入地址等等信息。PEB在用戶空間中的位置是固定的,總是在 0x7ffdf000。在Windows中,用戶空間和系統(tǒng)空間的分界線是2GB、即0x80000000,所以PEB在靠近用戶空間頂端的地方。
“Internals”書中并未給出有關(guān)數(shù)據(jù)結(jié)構(gòu)的定義,但是通過Debug手段給出了EPROCESS的內(nèi)部結(jié)構(gòu): +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
可見,EPROCESS的第一個成分是Pcb,其類型是_KPROCESS、即KPROCESS,這是一個大小為0x6c的數(shù)據(jù)結(jié)構(gòu)。書中也給出了它的內(nèi)部結(jié)構(gòu)。 “Undocumented Windows 2000 Secrets”一書也以Debug手段給出了這個數(shù)據(jù)結(jié)構(gòu)的內(nèi)部結(jié)構(gòu),但是列出的結(jié)構(gòu)與此有很大的不同,也許是因?yàn)榘姹镜年P(guān)系。從所列的內(nèi)容看,似乎 “Secrets”一書倒是正確的,因?yàn)槟抢锼械腅PROCESS結(jié)構(gòu)中有關(guān)于虛存的成分Vm,是一個大小為0x50的數(shù)據(jù)結(jié)構(gòu),而這里沒有,但是虛存 (地址空間)顯然是進(jìn)程的主要資源,所以EPROCESS數(shù)據(jù)結(jié)構(gòu)中理應(yīng)有它的位置。由此看來,“Secrets”一書所述更接近于桌面和服務(wù)器系統(tǒng)的現(xiàn)實(shí),而“Internals”書中所列可能更接近于不帶MMU的嵌入式系統(tǒng)。而且,“Secrets”一書還在附錄C中給出了通過逆向工程手段得到的 EPROCESS和PEB等數(shù)據(jù)結(jié)構(gòu)的定義(代碼),這當(dāng)然是很有價值的。 那么,如果確有不同版本的EPROCESS,這會有什么影響呢?首先,用戶空間的應(yīng)用程序不能直接訪問內(nèi)核中的EPROCESS數(shù)據(jù)結(jié)構(gòu),所以具體的 EPROCESS數(shù)據(jù)結(jié)構(gòu)屬于內(nèi)核的內(nèi)部實(shí)現(xiàn),只要內(nèi)核中的各種成分、各個環(huán)節(jié)都配套成龍,“自圓其說”,就沒有什么問題,這跟Linux內(nèi)核中一些條件編譯和裁剪的效果是類似的。可是,另一方面,對于可以動態(tài)裝入的.sys模塊,如果在模塊中需要訪問這些數(shù)據(jù)結(jié)構(gòu),那就可能有問題了,因?yàn)?sys模塊都是以二進(jìn)制映像的形式提供的,不像在Linux中那樣可以由源代碼重新編譯。怎么辦呢?我們可以到Windows的DDK中去找找答案。 在DDK的.h文件中,有函數(shù)IoGetCurrentProcess()的申明:
NTKERNELAPI PEPROCESS IoGetCurrentProcess( VOID );
這個函數(shù)是內(nèi)核為.sys模塊提供的支撐函數(shù),相當(dāng)于由Linux內(nèi)核導(dǎo)出的函數(shù)。其返回值類型是PEPROCESS,就是指向EPROCESS數(shù)據(jù)結(jié)構(gòu)的指針。顯然,這跟Linux內(nèi)核中的current相似,調(diào)用的目的是獲取當(dāng)前進(jìn)程的EPROCESS數(shù)據(jù)結(jié)構(gòu)(指針)。但是,DDK的.h文件中卻并未給出EPROCESS數(shù)據(jù)結(jié)構(gòu)的定義,所以調(diào)用這個函數(shù)所得到的僅僅是個指針,實(shí)際上與void*并無區(qū)別。這意味著在.sys模塊中是不允許直接訪問其內(nèi)部成分的。那么,.sys模塊如何使用這個指針呢?下面就是一個例子,還是在DDK中:
NTKERNELAPI VOID MmProbeAndLockProcessPages ( IN OUT PMDL MemoryDescriptorList, IN PEPROCESS Process, IN KPROCESSOR_MODE AccessMode, IN LOCK_OPERATION Operation );
這個函數(shù)的作用是鎖定某個進(jìn)程的某些存儲頁面(不讓換出),其輸入?yún)?shù)之一就是指向該進(jìn)程的EPROCESS結(jié)構(gòu)的指針。當(dāng)然,這個函數(shù)也是由內(nèi)核提供的 (屬于我們所說的設(shè)備驅(qū)動界面)。所以指針的提供者和使用者都是內(nèi)核,只要這二者配套即可,.sys模塊在這里只不過是傳遞了一下,所以也不會有問題。 假定proc是指向進(jìn)程控制塊的指針,并且進(jìn)程控制塊中有個成份X,是個整數(shù),那么在Linux的動態(tài)安裝模塊中可以直接用“proc->X”訪問這個成分,但是在Windows的.sys模塊中則只能通過類似于get_X()、set_X()一類的支撐函數(shù)訪問這個成分。將數(shù)據(jù)結(jié)構(gòu)的內(nèi)容跟對于這些內(nèi)容的操作(method)相分離,正是“對象”與“數(shù)據(jù)結(jié)構(gòu)”的區(qū)別所在。而將數(shù)據(jù)結(jié)構(gòu)的內(nèi)容“封裝”起來,也正是微軟所需要的,因?yàn)樗辉敢夤_這些數(shù)據(jù)結(jié)構(gòu)。 對于兼容內(nèi)核的開發(fā),這意味著我們不必拘泥于采用與Windows完全一致的EPROCESS數(shù)據(jù)結(jié)構(gòu)(盡管“Secrets”的附錄C已經(jīng)給出了它的定義),一些內(nèi)部的操作和處理也不必完全與之相同,而只要與DDK所規(guī)定的界面相符就可以了。
了解了有關(guān)的進(jìn)程對象,我們可以言歸正傳了。 所謂創(chuàng)建內(nèi)核中的進(jìn)程對象,實(shí)際上就是創(chuàng)建以EPROCESS為核心、為基礎(chǔ)的相關(guān)數(shù)據(jù)結(jié)構(gòu),這就是系統(tǒng)調(diào)用NtCreateProcess()要做的事情,主要包括: ● 分配并設(shè)置EPROCESS數(shù)據(jù)結(jié)構(gòu)。 ● 其他相關(guān)的數(shù)據(jù)結(jié)構(gòu)的設(shè)置,例如“打開對象表”。 ● 為目標(biāo)進(jìn)程創(chuàng)建初始的地址空間。 ● 對目標(biāo)進(jìn)程的“內(nèi)核進(jìn)程塊”KPROCESS進(jìn)行初始化。 ● 將系統(tǒng)DLL的映像映射到目標(biāo)進(jìn)程的(用戶)地址空間。 ● 將目標(biāo)進(jìn)程的映像映射到其自身的用戶空間。 ● 設(shè)置好目標(biāo)進(jìn)程的“進(jìn)程環(huán)境塊”PEB。 ● 映射其他需要映射到用戶空間的數(shù)據(jù)結(jié)構(gòu),例如與“當(dāng)?shù)卣Z言支持”、即NLS有關(guān)的數(shù)據(jù)結(jié)構(gòu)。 ● 完成EPROCESS創(chuàng)建,將其掛入進(jìn)程隊列(注意受調(diào)度的是線程隊列而不是進(jìn)程隊列)。 這里將系統(tǒng)DLL、實(shí)際上是ntdll.dll、映射到目標(biāo)進(jìn)程的用戶空間是很關(guān)鍵的。這是因?yàn)?#xff0c;除別的、主流的功能和作用外,ntdll.dll同時也起著相當(dāng)于Linux中ELF“解釋器”的作用,也擔(dān)負(fù)著為目標(biāo)映像建立動態(tài)連接的任務(wù)。
值得注意的是,NtCreateProcess()與CreateProcess()不同。CreateProcess()創(chuàng)建一個進(jìn)程并使其(初始線程)運(yùn)行,除非在創(chuàng)建時就指定要將其掛起。而NtCreateProcess(),則只是在內(nèi)核中創(chuàng)建該進(jìn)程的EPROCESS數(shù)據(jù)結(jié)構(gòu)并為其建立起地址空間。這只是個空殼架子,因?yàn)闆]有線程就談不上運(yùn)行,調(diào)度的目標(biāo)是線程而不是線程。而且,對NtCreateProcess()的調(diào)用還有個條件,那就是目標(biāo)映像已經(jīng)被映射到一個存儲區(qū)間(Section)中。
第三階段:創(chuàng)建初始線程 如上所述,進(jìn)程只是個空架子,實(shí)際的運(yùn)行實(shí)體是里面的線程。所以下一步就是創(chuàng)建目標(biāo)進(jìn)程的初始線程,即其第一個線程。 與EPROCESS相對應(yīng),線程的數(shù)據(jù)結(jié)構(gòu)是ETHREAD,并且其第一個成分是數(shù)據(jù)結(jié)構(gòu)KTHREAD,稱為TCB。同樣,“Internals”和 “Secrets”兩本書中所列的ETHREAD內(nèi)部結(jié)構(gòu)有所不同,后者的附錄C中給出了通過逆向工程得到的ETHREAD數(shù)據(jù)結(jié)構(gòu)定義。 同樣,從Windows DDK中申明的一些函數(shù)也可以看出,.sys模塊只是傳遞ETHREAD指針或KTHREAD指針(由于KTHREAD是ETHREAD中的第一個成分,二者實(shí)際上是一回事),而不會直接訪問它的具體成分。
PKTHREAD NTAPI KeGetCurrentThread();
NTKERNELAPI KPRIORITY KeQueryPriorityThread (IN PKTHREAD Thread);
NTKERNELAPI LONG KeSetBasePriorityThread (IN PKTHREAD Thread, IN LONG Increment);
NTKERNELAPI PDEVICE_OBJECT IoGetDeviceToVerify(IN PETHREAD Thread);
此外,就像進(jìn)程有“進(jìn)程環(huán)境塊”PEB一樣,線程也有“線程環(huán)境塊”TEB,KTHREAD結(jié)構(gòu)中有個指針指向其存在于用戶空間的TEB。前面講過, PEB在用戶空間的位置是固定的,PEB下方就是TEB,進(jìn)程中有幾個線程就有幾個TEB,每個TEB占一個4KB的頁面。
這個階段的操作是通過系統(tǒng)調(diào)用NtCreateThread()完成的,主要包括: ● 創(chuàng)建和設(shè)置目標(biāo)線程的ETHREAD數(shù)據(jù)結(jié)構(gòu),并處理好與EPROCESS的關(guān)系(例如進(jìn)程塊中的線程計數(shù)等等)。 ● 在目標(biāo)進(jìn)程的用戶空間創(chuàng)建并設(shè)置目標(biāo)線程的TEB。 ● 將目標(biāo)線程在用戶空間的起始地址設(shè)置成指向Kernel32.dll中的BaseProcessStart()或BaseThreadStart(),前者用于進(jìn)程中的第一個線程,后者用于隨后的線程。用戶程序在調(diào)用NtCreateThread()時也要提供一個用戶級的起始函數(shù)(地址), BaseProcessStart()和BaseThreadStart()在完成初始化時會調(diào)用這個起始函數(shù)。ETHREAD數(shù)據(jù)結(jié)構(gòu)中有兩個成份,分別用來存放這兩個地址。 ● 設(shè)置目標(biāo)線程的KTHREAD數(shù)據(jù)結(jié)構(gòu)并為其分配堆棧。特別地,將其上下文中的斷點(diǎn)(返回點(diǎn))設(shè)置成指向內(nèi)核中的一段程序KiThreadStartup,使得該線程一旦被調(diào)度運(yùn)行時就從這里開始執(zhí)行。 ● 系統(tǒng)中可能登記了一些每當(dāng)創(chuàng)建線程時就應(yīng)加以調(diào)用的“通知”函數(shù),調(diào)用這些函數(shù)。
第四階段:通知Windows子系統(tǒng) Windows、確切地說是Windows NT、當(dāng)初的設(shè)計目標(biāo)是支持三種不同系統(tǒng)的應(yīng)用軟件。第一種是Windows本身的應(yīng)用軟件,即所謂“Native”Windows軟件,這是微軟開發(fā) Windows NT的真正目的。第二種是OS/2的應(yīng)用軟件,這是因?yàn)楫?dāng)時微軟與IBM還有合作關(guān)系。第三種是與Unix應(yīng)用軟件相似、符合POSIX標(biāo)準(zhǔn)的軟件,那是因?yàn)楫?dāng)時美國的軍方采購有這樣的要求。不過實(shí)際上微軟對后兩種應(yīng)用的支持從一開始就是半心半意的,后來翅膀長硬了,就更不必勉為其難了。但是,盡管如此,當(dāng)初在設(shè)計的時候還是考慮了對不同“平臺”的支持,即在同一個內(nèi)核的基礎(chǔ)上配以不同的外圍軟件,形成不同的應(yīng)用軟件運(yùn)行環(huán)境,微軟稱之為“子系統(tǒng)(Subsystem)”。于是,在Windows內(nèi)核上就有了所謂“Windows子系統(tǒng)”、“OS/2子系統(tǒng)”、和“POSIX子系統(tǒng)”。當(dāng)然,時至今日,實(shí)際上只剩下Windows子系統(tǒng)了。 那么,所謂子系統(tǒng)是怎樣構(gòu)成的呢?“Internals”書中闡明了Windows子系統(tǒng)的構(gòu)成,說這是由下列幾個要素構(gòu)成的。 一、子系統(tǒng)進(jìn)程csrss.exe。包括了對下列成分和功能的支持: ● 控制臺(字符型)窗口的操作。面向控制臺/終端的應(yīng)用本身不支持窗口操作(例如窗口的移動、大化/小化、遮蓋等等),但是又需要在窗口中運(yùn)行,所以需要有額外的支持。 ● 進(jìn)程和線程的管理。例如彈出一個對話窗,說某個進(jìn)程沒有響應(yīng),讓使用者選擇是否結(jié)束該進(jìn)程的運(yùn)行,等等。每個Windows進(jìn)程/線程再創(chuàng)建/退出時都要向csrss.exe進(jìn)程發(fā)出通知。 ● DOS軟件和16位Windows軟件在(32位)Windows上的運(yùn)行。 ● 其它。包括對當(dāng)?shù)卣Z言(輸入法)的支持。 這個進(jìn)程之所以叫csrss,是“C/S Run-time SubSystem”的意思,csrss是Windows子系統(tǒng)的服務(wù)進(jìn)程。其實(shí)三個子系統(tǒng)都是C/S結(jié)構(gòu),但是OS/2子系統(tǒng)的服務(wù)進(jìn)程稱為 os2ss,POSIX子系統(tǒng)的服務(wù)進(jìn)程稱為Psxss。之所以如此,據(jù)“Internals”說,是因?yàn)樽畛鯐r三個子系統(tǒng)的服務(wù)進(jìn)程是合在一起的,就叫 csrss,后來才把那兩個子系統(tǒng)移了出來另立門戶,但剩下的還繼續(xù)叫csrss。 二、內(nèi)核中的圖形設(shè)備驅(qū)動、即Win32k.sys模塊。其功能包括: ● 視窗管理,控制著窗口的顯示和各種屏幕輸出(例如光標(biāo)),還擔(dān)負(fù)著從鍵盤、鼠標(biāo)等設(shè)備接收輸入并將它們分發(fā)給各個具體應(yīng)用的任務(wù)。 ● 為應(yīng)用軟件提供一個圖形函數(shù)庫。 三、若干“系統(tǒng)DLL”,如Kernel32.dll、Advapi32.dll、User32.dll、以及Gdi32.dll。
上述的第二個要素Win32k.sys原先也是和csrss.exe合在一起的,這部分功能也由服務(wù)進(jìn)程在用戶空間提供。應(yīng)用進(jìn)程通過進(jìn)程間通信向 csrss發(fā)出圖形操作請求,由csrss完成有關(guān)的圖形操作。但是后來發(fā)現(xiàn)頻繁的進(jìn)程間通信和調(diào)度成了瓶頸,所以就把這一部分功能剝離出來,移進(jìn)了內(nèi)核,這就是Win32k.sys。這一來,對于一般的32位Windows應(yīng)用而言,留給csrss、或者說必須要通過csrss辦的事就很少了。但是,盡管如此,在創(chuàng)建WIndows進(jìn)程時還是要通知csrss,因?yàn)樗鼡?dān)負(fù)著對所有WIndows進(jìn)程的管理。另一方面,csrss在接到通知以后就會在屏幕上顯示那個沙漏狀的光標(biāo),如果這是個有窗口的進(jìn)程的話。 注意這里向csrss發(fā)出通知的是父進(jìn)程、即調(diào)用CreateProcess()的進(jìn)程,而不是新創(chuàng)建出來的進(jìn)程,它還沒有開始運(yùn)行。
至此CreateProcess()的操作已經(jīng)完成,從CreateProcess()返回就退出了kernel32.dll,回到了應(yīng)用程序或更高層的 DLL中。這四個階段都是立足于父進(jìn)程的用戶空間,在整個過程中進(jìn)行了多次系統(tǒng)調(diào)用,每次系統(tǒng)調(diào)用完成后都回到用戶空間中。例如,在第二階段中就調(diào)用了 NtCreateProcess(),第三階段中就調(diào)用了NtCreateThread(),而整個創(chuàng)建進(jìn)程的過程包括了許多次系統(tǒng)調(diào)用(有些系統(tǒng)調(diào)用屬于細(xì)節(jié),所以上面并未提及)。 其實(shí)Linux的進(jìn)程創(chuàng)建也不是一次系統(tǒng)調(diào)用就可完成的,典型的過程就包括fock()、execve()等系統(tǒng)調(diào)用,但是在Windows上就更多了。這跟Windows的整個系統(tǒng)調(diào)用界面的設(shè)計有關(guān)。以用戶空間的內(nèi)存分配為例,Linux的系統(tǒng)調(diào)用brk()只有一個參數(shù),那就是區(qū)間的長度,但是 Windows的系統(tǒng)調(diào)用NtAllocateVirtualMemory()卻有6個參數(shù),其第一個參數(shù)是ProcessHandle,這是標(biāo)志著一個已打開進(jìn)程對象的Handle。這說明什么呢?這說明Linux進(jìn)程只能為自己分配空間,而Windows進(jìn)程卻可以為別的進(jìn)程分配空間。或者說,在存儲空間的分配上Linux進(jìn)程是“自力更生”的,而Windows進(jìn)程卻可以“包辦代替”。 這對于系統(tǒng)設(shè)計的影響可能遠(yuǎn)超讀者的想像。就拿為子進(jìn)程的第一個線程分配用戶空間堆棧而言,既然Linux進(jìn)程(線程)只能為自己分配空間,而用戶空間堆棧又必須在進(jìn)入用戶空間運(yùn)行之前就已存在,那就只好在內(nèi)核中完成用戶空間堆棧的分配。相比之下,Windows進(jìn)程可以為別的進(jìn)程分配空間,于是就可以由父進(jìn)程在用戶空間中為子進(jìn)程完成這些操作。這樣,有些事情Linux只能在內(nèi)核中做,而Windows可以在用戶空間做。有些人稱Windows為“微內(nèi)核”,這或許也是個原因。而Windows的CreateProcess()中包含著更多的系統(tǒng)調(diào)用,也就很好理解了。 現(xiàn)在,雖然父進(jìn)程已經(jīng)從庫函數(shù)CreateProcess()中返回了,子進(jìn)程的運(yùn)行卻還未開始,它的運(yùn)行還要經(jīng)歷下面的第五和第六兩個階段。
第五階段:啟動初始線程 新創(chuàng)建的線程未必是可以被立即調(diào)度運(yùn)行的,因?yàn)橛脩艨赡茉趧?chuàng)建時把標(biāo)志位CREATE_ SUSPENDED設(shè)成了1。如果那樣的話,就需要等待別的進(jìn)程通過系統(tǒng)調(diào)用恢復(fù)其運(yùn)行資格以后才可以被調(diào)度運(yùn)行。否則現(xiàn)在已經(jīng)可以被調(diào)度運(yùn)行了。至于什么時候才會被調(diào)度運(yùn)行,則就要看優(yōu)先級等等條件了。而一旦受調(diào)度運(yùn)行,那就是以新建進(jìn)程的身份在運(yùn)行、與CreateProcess()的調(diào)用者無關(guān)了。 如前所述,當(dāng)進(jìn)程的第一個線程首次受調(diào)度運(yùn)行時,由于線程(系統(tǒng)空間)堆棧的設(shè)置,首先執(zhí)行的是KiThreadStartup。這段程序把目標(biāo)線程的IRQL從DPC級降低到APC級,然后調(diào)用內(nèi)核函數(shù)PspUserThreadStartup()。 最后,PspUserThreadStartup()將用戶空間ntdll.dll中的函數(shù)LdrInitializeThunk()作為APC函數(shù)掛入 APC隊列,再企圖“返回到”用戶空間。Windows的APC跟Linux的signal機(jī)制頗為相似,相當(dāng)于用戶空間的“中斷服務(wù)”。所以,在返回用戶空間的前夕,就會檢查APC函數(shù)的存在并加以執(zhí)行(如果存在的話)。 于是,此時的CPU將兩次進(jìn)入用戶空間。第一次是因?yàn)锳PC請求的存在而進(jìn)入用戶空間,執(zhí)行APC函數(shù)LdrInitializeThunk(),執(zhí)行完畢以后仍回到系統(tǒng)空間。然后,第二次進(jìn)入用戶空間才是“返回”用戶空間。返回到用戶空間的什么地方呢?前面已經(jīng)講到,返回到Kernel32.dll中的 BaseProcessStart()或BaseThreadStart(),對于進(jìn)程中的第一個線程是BaseProcessStart()。至于用戶程序所提供的(線程)入口,則是作為參數(shù)(函數(shù)指針)提供給BaseProcessStart()或BaseThreadStart()的,這兩個函數(shù)都會使用這指針調(diào)用由用戶提供的入口函數(shù)。
第六階段:用戶空間的初始化和DLL的連接 用戶空間的初始化和DLL的連接是由LdrInitializeThunk()作為APC函數(shù)的執(zhí)行來完成的。 在應(yīng)用軟件與動態(tài)連接庫的連接這一點(diǎn)上,我們已經(jīng)看到,不管是Linux、Windows、還是Wine,都是一致的,那就是在用戶空間完成: ● Linux的.so模塊連接由“解釋器”在用戶空間完成。“解釋器”相當(dāng)于一個不需要事先連接的動態(tài)庫,因?yàn)樗娜肟谑枪潭ǖ摹!敖忉屍鳌钡挠诚袷怯蓛?nèi)核裝入用戶空間的。 ● Windows的DLL連接由ntdll.dll中的LdrInitializeThunk()在用戶空間完成。在此之前ntdll.dll與應(yīng)用軟件尚未連接,但是已經(jīng)被映射到了用戶空間。函數(shù)LdrInitializeThunk()在映像中的位置是系統(tǒng)初始化時就預(yù)先確定并記錄在案的,所以在進(jìn)入這個函數(shù)之前也不需要連接。 ● Wine的動態(tài)庫連接分兩種情況。一種是ELF格式的.so模塊,另一種是PE格式的DLL。二者的連接都是在用戶空間完成的,前者仍由ELF解釋器ld-linux.so.2完成,后者則由工具軟件wine-kthread完成。后者的具體調(diào)用路徑是: main() > wine_init() > __wine_process_init() > __wine_kernel_init() > wine_switch_to_stack() > start_process() > LdrInitializeThunk() 這在“Wine的二進(jìn)制映像裝入和啟動”那篇漫談中已經(jīng)講到過了。注意這里最終完成DLL連接的函數(shù)也叫LdrInitializeThunk(),顯然Wine的作者對于Windows的這一套是清楚的。
通過以上的敘述,我們可以看到Windows的進(jìn)程創(chuàng)建過程與Linux有較大的不同,但是裝入PE映像和實(shí)現(xiàn)DLL連接的過程卻與Linux的對應(yīng)過程相似,只是把“解釋器”集成到了“系統(tǒng)DLL”里面,并且是作為APC函數(shù)執(zhí)行的,其他就沒有太大的區(qū)別了。但是,如果跟Wine的PE映像裝入過程相比,則顯然Wine的過程(見“Wine的二進(jìn)制映像裝入和啟動”)是比較復(fù)雜、效率也比較低的。
轉(zhuǎn)載于:https://www.cnblogs.com/NBSTAR/archive/2013/01/06/2848402.html
總結(jié)
以上是生活随笔為你收集整理的转载:Windows的进程创建和映像装入的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 图灵学院VIP课程第五期学习笔记
- 下一篇: 【Cocos 3d】金币粒子特效制作