[轉貼]Gloomy對Windows核心的分析(研究CreateProcess)

來源:互聯網
上載者:User

我給出一個反組譯碼Win32 API函數CreateProcess的例子,來示範研究子系統的技術,同時演
示Win32是如何與Windows NT的執行系統協同工作的。

從MSDN中得到函數原型:

BOOL CreateProcess(
  LPCTSTR lpApplicationName,// pointer to name of executable module
  LPTSTR lpCommandLine, // pointer to command line string
  LPSECURITY_ATTRIBUTES lpProcessAttributes, // process security attributes
  LPSECURITY_ATTRIBUTES lpThreadAttributes,   // thread security attributes
  BOOL bInheritHandles, // handle inheritance flag
  DWORD dwCreationFlags, // creation flags
  LPVOID lpEnvironment, // pointer to new environment block
  LPCTSTR lpCurrentDirectory,   // pointer to current directory name
  LPSTARTUPINFO lpStartupInfo, // pointer to STARTUPINFO
  LPPROCESS_INFORMATION lpProcessInformation // pointer to PROCESS_INFORMATION
);

函數中所有的參數都沒有詳盡的描述。很快,在開始的幾行中,建立了異常處理__except_h
andler3(堆棧中的結構體對應於Visual C的結構體)。然後根據dwCreationFlags進行相應
有趣的處理。在任何情況下都會在dwCreationFlags裡去掉標誌CREATE_NO_WINDOW(對於我來
說這是個迷)。之後檢查不允許的標誌組合DETACH_PROCESS | CREATE_NEW_CONSOLE。如果這
些位被同時設定就會輸出錯誤。從新進程優先順序中選擇一個優先順序(除一個之外,清除所有
dwCreationFlags中的優先順序位)。如果要求是REAL_TIME優先順序,但不能分到處理器,則設
置為HIGH_PRIORITY。接下來是對參數lpApplicationName、lpCommandLine、lpEnvironment
的繁瑣處理。分析結果標明,函數CreateProcessW實際上在文檔中已經寫明。因此,我們考
慮到命令列和應用程式名稱已不相同。DOS風格的形式為完整的路徑。使用未公開的ntdll.dll
中的函數:

NTSYSAPI
BOOLEAN
NTAPI
RtlDosPathNameToNtPathName_U (char* lpPath,
                    RTL_STRING *NtPath,
                    BOOLEAN AllocFlag,
                    RTL_STRING *Reserved);

結果會得到/??/:/樣的路徑。然後,填充公開了的OBJECT_ATTRIBUTES結構體,在ObjectNam
e域中放入指向獲得的路徑的指標並調用未公開的函數:

NTSYSAPI
IOSTATUS
NTAPI
NtOpenFile (OUT DWORD* Handle, IN ACCESS_MASK DesiredAccess,
      OBJECT_ATTRIBUTES* ObjAttr, PIO_STATUS_BLOCK IoStatusBlock,
      DWORD ShareAccess, DWORD OpenOptions);

訪問使用的是SYNCHRONYZE | FILE_EXECUTE。取得開啟檔案的控制代碼用於調用另外一個未公開
的函數:

NTSYSAPI
NTSTATUS
NTAPI
NtCreateSection(
  OUT PHANDLE SectionHandle,
  IN ACCESS_MASK DesiredAccess,
  IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
  IN PLARGE_INTEGER MaximumSize OPTIONAL,
  IN ULONG Protect,
  IN ULONG Attributes,
  IN HANDLE FileHandle OPTIONAL
  );

大多數未公開的系統函數都是由相應的公開的Win32 API調用的。API函數CreateFileMappin
g是對NtCreateSection的封裝。實際上,即使系統直接調用這些函數,也沒人會干擾(而且
還節省開銷)。有趣的是NtCreateSection的一個主要的、由API函數產生的參數:

DesiredAccess=(flProtectLow==PAGE_READWRITE)?STANDARD_RIGHTS_REQUIRED|7 :
                STANDART_RIGHTS_REQUIRED | 5;

DesiredAccess只可以取兩個值。從CreateProcessW中調用的形式如下:

NtCreateSection ( &SectionHandle, STANDARD_RIGHTS_REQUIRED| 0x1F,
            NULL, &qwMaximumSize,
            PAGE_EXECUTEREAD, SEC_IMAGE, NtFileHandle );

這樣就得到了映象,並將檔案——映象源——關閉。這是用公開的NtClose函數進行的。來分
析一下NtCreateSection返回後的代碼。對錯誤處理這裡就不進行討論了,否則會十分繁瑣,
要討論大量的次要的函數。我們來研究沒有發生錯誤而且映象是PE映象的情況。調用著名的
未公開函數:

NTSYSAPI NTSTATUS NTAPI
    NtQuerySection(
        IN HANDLE SectionHandle,
        IN SECTIONINFOCLASS SectionInformationClass,
        OUT PVOID SectionInformation,
        IN ULONG SectionInformationLength,
        OUT PULONG ReturnLength OPTIONAL
        );

