进程是资源分配的基本单位,线程是CPU调度的基本单位,一个进程里面至少需要有一个线程,这是操作系统的知识,很多人都非常清晰,但是对背后原理并不熟悉,本文通过CreateProcess解读Windows进程的本质。

1.CreateProcess

BOOL CreateProcess(  
 LPCTSTR lpApplicationName, // 应用程序名称  
 LPTSTR lpCommandLine, // 命令行字符串  
 LPSECURITY_ATTRIBUTES lpProcessAttributes, // 进程的安全属性 
                         //ECURITY_ATTRIBUTES结构体,这个结构体决定是否返回的句柄可以被子进程继承。如果lpProcessAttributes参数为空(NULL),那么句柄不能被继承。
						 //typedef struct _SECURITY_ATTRIBUTES {
						 //    DWORD nLength; //结构体的大小,可用sizeof取得
						 //    LPVOID lpSecurityDescriptor; //安全描述符
						 //    BOOL bInheritHandle ;//安全描述的对象能否被新创建的进程继承
						 //} SECURITY_ATTRIBUTES,* PSECURITY_ATTRIBUTES;
 LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程的安全属性,跟进程的安全属性一样,只不过这是线程的属性
 BOOL bInheritHandles, // 是否继承父进程的属性(父进程可继承的打开句柄)
 DWORD dwCreationFlags, // 创建标志 
                         //CREATE_DEFAULT_ERROR_MODE:新的进程不继承调用进程的错误模式,默认的行为是为新进程继承调用者的错误模式(可以指定该标志修改)
						 //CREATE_NEW_CONSOLE:新的进程将使用一个新的控制台,而不是继承父进程的控制台。这个标志不能与DETACHED_PROCESS标志一起使用
						 //CREATE_NEW_PROCESS_GROUP:新进程将是一个进程树的根进程。进程树中的全部进程都是根进程的子进程。
						 //CREATE_SEPARATE_WOW_VDM:DOS环境相关,现在无用途
						 //CREATE_SHARED_WOW_VDM:DOS环境相关,现在无用途
						 //CREATE_SUSPENDED:新进程的主线程会以暂停的状态被创建,直到调用ResumeThread函数被调用时才运行
						 //CREATE_UNICODE_ENVIRONMENT:如果被设置,由lpEnvironment参数指定的环境块使用Unicode字符,如果为空,环境块使用ANSI字符
						 //DEBUG_PROCESS:如果这个标志被设置,调用进程将被当做一个调试程序,并且新进程会被当做被调试的进程
						 //DEBUG_ONLY_THIS_PROCESS:如果此标志没有被设置且调用进程正在被调试,新进程将成为调试调用进程的调试器的另一个调试对象
						 //DETACHED_PROCESS:对于控制台进程,新进程没有访问父进程控制台的权限。新进程可以通过AllocConsole函数自己创建一个新的控制台。这个标志不可以与CREATE_NEW_CONSOLE标志一起使用。
						 //CREATE_NO_WINDOW:系统不为新进程创建CUI窗口,使用该标志可以创建不含窗口的CUI程序。
 LPVOID lpEnvironment, // 指向新的环境块的指针,指向一个新进程的环境块。如果此参数为空,新进程使用调用进程的环境。
 LPCTSTR lpCurrentDirectory, // 指向一个以NULL结尾的字符串,这个字符串用来指定子进程的工作路径。这个字符串必须是一个包含驱动器名的绝对路径。
 LPSTARTUPINFO lpStartupInfo, // 传递给新进程的信息  
 LPPROCESS_INFORMATION lpProcessInformation // 新进程返回的信息  
);  

2.CreateProcess到底都干了什么(需要有PE文件基础知识才能理解)

(1)当我们双击一个程序test.exe运行的时候,一定是被另一个程序(Explorer.exe:桌面进程)调用了CreateProcess函数 打开的 (2)test.exe是PE文件,CreateProcess进入内核(NtCreateProcess),开始为test.exe创造运行条件,在内核空间分配4GB地址空间,首先创建一个句柄表(主要2项是传递给应用层的HANDLE和自己使用的一个指针,内核不允许用户进程直接操作这个指针,必须通过HANDLE传递,内核查表查出HANDLE对应的指针) (3)将test.exe拉伸,加载到指定位置(具体位置:根据ImageBase+EntryPoint加载到4GB地址空间) (4)加载导入表的DLL,以及DLL依赖的DLL(递归的) (5)修复(EXE和DLL的)IAT表 (6)创建线程、设置线程CONTEXT,设置CONTEXT的EIP为test.exe加载地址(ImageBase+EntryPoint),开始执行(这个可以看我的上一篇线程本质的文章) (7)创建完成后,通过:PROCESS_INFORMATION返回信息:

typedef struct _PROCESS_INFORMATION					
{					
   HANDLE hProcess;				//进程句柄	
   HANDLE hThread;				//主线程句柄	
   DWORD dwProcessId;			//进程ID	
   DWORD dwThreadId;			//线程ID	
} PROCESS_INFORMATION;