CreateProcess函数
使用CreateProcess函数来创建一个进程
BOOL CreateProcess(
LPCTSTR pszApplicationName,// name of executable module
LPTSTR pszCommandLine,// command line string
LPSECURITY_ATTRIBUTES psaProcess,// SD
LPSECURITY_ATTRIBUTES psaThread,// SD
BOOL bInheritHandles,// handle inheritance option
DWORD fdwCreationFlags,// creation flags
LPVOID pvEnvironment,// new environment block
LPCTSTR pszCurDir,// current directory name
LPSTARTUPINFO psiStartupInfo,// startup information
LPPROCESS_INFORMATION lpProcessInformation // process information
);
一个线程调用CreateProcess时,系统将创建一个进程内核对象,其初始使用计数为1,然后系统为新进程创建一个虚拟地址空间,并将可执行文件(和所有必要的DLL)的代码及数据加载到进程的地址空间。然后,系统为新进程的主线程创建一个线程内核对象(其使用计数为1)。这个主线程一开始就会执行C\C++运行时的启动例程,它是由链接器设为应用程序入口的,最终会调用应用程序WinMain,wWinMain,main或wmain函数。如果系统成功创建了新进程和主线程,CreateProcess返回TRUE。
注意:
CreateProcess在进程完全初始化好之前就返回true,如果一个dll找不到或不能正确初始化,进程就会终止,而父进程不会注意到任何初始化问题。
4.2.1
pszApplicationName 和 pszCommandLine
pszCommandLine类型为PTSTR,意味着不能传入常量,使用前应赋值给变量。CreateProcess内部会修改我们传入的字符串,但在返回前会还原。函数会检查字符串中的第一个标记(token),并假定此标记是我们想运行的可执行文件的名称。
如果指定的字符串没有扩展名:会默认是.exe,当然,加入文件名包含一个完整路径,系统就会用这个完整路径查找可执行文件
CreateProcess按以下的顺序搜索可执行文件:
1.主调进程.exe文件所在的目录
2.主调进程的当前目录
3.windows系统目录,即GetSystemDirectory()返回的System32文件夹
4.Windows目录
5.PATH环境变量中列出的目录
(c/c++运行时启动例程会检查进程的命令行,将可执行文件名之后的第一个实参的地址传给WinMain的pszCmdLine参数)
lpApplicationName:
指定了新进程要使用的可执行文件的名称
一般此参数为NULL 如果不为NULL可以传递一个字符串地址,并在字符串中包含想要运行的可执行文件的名称,且要指定扩展名,系统不会自动加个.exe的扩展名
如果不指定路径,CreateProcess只会在当前路径下寻找文件,如果找不到调用就会失败,然而即使在pszApplicatioName中指定文件名,CreateProcess也会将pszCommandLine参数作为新进程的命令行传给他,例如:
#include
#include
int main()
{
char buf[]="WORDPAD README.txt";
STARTUPINFO si ={sizeof(STARTUPINFO)};
PROCESS_INFORMATION pi;
CreateProcess("C:\\WINDOWS\\SYSTEM32\\NOTEPAD.EXE",buf,NULL,NULL,false,0,NULL,NULL,&si,&pi);
}
lpApplicationName不为NULL时,必须指定文件扩展名,且在当前文件目录下查找,并且将WORDPAD作为进程名,README.txt作为命令行传入main。
lpApplicationName设为NULL时写法:
#include
#include
int main()
{
char buf[]="C:\\WINDOWS\\SYSTEM32\\NOTEPAD README.txt";
STARTUPINFO si ={sizeof(STARTUPINFO)};
PROCESS_INFORMATION pi;
CreateProcess(NULL,buf,NULL,NULL,false,0,NULL,NULL,&si,&pi);
}
4.2.2 psaProcess,psaThread和bInheritHandles参数
psaProcess psaThread 为进程和主线程的安全属性,为NULL时,系统指定默认安全描述符。bInheritHandles 继承性
4.2.3
fdwCreationFlags
参数
fdwCreate参数用于标识标志,以便规定用来如何创建新进程,见MSDN
4.2.4 pvEnvironment参数
传入NULL时,等于调用PVOID GetEnvironmentStrings(),将其返回地址作为参数。不再需要这块内存时,需调用FreeEnvironmentStrings函数释放
4.2.5 pszCurDir参数
允许父进程设置子进程的当前驱动和目录。注意:必须在路径中指定一个驱动号,或NULL。
4.2.6 psiStartupInfo参数
指向一个
STARTUPINFO结构体,STARTUPINFO si = {sizeof(si)};//将结构体第一个值设置为大小,其他初始化为0。若没有把其他的清0,则成员将包含主调线程的栈上的垃圾数据。导致有时能创建进程有时不能,具体取决于垃圾数据的内容。
4.2.7 lpProcessInformation 参数
Pointer to a
PROCESS_INFORMATION
structure that receives identification information about the new process.
Handles inPROCESS_INFORMATIONmust be closed with
CloseHandle
when they are no longer needed.在CreateProcess返回之前,会打开进程对象和线程对象,这时,每个对象的计数变为2,着意味着系统想要释放进程对象,进程必须终止(使用计数递减1),而且父进程必须调用CloseHandle(使用计数再次递减1,变成0)
每个进程内核对象,线程内核对象都有一个唯一的标识符,进程ID和线程ID共享一个ID池。获取id:GetCurrentProcessId,GetCurrentThreadId,GetProcessId,GetThreadId,GetProcessIdOfThread.一旦使用CloseHandle,id就可能会改变,当子进程开始执行代码之前的那一刻,windows就已经认为不存在任何父子关系了。
进程--终止进程和子进程
4.3 有以下4种方式:
1.主线程的返回退出(强烈推荐)
2.使用函数ExitProcess(要避免这种方式)
3.使用函数TerminateProcess(
要避免这种方式
)
4.进程中的所有线程终止运行(几乎不会发生)
4.3.1
主线程的返回退出
应保证只有在主线程的入口点函数返回之后,进程才会终止,这样最好原因:
1.
C++代码的析构函数能够被正确执行
2.
系统会正确的释放线程堆栈使用的内存
3.
系统会将进程的退出代码设置为进入点函数的返回值
4.
进程的内核对象会减1
4.3.2
ExitProcess函数
当主线程的入口点函数(Winmain。。。)返回时,会返回到C\C++运行库启动代码,后者将正确清理进程使用的全部C运行时资源。释放了C运行时资源后,C运行时启动代码将显式的调用ExitProcess,并将入口点函数返回值传给它。如果在入口点函数返回之前调用ExitProcess,会导致进程或线程直接终止C\C++运行库可能不能正确执行清理工作。例:
#include
#include
usingnamespace std;
class PPP
{
public:
PPP(){ cout <<"this is constructor!"<< endl;}
~PPP(){ cout <<"this is destructor!"<< endl;}
};
PPP p1;
int main()
{
PPP p;
ExitProcess(0);
cout <<"run this ?"<< endl;
}
程序不会调用析构函数和执行后面的语句,进程当场终止,C\c++运行库没有机会执行清理工作。
4.3.3 TerminateProcess函数
BOOL TerminateProcess(
HANDLE hProcess,// handle to the process
UINT uExitCode // exit code for the process
);
异步执行,不能被正常清理,不能保证已经被强行终止。so ,为了确定进程是否被终止,可调用WaitForSingleObject或一个类似的函数。
4.3.4 当进程中的所有线程终止时
一旦系统检测到一个进程中没有任何线程在执行,就会终止这个进程,进程的退出码会被设为最后一个终止的那个线程的退出码。
4.3.5 当进程终止运行时
- 进程中剩余的所有线程全部终止运行。
- 释放该进程引用的GDI和用户对象,内核对象被关闭(别的进程有引用则计数器减1,如果没有引用则关闭内核对象)。
- 推出代码将从STILL_ACTIVE(后面章节线程将介绍该结构)改为传递给ExitProcess和TerminiateProcess代码。
- 内核对象状态变为收到通知状态(线程中介绍),其他线程挂起,知道进程终止。
- 进程内核对象计数减去1,或者关闭。
注意:
进程内核对象的寿命可能远远大于进程本身,父进程保留子进程内核对象可以查看它的推出代码调用下面函数:
BOOL GetExitCodeProcess(HANDLE hProcess, PDWORD pdwExitCode);
4.4 子进程
没什么好说的,前面的都说了,用CloseHandle关闭子进程和子进程中主线程的句柄值来切断父进程和子进程的所有联系。
进程的权限
用户账户控制(UAC)技术
Windows操作系统会创建一个安全令牌(security token).每当有代码试图访问一个受保护的安全资源时时,操作系统就会使用(出示)这个安全令牌,
从Windows在内的第一个进程开始,这个令牌会与新建的所有进程关联,这样如果新下载了一个恶意程序,就会继承管理员的高权限,------因此可以肆无忌惮的修改机器上的任何内容,甚至启动同一个高特权的进程。
那么从vista开始,如果用户使用管理员帐号来登录这样一个被授予高特权的账户登录,那么除了与这个账户登录对应的安全令牌之外,还会创建一个进过筛选的令牌(filtered token)后者只给标准用户的权限(Standard User).以后包括Windows资源管理器在内的第一个进程开始,这个筛选后的令牌会与系统表示最终用户启动启动的所有新进程关联,权限受限制的进程无法访问需要更高权限才能访问的安全资源.4.5.1 自动提升进程的权限
manifest文件
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="highestAvailable">requestedExecutionLevel>
requestedPrivileges>
security>
trustInfo>
如上 被称为一种特殊的资源RT_MANIFEST特殊资源,将其编译到exe资源节区中,系统会按照requestedExecutionLevel节来判断进程的权限,并执行一些操作
requireAdministrator -->应用程序必须以管理员权限启动,否则不会运行
highestAvailable-->应用程序以当前可用的最高权限运行
-->如果用户使用管理员帐号登录,就会出现一个要求批准提升权限的对话框
-->如果用户使用普通用户账户登录系统,就使用标准权限来启动
asInvoker -->应用程序以主调应用程序一样的权限来启动
可将清单嵌入到可执行文件的资源中,或保存到可执行文件所在目录下,扩展名用.
manifest[注:不会立即被系统发现,有时需注销用户]
4.5.2 手动提升进程的权限
4.5.3当前权限上下文
4.5.4枚举运行进程
4.5.5 Process Information 示例程序