进程
一、进程:
1.1、进程是一段内存空间,线程是执行这段内存空间中的代码。
1.2、进程和线程其实是分开的,不存在包含关系。
1.3、切换进程上下文:首先需要将进程从内存中卸下来(因为我们的内存有限)、然后在装载另一个进程进行执行。
1.4、切换线程上下文:只需要保存线程的状态。
1.5、以上两种上下文的切换的成本不同。
1.6、进程由两个东西组成:内核对象和地址空间。
1.7、GUI和CUI:在VS中,CUI程序的连接器开关为SUBSYSTEM:CONSOLE,GUI程序的连接器开关为SUBSYSTEM:WINDOWS,在加载时
1.8、会获得此值,如果是一个文本控制台窗口,操作系统会使用命令提示符启动这个程序,否则它只是加载这个由应用程序来管理自己的窗口。
1.9、启动函数不同,GUI的启动函数为WinMainCRTStartup或wWinMainCRTStartup,CUI的启动函数为mainCRTStartup或wmainCRTStartup。
1.10、
int CALLBACK WinMain( _In_ HINSTANCE hInstance, //当前进程句柄。 _In_ HINSTANCE hPrevInstance, //前一个句柄,父句柄, 在当前程序中永远不要使用。 _In_ LPSTR lpCmdLine, //命令行 _In_ int nCmdShow //显示方式。SW_SHOW, SW_HIDE. );
1.11、进程句柄:加载到进程地址空间的每一个执行体或者DLL文件都被赋予了一个独一无二的实例句柄(内核对象)。
1.12、默认的基地址:0x400000。
1.13、关于目录:1、相对路径:在所在目录下操作文件。2、绝对路径.,3、Module路径:指的是exe的路径。
1.14、GetCurrentDirectory();函数是获得当前所在路径。
1.15、GetModuleFileName();函数是获得Dug下面的exe。程序存在的目录
1.16、更改所在目录,环境变量不会改变,但是当前目录会被更改。
1.17、环境变量:=C: 、 =D: 、 =E:。 等等,有多少个盘符就有多少个,默认为当前目录。
1.18、
BOOL WINAPI CreateProcess( _In_opt_ LPCTSTR lpApplicationName, //执行体文件的名称(exe文件),可以为NULL,如果为NULL函数就会去执行lpCommandLine指定的文件。 不为NULL时lpCommandLine会被当做命令行参数专递。 _Inout_opt_ LPTSTR lpCommandLine, //转给新进程的命令行字符串,一定的是一个缓冲区,没有加上后缀名的话,默认的会帮我们加上一个后缀名。 (1、所在目录。2、当前目录。3、Windows系统目录(System32目录)。4、Windows目录。5、Path环境 变量中列出的目录。),在这些目录中依次的进行扫描。不应该专递一个常量指针。 _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes, //可以NULL _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, //可以NULl _In_ BOOL bInheritHandles, //可以FALSE _In_ DWORD dwCreationFlags, //可以NULL _In_opt_ LPVOID lpEnvironment, //可以NULL,这个参数会继承父进程。 _In_opt_ LPCTSTR lpCurrentDirectory, //当前目录,也是无需进行设置。可以NULL _In_ LPSTARTUPINFO lpStartupInfo, _Out_ LPPROCESS_INFORMATION lpProcessInformation );
1.18.1、此函数相对复杂,因为这个函数是我们用来创建一个进程的API。
1.18.2、当我们来进行进程的创建的时候操作系统会来帮我们做几件事情:
1.18.2.1、第一个是来帮我们创建一个内核对像。但是这个内核对象并不代表我们进程的本身。
因为我们进程代表的是一块内存的区域,那操作系统为什么给我们分配一个内核对象呢:因为便于操作系统对进程的管理。
1.18.2.2、使用计数加一:内核对象时一个结构体,这个结构体中存在一个使用计数,这个使用计数主要作用Windows进行内核对象的清理。
当我们创建了一个内核对象的时候这个使用计数就会加一。当内核对象使用完了这个使用计数会减一。当操作系统发现这个
使用计数为0的时候,这个内核对象就会被回收。
1.18.2.3、然后系统为新进程创建一个虚拟地址空间。将所需的代码以及数据加载到进程的地址空间当中。
1.18.2.4、之后系统为新的进程的主线程创建一个线程内核对象,主线程最终调用你的main函数。而线程当中也会有一个使用计数,此时也是加一。
1.18.3、当在一个进程中调用另一个进程的时候就形成了父子进程的关系:但是这个父子其实也是很模糊的关系。因为有虚拟内存的存在,
将我们的进程分在了一个个不同的虚拟地址空间中,相当于一个个的盒子中,此时两个进程之间是不能直接通信的。所以形成了两个独立的进程。
1.18.4、
LPSTARTUPINFO 结构体:
typedef struct _STARTUPINFO { DWORD cb; //结构体大小。 LPTSTR lpReserved; //保留,必须初始化为NULL 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;
1.18.4.1、这个结构体是用于设置我们新进程的一个结构体。
1.18.4.2、这个结构体必须在CreateProcess之前被初始化。这个结构体一定要进行清零操作,否则成员间包含主调线程推栈上的垃圾数据。
1.18.5、
LPPROCESS_INFORMATION 结构体:typedef struct _PROCESS_INFORMATION { HANDLE hProcess; HANDLE hThread; DWORD dwProcessId; DWORD dwThreadId; } PROCESS_INFORMATION, *LPPROCESS_INFORMATION;
1.18.5.1、这个参数(结构体)非常非常的重要,因为这个参数是作为返回值进行专递的。
1.18.5.2、CreateProcess会帮我们创建两个内核对象:一个是进程内核对象,一个是线程内核对象。
1.18.5.3、CreateProcess函数执行成功,会把两个内核对象写在这个结构体中。
1.18.5.4、每个进程都有一个ID(PID),同样的每一线程都有一个ID(TID)。
1.19、终止进程:
1.19.1、第一种是:入口函数的返回,这是最正确的方式,只有这样才能够保证主线程的所有资源都已经被正确的清理。
入口函数在返回时,为确保一下几件事情已经完成:
1、主线程所创建的任何对象都已经被正确地销毁。
2、操作系统会正确的释放线程的推栈。
3、将进程的退出代码设置成入口函数的返回值。
4、递减内核对象的使用计数。
1.19.2、进程中的一个线程调用VOID ExitProcess(UINT uExitCode);
不应调用此函数结束进程,当函数被调用的时候会强制结束进程,并将退出代码设置为uExitCode,但此时线程并未正确结束,会导致线程无法正确被清理。
1.19.3、另一个进程中的线程调用BOOL TermitateProcess(HANDLE hProcess, DWORD uExitCode);
这也是不应该用的方法:不应该用此函数来结束进程,此函数能够结束其他进程。
1.19.4、进程中的所有线程都自然死亡。
很少碰到这种情况,理论存在。
1.20、进程权限:
1.20.1、虚拟内存:杜绝了修改操作系统代码的可能。
1.20.2、使用CreateProcess 打开一个子进程, 此时父进程拥有子进程的访问权限。
1.20.3、UAC:带过滤表的权限。
1.20.4、在Windows Vasta之后,加上了一个UAC权限令牌机制:使得我们的任何的程序都不能拥有完全的管理员权限。只能在我们程序的边界上进行权限的提升。
1.20.5、程序边界:指的是在程序启动的时候提升权限,启动完了之后就不能再进行权限的提升。而且在提升权限的时候必须通知用户,对于进程的提权都是单次的。
1.20.6、进程设计的时候 -> 权限继承的机制。由于有UAC机制的存在,子进程只能获得被UAC过滤之后的有限的权限。
1.20.7、我们的双击或者单击打开等等等。。的程序都是资源管理器调用CreateProcess进行的打开的,
1.20.8、
手动提权:
BOOL ShellExecuteEx( //在指定文件上执行操作。 _Inout_ SHELLEXECUTEINFO *pExecInfo );
1.20.8.1、SHELLEXECUTEINFO 结构体:
typedef struct _SHELLEXECUTEINFO { DWORD cbSize; //结构体大小。 ULONG fMask; //标识。 HWND hwnd; //窗口句柄。 LPCTSTR lpVerb; //执行的操作。 LPCTSTR lpFile; //执行的文件路径。 LPCTSTR lpParameters; //参数。 LPCTSTR lpDirectory; //目录。 int nShow; //显示方式。 HINSTANCE hInstApp; //返回的HINSTANCE。 LPVOID lpIDList; //特殊标识符。 LPCTSTR lpClass; //指明GUID或类别名。 HKEY hkeyClass; //获得已在系统注册的文件类型。 DWORD dwHotKey; //热键。 union { HANDLE hIcon; //图标。 HANDLE hMonitor; //显示器的HANDLE。 } DUMMYUNIONNAME; HANDLE hProcess; //新启动程序的句柄。 } SHELLEXECUTEINFO, *LPSHELLEXECUTEINFO;
1.20.8.2、CreateProcess是不拥有提权的功能的。只能做到权限的向下继承。
1.20.8.3、权限的继承:以管理员运行的程序 -> 这个程序CreateProcess另一个程序(这个程序会继承提升后的权限)。
如果是经过UAC过滤后执行的程序 -> 这个程序CreateProcess另一个程序(这个程序如果是需要提权的,此时会失败(返回ERROR_ELEVATION_REQUIRED)表明你的要求过高 了)
1.20.9、因为UAC会最大限度的来保证用户的安全,而且在你的程序中并不是那么的需要整个的管理员权限,所以通过UAC获得必要的权限,来提示用户才是正确的方式。
1.21、进程概念的总结:
1.21.1、用户:超级管理员(Admin)、管理员(大部分操作)、用户(基础的操作)但是用户可以以管理员的身份来运行程序(但这是很麻烦的)。
1.21.2、UAC:主要是用来解决管理员权限继承的问题,不让完全管理员权限继承。
1.21.3、进程在启动的时候100%是会产生两个对象的:进程的内核对象(是一个HANDLE,和hInstance是不一样的,hInstance是main专递进来的,本质是我们当前进程的基地 址)、HANDLE, INSTANCE。
1.21.4、在程序退出之后使用计数没有被清零的话,我们就说内核对象泄露了。
1.22、进程的遍历:
1.22.1、进程被放在操作系统的一个表中。