Windows核心编程【4】小结

本文主要介绍了Windows进程的基础知识,包括进程的定义、环境变量、错误模式等。详细讲解了CreatProcess函数的使用,包括参数含义和注意事项。还讨论了进程的终止、子进程的创建以及UAC(用户账号控制)的相关概念,如安全令牌、权限提升等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第四章进程
 
一、基本知识

1、  进程的定义,一个正在运行的程序的一个实例。由两部分组成,内核对象(系统保存进程统计信息的地方,用来管理进程)和地址空间(进程的所有执行体、数据段、堆栈等)。

2、  Windows有2大类应用程序。CUI(控制台用户界面)和GUI(图形用户界面),划分界限模糊。其中,关联到了VS集成环境链接器的各种开关。

3、  启动函数在进行一系列初始化(获取新进程的完整命令行的指针、环境变量指针、初始化C/C++运行库的全局变量、C运行库内存分配函数malloc和calloc及其他低级I/O历程使用的堆、调用全局和静态C++类对象的构造函数)后进入入口函数。

4、  进程实例句柄,独一无二。HMODULE和HINSTANCE是一回事吧,16位Windows除外。相关的函数就有HMOUDULEGetModuleHandle(PCTSTR pszModule),参数为NULL则是主调进程的(在DLL中调用的话,既是调用DLL的进程)可执行文件的基地址(HINSTANCE)。

5、  进程的命令行,一个新进程在创建时,都会接收一个命令行。WinMain的pszCmdline相关(忽略了执行体文件的名称的)。GetCommandLine返回的是同一块命令行内存,一般用来只读。好习惯的话,应该创建个缓冲区来。

6、  进程的环境变量,每个进程都有这么个与之关联的环境块,是在进程地址空间内分配的一个内存块。可通过GetEnvironmentStrings函数来获取完整的环境块。格式就不多说了。释放环境变量应该用BOOL FreeEnvironmentStrings(PTSTR pszEnvironmentBlock)函数。(这样就可以不管它’\0’位置怎样了)        CUI程序还有专用的访问方法,通过main入口函数所接收的TCHAR* env[]参数来实现。env是一个字符串指针数组,每个指针都指向一个不同的环境变量(定义采用常规的“名称=值”的格式),NULL指针表示数组尾。

7、  用户登录Windows时,系统会创建shell进程。通过检查注册表中的两个项来获得初始的环境字符串。第一个项包含应用于系统的所有环境变量的列表:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SessionManager\Environment第二个项包含应用于当前登录用户的所有环境变量的列表:HKEY_CURRENT_USER\Environment。

8、  通过修改注册表修改环境变量后,用户需要注销并重新登录才能生效。或者给其它主窗口发送WM_SETTINGCHANGE消息。SendMessage(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM)TEXT("Environment"))。

9、  子进程会继承父进程的环境变量,但会受到父进程的控制(访问权限)。不过,子进程的环境块和父进程的环境块不共享一块内存。既是子进程的环境变量改变不影响父进程。

10、             环境变量的其它相关操作:GetEnvironmentVariable,ExandEnvironmentStrings,SetEnvironmentVariable。

11、             进程的错误模式,SetErrorMode函数。

12、             进程当前所在的驱动器和目录,文件路径查找使用。该信息以进程为单位进行维护(GetCurrentDirectory、SetCurrentDirectory)。可以提供多个驱动器的当前目录。GetFullPathName获取当前完整目录。

13、             系统版本,暂时过滤……

 

二、CreatProcess函数

1、  一个线程调用CreatProcess时,系统将创建一个进程的内核对象,初始化计数器为1。然后为新进程创建一个虚拟地址空间,并将执行体文件(和所有必要DLL)的代码及数据加载到进程的地址空间。

2、  CreatProcess在进程完全初始化之前就返回TRUE了,这意味着操作系统的加载器尚未定位到所有必要的DLL。如果说有DLL找不到或者无法初始化,那么这个进程就会终止。

CreatProcess函数的原型如下:
BOOL WINAPI CreateProcess(

__in_opt     LPCTSTRlpApplicationName,

__inout_opt  LPTSTRlpCommandLine,

__in_opt     LPSECURITY_ATTRIBUTESlpProcessAttributes,

__in_opt     LPSECURITY_ATTRIBUTESlpThreadAttributes,

__in         BOOLbInheritHandles,

__in         DWORDdwCreationFlags,

__in_opt     LPVOIDlpEnvironment,

__in_opt     LPCTSTRlpCurrentDirectory,

__in         LPSTARTUPINFOlpStartupInfo,

__out        LPPROCESS_INFORMATIONlpProcessInformation

);

