windows程式/執行緒建立過程 --- windows作業系統學習

Andrew.Hann發表於2013-12-06

有了之前的對程式和執行緒物件的學習的鋪墊後,我們現在可以開始學習windows下的程式建立過程了,我將嘗試著從原始碼的層次來分析在windows下建立一個程式都要涉及到哪些步驟,都要涉及到哪些資料結構。

 

1. 相關閱讀材料

《windows 核心原理與分析》 --- 潘愛民

《深入解析windows作業系統(第4版,中文版)》

http://bbs.pediy.com/showthread.php?p=819417#post819417      看雪上的精華貼

http://undoc.airesoft.co.uk/      查閱windows未公開的函式的網站

 

 

由於程式的建立可以在ring3模式下,也可以在ring0模式下,所以我們分兩個部分來分別說明!!

 

一 . Ring3程式建立流程

1. 簡介

當一個應用程式呼叫某個程式建立函式,比如CreateProcess、CreateProcessAsUser、CreateProcessWithTokenW、CreateProcessWithLogonW時,一個windows程式就被建立起來了。

建立一個windows程式的過程,是由作業系統的三個部分執行一些列步驟來完成的(之後會詳細介紹):

1. 客戶方的windows庫Kernel32.dll
2. windows執行體
3. windows子系統程式(Csrss.exe)

由於windows是多環境子系統的體系結構,因此,建立一個windows執行體程式物件(其他的子系統也可以使用),與建立一個windows程式的工作是分離的。

也就是說windows在建立程式的過程中有兩大類的工作要做:

1. windows系統加入的語義
2. 執行體/核心層物件等的建立

下面概括了一下在利用windows的CreateProcess函式來建立一個程式時所涉及的主要階段:

1. 開啟將要在該程式中執行的映像檔案(.exe)
2. 建立windows執行體程式物件
3. 建立初始執行緒(棧、執行環境、windows執行體執行緒物件)
4. 通知windows子系統新程式建立了,所以它可以為新程式和執行緒做好準備
5. 開始執行初始執行緒(除非指定了CREATE_SUSPENDING標誌)
6. 在新程式和執行緒的環境中,完成地址空間中的初始化(比如載入必要的DLL),並開始執行程式

在開始學習程式建立的詳細過程之前,有幾點是我們要注意的:

1. 在CreateProcess中,新程式的優先順序類別是在CreationFlags引數總由單獨的位來決定的,因此,我們可以為單個CreateProcess呼叫多個優先順序類別。
windows通過選取"最低優先順序類別集合",來解決為程式分配優先順序類別的問題 BOOL WINAPI CreateProcess( _In_opt_ LPCTSTR lpApplicationName, _Inout_opt_ LPTSTR lpCommandLine, _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes, _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, _In_ BOOL bInheritHandles, _In_ DWORD dwCreationFlags, _In_opt_ LPVOID lpEnvironment, _In_opt_ LPCTSTR lpCurrentDirectory, _In_ LPSTARTUPINFO lpStartupInfo, _Out_ LPPROCESS_INFORMATION lpProcessInformation ); 2. 如果建立新程式時沒有指定優先順序類別,那麼,它的優先順序類別預設被設定為Normal(程式/執行緒優先順序的問題參考 學習筆記(2)),除非建立執行緒的優先順序類別是Idle或者Below Normal,
在這種情況下,新程式的優先順序類別與建立程式時指定的的優先順序相同
3. 如果為新程式指定了Real-Time優先順序類別,但是該程式的呼叫者沒有"Increase Scheduling Priority(增加排程優先順序)"特權,則使用High優先順序類別。換句話說,CreateProcess
不會僅僅因為呼叫者沒有足夠的特權來建立Real-Time優先順序類別的程式而失敗,而是自動"降低"一點,新程式只是沒有Real-Time那麼高的優先順序而已 4. 所有的視窗都與桌面有關聯,從作業系統的角度理解,桌面是一個工作區的圖形表示。如果在CreateProcess中沒有指定桌面,那麼,該程式就與呼叫者的當前桌面關聯在一起

 

 

2. 詳細分析(CreateProcess)

 

階段一: 開啟將要被執行的映像

從最開始的地方,那就是我們點選滑鼠或者在命令列中輸入可執行程式的路徑來進行所謂的"開啟"程式。
在CreateProcess中的第一個階段是: 先找到適當的windows映像(.exe檔案),它將執行由呼叫者指定的可執行檔案(.exe)。然後建立一個記憶體區物件,以便稍後將它對映到新程式的地址空間中。如果沒有指定映像名稱,則該命令列的第一個符號(被定義成命令列字串中第一個空格或製表符之前的、合乎檔案規範的那部分)被用作映像檔名。

在windows XP和windows Server 2003上,CreateProcess會首先檢查機器上的"軟體限制策略"是否允許該映像被執行起來(《深入解析windows作業系統(第4版)》8.6軟體限制策略)

如果指定的可執行檔案是一個windows.exe型別的檔案,那麼,它可被直接使用。如果它不是一個windows.exe(例如MS-DOS、Win16、POSIX應用程式),那麼CreateProcess通過一系列的步驟來找到一個windows支援映像(support image),以便執行它。這個過程是必須的,因為"非windows"應用程式不能直接被執行,相反,windows使用少數幾個特定的"支援映像"中的某一個,由它負責實際執行這一個"非windows"程式。
例如:

1. 如果你試圖執行一個POSIX應用程式,那麼CreateProcess把它識別出來,並改變該映像,改成可以在windows可執行檔案Posix.exe上執行的新映像。
2. 如果你試圖執行一個MS-DOS或者Win16可執行檔案,那麼,被執行的映像變成了windows的可執行檔案Ntvdm.exe。

總之,我們不能直接建立一個非windows程式的程式,如果windows不能找到一種方法把要啟用的映像解析成一個windows程式,那麼CreateProcess就會"失敗"。

(這張圖以輻射狀展示windows對可執行檔案的型別判斷並自動進行映像轉換)

(這就是為什麼我們執行windows的bash指令碼.bat時彈出的是cmd的視窗的原因)

階段一屬於作業系統對的工作,和CreateProcess程式碼本身的關係並不是很大,但是卻是作業系統很重要的一步

 

 

階段二: 檢查和轉換工作

在CreateProcessA函式中呼叫了CreateProcessInternalA,呼叫該函式時,增加了兩個引數(一頭一尾加了個零引數),在該函式裡面也沒看到對這兩個引數的引用,而是直接傳遞給CreateProcessInternalW函式。

IDA->Kernel32.dll:

BOOL __stdcall CreateProcessA(LPCSTR lpApplicationName, LPSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, 
      LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment,
      LPCSTR lpCurrentDirectory, LPSTARTUPINFOA lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation) {
return CreateProcessInternalA( 0, lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation, 0); }

OD->動態除錯(C程式碼會在文章的最後給出):

進入CreateProcessInternalA的程式碼中,發現了大量的程式碼,下面請允許我一口氣把程式碼都貼出來,因為我覺得說什麼都不如原始的windows原始碼來的實在,我們一行一行地來分析這些程式碼的邏輯,我會盡我的能力解析程式碼的意思:

int __stdcall CreateProcessInternalA(int var_0, int lpApplicationName, int lpCommandLine, int lpProcessAttributes, int lpThreadAttributes, 
    int bInheritHandles, int dwCreationFlags, int lpEnvironment, int lpCurrentDirectory, int lpStartupInfo, int lpProcessInformation,
    int var_0_) { int result; // eax@2 int v13; // eax@7 int v14; // eax@10 int v15; // eax@17 int v16; // eax@19 int v17; // eax@21 int v18; // eax@24 int v19; // eax@26 int v20; // eax@30 int v21; // eax@32 signed int v22; // [sp-4h] [bp-B8h]@20 char v23; // [sp+14h] [bp-A0h]@15 int v24; // [sp+18h] [bp-9Ch]@15 unsigned __int32 v25; // [sp+1Ch] [bp-98h]@32 char Dst; // [sp+20h] [bp-94h]@3 int v27; // [sp+24h] [bp-90h]@3 int v28; // [sp+28h] [bp-8Ch]@3 int v29; // [sp+2Ch] [bp-88h]@3 char *v30; // [sp+68h] [bp-4Ch]@15 int v31; // [sp+6Ch] [bp-48h]@21 char v32; // [sp+70h] [bp-44h]@4 int v33; // [sp+74h] [bp-40h]@3 char v34; // [sp+78h] [bp-3Ch]@6 int v35; // [sp+7Ch] [bp-38h]@3 char v36; // [sp+80h] [bp-34h]@2 int v37; // [sp+84h] [bp-30h]@10 int v38; // [sp+88h] [bp-2Ch]@11 char v39; // [sp+8Ch] [bp-28h]@21 __int16 v40; // [sp+8Eh] [bp-26h]@19 int v41; // [sp+90h] [bp-24h]@21 unsigned __int16 v42; // [sp+94h] [bp-20h]@16 CPPEH_RECORD ms_exc; // [sp+9Ch] [bp-18h]@3 if ( !lpCommandLine ) { v37 = 0; v30 = &v23; v24 = 0; LABEL_3: v33 = 0; v35 = 0; _memmove(&Dst, (const void *)lpStartupInfo, 0x44u); v27 = 0; v28 = 0; v29 = 0; ms_exc.disabled = 1; if ( lpApplicationName && !Basep8BitStringToDynamicUnicodeString(&v32, lpApplicationName) || lpCurrentDirectory && !Basep8BitStringToDynamicUnicodeString(&v34, lpCurrentDirectory) ) goto LABEL_14; v13 = *(_DWORD *)(lpStartupInfo + 4); if ( v13 ) { ms_exc.disabled = 2; RtlInitAnsiString(&v42, v13); ms_exc.disabled = 1; if ( (_BYTE)NlsMbCodePageTag ) LOWORD(v15) = RtlxAnsiStringToUnicodeSize(&v42); else v15 = 2 * v42 + 2; v40 = v15; v16 = RtlAllocateHeap(*(_DWORD *)(*(_DWORD *)(__readfsdword(24) + 48) + 24), BaseDllTag, (unsigned __int16)v15); v27 = v16; if ( !v16 ) goto LABEL_20; v41 = v16; v17 = RtlAnsiStringToUnicodeString(&v39, &v42, 0); v31 = v17; if ( v17 < 0 ) goto LABEL_34; } if ( *(_DWORD *)(lpStartupInfo + 8) ) { RtlInitAnsiString(&v42, *(_DWORD *)(lpStartupInfo + 8)); if ( (_BYTE)NlsMbCodePageTag ) LOWORD(v18) = RtlxAnsiStringToUnicodeSize(&v42); else v18 = 2 * v42 + 2; v40 = v18; v19 = RtlAllocateHeap(*(_DWORD *)(*(_DWORD *)(__readfsdword(24) + 48) + 24), BaseDllTag, (unsigned __int16)v18); v28 = v19; if ( !v19 ) goto LABEL_20; v41 = v19; v17 = RtlAnsiStringToUnicodeString(&v39, &v42, 0); v31 = v17; if ( v17 < 0 ) { LABEL_34: v22 = v17; goto LABEL_35; } } if ( !*(_DWORD *)(lpStartupInfo + 12) ) { LABEL_10: ms_exc.disabled = 0; v14 = v37; if ( !v37 ) v14 = *((_DWORD *)v30 + 1); v38 = CreateProcessInternalW( var_0, v33, v14, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, v35, &Dst, lpProcessInformation, var_0_); goto LABEL_12; } RtlInitAnsiString(&v42, *(_DWORD *)(lpStartupInfo + 12)); if ( (_BYTE)NlsMbCodePageTag ) LOWORD(v20) = RtlxAnsiStringToUnicodeSize(&v42); else v20 = 2 * v42 + 2; v40 = v20; v25 = __readfsdword(24); v21 = RtlAllocateHeap(*(_DWORD *)(*(_DWORD *)(v25 + 48) + 24), BaseDllTag, (unsigned __int16)v20); v29 = v21; if ( v21 ) { v41 = v21; v17 = RtlAnsiStringToUnicodeString(&v39, &v42, 0); v31 = v17; if ( v17 >= 0 ) goto LABEL_10; goto LABEL_34; } LABEL_20: v22 = -1073741801; LABEL_35: BaseSetLastNTError(v22); LABEL_14: v38 = 0; ms_exc.disabled = 0; LABEL_12: ms_exc.disabled = -1; RtlFreeUnicodeString(&v36); RtlFreeUnicodeString(&v32); RtlFreeUnicodeString(&v34); RtlFreeHeap(*(_DWORD *)(*(_DWORD *)(__readfsdword(24) + 48) + 24), 0, v27); RtlFreeHeap(*(_DWORD *)(*(_DWORD *)(__readfsdword(24) + 48) + 24), 0, v28); return RtlFreeHeap(*(_DWORD *)(*(_DWORD *)(__readfsdword(24) + 48) + 24), 0, v29); } result = Basep8BitStringToDynamicUnicodeString(&v36, lpCommandLine); if ( result ) goto LABEL_3; return result; }

 

1. 函式的開始首先是一大段的臨時棧區變數的申請。由於是IDA反編譯出來的,名字都是這個遞增名字,其中有一個結構體變數CPPEH_RECORD注意一下:

CPPEH_RECORD    struc ; (sizeof=0x18, standard type)
   old_esp         dd ?
   exc_ptr         dd ?                    ; offset
   prev_er         dd ?                    ; offset
   handler         dd ?                    ; offset
   msEH_ptr        dd ?                    ; offset
   disabled        dd ?
CPPEH_RECORD    ends

 

2. 判斷lpCommandLine(就是我們輸入的notepad.exe這個命令)是否為空。即判斷是否為0,不為0則將lpCommandLine初始化為UNICODE字串

if ( !lpCommandLine )
{
    ...
}
result = Basep8BitStringToDynamicUnicodeString(&lpCommandLine_UNICODE, lpCommandLine);
if ( result )
    goto LABEL_3;
...

這裡的Basep8BitStringToDynamicUnicodeString()函式是windows的未公開undocumented函式(在文章的開頭有給出網址),它的定義如下:

BOOL WINAPI Basep8BitStringToDynamicUnicodeString ( 
    PUNICODE_STRING pConvertedStr, 
    LPCSTR pszAnsiStr 
)

接著繼續檢查引數(lpApplicationName / lpCurrentDirectory),我們跟隨程式碼來到LABEL_3:

1. 先將lpStartupInfor的內容拷貝到一個區域性變數lpStartupInfo_buf
2. 然後判斷引數lpApplicationName是否為0,不為0則引數轉換為UNICODE字串
3. 為0則跳過轉換繼續判斷引數lpCurrentDirectory是否為0(思考"與運算子""或運算子"在彙編程式碼中的表現形式,即"與運算子"的提前退出性,"或運算子"的並行檢查性),
不為0則引數轉換為UNICODE字串
4. lpApplicationName和lpCurrentDirectory中只有至少有一個不為0,則這個if判斷成立,繼續檢查引數
LABEL_3:
    ..
    _memmove(&lpStartupInfo_buf, (const void *)lpStartupInfo, 0x44u);
    .. 
    if ( lpApplicationName && !Basep8BitStringToDynamicUnicodeString(&v32, lpApplicationName)
      || lpCurrentDirectory && !Basep8BitStringToDynamicUnicodeString(&v34, lpCurrentDirectory) ) 
    ..

 

3. 繼續檢查引數(lpStartupInfo.lpReserved / lpStartupInfo.lpDesktop / lpStartupInfo.lpTitle),接著判斷STARTUPINFOA.lpReserved域是否為0,如果不為0,則申請了一個堆空間並將STARTUPINFOA.lpReserved的ASCII字串轉換成了UNICODE,接下來判斷lpStartupInfo.lpDesktop、lpStartupInfo.lpTitle。程式碼模式都是一樣的:

if ( STARTUPINFOA.lpReserved )  //STARTUPINFOA.lpReserved
{
      ms_exc.disabled = 2;
      RtlInitAnsiString(&STARTUPINFOA.lpReserved_buf, STARTUPINFOA.lpReserved);
      ms_exc.disabled = 1;
      if ( (_BYTE)NlsMbCodePageTag )
        LOWORD(STARTUPINFOA.lpReserved_buf_len) = RtlxAnsiStringToUnicodeSize(&STARTUPINFOA.lpReserved_buf);
      else
        STARTUPINFOA.lpReserved_buf_len = 2 * STARTUPINFOA.lpReserved_buf + 2;
      STARTUPINFOA.lpReserved_buf_len_ = STARTUPINFOA.lpReserved_buf_len;
      v16 = RtlAllocateHeap(
              *(_DWORD *)(*(_DWORD *)(__readfsdword(24) + 48) + 24),
              BaseDllTag,
              (unsigned __int16)STARTUPINFOA.lpReserved_buf_len);
      v27 = v16;
      if ( !v16 )
        goto LABEL_20;
      v41 = v16;
      v17 = RtlAnsiStringToUnicodeString(&v39, &STARTUPINFOA.lpReserved_buf, 0);
      v31 = v17;
      if ( v17 < 0 )
        goto LABEL_34;
    }
    if ( *(_DWORD *)(lpStartupInfo + 8) )  //STARTUPINFOA.lpDesktop 
    {
      RtlInitAnsiString(&STARTUPINFOA.lpReserved_buf, *(_DWORD *)(lpStartupInfo + 8));
      if ( (_BYTE)NlsMbCodePageTag )
        LOWORD(v18) = RtlxAnsiStringToUnicodeSize(&STARTUPINFOA.lpReserved_buf);
      else
        v18 = 2 * STARTUPINFOA.lpReserved_buf + 2;
      STARTUPINFOA.lpReserved_buf_len_ = v18;
      v19 = RtlAllocateHeap(*(_DWORD *)(*(_DWORD *)(__readfsdword(24) + 48) + 24), BaseDllTag, (unsigned __int16)v18);
      v28 = v19;
      if ( !v19 )
        goto LABEL_20;
      v41 = v19;
      v17 = RtlAnsiStringToUnicodeString(&v39, &STARTUPINFOA.lpReserved_buf, 0);
      v31 = v17;
      if ( v17 < 0 )
      {
LABEL_34:
        v22 = v17;
        goto LABEL_35;
      }
    }
    if ( !*(_DWORD *)(lpStartupInfo + 12) )  //STARTUPINFOA.lpTitle 
    {
    ...

這裡附上STARTUPINFOA資料結構的定義,這個資料結構負責在建立程式的時候給作業系統傳遞必要的和這個程式相關的資訊,非常重要

typedef struct _STARTUPINFO 
{
    DWORD cb;
    LPTSTR lpReserved;
    LPTSTR lpDesktop;
    LPTSTR lpTitle;
    DWORD dwX;
    DWORD dwY;
    DWORD dwXSize;
    DWORD dwYSize;
    DWORD dwXCountChars;
    DWORD dwYCountChars;
    DWORD dwFillAttribute;
    DWORD dwFlags;
    WORD wShowWindow;
    WORD cbReserved2;
    LPBYTE lpReserved2;
    HANDLE hStdInput;
    HANDLE hStdOutput;
    HANDLE hStdError;
} STARTUPINFO, *LPSTARTUPINFO; 

4. 這些引數都判斷完之後,就開始呼叫CreateProcessInternalW(),注意,這次傳入的引數全部都是UNICODE字串了,也就是符合NT式的標準,我們知道,windows在NT系統以後把所有的API(例如CreateProcessInternalW)實現都以UNICODE實現了,而保留原本的ASCII版本的API(例如CreateProcessInternalA)只是在中間做了一個"轉階層",最終都一定要呼叫UNICODE版本的API,從這個例子我們可以清除地看到這點。

...
 v38 = CreateProcessInternalW(
              var_0,
              v33,
              v14,
              lpProcessAttributes,
              lpThreadAttributes,
              bInheritHandles,
              dwCreationFlags,
              lpEnvironment,
              v35,
              &lpStartupInfo_buf,
              lpProcessInformation,
              var_0_);
...

看上面的步驟大家應該知道了其實CreateProcessInternalA函式只是對字串引數或者結構體中包含字串型別的域的作了檢查和轉換工作,然後就呼叫了下層函式

 

 

 

階段三: CreateProcessInternalW(),ring3階段程式建立流程

在開始分析之前,先明確一點:

我們現在還處在ring3層,也就是使用者模式的程式碼空間中,我們之前和我們接下來分析的程式碼在kernel32.dll中是可以逆向出原始碼的,到了ring0層我們就沒法直接逆向出原始碼了,
心理一定要明確這一點,因為程式的建立從大的分類來看是分ring3(使用者模式)和ring0(核心模式)的。

我們從CreateProcessInternalA()進入到了kernel32.dll中的關於程式建立的更底層的函式CreateProcessInternalW(),接下來分析這個函式的原始碼。

請原諒我又貼出一大段"雜亂"的程式碼,我思考了一下,為了保持思路的完整性,還是把原生的完整程式碼貼出來,然後再進行程式碼邏輯的分析,所以可能會造成朋友們看的不舒服,請多多包涵了。

int __stdcall CreateProcessInternalW(int var_0, size_t lpApplicationName, wchar_t *lpCommandLine, int lpProcessAttributes, int lpThreadAttributes, 
    int bInheritHandles, int dwCreationFlags, int lpEnvironment, const WCHAR *lpCurrentDirectory, const void *lpStartupInfo,
    int lpProcessInformation, int var_0_) { unsigned __int16 v12; // ax@22 int v13; // eax@22 int v14; // esi@23 size_t v15; // edi@33 int v16; // eax@34 int v17; // edi@39 int v18; // eax@40 signed int v19; // esi@42 int v20; // eax@47 int v21; // eax@50 signed int v22; // edi@55 int v23; // eax@55 HMODULE v24; // eax@65 HMODULE v25; // esi@65 signed int v26; // esi@76 int v27; // eax@76 int v28; // ecx@81 int v29; // eax@2 int v30; // eax@17 int v31; // edi@89 DWORD v32; // eax@91 int v33; // ecx@97 signed int v34; // eax@101 int v35; // eax@121 int v36; // esi@123 void (__stdcall *v37)(_DWORD, _DWORD, _DWORD); // esi@127 int *v38; // esi@131 int v39; // edi@131 int v40; // eax@132 int v41; // eax@135 int v42; // ecx@136 int v43; // esi@138 int result; // eax@154 int *v45; // esi@161 int v46; // edi@161 int v47; // ecx@161 int v48; // edx@162 int v49; // ecx@163 int v50; // eax@165 int v51; // esi@167 HMODULE v52; // eax@182 int v53; // eax@182 signed int v54; // esi@198 unsigned int v55; // eax@201 const wchar_t *v56; // edi@201 int v57; // edi@206 size_t v58; // eax@209 int v59; // eax@209 wchar_t *v60; // esi@209 wchar_t *v61; // edi@226 DWORD v62; // eax@259 int v63; // esi@259 DWORD v64; // eax@261 size_t v65; // eax@270 wchar_t v66; // ax@272 HANDLE v67; // eax@297 int *v68; // eax@329 int v69; // ecx@329 int v70; // ecx@330 size_t v71; // eax@358 int v72; // esi@358 unsigned __int32 v73; // eax@377 unsigned __int32 v74; // esi@377 size_t v75; // eax@377 wchar_t *v76; // eax@377 const wchar_t *v77; // edi@378 int v78; // eax@393 int v79; // eax@398 int v80; // esi@398 signed int v81; // [sp-4h] [bp-A28h]@173 signed int v82; // [sp-4h] [bp-A28h]@179 DWORD v83; // [sp-4h] [bp-A28h]@277 int v84; // [sp-4h] [bp-A28h]@289 signed int v85; // [sp-4h] [bp-A28h]@235 int v86; // [sp+18h] [bp-A0Ch]@22 unsigned __int32 v87; // [sp+28h] [bp-9FCh]@394 char v88; // [sp+2Ch] [bp-9F8h]@14 int v89; // [sp+30h] [bp-9F4h]@182 int v90; // [sp+34h] [bp-9F0h]@47 int v91; // [sp+38h] [bp-9ECh]@103 unsigned __int32 v92; // [sp+3Ch] [bp-9E8h]@140 unsigned __int32 v93; // [sp+40h] [bp-9E4h]@192 unsigned __int32 v94; // [sp+44h] [bp-9E0h]@171 unsigned __int32 v95; // [sp+48h] [bp-9DCh]@191 unsigned __int32 v96; // [sp+4Ch] [bp-9D8h]@409 unsigned __int32 v97; // [sp+50h] [bp-9D4h]@241 unsigned int v98; // [sp+54h] [bp-9D0h]@201 unsigned __int32 v99; // [sp+58h] [bp-9CCh]@190 unsigned __int32 v100; // [sp+5Ch] [bp-9C8h]@309 unsigned __int32 v101; // [sp+60h] [bp-9C4h]@247 HANDLE v102; // [sp+64h] [bp-9C0h]@297 unsigned __int32 v103; // [sp+68h] [bp-9BCh]@189 unsigned __int32 v104; // [sp+6Ch] [bp-9B8h]@220 unsigned __int32 v105; // [sp+70h] [bp-9B4h]@418 unsigned __int32 v106; // [sp+74h] [bp-9B0h]@144 unsigned __int32 v107; // [sp+78h] [bp-9ACh]@144 unsigned __int32 v108; // [sp+7Ch] [bp-9A8h]@377 unsigned int v109; // [sp+80h] [bp-9A4h]@209 DWORD v110; // [sp+84h] [bp-9A0h]@91 int v111; // [sp+88h] [bp-99Ch]@50 DWORD v112; // [sp+8Ch] [bp-998h]@90 unsigned __int32 v113; // [sp+90h] [bp-994h]@310 unsigned __int32 v114; // [sp+94h] [bp-990h]@89 unsigned __int32 v115; // [sp+98h] [bp-98Ch]@84 unsigned __int32 v116; // [sp+9Ch] [bp-988h]@158 int v117; // [sp+A0h] [bp-984h]@34 HMODULE v118; // [sp+A4h] [bp-980h]@65 int v119; // [sp+A8h] [bp-97Ch]@248 unsigned __int32 v120; // [sp+ACh] [bp-978h]@358 unsigned __int32 v121; // [sp+B0h] [bp-974h]@269 unsigned __int32 v122; // [sp+B4h] [bp-970h]@358 unsigned __int32 v123; // [sp+B8h] [bp-96Ch]@231 unsigned __int32 v124; // [sp+BCh] [bp-968h]@358 unsigned __int32 v125; // [sp+C0h] [bp-964h]@20 unsigned __int32 v126; // [sp+C4h] [bp-960h]@352 unsigned __int32 v127; // [sp+C8h] [bp-95Ch]@144 int v128; // [sp+CCh] [bp-958h]@209 unsigned __int32 v129; // [sp+D0h] [bp-954h]@144 unsigned __int32 v130; // [sp+D4h] [bp-950h]@209 unsigned __int32 v131; // [sp+D8h] [bp-94Ch]@181 char v132; // [sp+DCh] [bp-948h]@411 int v133; // [sp+E0h] [bp-944h]@411 DWORD v134; // [sp+E4h] [bp-940h]@261 char *v135; // [sp+E8h] [bp-93Ch]@224 char *v136; // [sp+ECh] [bp-938h]@224 char *v137; // [sp+F0h] [bp-934h]@224 char *v138; // [sp+F4h] [bp-930h]@1 char *v139; // [sp+F8h] [bp-92Ch]@1 char *v140; // [sp+FCh] [bp-928h]@1 char *v141; // [sp+100h] [bp-924h]@1 char *v142; // [sp+104h] [bp-920h]@1 int *v143; // [sp+108h] [bp-91Ch]@409 const void *v144; // [sp+110h] [bp-914h]@1 int v145; // [sp+114h] [bp-910h]@193 int v146; // [sp+118h] [bp-90Ch]@1 size_t v147; // [sp+11Ch] [bp-908h]@209 int v148; // [sp+120h] [bp-904h]@1 int v149; // [sp+124h] [bp-900h]@76 int v150; // [sp+128h] [bp-8FCh]@163 int *v151; // [sp+12Ch] [bp-8F8h]@86 int v152; // [sp+130h] [bp-8F4h]@198 int v153; // [sp+134h] [bp-8F0h]@81 int v154; // [sp+138h] [bp-8ECh]@22 int v155; // [sp+13Ch] [bp-8E8h]@17 int v156; // [sp+140h] [bp-8E4h]@1 char v157; // [sp+144h] [bp-8E0h]@23 __int16 v158; // [sp+146h] [bp-8DEh]@23 int v159; // [sp+148h] [bp-8DCh]@22 int v160; // [sp+14Ch] [bp-8D8h]@1 int v161; // [sp+150h] [bp-8D4h]@208 int v162; // [sp+154h] [bp-8D0h]@1 char v163[4]; // [sp+158h] [bp-8CCh]@1 int v164; // [sp+15Ch] [bp-8C8h]@1 char v165; // [sp+163h] [bp-8C1h]@33 int v166; // [sp+164h] [bp-8C0h]@1 int v167; // [sp+168h] [bp-8BCh]@232 int v168; // [sp+16Ch] [bp-8B8h]@409 int v169; // [sp+170h] [bp-8B4h]@1 int v170; // [sp+174h] [bp-8B0h]@25 DWORD dwErrCode; // [sp+178h] [bp-8ACh]@30 int v172; // [sp+17Ch] [bp-8A8h]@259 int v173; // [sp+180h] [bp-8A4h]@161 int v174; // [sp+184h] [bp-8A0h]@25 int v175; // [sp+18Ch] [bp-898h]@38 int v176; // [sp+1B0h] [bp-874h]@25 LPCWSTR lpFileName; // [sp+1C8h] [bp-85Ch]@1 size_t v178; // [sp+1CCh] [bp-858h]@356 wchar_t v179; // [sp+1D0h] [bp-854h]@252 wchar_t *v180; // [sp+1D4h] [bp-850h]@226 int v181; // [sp+1D8h] [bp-84Ch]@1 int v182; // [sp+1DCh] [bp-848h]@88 int v183; // [sp+1E0h] [bp-844h]@1 int v184; // [sp+1E4h] [bp-840h]@1 int v185; // [sp+1E8h] [bp-83Ch]@1 int v186; // [sp+1ECh] [bp-838h]@70 int v187; // [sp+1F0h] [bp-834h]@55 unsigned int v188; // [sp+1F8h] [bp-82Ch]@101 int v189; // [sp+1FCh] [bp-828h]@103 int v190; // [sp+200h] [bp-824h]@61 unsigned __int16 v191; // [sp+204h] [bp-820h]@63 unsigned __int16 v192; // [sp+206h] [bp-81Eh]@63 char v193; // [sp+20Dh] [bp-817h]@56 unsigned __int16 v194; // [sp+210h] [bp-814h]@59 int v195; // [sp+220h] [bp-804h]@72 wchar_t *Dest; // [sp+224h] [bp-800h]@25 int v197; // [sp+228h] [bp-7FCh]@1 int v198; // [sp+22Ch] [bp-7F8h]@25 char v199; // [sp+230h] [bp-7F4h]@87 int v200; // [sp+234h] [bp-7F0h]@88 wchar_t *v201; // [sp+248h] [bp-7DCh]@1 LPWSTR Str1; // [sp+24Ch] [bp-7D8h]@25 char v203; // [sp+253h] [bp-7D1h]@30 int v204; // [sp+254h] [bp-7D0h]@1 int v205; // [sp+258h] [bp-7CCh]@37 int v206; // [sp+25Ch] [bp-7C8h]@37 int *v207; // [sp+260h] [bp-7C4h]@37 int v208; // [sp+264h] [bp-7C0h]@37 int v209; // [sp+268h] [bp-7BCh]@37 int v210; // [sp+26Ch] [bp-7B8h]@37 char v211; // [sp+270h] [bp-7B4h]@103 int v212; // [sp+278h] [bp-7ACh]@104 int i; // [sp+284h] [bp-7A0h]@81 char v214; // [sp+28Ah] [bp-79Ah]@122 char v215; // [sp+28Bh] [bp-799h]@1 int v216; // [sp+28Ch] [bp-798h]@188 int v217; // [sp+290h] [bp-794h]@1 int v218; // [sp+294h] [bp-790h]@1 int v219; // [sp+298h] [bp-78Ch]@1 int v220; // [sp+29Ch] [bp-788h]@1 int v221; // [sp+2A0h] [bp-784h]@1 int v222; // [sp+2A4h] [bp-780h]@1 int v223; // [sp+2A8h] [bp-77Ch]@1 int v224; // [sp+2ACh] [bp-778h]@1 int v225; // [sp+2B0h] [bp-774h]@86 int v226; // [sp+2B4h] [bp-770h]@375 int v227; // [sp+2B8h] [bp-76Ch]@375 int v228; // [sp+2BCh] [bp-768h]@375 int v229; // [sp+2C0h] [bp-764h]@1 int v230; // [sp+2C4h] [bp-760h]@1 int v231; // [sp+2C8h] [bp-75Ch]@1 int v232; // [sp+2CCh] [bp-758h]@1 int v233; // [sp+2D0h] [bp-754h]@1 int v234; // [sp+2D4h] [bp-750h]@1 char v235; // [sp+2DBh] [bp-749h]@1 int v236; // [sp+2DCh] [bp-748h]@37 int v237; // [sp+2E0h] [bp-744h]@33 int v238; // [sp+2E4h] [bp-740h]@176 int v239; // [sp+2E8h] [bp-73Ch]@36 int v240; // [sp+2ECh] [bp-738h]@1 int v241; // [sp+2F0h] [bp-734h]@1 int v242; // [sp+2F4h] [bp-730h]@1 int v243; // [sp+2F8h] [bp-72Ch]@1 int v244; // [sp+2FCh] [bp-728h]@1 int v245; // [sp+300h] [bp-724h]@1 wchar_t *Source; // [sp+304h] [bp-720h]@1 size_t Size; // [sp+308h] [bp-71Ch]@1 char v248; // [sp+30Eh] [bp-716h]@25 char v249; // [sp+30Fh] [bp-715h]@1 char v250; // [sp+310h] [bp-714h]@37 char v251; // [sp+31Bh] [bp-709h]@30 char *v252; // [sp+31Ch] [bp-708h]@1 char *v253; // [sp+320h] [bp-704h]@1 char *v254; // [sp+324h] [bp-700h]@1 char *v255; // [sp+328h] [bp-6FCh]@1 int *v256; // [sp+32Ch] [bp-6F8h]@1 int *v257; // [sp+330h] [bp-6F4h]@1 int v258; // [sp+334h] [bp-6F0h]@1 int v259; // [sp+338h] [bp-6ECh]@35 int v260; // [sp+33Ch] [bp-6E8h]@106 int v261; // [sp+340h] [bp-6E4h]@107 char v262; // [sp+344h] [bp-6E0h]@324 int v263; // [sp+348h] [bp-6DCh]@3 char v264; // [sp+34Ch] [bp-6D8h]@324 int v265; // [sp+350h] [bp-6D4h]@3 char v266; // [sp+354h] [bp-6D0h]@99 wchar_t *v267; // [sp+358h] [bp-6CCh]@25 int v268; // [sp+35Ch] [bp-6C8h]@1 int v269; // [sp+360h] [bp-6C4h]@181 __int16 v270; // [sp+364h] [bp-6C0h]@154 __int16 v271; // [sp+366h] [bp-6BEh]@358 wchar_t *v272; // [sp+368h] [bp-6BCh]@25 int v273; // [sp+36Ch] [bp-6B8h]@33 int v274; // [sp+370h] [bp-6B4h]@34 char v275; // [sp+377h] [bp-6ADh]@30 char v276; // [sp+378h] [bp-6ACh]@120 char v277; // [sp+37Ch] [bp-6A8h]@67 int v278; // [sp+380h] [bp-6A4h]@370 LPWSTR FilePart; // [sp+384h] [bp-6A0h]@25 int v280; // [sp+388h] [bp-69Ch]@25 int v281; // [sp+38Ch] [bp-698h]@1 int v282; // [sp+390h] [bp-694h]@1 int v283; // [sp+394h] [bp-690h]@1 int v284; // [sp+398h] [bp-68Ch]@1 int v285; // [sp+39Ch] [bp-688h]@1 int v286; // [sp+3A0h] [bp-684h]@25 int v287; // [sp+3A4h] [bp-680h]@1 int v288; // [sp+3A8h] [bp-67Ch]@25 int v289; // [sp+3ACh] [bp-678h]@25 int v290; // [sp+3B0h] [bp-674h]@1 int v291; // [sp+3B4h] [bp-670h]@25 int v292; // [sp+3B8h] [bp-66Ch]@25 char v293; // [sp+3BCh] [bp-668h]@10 char v294; // [sp+3BDh] [bp-667h]@9 char v295; // [sp+3C0h] [bp-664h]@104 char v296; // [sp+68Ch] [bp-398h]@115 int v297; // [sp+6ACh] [bp-378h]@116 int v298; // [sp+6B4h] [bp-370h]@107 int v299; // [sp+6B8h] [bp-36Ch]@107 int v300; // [sp+6BCh] [bp-368h]@107 int v301; // [sp+6C0h] [bp-364h]@107 int v302; // [sp+6C4h] [bp-360h]@109 int v303; // [sp+6C8h] [bp-35Ch]@109 int v304; // [sp+6CCh] [bp-358h]@109 int v305; // [sp+6D0h] [bp-354h]@113 int v306; // [sp+6D4h] [bp-350h]@395 int v307; // [sp+6D8h] [bp-34Ch]@395 int v308; // [sp+6DCh] [bp-348h]@1 char v309; // [sp+6E8h] [bp-33Ch]@224 char v310; // [sp+710h] [bp-314h]@224 char v311; // [sp+716h] [bp-30Eh]@329 char v312; // [sp+734h] [bp-2F0h]@224 __int64 v313; // [sp+73Ch] [bp-2E8h]@107 signed __int16 v314; // [sp+744h] [bp-2E0h]@108 char v315; // [sp+74Ch] [bp-2D8h]@1 int v316; // [sp+760h] [bp-2C4h]@81 char v317; // [sp+770h] [bp-2B4h]@1 int v318; // [sp+784h] [bp-2A0h]@81 char v319; // [sp+794h] [bp-290h]@1 int v320; // [sp+7A8h] [bp-27Ch]@81 char v321; // [sp+7B8h] [bp-26Ch]@1 int v322; // [sp+7CCh] [bp-258h]@81 char v323; // [sp+7DCh] [bp-248h]@1 int v324; // [sp+7F0h] [bp-234h]@81 __int16 v325; // [sp+800h] [bp-224h]@57 CPPEH_RECORD ms_exc; // [sp+A0Ch] [bp-18h]@25 int v327; // [sp+A44h] [bp+20h]@2 v185 = var_0; Size = lpApplicationName; Source = lpCommandLine; v169 = lpProcessAttributes; v164 = lpThreadAttributes; v234 = lpEnvironment; lpFileName = lpCurrentDirectory; v144 = lpStartupInfo; v166 = lpProcessInformation; v148 = var_0_; v290 = 0; v201 = 0; v287 = 0; v233 = 0; v204 = 0; v160 = 0; v146 = dwCreationFlags & 0x8000000; v235 = 0; v162 = 0; v284 = 0; v282 = 0; v249 = 0; v285 = 0; v281 = 0; v283 = 0; v181 = 0; v138 = &v317; v139 = &v321; v140 = &v323; v141 = &v315; v142 = &v319; v229 = 0; v230 = 0; v231 = 0; v232 = 0; v221 = 0; v222 = 0; v223 = 0; v224 = 0; v256 = &v268; v257 = &v258; v252 = &v317; v253 = &v315; v254 = &v321; v255 = &v319; v217 = 0; v218 = 0; v219 = 0; v220 = 0; v197 = 0; v156 = 0; v184 = 0; v183 = 0; *(_DWORD *)v163 = 0; v243 = 0; v244 = 0; v245 = 0; v240 = 0; v241 = 0; v242 = 0; v215 = 0; memset(&v308, 0, 0x60u); *(_DWORD *)lpProcessInformation = 0; *(_DWORD *)(lpProcessInformation + 4) = 0; *(_DWORD *)(lpProcessInformation + 8) = 0; *(_DWORD *)(lpProcessInformation + 12) = 0; if ( var_0_ ) *(_DWORD *)var_0_ = 0; v29 = dwCreationFlags & 0xF7FFFFFF; v327 = v29; if ( (v29 & 0x18) == 24 ) goto LABEL_279; v265 = 0; v263 = 0; if ( v29 & 0x40 ) { v294 = 1; } else { if ( BYTE1(v29) & 0x40 ) { v294 = 5; } else { if ( v29 & 0x20 ) { v294 = 2; } else { if ( SBYTE1(v29) < 0 ) { v294 = 6; } else { if ( (char)v29 < 0 ) { v294 = 3; } else { if ( BYTE1(v29) & 1 ) v294 = (BasepIsRealtimeAllowed(0) != 0) + 3; else v294 = 0; } } } } } v293 = 0; LOWORD(v327) = v327 & 0x3E1F; if ( v327 & 0x800 ) { if ( !(v327 & 0x1000) ) goto LABEL_13; LABEL_279: SetLastError(0x57u); return 0; } if ( !(v327 & 0x1000) && *(_BYTE *)(BaseStaticServerData + 6644) ) v327 |= 0x800u; LABEL_13: if ( !(v327 & 0x800) && NtQueryInformationJobObject(0, 4, &v88, 4, 0) != -1073741790 ) v327 = v327 & 0xFFFFEFFF | 0x800; if ( !v234 || BYTE1(v327) & 4 ) goto LABEL_25; v30 = v234; v155 = v234; while ( *(_BYTE *)v30 || *(_BYTE *)(v30 + 1) ) ++v30; LOWORD(v154) = v30 - (_WORD)v234 + 1; v12 = v30 - (_WORD)v234 + 2; HIWORD(v154) = v12; v86 = 2 * v12; v159 = 0; v13 = NtAllocateVirtualMemory(-1, &v159, 0, &v86, 4096, 4); if ( v13 < 0 ) { v84 = v13; LABEL_290: BaseSetLastNTError(v84); return 0; } v158 = v86; v14 = RtlAnsiStringToUnicodeString(&v157, &v154, 0); if ( v14 < 0 ) { NtFreeVirtualMemory(-1, &v159, &v86, 32768); v84 = v14; goto LABEL_290; } v234 = v159; LABEL_25: v289 = 0; v291 = 0; v292 = 0; v288 = 0; v198 = 0; Str1 = 0; v267 = 0; v280 = 1; v286 = 0; v170 = 0; FilePart = 0; v272 = 0; v248 = 0; Dest = 0; ms_exc.disabled = 0; memcpy(&v174, v144, 0x44u); if ( BYTE1(v176) & 1 && BYTE1(v176) & 6 ) BYTE1(v176) &= 0xFEu; while ( 1 ) { if ( Str1 ) { v123 = __readfsdword(24); RtlFreeHeap(*(_DWORD *)(*(_DWORD *)(v123 + 48) + 24), 0, Str1); Str1 = 0; } if ( v198 ) { v104 = __readfsdword(24); RtlFreeHeap(*(_DWORD *)(*(_DWORD *)(v104 + 48) + 24), 0, v198); v198 = 0; } if ( v289 ) { NtClose(v289); v289 = 0; } dwErrCode = 0; v203 = 1; v251 = 0; v275 = 0; if ( Size ) { if ( !Source || !*Source ) { v275 = 1; Source = (wchar_t *)Size; } goto LABEL_33; } v121 = __readfsdword(24); Str1 = (LPWSTR)RtlAllocateHeap(*(_DWORD *)(*(_DWORD *)(v121 + 48) + 24), BaseDllTag, 520); if ( !Str1 ) goto LABEL_293; v65 = (size_t)Source; Size = (size_t)Source; v201 = Source; v61 = Source; v180 = Source; if ( *Source != 34 ) goto LABEL_271; v203 = 0; v61 = Source + 1; v180 = Source + 1; Size = (size_t)(Source + 1); while ( *v61 ) { if ( *v61 == 34 ) { v201 = v61; v248 = 1; break; } ++v61; v180 = v61; v201 = v61; } LABEL_259: v179 = *v201; *v201 = 0; v62 = SearchPathW(0, (LPCWSTR)Size, L".exe", 0x104u, Str1, 0); v63 = 2 * v62; v172 = 2 * v62; if ( 2 * v62 && (unsigned int)v63 < 0x208 ) { v64 = GetFileAttributesW(Str1); v134 = v64; if ( v64 != -1 && v64 & 0x10 ) { v63 = 0; } else { v172 = v63 + 1; v63 += 2; } v172 = v63; } if ( !v63 || (unsigned int)v63 >= 0x208 ) { v119 = RtlDetermineDosPathNameType_U(Size); if ( v119 == 5 ) goto LABEL_249; v67 = CreateFileW((LPCWSTR)Size, 0x80000000u, 3u, 0, 3u, 0x80u, 0); v102 = v67; if ( v67 != (HANDLE)-1 ) { CloseHandle(v67); LABEL_249: BaseSetLastNTError(-1073741772); } if ( dwErrCode ) SetLastError(dwErrCode); else dwErrCode = GetLastError(); *v201 = v179; Size = (size_t)Str1; if ( !*v61 || !v203 ) goto LABEL_173; ++v61; v180 = v61; v201 = v61; v251 = 1; v248 = 1; v65 = (size_t)Source; LABEL_271: Size = v65; while ( 1 ) { v66 = *v61; if ( !*v61 ) goto LABEL_259; if ( v66 == 32 || v66 == 9 ) { v201 = v61; goto LABEL_259; } ++v61; v180 = v61; v201 = v61; } } *v201 = v179; Size = (size_t)Str1; if ( BasepIsSetupInvokedByWinLogon(Str1) && !(BYTE3(v327) & 0x80) ) BYTE3(v327) |= 0x80u; LABEL_33: v15 = Size; v165 = RtlDosPathNameToNtPathName_U(Size, &v273, 0, &v237); if ( !v165 ) { v83 = 3; goto LABEL_278; } v198 = v274; RtlInitUnicodeString(&v268, v15); v16 = RtlDetermineDosPathNameType_U(v15); v117 = v16; if ( v16 != 2 && v16 != 1 ) { if ( !v197 ) { v94 = __readfsdword(24); v197 = RtlAllocateHeap(*(_DWORD *)(*(_DWORD *)(v94 + 48) + 24), 0, 522); if ( !v197 ) { v83 = 8; goto LABEL_278; } } RtlGetFullPathName_U(v15, 522, v197, 0); RtlInitUnicodeString(&v268, v197); } v258 = v273; v259 = v274; if ( (_WORD)v237 ) { v273 = v237; v274 = v238; } else { v239 = 0; } v205 = 24; v206 = v239; v208 = 64; v207 = &v273; v209 = 0; v210 = 0; v236 = NtOpenFile(&v289, 1048737, &v205, &v250, 5, 96); if ( v236 < 0 ) { v236 = NtOpenFile(&v289, 1048608, &v205, &v250, 5, 96); if ( v236 < 0 ) break; } if ( !v175 ) { v115 = __readfsdword(24); v175 = *(_DWORD *)(*(_DWORD *)(*(_DWORD *)(v115 + 48) + 16) + 124); } v17 = NtCreateSection(&v291, 983071, 0, 0, 16, 16777216, v289); v236 = v17; if ( v17 < 0 ) goto LABEL_425; v18 = BasepIsProcessAllowed(Size); v17 = v18; v236 = v18; if ( v18 < 0 ) { BaseSetLastNTError(v18); NtClose(v291); goto LABEL_173; } if ( BYTE1(v327) & 0x20 && *(_BYTE *)(BaseStaticServerData + 6645) ) { BYTE1(v327) &= 0xCFu; v19 = 2048; v327 |= 0x800u; v17 = -1073741519; v236 = -1073741519; v160 = 1; NtClose(v291); v291 = 0; } else { LABEL_425: v19 = 2048; } if ( !v249 ) { if ( v17 >= 0 || v17 == -1073741521 && !BaseIsDosApplication(&v273, -1073741521) ) { if ( v284 ) { v100 = __readfsdword(24); RtlFreeHeap(*(_DWORD *)(*(_DWORD *)(v100 + 48) + 24), 0, v284); v284 = 0; } if ( v285 ) { v113 = __readfsdword(24); RtlFreeHeap(*(_DWORD *)(*(_DWORD *)(v113 + 48) + 24), 0, v285); v285 = 0; } v20 = BasepCheckBadapp(v289, v274, v234, &v284, &v282, &v285, &v281); v90 = v20; if ( v20 < 0 ) { if ( v20 == -1073741790 ) SetLastError(0x4C7u); else BaseSetLastNTError(v20); if ( v291 ) { NtClose(v291); v291 = 0; } goto LABEL_173; } } if ( !v249 && !(BYTE3(v327) & 2) ) { v21 = BasepCheckWinSaferRestrictions(v185, Size, v289, &v156, &v183, &v184); v111 = v21; if ( v21 == -1 ) { SetLastError(0x4ECu); } else { if ( v21 >= 0 ) goto LABEL_52; BaseSetLastNTError(v21); } v214 = 0; goto LABEL_126; } } LABEL_52: if ( v17 >= 0 ) goto LABEL_53; if ( v17 == -1073741541 ) goto LABEL_198; if ( v17 <= -1073741522 ) goto LABEL_277; if ( v17 <= -1073741520 ) { LABEL_198: v54 = 1; v152 = 1; if ( v17 != -1073741520 ) { if ( v17 != -1073741541 ) { v54 = BaseIsDosApplication(&v273, v17); v152 = v54; if ( !v54 ) { v55 = (unsigned int)(unsigned __int16)v273 >> 1; v56 = (const wchar_t *)(v274 + 2 * v55 - 8); v98 = v274 + 2 * v55 - 8; if ( (unsigned __int16)v273 < 8u || __wcsnicmp((const wchar_t *)(v274 + 2 * v55 - 8), L".bat", 4u) && __wcsnicmp(v56, L".cmd", 4u) ) goto LABEL_277; v57 = v275 || v248; if ( v275 || (v161 = 0, v248) ) v161 = 1; v147 = _wcslen(Source); v58 = _wcslen(Str); v109 = v161 + v147 + v58 + v57 + 1; v130 = __readfsdword(24); v59 = RtlAllocateHeap(*(_DWORD *)(*(_DWORD *)(v130 + 48) + 24), BaseDllTag, 2 * v109); v60 = (wchar_t *)v59; v128 = v59; if ( !v59 ) goto LABEL_293; _wcscpy((wchar_t *)v59, Str); if ( v275 || v248 ) _wcscat(v60, L"\""); _wcscat(v60, Source); if ( v275 || v248 ) _wcscat(v60, L"\""); RtlInitUnicodeString(&v270, v60); LABEL_215: Source = v272; Size = 0; goto LABEL_216; } } } v204 = 16; if ( !BaseCreateVDMEnvironment(v234, &v264, &v262) || !BaseCheckVDM(v54 | 0x10, Size, Source, lpFileName, &v264, &v296, &v287, v327, &v174) ) goto LABEL_173; v68 = &v298; v69 = (v311 & 7) - 1; if ( (v311 & 7) == 1 ) { v233 = 1; if ( v327 & 8 ) { v83 = 5; goto LABEL_278; } if ( !BaseGetVdmConfigInfo(Source, v287, 16, &v266, &v286) ) goto LABEL_344; Source = v267; Size = 0; LABEL_346: v35 = v290; goto LABEL_347; } goto LABEL_330; } if ( v17 == -1073741519 ) { if ( BYTE1(v327) & 0x20 ) goto LABEL_198; v235 = 1; if ( !BaseCreateVDMEnvironment(v234, &v264, &v262) ) goto LABEL_173; while ( 1 ) { v204 = (v19 & v327) != 0 ? 64 : 32; if ( BaseCheckVDM((v19 & v327) != 0 ? 64 : 32, Size, Source, lpFileName, &v264, &v296, &v287, v327, &v174) ) break; if ( v204 != 32 || GetLastError() != 5 ) goto LABEL_173; v327 |= v19; } v68 = &v298; v69 = (v311 & 7) - 1; if ( (v311 & 7) == 1 ) { v233 = 1; if ( v160 ) v286 = 1; if ( !BaseGetVdmConfigInfo(Source, v287, v204, &v266, &v286) ) { LABEL_344: v82 = v17; goto LABEL_180; } Source = v267; Size = 0; BYTE3(v327) |= 8u; v327 &= 0xFFFFFFE7u; v176 |= 0x40u; goto LABEL_346; } LABEL_330: v70 = v69 - 1; if ( !v70 ) { v83 = 21; goto LABEL_278; } if ( v70 != 2 ) goto LABEL_346; v233 = 4; v35 = v68[3]; v290 = v35; LABEL_347: --v286; if ( v35 ) goto LABEL_122; bInheritHandles = 0; if ( v234 && !(BYTE1(v327) & 4) ) RtlDestroyEnvironment(v234); v234 = v263; LABEL_216: v249 = 1; } else { if ( v17 != -1073741209 ) goto LABEL_277; SetLastError(0x10FEu); LABEL_53: if ( !v235 && v19 & v327 ) BYTE1(v327) &= 0xF7u; v22 = 1; v23 = NtQuerySection(v291, 1, &v187, 48, 0); v236 = v23; if ( v23 < 0 ) goto LABEL_242; if ( v193 & 0x20 ) goto LABEL_277; v325 = 0; if ( !(v327 & 3) || (v126 = __readfsdword(24), *(_BYTE *)(*(_DWORD *)(v126 + 48) + 1)) ) LdrQueryImageFileExecutionOptions(&v273, L"Debugger", 1, &v325, 520, 0); if ( v194 < v7FFE002C || v194 > v7FFE002E ) { v168 = 6; v143 = &v273; NtRaiseHardError(1073741859, 1, 1, &v143, 1, &v168); v96 = __readfsdword(24); if ( *(_DWORD *)(*(_DWORD *)(v96 + 48) + 184) <= 3u ) LABEL_277: v83 = 193; else v83 = 216; goto LABEL_278; } if ( v190 != 2 && v190 != 3 ) { NtClose(v291); v291 = 0; if ( v190 != 7 ) { v83 = 129; goto LABEL_278; } if ( !BuildSubSysCommandLine(L"POSIX /P ", Size, Source, &v270) ) goto LABEL_173; goto LABEL_215; } if ( !BasepIsImageVersionOk(v192, v191) ) goto LABEL_277; if ( !v325 ) { v24 = LoadLibraryA("advapi32.dll"); v25 = v24; v118 = v24; if ( v24 ) { if ( GetProcAddress(v24, "CreateProcessAsUserSecure") ) { v236 = NtQuerySystemInformation(71, &v277, 4, 0); if ( !v236 ) v215 = 1; } FreeLibrary(v25); } v186 = BaseFormatObjectAttributes(&v205, v169, 0); if ( v215 && v185 && v169 ) { v243 = *(_DWORD *)v169; v244 = *(_DWORD *)(v169 + 4); v245 = *(_DWORD *)(v169 + 8); v244 = 0; v186 = BaseFormatObjectAttributes(&v205, &v243, 0); v22 = 1; } v195 = 0; if ( BYTE3(v327) & 1 ) v195 = v22; if ( v327 & 3 ) { v23 = DbgUiConnectToDbg(); v236 = v23; if ( v23 < 0 ) goto LABEL_242; v162 = DbgUiGetThreadDebugObject(); if ( v327 & 2 ) v195 |= 2u; } if ( bInheritHandles ) v195 |= 4u; v149 = v194 == 332 ? v284 : 0; v26 = -1; v27 = NtCreateProcessEx(&v292, 2035711, v186, -1, v195, v291, v162, 0, v156); v236 = v27; if ( v27 < 0 ) goto LABEL_426; if ( v294 ) { v167 = 0; if ( v294 == 4 ) v167 = BasepIsRealtimeAllowed(v22); v236 = NtSetInformationProcess(v292, 18, &v293, 2); if ( v167 ) BasepReleasePrivilege(v167); if ( v236 < 0 ) { v85 = v236; LABEL_363: BaseSetLastNTError(v85); LABEL_364: v81 = v26; goto LABEL_174; } } if ( BYTE3(v327) & 4 ) { v145 = v22; NtSetInformationProcess(v292, 12, &v145, 4); } if ( v204 ) { v290 = v292; if ( !BaseUpdateVDMEntry(v22, &v290, v287, v204) ) { v290 = 0; goto LABEL_364; } v233 |= 2u; } if ( v286 ) { v278 = v286; v27 = NtAllocateVirtualMemory(v292, &v280, 0, &v278, 8192, 64); v236 = v27; if ( v27 < 0 ) goto LABEL_426; } v318 = (unsigned __int16)v268 + 20; v322 = (unsigned __int16)v268 + 16; v324 = (unsigned __int16)v268 + 2; v316 = (unsigned __int16)v258 + 20; v320 = (unsigned __int16)v258 + 16; v28 = 0; v153 = 0; for ( i = 0; i != 5; ++i ) { v28 += *((_DWORD *)(&v138)[4 * i] + 5); v153 = v28; } v116 = __readfsdword(24); v181 = RtlAllocateHeap(*(_DWORD *)(*(_DWORD *)(v116 + 48) + 24), 0, v28); if ( !v181 ) { v85 = -1073741801; goto LABEL_363; } i = 0; while ( i != 5 ) { v45 = (int *)&(&v138)[4 * i]; v46 = *v45; v47 = *(_DWORD *)(*v45 + 20); v173 = *(_DWORD *)(*v45 + 20); if ( i ) v48 = *(_DWORD *)(*(v45 - 1) + 8) + *(_DWORD *)(*(v45 - 1) + 20); else v48 = v181; v150 = v48; v49 = v47 & 0xFFFFFFFE; v173 = v49; if ( (unsigned int)v49 > 0xFFFE ) { v49 = 65534; v173 = 65534; } if ( (unsigned int)v49 < 2 ) { v48 = v46 + 32; v150 = v46 + 32; v49 = 2; v173 = 2; } v50 = *v45; *(_DWORD *)(*v45 + 8) = v48; *(_DWORD *)(v50 + 16) = v49; *(_DWORD *)(v50 + 12) = v48; *(_DWORD *)(v50 + 20) = v49; *(_DWORD *)(v50 + 4) = v48; if ( v48 ) **(_WORD **)(v46 + 4) = 0; v51 = *v45; *(_WORD *)v51 = 0; *(_WORD *)(v51 + 2) = v49; ++i; v26 = -1; } v230 = v292; v229 = v289; v231 = v291; if ( v285 ) { v225 = v268; v226 = v269; v227 = v285; v228 = v281; } v151 = &v308; v27 = BasepSxsCreateProcessCsrMessage( v285 != 0 ? (int)&v225 : 0, 0, &v252, &v221, &v256, &v229, &v254, &v217, &v323, &v308); v236 = v27; if ( v27 < 0 || (v27 = NtQueryInformationProcess(v292, 0, &v199, 24, 0), v236 = v27, v27 < 0) ) { LABEL_426: v85 = v27; goto LABEL_363; } v182 = v200; if ( lpFileName ) { v114 = __readfsdword(24); v31 = RtlAllocateHeap(*(_DWORD *)(*(_DWORD *)(v114 + 48) + 24), BaseDllTag, 522); v170 = v31; if ( !v31 ) { LABEL_293: v82 = -1073741801; goto LABEL_180; } v112 = GetFullPathNameW(lpFileName, 0x104u, (LPWSTR)v31, &FilePart); if ( v112 > 0x104 || (v32 = GetFileAttributesW((LPCWSTR)v31), v110 = v32, v32 == -1) || !(v32 & 0x10) ) { v83 = 267; goto LABEL_278; } } if ( v251 || v275 ) { v73 = __readfsdword(24); v74 = v73; v108 = v73; v75 = _wcslen(Source); v76 = (wchar_t *)RtlAllocateHeap(*(_DWORD *)(*(_DWORD *)(v74 + 48) + 24), 0, 2 * v75 + 6); Dest = v76; if ( v76 ) { _wcscpy(v76, L"\""); v77 = v201; if ( v251 ) { v179 = *v201; *v201 = 0; } _wcscat(Dest, Source); _wcscat(Dest, L"\""); if ( v251 ) { *v77 = v179; _wcscat(Dest, v77); } } else { if ( v251 ) v251 = 0; if ( v275 ) v275 = 0; } } if ( *(_BYTE *)v151 & 1 ) *(_DWORD *)v163 |= 1u; if ( v251 || (v33 = (int)Source, v275) ) v33 = (int)Dest; if ( !BasePushProcessParameters( v163[0], v292, v182, (LPCWSTR)Size, v170, v33, v234, (int)&v174, v327 | v146, bInheritHandles, v235 != 0 ? 2 : 0, v149, v282) ) goto LABEL_173; RtlFreeUnicodeString(&v266); v267 = 0; if ( !v204 ) { if ( !bInheritHandles ) { if ( !(BYTE1(v176) & 1) ) { if ( !(v327 & 0x8000018) ) { if ( v190 == 3 ) { v236 = NtReadVirtualMemory(v292, v182 + 16, &v216, 4, 0); if ( v236 >= 0 ) { v103 = __readfsdword(24); if ( (*(_DWORD *)(*(_DWORD *)(*(_DWORD *)(v103 + 48) + 16) + 24) & 0x10000003) != 3 ) { v101 = __readfsdword(24); StuffStdHandle(v292, *(_DWORD *)(*(_DWORD *)(*(_DWORD *)(v101 + 48) + 16) + 24), v216 + 24); } v99 = __readfsdword(24); if ( (*(_DWORD *)(*(_DWORD *)(*(_DWORD *)(v99 + 48) + 16) + 28) & 0x10000003) != 3 ) { v97 = __readfsdword(24); StuffStdHandle(v292, *(_DWORD *)(*(_DWORD *)(*(_DWORD *)(v97 + 48) + 16) + 28), v216 + 28); } v95 = __readfsdword(24); if ( (*(_DWORD *)(*(_DWORD *)(*(_DWORD *)(v95 + 48) + 16) + 32) & 0x10000003) != 3 ) { v93 = __readfsdword(24); StuffStdHandle(v292, *(_DWORD *)(*(_DWORD *)(*(_DWORD *)(v93 + 48) + 16) + 32), v216 + 32); } } } } } } } v34 = 262144; if ( v188 >= 0x40000 ) v34 = v188; v23 = BaseCreateStack(v292, v189, v34, &v211); v91 = v23; if ( v23 < 0 ) goto LABEL_242; BaseInitializeContext(&v295, v182, v187, v212, 0); v186 = BaseFormatObjectAttributes(&v205, v164, 0); if ( v215 && v185 && v164 ) { v240 = *(_DWORD *)v164; v241 = *(_DWORD *)(v164 + 4); v242 = *(_DWORD *)(v164 + 8); v241 = 0; v186 = BaseFormatObjectAttributes(&v205, &v240, 0); } v23 = NtCreateThread(&v288, 2032639, v186, v292, &v260, &v295, &v211, 1); v236 = v23; if ( v23 < 0 ) goto LABEL_242; v313 = v182; v298 = v292; v299 = v288; v300 = v260; v301 = v261; switch ( v194 ) { case 0x14Cu: v314 = 0; break; case 0x200u: v314 = 6; break; case 0x8664u: v314 = 9; break; default: DbgPrint("kernel32: No mapping for ImageInformation.Machine == %04x\n", v194); v314 = -1; break; } v304 = v327 & 0xFFFFFFFC; v302 = 0; v303 = 0; if ( v190 == 2 || v235 ) { v298 |= 2u; v52 = GetModuleHandleA(0); v53 = RtlImageNtHeader(v52); v89 = v53; if ( v53 ) { if ( *(_WORD *)(v53 + 92) == 2 ) v298 |= 1u; } } if ( v176 & 0x40 ) v298 |= 1u; if ( v176 & 0x80 ) v298 &= 0xFFFFFFFEu; v305 = v204; if ( v204 ) { if ( v287 ) { v78 = 0; } else { v87 = __readfsdword(24); v78 = *(_DWORD *)(*(_DWORD *)(*(_DWORD *)(v87 + 48) + 16) + 16); } v307 = v78; v306 = v287; } memcpy(&v298, &v298, 0x98u); if ( v308 ) { v135 = &v309; v136 = &v310; v137 = &v312; v23 = CsrCaptureMessageMultiUnicodeStringsInPlace(&v283, 3, &v135); v236 = v23; if ( v23 < 0 ) { LABEL_242: v82 = v23; LABEL_180: BaseSetLastNTError(v82); goto LABEL_173; } } CsrClientCallServer(&v296, v283, 65536, 152); if ( v283 ) { CsrFreeCaptureBuffer(v283); v283 = 0; } if ( v297 < 0 ) { BaseSetLastNTError(v297); NtTerminateProcess(v292, v297); goto LABEL_173; } if ( v183 ) { if ( !v185 ) { v79 = BasepReplaceProcessThreadTokens(v183, v292, v288); v80 = v79; v236 = v79; if ( v79 < 0 ) { NtTerminateProcess(v292, v79); v82 = v80; goto LABEL_180; } } } if ( v184 ) { v236 = NtAssignProcessToJobObject(v184, v292); if ( v236 < 0 ) { NtTerminateProcess(v292, -1073741790); LABEL_179: v82 = v236; goto LABEL_180; } } if ( !(v327 & 4) ) NtResumeThread(v288, &v276); v35 = v290; LABEL_122: v214 = 1; if ( v233 ) v233 |= 8u; ms_exc.disabled = 1; v36 = v166; if ( v35 ) { if ( v204 == 32 ) { *(_DWORD *)v166 = v35 | 2; if ( v233 & 4 ) { v260 = 0; v261 = 0; } } else { *(_DWORD *)v166 = v35 | 1; } if ( v292 ) NtClose(v292); } else { *(_DWORD *)v166 = v292; } *(_DWORD *)(v36 + 4) = v288; *(_DWORD *)(v36 + 8) = v260; *(_DWORD *)(v36 + 12) = v261; v292 = 0; v288 = 0; ms_exc.disabled = 0; LABEL_126: ms_exc.disabled = -1; if ( v197 ) { v269 = 0; v268 = 0; v131 = __readfsdword(24); v37 = RtlFreeHeap; RtlFreeHeap(*(_DWORD *)(*(_DWORD *)(v131 + 48) + 24), 0, v197); v197 = 0; } else { v37 = RtlFreeHeap; } if ( !v204 ) { BasepSxsCloseHandles(&v221); BasepSxsCloseHandles(&v217); if ( v181 ) { i = 0; do { v38 = (int *)&(&v138)[4 * i]; v39 = *v38; if ( *v38 ) { v40 = v39 + 8; if ( v39 != -8 && *(_DWORD *)v40 ) { if ( *(_DWORD *)(v39 + 8) != *(_DWORD *)(v39 + 12) ) { v133 = *(_DWORD *)v40; RtlFreeUnicodeString(&v132); } v41 = *v38; *(_DWORD *)(*v38 + 8) = *(_DWORD *)(*v38 + 12); *(_DWORD *)(v41 + 16) = *(_DWORD *)(v41 + 20); } v42 = *(_DWORD *)(*v38 + 12); *(_DWORD *)(*v38 + 4) = v42; if ( v42 ) **(_WORD **)(v39 + 4) = 0; v43 = *v38; *(_WORD *)v43 = 0; *(_WORD *)(v43 + 2) = *(_WORD *)(v43 + 20); } ++i; } while ( i != 5 ); v92 = __readfsdword(24); RtlFreeHeap(*(_DWORD *)(*(_DWORD *)(v92 + 48) + 24), 0, v181); v37 = RtlFreeHeap; } } if ( v234 && !(BYTE1(v327) & 4) ) { RtlDestroyEnvironment(v234); v234 = 0; } v129 = __readfsdword(24); v37(*(_DWORD *)(*(_DWORD *)(v129 + 48) + 24), 0, Dest); v107 = __readfsdword(24); v37(*(_DWORD *)(*(_DWORD *)(v107 + 48) + 24), 0, Str1); v127 = __readfsdword(24); v37(*(_DWORD *)(*(_DWORD *)(v127 + 48) + 24), 0, v170); v106 = __readfsdword(24); v37(*(_DWORD *)(*(_DWORD *)(v106 + 48) + 24), 0, v198); if ( v289 ) NtClose(v289); if ( v291 ) NtClose(v291); if ( v288 ) { NtTerminateProcess(v292, 0); NtClose(v288); } if ( v292 ) NtClose(v292); if ( v184 ) NtClose(v184); if ( v183 ) { if ( v185 ) *(_DWORD *)v148 = v183; else NtClose(v183); } if ( v284 ) { v125 = __readfsdword(24); v37(*(_DWORD *)(*(_DWORD *)(v125 + 48) + 24), 0, v284); } if ( v285 ) { v105 = __readfsdword(24); v37(*(_DWORD *)(*(_DWORD *)(v105 + 48) + 24), 0, v285); } RtlFreeUnicodeString(&v266); result = RtlFreeUnicodeString(&v270); if ( v265 || v263 ) result = BaseDestroyVDMEnvironment(&v264, &v262); if ( v233 ) { if ( !(v233 & 8) ) { result = BaseUpdateVDMEntry(0, &v287, v233, v204); if ( v290 ) result = NtClose(v290); } } return result; } v178 = _wcslen(Source); if ( !v178 ) { Source = (wchar_t *)Size; v178 = _wcslen((const wchar_t *)Size); } v71 = _wcslen((const wchar_t *)&v325); v72 = 2 * (v178 + v71 + 3); v178 = v72; v124 = __readfsdword(24); v272 = (wchar_t *)RtlAllocateHeap(*(_DWORD *)(*(_DWORD *)(v124 + 48) + 24), BaseDllTag, v72); v270 = 0; v271 = v72; RtlAppendUnicodeToString(&v270, &v325); RtlAppendUnicodeToString(&v270, L" "); RtlAppendUnicodeToString(&v270, Source); Source = v272; Size = 0; NtClose(v291); v291 = 0; v122 = __readfsdword(24); RtlFreeHeap(*(_DWORD *)(*(_DWORD *)(v122 + 48) + 24), 0, Str1); Str1 = 0; v120 = __readfsdword(24); RtlFreeHeap(*(_DWORD *)(*(_DWORD *)(v120 + 48) + 24), 0, v198); v198 = 0; } } if ( !RtlIsDosDeviceName_U(v15) ) goto LABEL_179; v83 = 1200; LABEL_278: SetLastError(v83); LABEL_173: v81 = -1; LABEL_174: _local_unwind2(&ms_exc.prev_er, v81); return 0; }

 

1. 函式的一開始是一大段臨時變數宣告,然後將從CreateProcessInternalA傳過來的引數儲存到區域性變數中。

     ...
  v185 = var_0;
  Size = lpApplicationName;
  Source = lpCommandLine;
  v169 = lpProcessAttributes;
  v164 = lpThreadAttributes;
  v234 = lpEnvironment;
  lpFileName = lpCurrentDirectory;
  v144 = lpStartupInfo;
  v166 = lpProcessInformation;
  v148 = var_0_;
  v290 = 0;
  v201 = 0;
  v287 = 0;
  v233 = 0;
  v204 = 0;
  v160 = 0;
  v146 = dwCreationFlags & 0x8000000;
      ...
Ps: 插個題外話: v146 = dwCreationFlags & 0x8000000;這句話的意思是判斷dwCreationFlags的最高位是不是1,這裡使用了"與運算子"來達到目的

 

 

2. 對lpProcessInformation欄位進行初始化賦值,lpProcessInformation欄位是我們傳入的一個資料結構的引用,作業系統在建立完程式之後,會在這個資料結構中填入關於"這次"建立的程式的一些相關資訊

*(_DWORD *) lpProcessInformation = 0;
*(_DWORD *)(lpProcessInformation + 4) = 0;
*(_DWORD *)(lpProcessInformation + 8) = 0;
*(_DWORD *)(lpProcessInformation + 12) = 0;
A pointer to a PROCESS_INFORMATION structure that receives identification information about the new process.

關於這個資料結構的定義如下:

typedef struct _PROCESS_INFORMATION 
{
  HANDLE hProcess;
  HANDLE hThread;
  DWORD  dwProcessId;
  DWORD  dwThreadId;
} PROCESS_INFORMATION, *LPPROCESS_INFORMATION;

 

3. dwCreationFlags欄位的檢測和判斷

dwCreationFlags 的值至少由一個標誌組合成(一些建立標誌和優先順序型別)。

3.1 首先遮蔽 CREATE_NO_WINDOW 標誌,程式碼如下:

v29 = dwCreationFlags & 0xF7FFFFFF;
v327 = v29;

因為: CREATE_NO_WINDOW = 0x08000000,所以,為了將這個巨集代表的0x08000000給置0,所以採用了0xF7FFFFFF,這也是一種"位操作"的思想。

 

3.2 判斷dwCreationFlags中的非法位組合

經過遮蔽標誌位後,判斷dwCreationFlags中是否包含CREATE_NEW_CONSOLE | DETACHED_PROCESS的組合, 如果包含它們的組合(參考MSDN上的說明), 存在這種組合是不合法的, 因此跳轉到錯誤處理中:

if ( (v29 & 0x18) == 24 ) //24 = DETACHED_PROCESS | CREATE_NEW_CONSOLE
    goto LABEL_279;

我們繼續跟蹤錯誤程式碼 LABEL_279:

LABEL_279:
    SetLastError(0x57u);
    return 0;

繼續跟進SetLastError()函式.

void __stdcall SetLastError(DWORD dwErrCode)
{
  unsigned __int32 v1; // edi@1

  v1 = __readfsdword(24);
  if ( g_dwLastErrorToBreakOn && dwErrCode == g_dwLastErrorToBreakOn )
    DbgBreakPoint();
  if ( *(_DWORD *)(v1 + 52) != dwErrCode )
    *(_DWORD *)(v1 + 52) = dwErrCode;
}

將57h與Teb->LastErrorValue想比較,如果不相等,就更新LastErrorValue的值為57h, 實際上GetLastError() 函式的返回的錯誤碼就是從Teb->LastErrorValue獲取的。

 

3.3 判斷dwCreationFlags中的優先順序型別的組合

在一開始提到判斷dwCreationFlags中包含了"建立標誌"和"優先順序",接下來程式碼先判斷優先順序,我們接著分析:

.text:7C8199A6     mov     [ebp+var_6D4], ebx
.text:7C8199AC     mov     [ebp+var_6DC], ebx
.text:7C8199B2     test    al, 40h
.text:7C8199B4     jnz     IsIdlePriority  //優先順序為IDLE_PRIORITY_CLASS
.text:7C8199B4
.text:7C8199BA     test    ah, 40h
.text:7C8199BD     jnz     loc_7C84278E
.text:7C8199BD
.text:7C8199C3     test    al, 20h
.text:7C8199C5     jnz     IsNormalPriority  //優先順序為NORMAL_PRIORITY_CLASS
.text:7C8199C5
.text:7C8199CB     test    ah, ah
.text:7C8199CD     js      loc_7C84279A
.text:7C8199CD
.text:7C8199D3     test    al, al
.text:7C8199D5     js      IsHighPriotity   //優先順序為HIGH_PRIORITY_CLASS
.text:7C8199D5
.text:7C8199DB     test    ah, 1
.text:7C8199DE     jnz     IsRealTimePriority  //優先順序為REALTIME_PRIORITY_CLASS

判斷的順序依次是

IDLE_PRIORITY_CLASS -> NORMAL_PRIORITY_CLASS -> HIGH_PRIORITY_CLASS -> REALTIME_PRIORITY_CLASS 

只要滿足其中一個優先順序,就跳過其他優先順序的判斷,如果都不滿足, 將許可權級置為0.

(當滿足IDLE_PRIORITY_CLASS時),置優先順序標誌為1.
(當滿足NORMAL_PRIORITY_CLASS時),置優先順序標誌為2.
(當滿足HIGH_PRIORITY_CLASS時),置優先順序標誌3.
(當滿足REALTIME_PRIORITY_CLASS時),由於該優先順序別很高,比作業系統優先順序還高(參考MSDN),作了如下操作, 申請堆空間 -->開啟一個令牌物件與執行緒關聯,並返回一個控制程式碼可用於訪問
該令牌。-->調整優先順序令牌。 置優先順序標誌4。

這裡回想一點我們之前說的關於程式建立時如果指定了過高的優先順序時,作業系統是怎麼處理的:

3. 如果為新程式指定了Real-Time優先順序類別,但是該程式的呼叫者沒有"Increase Scheduling Priority(增加排程優先順序)"特權,則使用High優先順序類別。換句話說,CreateProcess
不會僅僅因為呼叫者沒有足夠的特權來建立Real-Time優先順序類別的程式而失敗,而是自動"降低"一點,新程式只是沒有Real-Time那麼高的優先順序而已

 

3.4 判斷dwCreationFlags中的建立標誌的組合

繼續判斷dwCreationFlag的情況,首先在dwCreationFlag過濾掉表示優先順序的標誌位,然後在判斷是什麼建立標誌。用與運算子把所有不屬於建立標誌的位都置0

LOWORD(dwCreationFlagsa) = dwCreationFlagsa & 0x3E1F;

判斷CREATE_SEPARATE_WOW_VDM / CREATE_SHARED_WOW_VDM(這兩個位是用來表示16bit的windows程式)

.text:7C8199F6      mov     edi, 800h
.text:7C8199FB      mov     esi, 1000h
.text:7C819A00      test    [ebp+dwCreationFlags], edi
.text:7C819A03      jnz     loc_7C8427CA   //dwCreationFlag = CREATE_SEPARATE_WOW_VDM 
.text:7C819A03
.text:7C819A09      test    [ebp+dwCreationFlags], esi
.text:7C819A0C      jnz     short loc_7C819A1F   //dwCreationFlag = CREATE_SHARED_WOW_VDM
.text:7C819A0C
.text:7C819A0E      mov     eax, _BaseStaticServerData
.text:7C819A13      cmp     [eax+19F4h], bl
.text:7C819A19      jnz     loc_7C8427D4

 

 

4.  判斷lpEnvironment,和可執行程式的路徑相關的處理

判斷lpEnvironment是否為空, 如果不為空,將lpEnvironment對應的ANSI字串轉換為UNICODE_STRING, 為空的話跳過這一步,接著呼叫RtlDosPathNameToNtPathName_U函式將DOS路徑轉換為NT路徑,由於使用者給定的路徑一般都是DOS路徑,而核心需要的是NT路徑,因此需要轉換一下。

if ( !lpEnvironment_ || BYTE1(dwCreationFlagsa) & 4 )  //判斷lpEnvironment是否為空
{
    ...
    v13 = NtAllocateVirtualMemory(-1, &v161, 0, &v88, 4096, 4);  //申請儲存UNICODE的空間
        ...
    v14 = RtlAnsiStringToUnicodeString(&v159, &v156, 0); //將ANSI轉換為UNICODE
    if ( v14 < 0 )
    {
        NtFreeVirtualMemory(-1, &v161, &v88, 32768);  //釋放臨時的儲存空間
        ..
    }
    ..
    goto LABEL_33;
    ...
LABEL_33:
    v15 = Size;
    v167 = RtlDosPathNameToNtPathName_U(Size, &v275, 0, &v239);  //將DOS路徑轉換為NT路徑

這個關鍵函式的宣告如下:

NTSTATUS  NtAllocateVirtualMemory(
    __in HANDLE  ProcessHandle,
    __inout PVOID  *BaseAddress,
    __in ULONG_PTR  ZeroBits,
    __inout PSIZE_T  RegionSize,
    __in ULONG  AllocationType,
    __in ULONG  Protect
    ); 
NTSTATUS RtlAnsiStringToUnicodeString(
    IN OUT PUNICODE_STRING  DestinationString,
    IN PANSI_STRING  SourceString,
    IN BOOLEAN  AllocateDestinationString
    );
RtlDosPathNameToNtPathName_U是一個windows未公開的函式,如果我們自己需要使用,需要自己在標頭檔案中定義
typedef  NTSTATUS (*DOSPATH_TO_NTPATH)(
           IN PCWSTR DosPathName,
           OUT PUNICODE_STRING NtPathName,
           OUT PWSTR* FilePathInNtPathName OPTIONAL,
           OUT PRELATIVE_NAME* RelativeName OPTIONAL
           );
DOSPATH_TO_NTPATH RtlDosPathNameToNtPathName_U;

 

 

5. 開啟檔案映像
5.1 接著呼叫NtOpenFile()得到檔案控制程式碼,我們先來看一下這個函式的宣告:

NTSTATUS NtOpenFile(
    OUT PHANDLE  FileHandle,
    IN ACCESS_MASK  DesiredAccess, //期望的訪問屬性
    IN POBJECT_ATTRIBUTES  ObjectAttributes, //物件屬性
    OUT PIO_STATUS_BLOCK  IoStatusBlock, //I/0狀態塊
    IN ULONG  ShareAccess,  //共享模式
    IN ULONG  OpenOptions  //開啟選項
    );
v238 = NtOpenFile(&v291, 1048737, &v207, &v252, 5, 96);
if ( v238 < 0 )
{
      v238 = NtOpenFile(&v291, 1048608, &v207, &v252, 5, 96);
      if ( v238 < 0 )
        break;
}

 

5.2 建立程式的記憶體區
接著通過NtOpenFile得到的handle接著呼叫了NtCreateSectiond函式得到記憶體區物件控制程式碼,即我們常說的程式使用者空間的虛擬地址空間,在這一步完成建立(事實上,這裡用建立這個詞不是非常恰當,因為程式的記憶體空間一直4GB,這裡做的實際是獲得這個記憶體區物件的控制程式碼,以方便我們之後使用)。

NTSTATUS NtCreateSection(
    OUT PHANDLE  SectionHandle,  //指向記憶體區物件的指標(傳出引數)
    IN ACCESS_MASK  DesiredAccess,  //訪問掩碼
    IN POBJECT_ATTRIBUTES  ObjectAttributes  OPTIONAL, //物件屬性
    IN PLARGE_INTEGER  MaximumSize  OPTIONAL,  //SETION大小
    IN ULONG  SectionPageProtection,  //記憶體區頁面保護屬性
    IN ULONG  AllocationAttributes,
    IN HANDLE  FileHandle  OPTIONAL  //檔案控制程式碼
    ); 
v17 = NtCreateSection(&v293, 983071, 0, 0, 16, 16777216, v291);
v238 = v17;
if ( v17 < 0 )
      goto LABEL_425;

 

 

6. 檢查windows程式執行授權策略

接著呼叫BasepIsProcessAllowed函式該函式用來判斷應用程式名是否在授權檔案列表中,函式實現呼叫了NtOpenKey函式開啟了登錄檔中的HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options鍵

v18 = BasepIsProcessAllowed(Size);
v17 = v18;
v238 = v18;
if ( v18 < 0 )
{
      BaseSetLastNTError(v18);
      NtClose(v293);
      goto LABEL_173;
}

輸入gpedit.msc->計算機配置->windows設定->安全設定->軟體限制策略->其他規則中可以設定我們的"軟體執行策略"

 

 

7. 獲取程式記憶體區物件的基本資訊

在得到程式對應的記憶體區物件控制程式碼後呼叫了NtQuerySection函式,返回後得到節的基本資訊(節基地址,大小,屬性),中間的那一大段程式碼我們們可以忽略,我通讀之後,發現只是一些無關緊要的記憶體區的申請,釋放操作,還有和前面說的win16的VDM相關的程式碼(在win32下不會執行),所以可以掠過。

我們直接來到NtQuerySection函式這個程式碼塊,先來看看這個函式的宣告:

//將記憶體區物件作為"映象執行"檔案來查詢資訊
typedef DWORD ( WINAPI* NTQUERYSECTION)(
            HANDLE, //記憶體區控制程式碼
            ULONG,  //edi = 1 = SectionImageInformation
            PVOID,  //接受記憶體區資訊Buffer
            ULONG,    //記憶體區資訊的長度
            PULONG  //接受返回的大小
        );  
NTQUERYSECTION NtQuerySection;                                              
v23 = NtQuerySection(v293, 1, &v189, 48, 0);
v238 = v23;
if ( v23 < 0 )
        goto LABEL_242;

 

 

8. 判斷映像檔案(剛才載入到程式記憶體物件區域中的映像檔案)資訊的有效性

8.1 然後判斷建立標誌中是否包含DEBUG_PROCESS或者DEBUG_ONLY_THIS_PROCESS

如果包含該標誌,判斷PEB->ReadImageFileExecOptions域是否為0,如果不為0, 呼叫LdrQueryImageFileExecutionOptions函式查詢該資訊,如果不包含該標誌,也用呼叫LdrQueryImageFileExecutionOptions函式。

if ( !(dwCreationFlagsa & 3) || (v128 = __readfsdword(24), *(_BYTE *)(*(_DWORD *)(v128 + 48) + 1)) )
//DEBUG_PROCESS OR DEBUG_ONLY_THIS_PROCESS = 3
        LdrQueryImageFileExecutionOptions(&v275, L"Debugger", 1, &v327, 520, 0);

 

8.2 下面檢查映象檔案的部分資訊的有效性

if ( MachineType < v7FFE002C || MachineType > v7FFE002E )
{
    //MachineType為機器型別,
    ..
}
if ( subsystem_machineType != 2 && subsystem_machineType != 3 )
{
    //子系統版本號 2: 控制檯  3: GUI
    ..  
    if ( subsystem_machineType != 7 )
    {
    ..
    }
        if ( !BuildSubSysCommandLine(L"POSIX /P ", Size, Source, &v272) )
          goto LABEL_173;
        goto LABEL_215;
}

接著呼叫函式BasepIsImageVersionOk判斷映象檔案版本是否合法

if ( !BasepIsImageVersionOk(subsystem_major_type, subsystem_minor_type) )
//subsystem_major_type: 子系統的主版本號
//subsystem_minor_type: 子系統的次版本號
        goto LABEL_277;

 

 

9. 載入advapi32.dll
如果建立標誌中是否包含DEBUG_PROCESS或者DEBUG_ONLY_THIS_PROCESS(即當前處在除錯模式中),就載advapi32.dll並獲得CreateProcessAsUserSecure函式的地址:

if ( !v327 )
{
        v24 = LoadLibraryA("advapi32.dll");
    ..
        if ( v24 )
        {
          if ( GetProcAddress(v24, "CreateProcessAsUserSecure") )
          {
            v238 = NtQuerySystemInformation(71, &v279, 4, 0);
        ..
          }
          FreeLibrary(v25);
        }

 

(Ps: 程式分析的過程非常之繁雜的瑣碎,很容易讓你陷入程式碼的細節而不能自拔,小瀚建議朋友們每分析一段時間後就翻到前面去仔細看看"程式建立流程"的概覽,不斷地提醒自己同時從巨集觀
和微觀的角度去看待這個過程,這樣,不至於迷失在windows的程式碼的海洋中,自己也可以準備一張程式建立的縱覽圖,補充著看)

 

10. NT物件屬性的建立

然後呼叫BaseFormatObjectAttributes將安全屬性結構(關於程式安全、許可權方面的資訊,例如普通應用程式如果想要刪除systrem32\calc.exe就必須利用這個欄位來提升許可權到TrustedInstaller(win7下))格式為NT物件屬性結構(得到了物件屬性)。

typedef struct _SECURITY_ATTRIBUTES {
  DWORD  nLength;
  LPVOID lpSecurityDescriptor;
  BOOL   bInheritHandle;
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
..
v188 = BaseFormatObjectAttributes(&v207, v171, 0);
..

 

接著呼叫了_DbgUiConnectToDbg在實現通過呼叫NtCreateDebugObject函式來建立除錯物件,呼叫DbgUiGetThreadDebugObject來獲得除錯物件(作為引數傳遞到0環)。

 

11. 建立除錯物件

接著呼叫了_DbgUiConnectToDbg在實現通過呼叫NtCreateDebugObject函式來建立除錯物件,然後呼叫DbgUiGetThreadDebugObject來獲得除錯物件(作為引數傳遞到0環)。

if ( dwCreationFlagsa & 3 )
{
          v23 = DbgUiConnectToDbg();
          v238 = v23;
          if ( v23 < 0 )
            goto LABEL_242;
          v164 = DbgUiGetThreadDebugObject();
    ...
}

 

 

12. 最後一步,準備進行模式穿越

最後呼叫NtCreateProcessEx函式完成3環的建立過程,我們先來看看NtCreateProcessEx的宣告:

NTSYSAPI NTSTATUS NTAPI NtCreateProcessEx(
                OUT PHANDLE ProcessHandle,  //保留程式控制程式碼值(傳出引數)
                IN ACCESS_MASK DesiredAccess,  //(訪問掩碼)
                IN POBJECT_ATTRIBUTES ObjectAttributes,  //物件屬性
                IN HANDLE InheritFromProcessHandle,  //父程式控制程式碼(FFFFFFFF)
                IN BOOLEAN InheritHandles,   //建立標誌
                IN HANDLE SectionHandle OPTIONAL,  //記憶體區物件控制程式碼
                IN HANDLE DebugPort OPTIONAL,  //除錯物件控制程式碼
                IN HANDLE ExceptionPort OPTIONAL,  //異常埠物件控制程式碼
                IN HANDLE Unknown  //作業級別
    );
v27 = NtCreateProcessEx(&v294, 2035711, v188, -1, v197, v293, v164, 0, v158);

這裡要注意一點,這個函式NtCreateProcessEx是一個ntdll.dll匯出的存根函式,它是我們從使用者模式穿越進核心層的一條"通道"

到了這一步,我們和ring3的"緣分""暫時"盡了,注意,是暫時盡了,也就是說,我們的程式碼在進入核心層之後,還有回到使用者層一次

繼續跟進NtCreateProcessEx這個函式,我們就要進入核心層的程式碼了。所以這裡是一個分界點。

下面附上函式的流程圖:

 

 

 

 

階段四: 建立程式的"執行體程式物件",(Ring0從這裡開始)

在開始分析ring0層的程式建立過程之前,我們做一個承上啟下的總結

1. 之前在ring3層,CreateProcess已經開啟了一個有效的windows可執行檔案,並且建立了一個記憶體區物件,以便稍後將它對映到新的程式地址空間中
2. 接下來,ring3的程式碼會呼叫NtCreateProcessEx,來建立一個"windows執行體程式物件",以執行該"程式映像"

建立執行體程式物件(EPROCESS)(過程中自然也包含了建立核心層程式物件KPROCESS)的大致過程如下:

1. 建立起EPROCESS塊
2. 建立初始的程式地址空間
3. 初始化核心程式塊(KPROCESS)
4. 結束程式地址空間的建立過程(包括初始化工作集列表和虛擬地址空間描述符,以及將映像對映到地址空間中)
5. 建立PEB
6. 完成執行體程式物件的建立過程

建立起EPROCESS

我們知道,在ring3的最後一次呼叫中,程式碼呼叫了NtCreateProcessEx()這個函式,從這個函式以後,程式碼進入了核心的範疇。所以我們從這個函式開始逐行分析

下面貼出NtCreateProcessEx()函式原始碼,它位於WRK的 base\ntos\ps\create.c 檔案中

NTSTATUS NtCreateProcessEx
    __out PHANDLE ProcessHandle, //輸出引數
    __in ACCESS_MASK DesiredAccess, //對新程式的訪問許可權
    __in_opt POBJECT_ATTRIBUTES ObjectAttributes, //程式物件的屬性
    __in HANDLE ParentProcess, //指向父程式的控制程式碼,
    __in ULONG Flags,  //建立標誌
    __in_opt HANDLE SectionHandle, //該程式的記憶體區物件
    __in_opt HANDLE DebugPort, //新程式的除錯埠
    __in_opt HANDLE ExceptionPort, //新程式的異常埠
    __in ULONG JobMemberLevel  //新程式在一個job集中的級別
    )  
{
    NTSTATUS Status;
    PAGED_CODE();
    if (KeGetPreviousMode() != KernelMode) 
    { 
        try 
      {
            ProbeForWriteHandle (ProcessHandle);
        } 
      except (EXCEPTION_EXECUTE_HANDLER) 
      {
            return GetExceptionCode ();
      }
    }
    if (ARGUMENT_PRESENT (ParentProcess)) 
    {
        Status = PspCreateProcess (ProcessHandle,
                                   DesiredAccess,
                                   ObjectAttributes,
                                   ParentProcess,
                                   Flags,
                                   SectionHandle,
                                   DebugPort,
                                   ExceptionPort,
                                   JobMemberLevel);
    } 
    else 
    {
        Status = STATUS_INVALID_PARAMETER;
    }
    return Status;
}

首先對NtCreateProcessEx的引數做一個說明:

1. ProcessHandle: 一個輸出引數,如果該程式建立成功,則它包含了所建立的程式的控制程式碼,windows中這種帶引用的傳出引數的程式設計非常常見
2. DesiredAccess: 包含了對新程式的訪問許可權
3. ObjectAttributes: 這是一個可選的"指標引數(即可以為NULL)",它指定了程式物件的屬性
4. ParentProcess: 指向父程式的控制程式碼,這是一個必需的引數,不能為NULL,並且呼叫者必須對該程式具有"PROCESS_CREATE_PROCESS"(這是FLAGS中的一個屬性)
5. Flags: 這是建立標誌,其中有一個標誌"PROCESS_CREATE_FLAGS_INHERIT_HANDLES"非常關鍵,表明新程式的"物件控制程式碼表"是否要複製父程式的控制程式碼表,或者初始設定為空。
6. SectionHandle: 這是一個可選的控制程式碼,指向一個記憶體區物件,代表了該程式的映像檔案,呼叫者對於此記憶體區物件必須具有"SECTION_MAP_EXECUTE"訪問許可權。
7. DebugPort: 這是一個可選的控制程式碼,指向一個埠物件,如果此控制程式碼引數不為NULL,則此埠被賦值為新程式的除錯埠,否則,新程式沒有除錯埠
8. ExceptionPort: 這是一個可選的埠,指向一個埠物件,如果此控制程式碼引數不為NULL,則此埠被賦值為新程式的異常埠,否則,新程式沒有異常埠。呼叫者對於異常埠物件必須具有
PORT_WRITE和PORT_READ訪問許可權
9. JobMemberLevel: 指定了要建立的程式在一個job集中的級別

NtCreateProcessEx的程式碼先判斷了執行緒的前一個模式是不是"核心態(KernelMode)",即判斷這個建立請求是從核心態來的還是從使用者態來的,我們現在分析的是從ring3建立程式的流程,所以這裡"執行緒的前一個模式"是"使用者態"。

接著程式碼會檢查ProcessHandle引數代表的控制程式碼是否可寫,然後才把真正的建立工作交給PspCreateProcess函式。

if (KeGetPreviousMode() != KernelMode) 
{ 
        //判斷了執行緒的前一個模式是不是"核心態(KernelMode)"
        try 
      {
            ProbeForWriteHandle (ProcessHandle);
        } 
        except (EXCEPTION_EXECUTE_HANDLER) 
        {
            return GetExceptionCode ();
        }
}
if (ARGUMENT_PRESENT (ParentProcess)) 
{
        //這是真正的建立程式的函式
        Status = PspCreateProcess 
        ...

 

接下來順藤摸瓜,我們繼續跟進程式碼,程式碼來到了PspCreateProcess ()這個函式。它位於 base\ntos\ps\create.c 中。

在開始分析PspCreateProcess 之前,要一個知識點要注意:

PspCreateProcess在windows中會被三個函式呼叫,它們是NtCreateProcessEx、PsCreateSystemProcess、PsInitPhase
1. PsInitPhase()是在系統初始化的早期被呼叫的,它建立的程式(即System程式)的控制程式碼儲存在全域性變數PspInitialSystemProcessHandle中,程式物件存放於全域性變數
  PsInitialSystemProcess中
2. PsCreateSystemProcess()可用於建立系統程式物件,它建立的程式都是PsInitialSystemProcess的子程式 3. NtCreateProcessEx()即我們接下來要分析的,同時這個NtCreateProcessEx也有可能會從ring3或ring0發出,文章的最後總結會再次提及這點 所以,PspCreateProcess()函式負責建立系統中的"所有"程式(注意,是所有),包括System程式

接下來結合WRK的原始碼來逐行分析此函式的流程,為了保持說明的完整性,我依然弱智地貼出完整程式碼,這次,因為程式碼實在太長了,我選擇直接在程式碼中新增註釋進行說明。當中涉及的資料結構的成員域在之前的EPROCESS/KPROCESS的學習筆記中都有介紹,請朋友結合起來看:

NTSTATUS PspCreateProcess(
    OUT PHANDLE ProcessHandle,
    IN ACCESS_MASK DesiredAccess,
    IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
    IN HANDLE ParentProcess OPTIONAL,
    IN ULONG Flags,
    IN HANDLE SectionHandle OPTIONAL,
    IN HANDLE DebugPort OPTIONAL,
    IN HANDLE ExceptionPort OPTIONAL,
    IN ULONG JobMemberLevel
    ) 
{

    NTSTATUS Status;
    PEPROCESS Process;
    PEPROCESS CurrentProcess;
    PEPROCESS Parent;
    PETHREAD CurrentThread;
    KAFFINITY Affinity;
    KPRIORITY BasePriority;
    PVOID SectionObject;
    PVOID ExceptionPortObject;
    PVOID DebugPortObject;
    ULONG WorkingSetMinimum, WorkingSetMaximum;
    HANDLE LocalProcessHandle;
    KPROCESSOR_MODE PreviousMode;
    INITIAL_PEB InitialPeb;
    BOOLEAN CreatePeb;
    ULONG_PTR DirectoryTableBase[2];
    BOOLEAN AccessCheck;
    BOOLEAN MemoryAllocated;
    PSECURITY_DESCRIPTOR SecurityDescriptor;
    SECURITY_SUBJECT_CONTEXT SubjectContext;
    NTSTATUS accesst;
    NTSTATUS SavedStatus;
    ULONG ImageFileNameSize;
    HANDLE_TABLE_ENTRY CidEntry;
    PEJOB Job;
    PPEB Peb;
    AUX_ACCESS_DATA AuxData;
    PACCESS_STATE AccessState;
    ACCESS_STATE LocalAccessState;
    BOOLEAN UseLargePages;
    SCHAR QuantumReset; 

    PAGED_CODE();

    CurrentThread = PsGetCurrentThread ();
    PreviousMode = KeGetPreviousModeByThread(&CurrentThread->Tcb);
    CurrentProcess = PsGetCurrentProcessByThread (CurrentThread);

    CreatePeb = FALSE;
    UseLargePages = FALSE;
    DirectoryTableBase[0] = 0;
    DirectoryTableBase[1] = 0;
    Peb = NULL; 

    if (Flags&~PROCESS_CREATE_FLAGS_LEGAL_MASK) 
    {
        return STATUS_INVALID_PARAMETER;
    } 

    /*
    如果父程式控制程式碼不為NULL,則通過ObReferenceObjectByHandle獲得父程式物件的EPROCESS指標,放在Parent區域性變數中 
    */
    if (ARGUMENT_PRESENT (ParentProcess)) 
    {
        Status = ObReferenceObjectByHandle (ParentProcess,
                                            PROCESS_CREATE_PROCESS,
                                            PsProcessType,
                                            PreviousMode,
                                            &Parent,
                                            NULL);
        if (!NT_SUCCESS (Status)) 
        {
            return Status;
        }

        if (JobMemberLevel != 0 && Parent->Job == NULL) 
        {
            ObDereferenceObject (Parent);
            return STATUS_INVALID_PARAMETER;
        }
        /*
        獲得父程式的Affinity(CPU親和性)設定,新程式工作集最大/最小值被初始化為全域性變數PsMaximumWorkingSet和PsMinimumWorkingSet
        */
        Affinity = Parent->Pcb.Affinity;
        WorkingSetMinimum = PsMinimumWorkingSet;
        WorkingSetMaximum = PsMaximumWorkingSet;  
    } 
    else 
    { 
        /*
        如果父程式控制程式碼為NULL,則Affinity設定為全域性變數KeActiveProcessors,即系統中當前的可用處理器。此時因為新程式物件尚未被建立,所以這些設定都儲存在區域性變數中
        */
        Parent = NULL;
        Affinity = KeActiveProcessors;
        WorkingSetMinimum = PsMinimumWorkingSet;
        WorkingSetMaximum = PsMaximumWorkingSet;
    } 
    /*
    呼叫ObCreateObject函式,建立一個型別為PsProcessType的"核心物件",並置於區域性變數Process中,這裡所謂的PsProcessType型別的核心物件指的就是"EPROCESS",
  即這一步開始建立了一個執行體程式物件
*/ Status = ObCreateObject (PreviousMode, PsProcessType, ObjectAttributes, PreviousMode, NULL, sizeof (EPROCESS), 0, 0, &Process); if (!NT_SUCCESS (Status)) { goto exit_and_deref_parent; } /* 把Process(EPROCESS)物件中的所有域置為0,然後初始化(或者直接繼承父程式的對應成員域)其中部分成員(RundownProtect、ProcessLock、ThreadListHead、QuotaUsage、
  QuotaPeak、QuotaBlock、DeviceMap)
*/ RtlZeroMemory (Process, sizeof(EPROCESS)); ExInitializeRundownProtection (&Process->RundownProtect); PspInitializeProcessLock (Process); InitializeListHead (&Process->ThreadListHead); PspInheritQuota (Process, Parent); ObInheritDeviceMap (Process, Parent); /* 如果父程式控制程式碼不為NULL,則將新程式的Process(EPROCESS)繼承父程式的DefaultHardErrorProcessing(預設的硬體錯誤處理)、
  InheritedFromUniqueProcessId(父程式的PID)
*/ if (Parent != NULL) { Process->DefaultHardErrorProcessing = Parent->DefaultHardErrorProcessing; Process->InheritedFromUniqueProcessId = Parent->UniqueProcessId; } else { Process->DefaultHardErrorProcessing = PROCESS_HARDERROR_DEFAULT; Process->InheritedFromUniqueProcessId = NULL; } /* 檢查記憶體區控制程式碼引數SectionHandle,對於系統程式,此引數為NULL,此時,除非父程式為PsInitialSystemProcess(System程式),否則記憶體區物件繼承自父程式,並且不得為NULL。 如果此引數不為NULL,則利用此控制程式碼引數呼叫ObReferenceObjectByHandle獲得記憶體區物件的指標。所以,新程式物件的記憶體區物件已經完成初始化。 Ps: 小瀚建議朋友看到這裡翻回去看看"階段四: 執行體程式建立"的總體概覽步驟,從巨集觀和微觀的角度不斷加深映像,不要一下子就陷入了程式碼中。通過總體路線,我們知道下一步基本
  是要建立"程式核心物件KPROCESS"了,基本的程式碼模式我們也能猜測出來了
*/ if (ARGUMENT_PRESENT (SectionHandle)) { Status = ObReferenceObjectByHandle (SectionHandle, SECTION_MAP_EXECUTE, MmSectionObjectType, PreviousMode, &SectionObject, NULL); if (!NT_SUCCESS (Status)) { goto exit_and_deref; } } else { SectionObject = NULL; if (Parent != PsInitialSystemProcess) { if (ExAcquireRundownProtection (&Parent->RundownProtect)) { SectionObject = Parent->SectionObject; if (SectionObject != NULL) { ObReferenceObject (SectionObject); } ExReleaseRundownProtection (&Parent->RundownProtect); } if (SectionObject == NULL) { Status = STATUS_PROCESS_IS_TERMINATING; goto exit_and_deref; } } } /* 完成新程式物件的記憶體區物件初始化為新程式的EPROCESS成員域賦值 */ Process->SectionObject = SectionObject; /* 根據DebugPort引數來初始化新程式物件的DebugPort(除錯埠)成員域 */ if (ARGUMENT_PRESENT (DebugPort)) { Status = ObReferenceObjectByHandle (DebugPort, DEBUG_PROCESS_ASSIGN, DbgkDebugObjectType, PreviousMode, &DebugPortObject, NULL); if (!NT_SUCCESS (Status)) { goto exit_and_deref; } Process->DebugPort = DebugPortObject; if (Flags&PROCESS_CREATE_FLAGS_NO_DEBUG_INHERIT) { PS_SET_BITS (&Process->Flags, PS_PROCESS_FLAGS_NO_DEBUG_INHERIT); } } else { if (Parent != NULL) { DbgkCopyProcessDebugPort (Process, Parent); } } /* 根據ExceptionPort引數來初始化新程式物件的ExceptionPort(異常埠)成員域 */ if (ARGUMENT_PRESENT (ExceptionPort)) { Status = ObReferenceObjectByHandle (ExceptionPort, 0, LpcPortObjectType, PreviousMode, &ExceptionPortObject, NULL); if (!NT_SUCCESS (Status)) { goto exit_and_deref; } Process->ExceptionPort = ExceptionPortObject; } /* 設定新程式的退出狀態,這裡設定為STATUS_PENDING表示尚未完成,正在處理ing */ Process->ExitStatus = STATUS_PENDING; /* 如果指定的父程式不為NULL,則建立一個全新的地址空間(最小工作集大小) */ if (Parent != NULL) { if (!MmCreateProcessAddressSpace (WorkingSetMinimum, Process, &DirectoryTableBase[0])) { Status = STATUS_INSUFFICIENT_RESOURCES; goto exit_and_deref; } } /* 如果指定的父程式為NULL(即系統程式,系統程式是沒有父程式的說法的),讓新程式的控制程式碼表指向當前程式(CurrentProcess)的控制程式碼表,並且利用空閒執行緒的地址空間來初始化新程式
  的地址空間
*/ else { Process->ObjectTable = CurrentProcess->ObjectTable; Status = MmInitializeHandBuiltProcess (Process, &DirectoryTableBase[0]); if (!NT_SUCCESS (Status)) { goto exit_and_deref; } } PS_SET_BITS (&Process->Flags, PS_PROCESS_FLAGS_HAS_ADDRESS_SPACE); Process->Vm.MaximumWorkingSetSize = WorkingSetMaximum; /* 呼叫KeInitializeProcess函式來初始化新程式核心物件(KPROCESS)的"基本優先順序(BasePriority)"、Affinity(CPU親和性)、程式頁面目錄(DirectoryTableBase)和超空間的
  頁幀號 Ps: 再次回顧階段四的"總路線圖",我們現在到了建立KPROCESS的那一步了
*/ KeInitializeProcess (&Process->Pcb, NORMAL_BASE_PRIORITY, Affinity, &DirectoryTableBase[0], (BOOLEAN)(Process->DefaultHardErrorProcessing & PROCESS_HARDERROR_ALIGNMENT_BIT)); /* 通過PspInitializeProcessSecurity函式初始化新程式的安全屬性,主要是從父程式複製一個令牌 */ Status = PspInitializeProcessSecurity (Parent, Process); if (!NT_SUCCESS (Status)) { goto exit_and_deref; } /* 設定新程式的優先順序類別: PROCESS_PRIORITY_CLASS_NORMAL(普通級別) */ Process->PriorityClass = PROCESS_PRIORITY_CLASS_NORMAL; /* 如果父程式控制程式碼不為NULL,則複製父程式的優先順序,這裡注意一個細節,就是要判斷一下指定的優先順序是否超過了IDLE,因為Real-Time的優先順序普通程式是不被允許建立的 */ if (Parent != NULL) { if (Parent->PriorityClass == PROCESS_PRIORITY_CLASS_IDLE || Parent->PriorityClass == PROCESS_PRIORITY_CLASS_BELOW_NORMAL) { Process->PriorityClass = Parent->PriorityClass; } /* 初始化新程式的控制程式碼表,若Flags引數中包含了控制程式碼繼承標誌,則把父程式控制程式碼表中凡是有繼承屬性的物件拷貝到新程式的控制程式碼表中(回想控制程式碼表的知識: 即使是複製,同一個物件的
     控制程式碼在不同的程式空間中是不同的)
*/ Status = ObInitProcess ((Flags&PROCESS_CREATE_FLAGS_INHERIT_HANDLES) ? Parent : NULL, Process); if (!NT_SUCCESS (Status)) { goto exit_and_deref; } } else { Status = MmInitializeHandBuiltProcess2 (Process); if (!NT_SUCCESS (Status)) { goto exit_and_deref; } } Status = STATUS_SUCCESS; SavedStatus = STATUS_SUCCESS; /* 接下來開始初始化新程式的程式地址空間 Ps: 再次回到總路線圖,我們現在已經到了"結束程式地址空間的建立過程(包括初始化工作集列表和虛擬地址空間描述符,以及將映像對映到地址空間中)"這一步,做到心中有數。 */ /* 接下來初始化新程式的程式地址空間,有三種可能性 */ if (SectionHandle != NULL) { /* 1. 新程式有新的可執行映像記憶體區物件SectionObject,呼叫MmInitializeProcessAddressSpace函式,根據指定的記憶體區物件來初始化程式地址空間 */ Status = MmInitializeProcessAddressSpace (Process, NULL, SectionObject, &Flags, &(Process->SeAuditProcessCreationInfo.ImageFileName)); if (!NT_SUCCESS (Status)) { goto exit_and_deref; } SavedStatus = Status; CreatePeb = TRUE; UseLargePages = ((Flags & PROCESS_CREATE_FLAGS_LARGE_PAGES) != 0 ? TRUE : FALSE); } else if (Parent != NULL) { /* 2. 沒有指定映像記憶體區物件SectionObject,但父程式也並非PsInitialSystemProcess(非NULL)。也呼叫MmInitializeProcessAddressSpace函式,但根據父程式來初始化
    程式地址空間。
*/ if (Parent != PsInitialSystemProcess) { Process->SectionBaseAddress = Parent->SectionBaseAddress; Status = MmInitializeProcessAddressSpace (Process, Parent, NULL, &Flags, NULL); if (!NT_SUCCESS (Status)) { goto exit_and_deref; } CreatePeb = TRUE; UseLargePages = ((Flags & PROCESS_CREATE_FLAGS_LARGE_PAGES) != 0 ? TRUE : FALSE); /* 並且把父程式的映像名稱ImageFileName字串拷貝到新程式物件的資料結構中 */ if (Parent->SeAuditProcessCreationInfo.ImageFileName != NULL) { ImageFileNameSize = sizeof(OBJECT_NAME_INFORMATION) + Parent->SeAuditProcessCreationInfo.ImageFileName->Name.MaximumLength; Process->SeAuditProcessCreationInfo.ImageFileName = ExAllocatePoolWithTag (PagedPool, ImageFileNameSize, 'aPeS'); if (Process->SeAuditProcessCreationInfo.ImageFileName != NULL) { RtlCopyMemory (Process->SeAuditProcessCreationInfo.ImageFileName, Parent->SeAuditProcessCreationInfo.ImageFileName, ImageFileNameSize); Process->SeAuditProcessCreationInfo.ImageFileName->Name.Buffer = (PUSHORT)(((PUCHAR) Process->SeAuditProcessCreationInfo.ImageFileName) + sizeof(UNICODE_STRING)); } else { Status = STATUS_INSUFFICIENT_RESOURCES; goto exit_and_deref; } } } else { /* 3. 沒有指定映像記憶體區物件SectionObject,但指定了PsInitialSystemProcess(System程式)作為父程式。這對應於系統程式的情形。
      呼叫MmInitializeProcessAddressSpace,不指定記憶體區和父程式,直接初始化。
*/ Flags &= ~PROCESS_CREATE_FLAGS_ALL_LARGE_PAGE_FLAGS; Status = MmInitializeProcessAddressSpace (Process, NULL, NULL, &Flags, NULL); if (!NT_SUCCESS (Status)) { goto exit_and_deref; } Process->SeAuditProcessCreationInfo.ImageFileName = ExAllocatePoolWithTag (PagedPool, sizeof(OBJECT_NAME_INFORMATION), 'aPeS'); /* 同樣地,把父程式的映像名稱字串ImageFileName拷貝到新程式物件的資料結構中 */ if (Process->SeAuditProcessCreationInfo.ImageFileName != NULL) { RtlZeroMemory (Process->SeAuditProcessCreationInfo.ImageFileName, sizeof(OBJECT_NAME_INFORMATION)); } else { Status = STATUS_INSUFFICIENT_RESOURCES; goto exit_and_deref; } } /* 實際上還有第四種可能性,即沒有指定映像記憶體區物件SectionObject,也沒有指定父程式。這對應於系統的引導程式(後蛻化成空閒程式),它的地址空間是在MmInitSystem執行過程
    中初始化的,由MiInitMachineDependent函式呼叫MmInitializeProcessAddressSpace來完成 (這是潘老師在書上說的,可以我在翻閱了WRK和ReactOS的原始碼後也沒有找到與這段描述對應的原始碼,我的是windows確實實現了,但是WRK中沒有提供)
*/ } /* 建立程式ID。利用ExCreateHandle函式在CID控制程式碼表中建立一個程式ID項 */ CidEntry.Object = Process; CidEntry.GrantedAccess = 0; Process->UniqueProcessId = ExCreateHandle (PspCidTable, &CidEntry); if (Process->UniqueProcessId == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto exit_and_deref; } ExSetHandleTableOwner (Process->ObjectTable, Process->UniqueProcessId); /* 對這次程式建立行為進行審計 */ if (SeDetailedAuditingWithToken (NULL)) { SeAuditProcessCreation (Process); } if (Parent) { /* 如果父程式屬於一個作業物件,則也加入到父程式所在的作業中,呼叫PspAddProcessToJob完成操作,注意這裡對作業的最大容納程式數有要求 */ Job = Parent->Job; if (Job != NULL && !(Job->LimitFlags & JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK)) { if (Flags&PROCESS_CREATE_FLAGS_BREAKAWAY) { if (!(Job->LimitFlags & JOB_OBJECT_LIMIT_BREAKAWAY_OK)) { Status = STATUS_ACCESS_DENIED; } else { Status = STATUS_SUCCESS; } } else { Status = PspGetJobFromSet (Job, JobMemberLevel, &Process->Job); if (NT_SUCCESS (Status)) { PACCESS_TOKEN Token, NewToken; Job = Process->Job; Status = PspAddProcessToJob (Job, Process); Token = Job->Token; if (Token != NULL) { Status = SeSubProcessToken (Token, &NewToken, FALSE, Job->SessionId); if (!NT_SUCCESS (Status)) { goto exit_and_deref; } SeAssignPrimaryToken (Process, NewToken); ObDereferenceObject (NewToken); } } } if (!NT_SUCCESS (Status)) { goto exit_and_deref; } } } /* 開始建立Peb,建立Peb分為兩種情況,建立程式時建立Peb、複製程式時建立Peb Ps: 再次翻閱總路線圖,我們現在來到了"建立PEB"這一步 */ if (Parent && CreatePeb) { RtlZeroMemory (&InitialPeb, FIELD_OFFSET(INITIAL_PEB, Mutant)); InitialPeb.Mutant = (HANDLE)(-1); InitialPeb.ImageUsesLargePages = (BOOLEAN) UseLargePages; if (SectionHandle != NULL) { /* 1. 對於通過映像記憶體區物件SectionObject來建立程式的情形,呼叫MmCreatePeb來建立一個Peb,建立資料結構的基本模式都是類似的:
        申請空間->初始化剛才申請的空間->為資料結構中的指定成員域賦值
*/ Status = MmCreatePeb (Process, &InitialPeb, &Process->Peb); if (!NT_SUCCESS (Status)) { Process->Peb = NULL; goto exit_and_deref; } Peb = Process->Peb; } else { /* 2. 對於程式拷貝(fork)的情形,則使用從父程式繼承而來的Peb(從這裡也可以明白為什麼說子程式繼承了大部分的父程式的程式碼空間,從原始碼上可以找到理論依據) */ SIZE_T BytesCopied; InitialPeb.InheritedAddressSpace = TRUE; Process->Peb = Parent->Peb; /* 直接複製父程式的Peb */ MmCopyVirtualMemory (CurrentProcess, &InitialPeb, Process, Process->Peb, sizeof (INITIAL_PEB), KernelMode, &BytesCopied); } } Peb = Process->Peb; /* 可以看到,在核心中對這種雙連結串列的操作進行"加鎖"是很常見的程式設計做法,為了保證一致性 */ PspLockProcessList (CurrentThread); /* 把新程式加入到全域性的程式連結串列PsActiveProcessHead中,從資料結構的角度理解就是把Process->ActiveProcessLinks這個連結串列項插入一個雙連結串列中(頭尾斷鏈再重新結合) Ps: 回想利用PsActiveProcessHead進行核心中程式列舉的思路 */ InsertTailList (&PsActiveProcessHead, &Process->ActiveProcessLinks); PspUnlockProcessList (CurrentThread); AccessState = NULL; if (!PsUseImpersonationToken) { AccessState = &LocalAccessState; Status = SeCreateAccessStateEx (NULL, (Parent == NULL || Parent != PsInitialSystemProcess)? PsGetCurrentProcessByThread (CurrentThread) : PsInitialSystemProcess, AccessState, &AuxData, DesiredAccess, &PsProcessType->TypeInfo.GenericMapping); if (!NT_SUCCESS (Status)) { goto exit_and_deref; } } Status = ObInsertObject (Process, AccessState, DesiredAccess, 1, // bias the refcnt by one for future process manipulations NULL, &LocalProcessHandle); if (AccessState != NULL) { SeDeleteAccessState (AccessState); } if (!NT_SUCCESS (Status)) { goto exit_and_deref_parent; } ASSERT(IsListEmpty(&Process->ThreadListHead) == TRUE); /* 呼叫PspComputeQuantumAndPriority計算新程式的"基本優先順序(BasePriority)"和"時限重置值(QuantumReset)" */ BasePriority = PspComputeQuantumAndPriority(Process, PsProcessPriorityBackground, &QuantumReset); /* 對新程式的KPROCESS賦值基本優先順序和時限重置值 */ Process->Pcb.BasePriority = (SCHAR)BasePriority; Process->Pcb.QuantumReset = QuantumReset; /* 設定新程式的"記憶體優先順序(程式的訪問許可權)GrantedAccess"。因為新程式已經被加入到控制程式碼表中了,所以它現在能夠被終止了(即具有了PROCESS_TERMINATE的許可權,即它有終止的權利) */ Process->GrantedAccess = PROCESS_TERMINATE; if (Parent && Parent != PsInitialSystemProcess) { /* 對於有父程式但父程式不是PsInitialSystemProcess(System系統程式)的程式,首先呼叫SeAccessCheck執行訪問檢查 */ Status = ObGetObjectSecurity (Process, &SecurityDescriptor, &MemoryAllocated); if (!NT_SUCCESS (Status)) { ObCloseHandle (LocalProcessHandle, PreviousMode); goto exit_and_deref; } SubjectContext.ProcessAuditId = Process; SubjectContext.PrimaryToken = PsReferencePrimaryToken(Process); SubjectContext.ClientToken = NULL; AccessCheck = SeAccessCheck (SecurityDescriptor, &SubjectContext, FALSE, MAXIMUM_ALLOWED, 0, NULL, &PsProcessType->TypeInfo.GenericMapping, PreviousMode, &Process->GrantedAccess, &accesst); PsDereferencePrimaryTokenEx (Process, SubjectContext.PrimaryToken); ObReleaseObjectSecurity (SecurityDescriptor, MemoryAllocated); if (!AccessCheck) { Process->GrantedAccess = 0; } /* 為程式授予訪問許可權 */ Process->GrantedAccess |= (PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_QUERY_INFORMATION | PROCESS_TERMINATE | PROCESS_CREATE_THREAD | PROCESS_DUP_HANDLE | PROCESS_CREATE_PROCESS | PROCESS_SET_INFORMATION | STANDARD_RIGHTS_ALL | PROCESS_SET_QUOTA); } else { /* 如果是PsInitialSystemProcess的子程式,則授予所有訪問許可權"PROCESS_ALL_ACCESS" */ Process->GrantedAccess = PROCESS_ALL_ACCESS; } /* 設定程式的建立時間 */ KeQuerySystemTime (&Process->CreateTime); try { if (Peb != NULL && CurrentThread->Tcb.Teb != NULL) { ((PTEB)(CurrentThread->Tcb.Teb))->NtTib.ArbitraryUserPointer = Peb; } /* 把新程式的控制程式碼賦值到輸出引數"ProcessHandle"中,從而使建立者(呼叫者)可以獲得新程式的控制程式碼 */ *ProcessHandle = LocalProcessHandle; } except (EXCEPTION_EXECUTE_HANDLER) { NOTHING; } if (SavedStatus != STATUS_SUCCESS) { Status = SavedStatus; } exit_and_deref: ObDereferenceObject (Process); exit_and_deref_parent: if (Parent != NULL) { ObDereferenceObject (Parent); } return Status; }

這是一個漫長的過程,但是程式碼邏輯是很清晰的,我們在看完原始碼後再次做一個"稍微"詳細一點的總結:

1. 分配並初始化windows EPROCESS執行體程式塊
2. 從父程式處繼承得到程式的親和性掩碼
3. 新程式的最小和最大工作集尺寸被分別設定為PsMinimumWorkingSet和PsMaximumWorkingSet
4. 將新程式的配額塊設定為其父程式配額塊的地址,並且遞增父程式配額塊的引用計數
5. 繼承windows的裝置名字空間(包括驅動器字母的定義、COM埠等等)
6. 將父程式的程式ID儲存在新程式物件的InheritedFromUniqueProcessId域中
7. 建立新程式的主訪問令牌(父程式主令牌的副本)。新程式繼承了其父程式的安全輪廓(安全描述符),如果通過CreateProcessAsUser來為新程式指定一個不同的訪問令牌,
  則該令牌將在後面被正確地改過來
8. 初始化新程式控制程式碼表,如果父程式已經被設定了繼承控制程式碼標誌,那麼,從父程式的控制程式碼表中將任何可繼承的控制程式碼拷貝到新程式中 9. 將新程式的退出狀態設定為STATUS_PENDING 10. 建立和初始新程式的地址空間 11. 建立核心程式塊(KPROCESS) 12. 結束程式地址空間的建立過程 13. 建立Peb 14. 完成執行體程式物件的建立過程(設定返回值,開啟審計等)

至此,這就是一個"程式物件(EPROCESS/KPROCESS)"的初始化過程了。然而,經過PspCreateProcess函式之後,新建的程式中並沒有任何執行緒物件,所以,它現在還是一個死的程式空間,也就是說,其中的程式碼並沒被"真正"執行起來。所以,我們接下來討論執行緒物件的的建立和初始化。

回到之前留下的一個問題,我們在分析Kernel32.dll中的NtCreateProcessEx()的時候,我們提到,那個時候是"暫時"離開ring3,穿越進ring0去建立程式物件(EPROCESS/KPROCESS)。
到了這裡,可以解釋這句話的意思了,我們的核心程式碼在建立完程式相關的結構後,就會退出核心模式,穿越回ring3使用者模式。繼續執行kernel32.dll中的程式碼邏輯。

 

 

 

 

 

 

階段五: 建立執行緒的核心物件

建立完程式物件後,程式碼從核心模式穿越會使用者模式,我們在kernel32.dll中的NtCreateProcessEx()程式碼處繼續往下翻:

 

1. 處理下建立程式後的殘餘工作
在建立程式返回後,此時EPROCESS,PEB結構已經建立,程式地址空間也已經建立並初始化了。接下處理下建立程式後的殘餘工作,呼叫NtSetInformationProcess函式設定程式的優先順序和預設處理模式.

NTSYSAPI NTSTATUS NTAPI NtSetInformationProcess(
    IN HANDLE ProcessHandle,  //程式控制程式碼
    IN PROCESS_INFORMATION_CLASS ProcessInformationClass, //程式資訊型別索引(18 = ProcessPriorityClass)
    IN PVOID ProcessInformation,  //程式資訊
    IN ULONG ProcessInformationLength //程式資訊的的大小
);
..
v238 = NtSetInformationProcess(v294, 18, &v295, 2);
..

 

2. 構建執行緒的環境

接下來要做的就是建立執行緒, 不過在在此之前還要構建執行緒的環境,呼叫BaseCreateStack函式建立棧:

NTSTATUS WINAPI BaseCreateStack    (    
        HANDLE hProcess,  //程式控制程式碼
        SIZE_T StackReserve,  //棧大小
        SIZE_T StackCommit,  //棧的最大值
        PINITIAL_TEB InitialTeb  //初始TEB
);    
...
v23 = BaseCreateStack(v294, v191, v36, &v213);
...

接著呼叫BaseInitializeContext初始化執行緒上下文, 然後呼叫BaseFormatObjectAttributes函式格式化物件(以便傳遞給NtCreateThread)

....
BaseInitializeContext(&v297, v184, v189, v214, 0);
v188 = BaseFormatObjectAttributes(&v207, v166, 0);
...

 

3. 最後呼叫NtCreateThread建立執行緒

NTSYSAPI NTSTATUS NTAPI NtCreateThread( 
    OUT PHANDLE ThreadHandle,  //執行緒控制程式碼指標
    IN ACCESS_MASK DesiredAccess,  //訪問掩碼
    IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,  //物件屬性
    IN HANDLE ProcessHandle,   //程式控制程式碼
    OUT PCLIENT_ID ClientId,   //客戶端ID結構指標
    IN PCONTEXT ThreadContext,   //執行緒上下文結構指標
    IN PINITIAL_TEB InitialTeb,  //初始化TEB
    IN BOOLEAN CreateSuspended   //是否建立後掛起(預設是掛起的,題外話: 這就是APC程式注入的原理所在了)
);
//kernel32.dll
..
v23 = NtCreateThread(&v290, 2032639, v188, v294, &v262, &v297, &v213, 1);
...

執行了一小段之後,又再次呼叫NtCreateThread穿越進核心模式,這次的目的是建立執行緒物件。

類似於程式的建立過程,執行緒的建立過程是從NtCreateThread()開始的, 它的原始碼位於 base\ntos\ps\create.c 中,我還是決定直接在程式碼中插入註釋,逐行的分析原始碼

NTSTATUS NtCreateThread(
    __out PHANDLE ThreadHandle,
    __in ACCESS_MASK DesiredAccess,
    __in_opt POBJECT_ATTRIBUTES ObjectAttributes,
    __in HANDLE ProcessHandle,
    __out PCLIENT_ID ClientId,
    __in PCONTEXT ThreadContext,
    __in PINITIAL_TEB InitialTeb,
    __in BOOLEAN CreateSuspended
    ) 
{
    NTSTATUS Status;
    INITIAL_TEB CapturedInitialTeb;

    PAGED_CODE(); 
    try 
    {
        if (KeGetPreviousMode () != KernelMode) 
        {
            ProbeForWriteHandle (ThreadHandle);
            if (ARGUMENT_PRESENT (ClientId)) 
            {
                ProbeForWriteSmallStructure (ClientId, sizeof (CLIENT_ID), sizeof (ULONG));
            }
            if (ARGUMENT_PRESENT (ThreadContext) ) 
            {
                ProbeForReadSmallStructure (ThreadContext, sizeof (CONTEXT), CONTEXT_ALIGN);
            } 
            else 
            {
                return STATUS_INVALID_PARAMETER;
            }
            ProbeForReadSmallStructure (InitialTeb, sizeof (InitialTeb->OldInitialTeb), sizeof (ULONG));
        }
        CapturedInitialTeb.OldInitialTeb = InitialTeb->OldInitialTeb;
        if (CapturedInitialTeb.OldInitialTeb.OldStackBase == NULL &&
            CapturedInitialTeb.OldInitialTeb.OldStackLimit == NULL) 
        { 
            CapturedInitialTeb = *InitialTeb;
        }
    } 
    except (ExSystemExceptionFilter ())
    {
        return GetExceptionCode ();
    }
    Status = PspCreateThread (ThreadHandle,
                              DesiredAccess,
                              ObjectAttributes,
                              ProcessHandle,
                              NULL,
                              ClientId,
                              ThreadContext,
                              &CapturedInitialTeb,
                              CreateSuspended,
                              NULL,
                              NULL);

    return Status;
}

NtCreateThread()所做的事情很簡單:

1. 對非核心模式傳遞過來的呼叫,檢查幾個引數是否可寫(輸出引數TheadHandle、ClientId、輸入引數ThreadContext、InitialTeb)
2. 處理InitialTeb引數,將它放到區域性變數CapturedInitialTeb中
3. 呼叫真正建立執行緒的函式CreateSuspended()。引數原封不動的傳過去,並增加了幾個引數

PspCreateThread函式的原型如下:

NTSTATUS PspCreateThread(
    OUT PHANDLE ThreadHandle, //輸出引數: 新執行緒的控制程式碼
    IN ACCESS_MASK DesiredAccess,  //新執行緒的訪問許可權
    IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, //新執行緒物件的屬性
    IN HANDLE ProcessHandle, //執行緒所屬的程式的控制程式碼
    IN PEPROCESS ProcessPointer, //所屬程式的EPROCESS物件,此引數僅當建立系統執行緒時才會指向全域性PsInitialSystemProcess
    OUT PCLIENT_ID ClientId OPTIONAL, //指向新執行緒的CLIENT_ID結構
    IN PCONTEXT ThreadContext OPTIONAL, //提供新執行緒的執行環境(之前建立好的), 它代表了使用者模式執行緒的初始執行環境
    IN PINITIAL_TEB InitialTeb OPTIONAL, //為新執行緒Teb提供初始值
    IN BOOLEAN CreateSuspended,  //指明新執行緒被建立起來後是否"被掛起"。如果為TRUE則意味著新執行緒建立完以後並不立即執行      
    
//以後通過NtResumeThread函式讓它開始執行 IN PKSTART_ROUTINE StartRoutine OPTIONAL, //系統執行緒啟動函式的地址 IN PVOID StartContext //系統執行緒啟動函式的執行環境 );

和談到PspCreateProcess類似,我們在談到PspCreateThread的時候也要注意一個知識點:

1. PspCteateThread函式僅僅被NtCreateThread和PsCreateSystemThread這兩個函式呼叫,分別用於建立使用者執行緒和系統執行緒物件。
2. 在PspCteateThread函式的引數中,ThreadContext和InitialTeb引數針對使用者執行緒的建立操作,而StartRoutine和StartContext引數則針對系統執行緒的建立操作

接下來將再次貼出PspCreateThread函式的一大段程式碼,我將盡我的能力在程式碼插入註釋,來逐行解釋執行緒建立的過程。

在開始之前,我們先做一個"路線概覽",之後在分析詳細的程式碼的時候我們需要不斷的回到這個"線路概覽"上來,不斷從巨集觀和微觀的角度來看待執行緒的建立過程。

1. 遞增程式物件中的執行緒計數值
2. 建立並初始化一個執行體執行緒塊(ETHREAD)
3. 為新執行緒生成一個執行緒ID
4. 在程式的使用者模式地址空間中建立Teb
5. 使用者模式執行緒的起始地址被儲存在ETHREAD中。對於windows執行緒,這是系統提供的執行緒啟動函式,位於Kernel32.dll中(對於程式中的第一個執行緒是BaseThreadStart,對於其他的執行緒則
  是BaseThreadStart)。使用者指定的windows啟動地址被儲存在ETHREAD塊中的另一個位置上,因而,系統提供的執行緒啟動函式可以呼叫使用者指定的啟動函式
6. 呼叫KeInitThread來建立起KTHREAD塊。該執行緒初始的基本優先順序和當前優先順序均被設定為所屬程式的基本優先順序,它的親和性和時限被設定為程式的親和性和時限。該函式也會設定初始執行緒
  的理想處理器。KeInitThread接下來為該執行緒分配一個核心棧(注意和使用者執行緒棧區分),並且為它初始化與機器相關的硬體環境,包括執行環境、陷阱和異常幀。該執行緒的執行環境也被建立
  起來,因而在核心模式下此執行緒可在KiThreadStartup中啟動起來。最後,KeInitThread將該執行緒的狀態設定為"已初始化",並返回到PspCreateThread 7. 呼叫任何已等級的系統全域性範圍的執行緒來建立通知例程 8. 該執行緒的訪問令牌被設定為指向程式的訪問令牌,然後做一次訪問檢查,以確定呼叫者是否有權建立該執行緒。如果你是在本地程式中建立一個執行緒,那麼這一檢查將總是成功,但是,如果你是在
  另一個程式中建立一個"遠端執行緒",並且建立執行緒的程式沒有""除錯特權,那麼這一訪問檢查可能會失敗 9. 最後,該執行緒做好執行準備

 接下來貼上原始碼,希望朋友們能拿起手邊的書本,資料,配合著耐心讀完:

NTSTATUS PspCreateThread(
    OUT PHANDLE ThreadHandle,
    IN ACCESS_MASK DesiredAccess,
    IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
    IN HANDLE ProcessHandle,
    IN PEPROCESS ProcessPointer,
    OUT PCLIENT_ID ClientId OPTIONAL,
    IN PCONTEXT ThreadContext OPTIONAL,
    IN PINITIAL_TEB InitialTeb OPTIONAL,
    IN BOOLEAN CreateSuspended,
    IN PKSTART_ROUTINE StartRoutine OPTIONAL,
    IN PVOID StartContext
    )  
{

    HANDLE_TABLE_ENTRY CidEntry;
    NTSTATUS Status;
    PETHREAD Thread;
    PETHREAD CurrentThread;
    PEPROCESS Process;
    PTEB Teb;
    KPROCESSOR_MODE PreviousMode;
    HANDLE LocalThreadHandle;
    BOOLEAN AccessCheck;
    BOOLEAN MemoryAllocated;
    PSECURITY_DESCRIPTOR SecurityDescriptor;
    SECURITY_SUBJECT_CONTEXT SubjectContext;
    NTSTATUS accesst;
    LARGE_INTEGER CreateTime;
    ULONG OldActiveThreads;
    PEJOB Job;
    AUX_ACCESS_DATA AuxData;
    PACCESS_STATE AccessState;
    ACCESS_STATE LocalAccessState;

    PAGED_CODE(); 

    /*
    首先獲得當前執行緒物件,以及獲取此次建立操作來自於核心模式還是使用者模式(PreviousMode)
    */
    CurrentThread = PsGetCurrentThread();

    if (StartRoutine != NULL) 
    {
        PreviousMode = KernelMode;
    } 
    else 
    {
        PreviousMode = KeGetPreviousModeByThread (&CurrentThread->Tcb);
    }

    Teb = NULL;

    Thread = NULL;
    Process = NULL;
    /*
    根據程式控制程式碼引數ProcessHandle獲得相應的程式物件,放到區域性變數Process中
    Ps: 觀察總路線圖,我們現在處於"遞增程式物件中的執行緒計數值" 
    */
    if (ProcessHandle != NULL) 
    { 
        Status = ObReferenceObjectByHandle (ProcessHandle,
                                            PROCESS_CREATE_THREAD,
                                            PsProcessType,
                                            PreviousMode,
                                            &Process,
                                            NULL);
    } 
    else 
    {
        if (StartRoutine != NULL) 
        {
            ObReferenceObject (ProcessPointer);
            Process = ProcessPointer;
            Status = STATUS_SUCCESS;
        } 
        else 
        {
            Status = STATUS_INVALID_HANDLE;
        }
    }

    if (!NT_SUCCESS (Status)) 
    {
        return Status;
    } 

    if ((PreviousMode != KernelMode) && (Process == PsInitialSystemProcess)) 
    {
        ObDereferenceObject (Process);
        return STATUS_INVALID_HANDLE;
    }
    /*
    呼叫ObCreateObject函式建立一個執行緒物件ETHREAD
    */
    Status = ObCreateObject (PreviousMode,
                             PsThreadType,
                             ObjectAttributes,
                             PreviousMode,
                             NULL,
                             sizeof(ETHREAD),
                             0,
                             0,
                             &Thread);

    if (!NT_SUCCESS (Status)) 
    {
        ObDereferenceObject (Process);
        return Status;
    }
    /*
    把整個ETHREAD結構清零
    */
    RtlZeroMemory (Thread, sizeof (ETHREAD)); 
    /*
    對ETHREAD的一些基本的域進行初始化(RundownProtect、ThreadsProcess(指向由程式控制程式碼引數解析出來的EPROCESS物件)、Cid(包括UniqueProcess和UniqueThread成員))
    這裡Cid.UniqueProcess是從Process物件中來的,而Cid.UniqueThread則是通過呼叫ExCreateHandle()函式在CID控制程式碼表中建立一個控制程式碼表項而獲得的
    */
    ExInitializeRundownProtection (&Thread->RundownProtect);  
    Thread->ThreadsProcess = Process;
    Thread->Cid.UniqueProcess = Process->UniqueProcessId;
    CidEntry.Object = Thread;
    CidEntry.GrantedAccess = 0;
    Thread->Cid.UniqueThread = ExCreateHandle (PspCidTable, &CidEntry);

    if (Thread->Cid.UniqueThread == NULL) 
    {
        ObDereferenceObject (Thread);
        return (STATUS_INSUFFICIENT_RESOURCES);
    } 
    /*
    繼續初始化新執行緒物件ETHREAD結構中的一些域(ReadClusterSize、LpcReplySemaphore、LpcReplyChain、IrpList、PostBlockList、ThreadLock(執行緒鎖成員)、
  ActiveTimerListLock、ActiveTimerListHead)
*/ Thread->ReadClusterSize = MmReadClusterSize; KeInitializeSemaphore (&Thread->LpcReplySemaphore, 0L, 1L); InitializeListHead (&Thread->LpcReplyChain); InitializeListHead (&Thread->IrpList); InitializeListHead (&Thread->PostBlockList); PspInitializeThreadLock (Thread); KeInitializeSpinLock (&Thread->ActiveTimerListLock); InitializeListHead (&Thread->ActiveTimerListHead); /* 獲得"程式"的RundownProtect鎖,以避免在建立過程中該程式被停掉(rundown)。直到該執行緒被插入到程式的執行緒連結串列中(通過KeStartThread函式)之後,PspCreateThread此釋放該鎖 */ if (!ExAcquireRundownProtection (&Process->RundownProtect)) { ObDereferenceObject (Thread); return STATUS_PROCESS_IS_TERMINATING; } if (ARGUMENT_PRESENT (ThreadContext)) { /* 如果ThreadContext非NULL,則此次建立的是使用者模式執行緒,於是呼叫MmCreateTeb建立一個Teb,並用InitialTeb進行初始化 */ Status = MmCreateTeb (Process, InitialTeb, &Thread->Cid, &Teb); if (!NT_SUCCESS (Status)) { ExReleaseRundownProtection (&Process->RundownProtect); ObDereferenceObject (Thread); return Status; } try { /* 利用ThreadContext中的程式指標(EIP暫存器)來設定執行緒的啟動地址StartAddress,並且將ThreadContext中的EAX暫存器設定到執行緒的Win32StartAddress域 */ Thread->StartAddress = (PVOID)CONTEXT_TO_PROGRAM_COUNTER(ThreadContext); } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } /* 呼叫KeInitThread函式,根據程式物件中的資訊來初始化新執行緒核心物件(KTHREAD)的一些屬性(包括跟同步相關的各個域: 同步頭(Header)、WaitBlock初始化、
     執行緒的系統服務表(SSDT ServiceTable域)、與APC、定時器相關的域、執行緒的核心棧的初始化等等)。最後,KeInitThread函式根據所提供的引數資訊呼叫
     KiInitializeContextThread以便完成與特定處理器相關的執行環境的初始化。因此,當這個函式執行完後,新執行緒的狀態是"已初始化(Initialized)" Ps: 回顧總路線圖,我們現在已經到了"建立並初始化一個執行體執行緒塊(ETHREAD)"這一步了
*/ if (NT_SUCCESS (Status)) { Status = KeInitThread (&Thread->Tcb, NULL, PspUserThreadStartup, (PKSTART_ROUTINE)NULL, Thread->StartAddress, ThreadContext, Teb, &Process->Pcb); } } else { /* 否則,則是系統執行緒。首先在CrossThreadFlags標誌中設定系統執行緒位。然後將執行緒的啟動地址設定為StartRoutine引數。最後同樣地呼叫KeInitThread函式,
     所以,KeInitThread函式既可以被用來初始化使用者模式執行緒,也可以被用來初始化系執行緒
*/ Teb = NULL; PS_SET_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_SYSTEM); Thread->StartAddress = (PKSTART_ROUTINE) StartRoutine; Status = KeInitThread (&Thread->Tcb, NULL, PspSystemThreadStartup, StartRoutine, StartContext, NULL, NULL, &Process->Pcb); } if (!NT_SUCCESS (Status)) { if (Teb != NULL) { MmDeleteTeb(Process, Teb); } ExReleaseRundownProtection (&Process->RundownProtect); ObDereferenceObject (Thread); return Status; } /* 鎖住程式,並確保此程式並不是在退出或終止過程中 */ PspLockProcessExclusive (Process, CurrentThread); if ((Process->Flags&PS_PROCESS_FLAGS_PROCESS_DELETE) != 0 || (((CurrentThread->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_TERMINATED) != 0) && (ThreadContext != NULL) && (THREAD_TO_PROCESS(CurrentThread) == Process))) { PspUnlockProcessExclusive (Process, CurrentThread); KeUninitThread (&Thread->Tcb); if (Teb != NULL) { MmDeleteTeb(Process, Teb); } ExReleaseRundownProtection (&Process->RundownProtect); ObDereferenceObject(Thread); return STATUS_PROCESS_IS_TERMINATING; } /* 然後程式的活動執行緒數加1,並且將新執行緒加入到程式的執行緒連結串列中。 */ OldActiveThreads = Process->ActiveThreads++; InsertTailList (&Process->ThreadListHead, &Thread->ThreadListEntry); /* 呼叫KeStartThread,初始化KTHREAD剩餘的域(和排程相關的域: 優先順序(BasePriority)、實現設定(Quantum)、處理器親和性(Affinity)等等)。經過這一步的處理,新執行緒就可以
  開始執行了 Ps: 在PspCreateThread和KeStartThread這兩個函式中,我們都可以看到InsertTailList(&Process->ThreadListHead, &Thread->ThreadListEntry);這樣的呼叫,這是分別
  在執行體層和核心層維護執行緒和程式的關係(執行緒從屬程式)即EPROCESS->ETHREAD、KPROCESS->KTHREAD。
*/ KeStartThread (&Thread->Tcb); PspUnlockProcessExclusive (Process, CurrentThread); ExReleaseRundownProtection (&Process->RundownProtect); /* 若這是程式中的第一個執行緒,則觸發該程式的建立通知 */ if (OldActiveThreads == 0) { PERFINFO_PROCESS_CREATE (Process); if (PspCreateProcessNotifyRoutineCount != 0) { ULONG i; PEX_CALLBACK_ROUTINE_BLOCK CallBack; PCREATE_PROCESS_NOTIFY_ROUTINE Rtn; for (i=0; i<PSP_MAX_CREATE_PROCESS_NOTIFY; i++) { CallBack = ExReferenceCallBackBlock (&PspCreateProcessNotifyRoutine[i]); if (CallBack != NULL) { Rtn = (PCREATE_PROCESS_NOTIFY_ROUTINE) ExGetCallBackBlockRoutine (CallBack); Rtn (Process->InheritedFromUniqueProcessId, Process->UniqueProcessId, TRUE); ExDereferenceCallBackBlock (&PspCreateProcessNotifyRoutine[i], CallBack); } } } } /* 如果新執行緒所屬的程式在一個作業中,則需要做特定的處理 */ Job = Process->Job; if (Job != NULL && Job->CompletionPort && !(Process->JobStatus & (PS_JOB_STATUS_NOT_REALLY_ACTIVE|PS_JOB_STATUS_NEW_PROCESS_REPORTED))) { PS_SET_BITS (&Process->JobStatus, PS_JOB_STATUS_NEW_PROCESS_REPORTED); KeEnterCriticalRegionThread (&CurrentThread->Tcb); ExAcquireResourceSharedLite (&Job->JobLock, TRUE); if (Job->CompletionPort != NULL) { IoSetIoCompletion (Job->CompletionPort, Job->CompletionKey, (PVOID)Process->UniqueProcessId, STATUS_SUCCESS, JOB_OBJECT_MSG_NEW_PROCESS, FALSE); } ExReleaseResourceLite (&Job->JobLock); KeLeaveCriticalRegionThread (&CurrentThread->Tcb); } PERFINFO_THREAD_CREATE(Thread, InitialTeb); /* 通知哪些接收執行緒建立事件的出調例程(callout routine),即之前繫結在這個執行緒建立事件的回撥函式的例程 */ if (PspCreateThreadNotifyRoutineCount != 0) { ULONG i; PEX_CALLBACK_ROUTINE_BLOCK CallBack; PCREATE_THREAD_NOTIFY_ROUTINE Rtn; for (i = 0; i < PSP_MAX_CREATE_THREAD_NOTIFY; i++) { CallBack = ExReferenceCallBackBlock (&PspCreateThreadNotifyRoutine[i]); if (CallBack != NULL) { Rtn = (PCREATE_THREAD_NOTIFY_ROUTINE) ExGetCallBackBlockRoutine (CallBack); Rtn (Thread->Cid.UniqueProcess, Thread->Cid.UniqueThread, TRUE); ExDereferenceCallBackBlock (&PspCreateThreadNotifyRoutine[i], CallBack); } } } /* 執行緒物件的引用計數加2: 一個針對當前的建立操作,另一個針對要返回的執行緒控制程式碼 */ ObReferenceObjectEx (Thread, 2); /* 如果CreateSuspended引數指示新執行緒立即被掛起,則呼叫KeSuspendThread掛起新執行緒 */ if (CreateSuspended) { try { KeSuspendThread (&Thread->Tcb); } except ((GetExceptionCode () == STATUS_SUSPEND_COUNT_EXCEEDED)? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { } if (Thread->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_TERMINATED) { KeForceResumeThread (&Thread->Tcb); } } /* 根據指定的期望訪問許可權,呼叫SeCreateAccessStateEx()函式建立一個訪問狀態結構(ACCESS_STATE) */ AccessState = NULL; if (!PsUseImpersonationToken) { AccessState = &LocalAccessState; Status = SeCreateAccessStateEx (NULL, ARGUMENT_PRESENT (ThreadContext)?PsGetCurrentProcessByThread (CurrentThread) : Process, AccessState, &AuxData, DesiredAccess, &PsThreadType->TypeInfo.GenericMapping); if (!NT_SUCCESS (Status)) { PS_SET_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_DEADTHREAD); if (CreateSuspended) { (VOID) KeResumeThread (&Thread->Tcb); } KeReadyThread (&Thread->Tcb); ObDereferenceObjectEx (Thread, 2); return Status; } } /* 呼叫ObInsertObject函式把新執行緒物件插入到當前程式的控制程式碼表中 */ Status = ObInsertObject (Thread, AccessState, DesiredAccess, 0, NULL, &LocalThreadHandle); if (AccessState != NULL) { SeDeleteAccessState (AccessState); } if (!NT_SUCCESS (Status)) { /* ObInsertObject呼叫如果不成功,則終止新執行緒 */ PS_SET_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_DEADTHREAD); if (CreateSuspended) { KeResumeThread (&Thread->Tcb); } } else { /* ObInsertObject呼叫成功,設定好輸入引數ThreadHandle和ClienId,準備返回 */ try { *ThreadHandle = LocalThreadHandle; if (ARGUMENT_PRESENT (ClientId)) { *ClientId = Thread->Cid; } } except(EXCEPTION_EXECUTE_HANDLER) { PS_SET_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_DEADTHREAD); if (CreateSuspended) { (VOID) KeResumeThread (&Thread->Tcb); } KeReadyThread (&Thread->Tcb); ObDereferenceObject (Thread); ObCloseHandle (LocalThreadHandle, PreviousMode); return GetExceptionCode(); } } /* 設定新執行緒的建立時間 */ KeQuerySystemTime(&CreateTime); ASSERT ((CreateTime.HighPart & 0xf0000000) == 0); PS_SET_THREAD_CREATE_TIME(Thread, CreateTime); if ((Thread->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_DEADTHREAD) == 0) { Status = ObGetObjectSecurity (Thread, &SecurityDescriptor, &MemoryAllocated); if (!NT_SUCCESS (Status)) { PS_SET_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_DEADTHREAD); if (CreateSuspended) { KeResumeThread(&Thread->Tcb); } KeReadyThread (&Thread->Tcb); ObDereferenceObject (Thread); ObCloseHandle (LocalThreadHandle, PreviousMode); return Status; } SubjectContext.ProcessAuditId = Process; SubjectContext.PrimaryToken = PsReferencePrimaryToken(Process); SubjectContext.ClientToken = NULL; /* 設定新執行緒的訪問許可權: GrantedAccess域 */ AccessCheck = SeAccessCheck (SecurityDescriptor, &SubjectContext, FALSE, MAXIMUM_ALLOWED, 0, NULL, &PsThreadType->TypeInfo.GenericMapping, PreviousMode, &Thread->GrantedAccess, &accesst); PsDereferencePrimaryTokenEx (Process, SubjectContext.PrimaryToken); ObReleaseObjectSecurity (SecurityDescriptor, MemoryAllocated); if (!AccessCheck) { Thread->GrantedAccess = 0; } Thread->GrantedAccess |= (THREAD_TERMINATE | THREAD_SET_INFORMATION | THREAD_QUERY_INFORMATION); } else { Thread->GrantedAccess = THREAD_ALL_ACCESS; } /* 最後,呼叫KeReadyThread函式,呼叫KeReadyThread函式將執行緒加入到程式物件中的執行緒就緒佇列中(kprocess->ReadyListHead)
  他使新執行緒進入"就緒(ready)狀態",準備馬上執行(就緒態只是表明它有機會獲得CPU的排程,並不是指立刻就能執行)。 或者,若此時程式被記憶體排程換出了記憶體,則新執行緒的狀態為"轉移(transition)",以等待換入記憶體後再執行
*/ KeReadyThread (&Thread->Tcb); /* 引用計數減1,當前操作完成,返回 */ ObDereferenceObject (Thread); return Status; }

執行緒建立完成後呼叫CsrClientCallServer通知CRSS 執行緒建立成功,所以這個通知過程,我們詳細說明一下:

到這個點上,所有必要的執行體程式物件和執行體執行緒物件都已經被建立出來了。接下來kernel32.dll給windows"子系統CSRSS"傳送一個訊息,從而它可以進一步建立起新程式和執行緒,
該訊息包含以下資訊:
1. 程式和執行緒的控制程式碼 2. 建立標誌中的各個專案 3. 該程式的建立者ID 4. 指示該程式是否屬於一個windows應用程式的標誌(Csrss可以確定是否要顯示啟動游標) 當windows子系統Csrss接收到此訊息時,它執行以下12個步驟 1. CreateProcess複製一份該程式和執行緒的控制程式碼。在這一步,程式和執行緒的使用計數從1(在建立時設定的)增加到2 2. 如果程式的優先順序和類別沒有指定的話,則CreateProcess自動進行優先順序類別設定 3. 分配Csrss程式塊 4. 新程式的異常埠被設定為windows子系統的通用功能埠,這樣,當該程式中發生異常時,windows子系統將會收到一個訊息 5. 如果該程式正在被除錯(即它被附載到一個偵錯程式程式)的話,則該程式的除錯埠被設定為windows子系統的通用功能埠。這一設定可以保證,windows將把在新程式中發生的除錯事件
  (比如執行緒建立和刪除、異常、等等)作為訊息傳送給windows子系統in個,從而它可以把這些事件分發給新程式的偵錯程式程式
6. 分配並初始化Csrss執行緒塊 7. CreateProcess把該執行緒插入到程式的執行緒列表中 8. 遞增該會話中的程式計數值 9. 程式的停機級別被設定為0x280,這是程式預設停機級別 10. 將新程式塊插入到windows子系統範圍內的程式的列表中 11. windows子系統的核心模式部分所用到的,針對每個程式的資料結構(W32PROCESS結構)被分配並初始化 12. 顯示應用程式啟動游標。即沙漏箭頭。這是windows提醒使用者的方式: "我正在啟動某些東西"

程式碼呼叫NtRequestWaitReplyPort()來等待迴應,最後呼叫NtResumeThread()函式恢復執行緒的執行。

在以上呼叫完後,會呼叫核心中執行緒的啟動函式KiThreadStartup, 該函式的實現中呼叫了PspUserThreadStartup,該函式初始化使用者APC,將LdrInitializeThunk函式作為APC函式掛入
APC佇列中,最後呼叫KiDeliverApc發生APC, 通過中斷返回3環(注意,這裡又再次回到ring3)。
//這裡插個題外話,在一個程式注入的技術中談到的APC注入指的就是這一步了,我們通過註冊"程式建立"回撥函式往"遠端程式"的APC佇列中插入了DLL檔案,即插入了遠端程式的APC佇列中,
那麼作業系統在這一步就會逐個執行APC佇列中的任務,同樣也把我們注入的DLL給執行了

當PspUserThreadStartup返回到KiThreadStartup中後,它從核心模式中返回,切換到3環KiUserApcDispatcher,實現中呼叫LdrInitializeThunk來載入需要的DLL,並且用DLL_PROCESS_ATTACH功能程式碼來呼叫DLL的入口點

KiUserApcDispatcher
lea     edi, [esp+arg_C]
pop     eax
call    eax            //LdrInitializeThunk
push    1
push    edi
call    _ZwContinue@8         ;載入完DLL後,呼叫該函式繼續返回到核心

切換到核心後作部分操作後,再次回到3環(注意,這裡又再次回到ring3,這一階段來回次數較多,放慢速度理解一下),

呼叫使用者空間的執行緒入口函式BaseProcessStart(Kernel32.dll中的BaseProcessStart函式), 該函式在3環中BaseInitializeContext中有指定。 這個時候,執行緒就從之前指定的入口點程式碼處開始執行起來了。當然,它要根據優先順序遵循CPU的排程,分配到了時間才能執行。

//上面說了這一大段,整理一下流程:

1. KiThreadStartup降低IRQL到APC_LEVEL
2. 然後呼叫系統初始的執行緒函式PspUserThreadStartup
3. PspUserThreadStartup把一個使用者模式的APC插入到執行緒的使用者APC佇列中,此APC例程是在全域性變數PspSystemDll中指定的,指向ntdll.dll的LdrInitializeThunk函式
4. PspUserThreadStartup函式返回之後,KiThreadStartup函式返回到使用者模式,此時,PspUserThreadStartup插入的APC被交付,於是LdrInitializeThunk函式被呼叫,
  這是映像載入器(image loader)的初始化函式。LdrInitializeThunk函式完成載入器、堆管理器等初始化工作,然後載入必要的DLL,並且呼叫這些DLL的入口函式。最後,
  當LdrInitializeThunk返回到使用者模式APC分發器時,該執行緒開始在使用者模式下執行,呼叫應用程式指定的執行緒啟動函式,此啟動函式的地址已經在APC交付時備被壓入到使用者棧中
5. 至此,程式已經完成建立起來了,開始執行使用者空間的程式碼

附上執行緒建立的流程圖

至此,階段5的執行緒建立也分析結束了,此後執行緒就正常的執行起來了,至於之後在程式碼中是否要載入DLL還是別的功能,那和具體的程式碼邏輯有關,CPU以執行緒為排程單位根據CPU排程演算法輪詢地執行執行緒中的程式碼邏輯。

這裡還有一個問題,我們在文章的最開始說過: 這個ring3層的程式建立分析,即這個程式建立的發起源地是ring3。那如果要從ring0開始建立程式呢?答案是95%幾乎一樣,唯一不一樣的的最開始的入口,我們在核心層建立程式呼叫的NtCreateProcess(),這也是一個"轉接層"函式

NTSTATUS NtCreateProcess(
    __out PHANDLE ProcessHandle,
    __in ACCESS_MASK DesiredAccess,
    __in_opt POBJECT_ATTRIBUTES ObjectAttributes,
    __in HANDLE ParentProcess,
    __in BOOLEAN InheritObjectTable,
    __in_opt HANDLE SectionHandle,
    __in_opt HANDLE DebugPort,
    __in_opt HANDLE ExceptionPort
    )
{
    ULONG Flags = 0;

    if ((ULONG_PTR)SectionHandle & 1) 
    {
        Flags |= PROCESS_CREATE_FLAGS_BREAKAWAY;
    }
    if ((ULONG_PTR) DebugPort & 1) 
    {
        Flags |= PROCESS_CREATE_FLAGS_NO_DEBUG_INHERIT;
    }
    if (InheritObjectTable) 
    {
        Flags |= PROCESS_CREATE_FLAGS_INHERIT_HANDLES;
    }

    return NtCreateProcessEx (ProcessHandle,
                              DesiredAccess,
                              ObjectAttributes OPTIONAL,
                              ParentProcess,
                              Flags,
                              SectionHandle,
                              DebugPort,
                              ExceptionPort,
                              0);
}

函式的原始碼位於 base\ntos\ps\create.c  中,它知識簡單地對引數稍作處理,然後把建立程式得分任務交給NtCreateProcessEx函式,之後的流程就和我們之前分析的一模一樣了。

ring3建立程式和ring0建立程式的大致流程是:

1.1. ring3: CreateProcessA->CreateProcessInternalA->CreateProcessInternalW->NtCreateProcessEx->PspCreateProcess->NtCreateThread->PspCreateThread->
KiThreadStartup->PspUserThreadStartup->LdrInitializeThunk->BaseProcessStart
////CreateProcessA()只是起來轉接層的作用
1.1 ring3: CreateProcess->CreateProcessInternalW->NtCreateProcessEx->PspCreateProcess->->NtCreateThread->PspCreateThread->KiThreadStartup->
PspUserThreadStartup->LdrInitializeThunk->BaseProcessStart
2. ring0: NtCreateProcess->NtCreateProcessEx->PspCreateProcss->->NtCreateThread->PspCreateThread->KiThreadStartup->PspUserThreadStartup-> LdrInitializeThunk->BaseProcessStart

 

 

至此,windows中建立程式/執行緒的全部流程我們都分析清楚了,現在我們可以理解了windows系統中一個使用者程式的整個建立過程,雖然有一部分工作是由windows子系統來完成的,但是從作業系統核心的角度,我們依然可以清晰地看到,windows為了支援程式和執行緒的概念,是如何以物件的方式來管理它們,並建立和初始化程式和執行緒,使它們變成真正可以工作的功能實體。

 

本文也到此結束,如有不對的地方,懇請朋友們指正,共同學習

相關文章