进程

一、进程:

 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、进程被放在操作系统的一个表中。

posted @ 2017-07-02 18:59  _xiaohaige  阅读(190)  评论(0编辑  收藏  举报