3、  参数lpApplicationName和lpCommandLine分别代表新进程所要使用的执行体文件名称,以及要传给新进程的命令行字符串。lpCommandLine是个LPSTR变量,也就是意味着需要一个非“常量字符串”。因为在CreatProcess内部会暂时地改变命令行字符串,所以不能为只读。否则会出错。所以需要先把常量字符串拷贝到一个临时缓冲区里。

4、  如果lpApplicationName参数为NULL的时候,可以使用lpCommandLine参数来指定一个完整的命令行,供CreateProcess创建新进程。CreateProcess解析lpCommandLine字符串时,会检查字符串中的第一个标记,如果是执行体文件的名称(无扩展名默认为exe),则为按照以下顺序搜索。a、主调用进程.exe文件所在的目录;b、住调用进程的当前目录;c、Windows系统目录,既是GetSystemDirectory返回的System32子文件夹;d、Windows目录;e、PATH环境变量中列出的目录。当然,如果命令行包含一个完整的路径的时候,就只会用该完整路径来查找执行体了。

5、  lpProcessAttributes、lpThreadAttributes和bInheritHandles参数,与新进程的内核对象操作权限有关。创建新的进程,系统必须创建一个进程内核对象和一个线程内核对象(用于进程的主线程)。一般设置前两个参数为NULL的话,系统会为这两个内核对象(进程内核对象和线程内核对象)指定默认的安全描述符。

6、  dwCreationFlags参数,标志了影响新进程创建方式的标志。多个标志可以用按位OR运算符来组合。同时,还允许指定一个优先级类。不过,书上说这样做没有多大必要,而且对于大多数应用程序,都不应该这样做,说是系统会为新进程分配一个默认的优先级类。

7、  lpEnvironment参数,指向一个内存块,该内存块包含新进程要使用的环境变量字符串。大多数时候为NULL,导致子进程继承父进程使用的环境变量。

8、  lpCurrentDirectory参数,允许父进程设置子进程的当前驱动和目录。如果为NULL,则子进程的工作目录与父进程的一样。该参数必须执行一个用0来终止的字符串,同时必须在路径中指定一个盘符(驱动器号)。

9、  lpStartupInfo参数,指向STARTUPINFO结构。然后就该学习这个结构了。Windows在创建新进程的时候使用这个结构的成员。大多数应用程序都希望生成的应用程序只是使用默认值。最起码要将此结构中的所有成员初始化为0,并将bd城花园设为此结构的大小(STARTUPINFO si = { sizeof(si) };)。如果没有把结构的内容清零,则成员将包含主调用线程的堆栈上的垃圾数据,可能会造成破坏。然后把就要结合刚刚的dwCreationFlags的标志了,既是是否允许使用STARTUPINFO的某些成员。

10、             关于STARTUPINFOEX的另外一个字段lpAttributeList,还需要再继续看看。

11、             lpProcessInformation参数,指向一个PROCESS_INFORMATION结构。就是用来保存新创建的进程的基本信息的结构吧。有hProcess、hThread、dwProcessId、dwThreadId。需要注意的都是ID的时效性,既是ID是随机分配的,一般不要使用ID,用内核对象或者窗口句柄等。

 

三、终止进程

1、  四种方式终止:a、主线程的入口函数返回(强烈推荐);b、进程中的一个线程调用ExitProcess函数(要避免这个方式);c、另一个进程中的线程调用TerminateProcess函数(要避免这种方式);d、进程中的所有线程都“自然死亡”(很难发生)。

2、  主线程的入口函数返回,可以保证主线程的所有资源都被正确清理。a、该线程创建的任何C++对象都将由这些对象的析构函数正确销毁;b、操作系统将正确释放线程堆栈使用的内存;c、系统将进程的退出代码(在进程内核对象中维护)设为我们的入口函数的返回值;d、系统会递减进程内核对象的使用计数。