系統中有一些類似於NtQueryInformationXxxxx這樣的函數(未公開)。要說是未公開的,在
NTDDK.H中還是描述了一些函數的原型和調用這些函數用到的結構體資訊。Matt Pietrek在其
在Microsoft Systems期刊(MSDN中有)的文章中詳細描述了NTDDK.H中的NtQueryInformati
onProcess的主要功能。遺憾的是,關於NtQuerySection函數的資訊是不存在的。所有這樣的
函數都有實際上相同的原型並處理作業系統中的對象。NtQuerySection返回兩類資訊(Sect
ionInformationClass可以為0或1)。對應於取0還是取1,結構體的大小為16或是58個位元組。
CreateProcessW調用的SectionInformation參數的資訊類是1。

Struct SECTION_INFO_CLASS1 {
DWORD EntryPoint;
DWORD field_4;
DWORD StackReserved;
DWORD StackCommited;
DWORD SubSystem;
DWORD ImageVersionMinor;
DWORD ImageVersionMajor;
DWORD unknown1;
DWORD characteristics;
DWORD Machine;
DWORD Unknown[4];
};

我們看到,這個資訊是從PE映象的首部中取得的。在主要的域characteristics中輸出的是映
象的類型(是否是可執行檔)。然後檢查機器類型,解析SubSystem域,檢查映象版本。並最
終調用未公開的函數:

NtCreateProcess(
  OUT PHANDLE ProcessHandle,
  IN ACCESS_MASK DesiredAccess,
  IN POBJECT_ATTRIBUTES ObjectAttributes,
  IN HANDLE ParentProcess, //-1
  IN BOOLEAN InheritHandles,
  IN HANDLE SectionHandle,
  IN HANDLE DebugPort OPTIONAL, // NULL
  IN HANDLE ExceptionPort OPTIONAL //NULL
  );

由此建立了Windows NT的進程對象。關閉映象,因為已經不再需要了。接著設定對象屬性,
調用未公開函數NtSetInformationProcess

NTSYSAPI
NTSTATUS
NTAPI
NtSetInformationProcess(
  IN HANDLE ProcessHandle,
  PROCESSINFOCLASS ClassInfo,
  IN PVOID Information,
  IN ULONG Length,
);

在NTDDK.H中有枚舉值_PROCESSINFOCLASS,這個值描述了資訊類。調整資訊類的值:Proces
sDefaultHardErrorMode,ProcessBasePriority。對於這些類,資訊結構體本身就是一個32
位的DWORD。然後調用未公開的函數,Matt Pietrek在其文章中介紹過該函數:

NTSYSAPI
NTSTATUS
NTAPI
NtQueryInformationProcess(
IN HANDLE ProcessHandle,
IN PROCESSINFOCLASS ProcessInformationClass,
OUT PVOID ProcessInformation,
IN ULONG ProcessInformationLength,
OUT PULONG ReturnLength OPTIONAL
);

我們取得的資訊是ProcessBasicInfo,NTDDK.H檔案中有對其的描述。

