Win32 API (4) CreateProcess

CreateProcess

CreateProcess 是一個宏,根據是否定義符號常量 UNICODE 展開爲 ASCII 和 UNICODE 兩個版本,其實如果不是必須更推薦使用 UNICODE 版本的 API 函數,因爲即便是 ASCII 版本的函數,內核層面也是通過 UNICODE 版本來實現的。

函數原型:
// ASCII 版本
BOOL CreateProcessA(
  LPCSTR                lpApplicationName,
  LPSTR                 lpCommandLine,
  LPSECURITY_ATTRIBUTES lpProcessAttributes,
  LPSECURITY_ATTRIBUTES lpThreadAttributes,
  BOOL                  bInheritHandles,
  DWORD                 dwCreationFlags,
  LPVOID                lpEnvironment,
  LPCSTR                lpCurrentDirectory,
  LPSTARTUPINFOA        lpStartupInfo,
  LPPROCESS_INFORMATION lpProcessInformation
);
// UNICODE 版本
BOOL CreateProcessW(
  LPCWSTR               lpApplicationName,
  LPWSTR                lpCommandLine,
  LPSECURITY_ATTRIBUTES lpProcessAttributes,
  LPSECURITY_ATTRIBUTES lpThreadAttributes,
  BOOL                  bInheritHandles,
  DWORD                 dwCreationFlags,
  LPVOID                lpEnvironment,
  LPCWSTR               lpCurrentDirectory,
  LPSTARTUPINFOW        lpStartupInfo,
  LPPROCESS_INFORMATION lpProcessInformation
);

該函數用於創建一個新的進程和他的主線程,新進程運行在父進程安全上下文(Security Context)中。

如果父進程正在模擬(impersonate)另一個用戶,此時創建的子進程使用父進程的訪問令牌,而非模擬令牌(impersonation token)。如果想在模擬令牌表示的安全上下文中創建新進程,應該使用 CreateProcessAsUser 或者 CreateProcessWithLogonW

參數說明:

lpApplicationName

該參數是待執行的模塊的名字,通常是一個基於 Windows 平臺的應用程序,如果本地計算機支持也可以是 MS-DOS 或者 OS/2 應用。

該參數可以指定可執行文件名和包含文件的完整目錄,當省略掉目錄的時候會默認是用進程的工作目錄來補全,而不會使用 PATH 變量搜索文件名。可執行文件必帶有後綴名,不能省略。

該參數可以爲 NULL,此時可執行文件名是 lpCommandLine (第二個參數)的第一個空字符分隔開的子字符串。此時如果 lpCommandLine 使用了一個包含空格的長路徑,爲了避免歧義應該使用引號區分開文件名和參數。例如如果第二個參數爲 c:\program files\sub dir\program name,系統將使用下面的順序來解析:

  1. c:\program.exe
  2. c:\program files\sub.exe
  3. c:\program files\sub dir\program.exe
  4. c:\program files\sub dir\program name.exe

對於16位應用程序來說,第一個參數必須爲 NULL ,第二個參數應該同時包含文件名和命令行參數。

lpCommandLine

該參數是包含最多32768個字符(結尾的空字符也算在內了)的命令行參數,如果 lpApplicationName 是 NULL,則該參數中模塊名最長爲 MAX_PATH 。函數的 UNICODE 版本可能修改字符串的值,因此如果參數指向一塊只讀的內存(比如const變量,字符串字面量),會導致非法訪問(Access Violation)錯誤。

這個參數也可以是 NULL,此時函數使用 lpApplicationName 指向的字符串作爲命令行。如果前兩個參數都不是 NULL,則 lpApplicationName 作爲可執行文件名,lpCommandLine 作爲命令行參數。新的進程可以使用函數 GetCommandLine 來獲取完整的命令行,C語言編寫的控制檯應用程序則可以使用主函數參數 argcargv 來獲取命令行。

lpApplicationName 是 NULL 時,第二個參數的首個空白分隔子字符串就是文件名,此時函數對文件名的處理方式和對第一個參數的處理有所區別,如果文件名沒有後綴則默認使用.exe,如果文件名包含路徑或者文件名以 . 結尾則不會自動附加文件後綴。如果文件名不包含目錄,則會按照啓動目錄,工作目錄,系統目錄,Windows目錄,PATH環境變量的順序來搜索可執行文件名(和 WinExec 的工作方式相同)。該函數不會搜索應用程序的 PATH(通過註冊表鍵 App Path 指定),如果需要可以通過 ShellExecute 函數來實現該功能。

lpProcessAttributes