3、  ExitProcess函数,进程中的其它任何线程都会随进程一起终止。Windows Platfrom SDK文档支出,一个进程在其所有线程都终止之后才会终止。从操作系统的角度出发,这种说法是正确的。但是C/C++运行时的启动代码确保了下面这一点:一旦你的应用程序的主线程从它的入口函数返回,那么不管当前在进程中是否正在运行其他线程,都会调用Exitprocess来终止进程。不过,如果在入口函数中调用ExitThread,而不是调用ExitProcess或者简单返回,应用程序的主线程将停止执行,但只要进程中还有其他线程正在运行,进程就不会终止。ExitProcess会造成进程“当场死亡”,C++对象不会被正确析构。

4、  TerminateProcess函数,任何线程都可以调用其来终止另一个进程或者它自己的进程。应该在无法通过其它方法来强制进程退出时使用。在这种情况下,进程无法将它再内存中的任何信息转存到磁盘上。因为没有被终止的通知。不过,操作系统会在进程终止之后彻底进行清理,确保不会泄露任何操作系统资源。但TerminateProcess是异步的,返回的时候不一定马上被杀死了,需要配合类似WaitForSingleObject(该进程对象变为signaled)的函数。

5、  GetExitProcess函数,可以用来获取已经死亡的一个进程的退出代码,需要句柄,既是在该进程死亡前,需要获得其句柄。任何时候都可以调用这个函数,如果进程没有终止,则是STILLZ_ACTIVE标志,如果终止了,则返回实际的而退出代码。

 

四、子进程

1、  单任务同步机制,共享数据方式(用于进程通信),内存映射文件是最方便的方式。

2、  运行分离的子进程,一旦进程创建并开始执行,父进程就不再与新进程通信,或者不用等他完成工作之后才继续自己的工作。Windows资源管理器就是这样工作的。方法:创建成功后关闭新的进程以及其主线程的句柄。

 

五、UAC(用户账号控制)

1、  安全令牌,每当有代码视图一个受保护的安全资源时,操作系统就会要求出示这个安全令牌。

2、  经过筛选的令牌,只授予标志用户的权限。权限受限的进程无法访问需要更高权限才能访问的安全资源。

3、  可以要求操作系统提升权限,但只能再进程边界上提升。默认情况下,一个进程启动时,它会与当前登录用户的筛选令牌关联起来。要为进程授予更多的权限,需要指示Windows做这样一件事情:在进程启动之前,友好地征求最终用户提权的同意。作为最终用户,可以通过以管理员身份运行来提权。

4、  系统程序,征求对话框是蓝色横幅;有签名的程序,征求对话框是灰色的横幅;没有签名的程序,对话框则是橙色的横幅。

5、  普通用户登录的话,就需要一个over-the-shoulder登录,既是需要输入管理员信息。

6、  Windows只运行在进程边界上进行提权。一旦进程启动,再要求更多的权限就已经迟了。不过,一个未提权的进程可以生成另一个提升了权限的进程,后者将包含一个COM服务器。

7、  自动提升进程的权限,应用程序的执行体中嵌入了一种特定的资源(RT_MAIFEST),系统就会检查<trustInfo>区域,并解析其内容。其中有level属性,可以用来设置需要的权限。更改后,需要注销登录。

8、  手动提升进程的权限,需要调用ShellExecuteExitProcess函数(BOOLShellExecuteEx(LPSHELLEXECUTEINFO pExecInfo);)。而SHELLEXECUTEINFO结构中唯一有趣的字段是lpVerb和lpFile。前者必须被设为“runas”,后者必须包含使用提升后的权限来启动的一个执行体文件的路径。如果用户拒绝提权,将返回FALSE,可以通过GetLastError来使用ERROR_CANCELLED值判断。当一个进程使用提升权后的权限启动时,它每次用CreatProcess来生成另一个进程时,子进程都会获得和它的父进程一样的提升后的权限。

9、  假如一个应用程序使用一个筛选后的令牌来运行的,那么一旦视图调用CreatProcess来生成一个要求提权的执行体,这个调用就会失败,GetLastError会返回ERROR_ELEVATION_REQUIRED。

10、             何为当前权限上下文,两个问题:如何判断应用程序是否以管理员身份运行;如何判断它是以提升的权限来启动的,还是正在使用筛选的令牌运行。函数GetProcessElevation(P106)能返回提升类型和一个指出是否正在以管理员身份运行的布尔值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值