typedef struct _PROCESS_BASIC_INFORMATION {
  NTSTATUS ExitStatus;
  PPEB PebBaseAddress;
  KAFFINITY AffinityMask;
  KPRIORITY BasePriority;
  ULONG UniqueProcessId;
  ULONG InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION;

對於CreateProcessW來說,必需的資訊是PEB的地址。因為在獲得這項資訊之後很快就調用內
部函數_BasePushProcessParameters。從參數判斷,其用途是調整僅由此進程產生的地址空
間。接下來調用兩個內部的複雜函數。先調用_BaseCreateStack。_BaseCreateStack分配並
調整進程堆棧。第一,選出用於保留和提交(reservrd和commited)堆棧的值。而且,如果
SizeReserved和SizeCommited為0,則要從發出CreateProcess的進程的PE檔案的首部中擷取
這些值。接著對這些值進行修整並在進程產生的地址空間中保留記憶體,對此用到未公開的函
數NtAllocateVirtualMemory(對應於Win32 API函數VirtualAllocEx,VirtualAllocEx是對
其非常簡單的封裝,而且這兩個函數的參數完全相同)。之後,進行兩個調用,用下面的偽
碼能更簡潔的說明:

FreeReserved=SizeReserv-SizeCommited;
ReservedAddr+=FreeReserved;
if(SizeReserved<=SizeCommited) fl=0;
else {
  ReservedAddr-=Delta;
  SizeCommited+=Delta;
  fl=1;
    }
NtAllocateVirtualMemory(Han,&ReservedAddr,0,SizeCommited,1000,4);

//[skipped]

NtProtectVirtualMemory
  (ProcHan,&ReservedAddr,Delta,PAGE_READWRITE|PAGE_GUARD,&OldProt);
                /* 對VirtualProtectEx的封裝 */

可見,這裡在保留地區中分配記憶體(在其末尾)。並且分配的記憶體大於Delta。這一部分(大
小為Delta)的屬性是PAGE_GUARD和PAGE_READWRITE。最後得到以下結構體:

***Stack***
---------------?-ReservedAddr
|         |
|        

|
| RESERVED   |<- SizeReserved - (SizeCommited+Delta)
|         |
|--------------|-CommitedAddr
| GUARD PAGE |<- Delta
|--------------|
| READ_WRITE |<- SizeCommited
|         |
L----------------SS:ESP

這樣,為堆棧分配了SizeCommited位元組。保留了SizeReserved。之後在堆棧之下的保留區分
配的記憶體被轉換為GUARD頁(轉換成這種頁可以引發異常)。從原始碼中可以看到,錯誤的D
elta的大小可能會產成悲慘的後果。因為這可是個關鍵的資訊——我們來看從哪裡找出Delt
a的值:

.text:77F04B99         mov   eax, large fs:18h
.text:77F04B9F         mov   ecx, [eax+30h] ; PEB
.text:77F04BA2         mov   eax, [ecx+54h] ; READ ONLY DATA
                ; ReadOnlyStaticServerData
.text:77F04BA5         mov   edx, [eax+4]
[skipped]
.text:77F04BB1         mov   esi, [edx+128h] ; Delta

在這一部分裡,EAX寄存器指向用於所有進程的全域地區。這個地區只允許被讀取。當然,已
給出的關於堆棧的更高層次的資訊是眾所周知的,而這些資訊的真實性在原始碼中得到了證
實。結果,執行BaseCreateStack函數填充StackInformation結構體。

Typedef struct _StackInformation
{
  DWORD Reserved0;
  DWORD Reserved1;
  DWORD AddressOfTop;
  DWORD CommitAddress;
  DWORD ReservedAddress;
} StackInformation;

從這個結構體中得到資訊本質上是個參數,用來調用下面這個有趣的函數BaseInitializeCo
ntext:

BaseInitializeContext(PCONTEXT Context, // 0x200 bytes
PPEB Peb,
PVOID EntryPoint,
DWORD StackTop,
int Type // union (Process, Thread, Fiber)
);

這個函數的幾個參數:PEB的地址,堆棧的進入點和參數定義了要建立的上下文(纖程,進程
、線程)。函數填充CONTEXT結構體(NTDDK.H中有)的幾個域。其中一個域很有意思,在其
中放置了起始點(BaseFiberStart、BaseProcessStartThunk、BaseThreadStartThunk中的一
個)。這個點“分娩”出了線程,產生的線程就在新的上下文中執行。實際上,所有三個偏
移處的代碼都很簡短——就是填充相應的堆棧映象並轉到兩個函數中的某一個。這兩個函數
分別是_BaseProcessStart和_BaseThreadStart。這兩個函數很是相象,我們只看_BaseProc
essStart函數。

這個函數在鏈表中建立了第一個異常處理(見TEB)。當對記憶體進行了錯誤的訪問時,正是這
個異常處理調用了那個有OK和CANCEL的對話方塊。這個處理常式會結束當前進程。但有時如果
異常由錯誤的服務線程產生,則只結束這個線程。

於是,在BaseInitializeContext返回後,就填充相應的結構體。並且這個結構體被用作未公
開的NtCreateThread函數的參數。NtCreateThread的原型如下:

NTSYSAPI
NTSTATUS
NTAPI
NtCreateThread(
    OUT PHANDLE ThreadHandle,
    IN ACCESS_MASK DesiredAccess,
    IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
    IN HANDLE ProcessHandle,
    OUT PCLIENT_ID ClientID,
    IN PCONTEXT Context, /* see _BaseInitializeContext */
    IN StackInformation* StackInfo, /* see _BaseCreateStack */
    IN BOOLEAN CreateSuspended /* ==1 */
);

終於,在對PE映象的SubSystem主要域的資料進行處理之後,通過LPC轉到Win32服務。進程應
該只在Win32子系統下建立。關於此原因的一些高層次資訊可以在Halen Kaster的書中讀到。

對於CreateProcess函數來說,必須完成的任務就是啟動線程(當然,如果沒有在參數dwCre
ationFlags中設定CREATE_SUSPEND標誌)。線程的啟動進行對NtResumeThread(對Win32的R
esumeThread的封裝)的調用。完成了!現在剩下的還有釋放記憶體和正確的退出。

到此對Win32子系統的CreateProcess函數的主要分析可以得出結論:子系統通常與Windows
NT的執行體系統協同工作,子系統大多都使用未公開的函數,子系統通過LPC與自己的伺服器
通訊,許多Win32 API函數都是對Nt函數的封裝。所有這些都是我們熟知的,但我們需要用反
彙編來證實

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.