這個參數是指向SECURITY_ATTRIBUTES 結構體的指針,用來決定 CreateProcess 得到的進程句柄能否被子進程所繼承,參數爲 NULL 表示不允許繼承。

SECURITY_ATTRIBUTES 結構體有一個名爲 lpSecurityDescriptor 的成員,該成員決定了新進程的安全描述符。如果參數 lpProcessAttributes 或者成員 lpSecurityDescriptor 爲 NULL,則進程使用默認安全描述符,對於 Windows XP 以上操作系統來說線程默認安全描述符的訪問控制列表(ACL) 來自創建者的主令牌(Primary Token),在此之前還可以是模擬令牌。

lpThreadAttributes

和上一個參數類似,也是 SECURITY_ATTRIBUTES 結構的指針,只不過此處用於描述線程。

bInheritHandles

子進程是否繼承父進程中的可繼承句柄,如果爲 TRUE 則繼承,爲 FALSE 則不繼承。繼承來的句柄和原句柄有着相同的值和訪問權限。

dwCreationFlags

進程創建標誌

lpEnvironment

進程環境塊指針,如果該參數爲 NULL 則子進程使用父進程的環境塊。

lpCurrentDirectory

進程工作目錄,該參數應該是進程當前目錄的完整路徑。如果這個參數爲 NULL 則新進程的工作目錄和父進程相同。

lpStartupInfo

指向 STARTUPINFO 或者 STARTUPINFOEX 結構體的指針。設置擴展屬性時要使用 STARTUPINFOEX 結構體,並且在參數 dwCreationFlags 中指定 EXTENDED_STARTUPINFO_PRESENT 。如果結構體 STARTUPINFO 或者 STARTUPINFOEX 中的句柄不再使用,必須通過函數 CloseHandle 關閉。

注意調用者要確保 STARTUPINFO 中的標準句柄成員取值合法。這些字段在拷貝給子進程時不經過校驗,即便 dwFlags 成員給了值 STARTF_USESTDHANDLES 。錯誤的句柄值將會導致子進程行爲異常或者崩潰。可以使用Application Verifier 運行時驗證工具來檢測非法句柄。

lpProcessInformation

這是一個輸出參數(由CreateProcess 函數負責填充),它是指向 PROCESS_INFORMATION 結構體的指針,該結構體包含子進程的身份信息。PROCESS_INFORMATION 中的句柄成員如果不再使用需要調用 CloseHandle 關閉。

PROCESS_INFORMATION 結構體定義如下:

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

四個成員從上到下分別是進程句柄,線程句柄,進程ID,線程ID。

返回值:

如果函數成功執行,返回非0值。

如果函數執行失敗,返回0,詳細錯誤信息可以通過調用 GetLastError 來獲得。

該函數在進程完成初始化以前就已經返回,因此如果 DLL 加載或者初始化失敗進程會終止。通過調用 GetExitCodeProcess 獲取進程終止狀態。

注意事項:

子進程創建以後會分配進程 ID ,子進程的主線程也會分配線程 ID ,他們可以分別用在 OpenProcessOpenThread 函數來獲得進程和線程的句柄。在相應進程和線程終止以前,進程ID和線程ID都可以作爲進程和線程的唯一合法標識。我們可以通過 PROCESS_INFORMATION 結構體來訪問進程 ID 和線程 ID 。

鑑於 CreateProcess 並不會等待子進程初始化,如果我們需要同步父進程和子進程,可以在父進程調用函數 WaitForInputIdle ,該函數會在子進程初始化完畢並開始等待用戶輸入後才返回。在某些情況下這很有用,比如我們在想要在創建子進程以後查找子進程的窗口。

最好使用 ExitProcess 來關閉一個進程,這個函數會向所有附加在該進程上的動態鏈接庫發送關閉通知。其他關閉方法不會通知進程相關的DLL。但線程調用 ExitProcess 之後,進程的其他線程也會立即關閉並且來不及執行任何其他代碼。

父進程在創建子進程時可以直接修改子進程的環境變量。這是一個進程直接修改另一個進程的環境設置的唯一機會。

lpApplicationName 爲 NULL 時,該函數和 WinExec 存在完全相同的安全隱患,具體可以參考這裏。爲了避免這個問題儘量不要給第一個參數設爲 NULL。

依賴信息:
名稱
Header processthreadsapi.h (include Windows Server 2003, Windows Vista, Windows 7, Windows Server 2008 Windows Server 2008 R2, Windows.h)
Library Kernel32.lib
DLL Kernel32.dll
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章