5、进程

进程是指运行中的一个程序。(X)

①什么是进程? 进程提供程序所需的资源,如:数据、代码等等。

程序运行起的状态与进程没有关系,举个例子,程序就相当于一个房子,进程就是其中的东西,而使用这些房子中的东西的人称为线程。

②进程内存空间的地址划分

分区 x86 32位Windows 空指针赋值区 0x00000000 -0x0000FFFF //操作系统都没有使用的空间 用户模式区 0x00010000-0x7FFEFFFF 64KB禁入区 0x7FFF0000-0x7FFFFFFF 内核 0x80000000-0xFFFFFFFF//实际上每个进程的内核都是相同 真正能够操作的空间为 2G - 128kb

③进程的创建: <1>任何进程都是别的进程创建的:CreateProcess() <2> 进程的创建过程 1、映射EXE文件 2、创建内核对象EPROCESS 3、映射系统DLL(ntdll.dll) 4、创建线程内核对象ETHREAD 5、系统启动线程(一个进程最少都有一个线程,用于执行程序) 映射DLL(ntdll.LdrlnitializeThunk) 线程开始执行

6、创建进程

进程的创建:

BOOL CreateProcess(
LPCTSTR IpApplicationName, // name of executable module
LPTSTR IpCommandLine, // command line string
LPSECURITY_ATTRIBUTES IpProcessAttributes, // SD
LPSECURITY_ATTRIBUTES IpThreadAttributes, //SD
BOOL blnheritHandles, // handle inheritance option
DWORD dwCreationFlags, // creation flags
LPVOID IpEnvironment, // new environment block
LPCTSTR IpCurrentDirectory, // current directory name
LPSTARTUPINFO IpStartupInfo, // startup information
LPPROCESS_INFORMATION IpProcessInformation // process information
);

//创建子进程 返回成功与失败

if (!CreateProcess(
szChildProcssName, //对象名称
szCommandLine, //命令行
NULL, //不继承进程句柄
NULL, //不继承线程句柄
FALSE, //不继承句柄
0, //没有创建标志
NULL, //使用父进程环境变量
NULL, //使用父进程目录作为当前目录,可以自己设置目录
&si, //STARTUPINFOw结构体详细信息
&pi) //PROCESS_INFORMATION结构体进程信息
)

szChildProcssName 实际上是完整的路径加上 exe 的名字,

 

&si即为 指向STARTUPINFO结构的指针,该结构指定新进程的主窗口应如何显示。

&pi相当于返回值,返回一个进程和一个线程。(其中结构体的成员为 进程ID、进程句柄和线程ID、线程句柄)

7、句柄表

① 什么是内核对象? 像进程、线程、文件、互斥体、事件等在内核都有一个对应的结构体,这些结构体由内核负责管理。我们管这样的对象叫做内核对象。 image-20230122103842074

②如何管理内核对象

image-20230122105704715

如果我们想要操作内核对象,操作系统是无法直接将内核对象的地址暴露给用户层或者应用层的,避免出现内存访问错误。例如进程,就会提供一个句柄表,表中的数据指向内核层中的内核对象。进而起到一个防火墙的作用,不会直接的暴露地址数据。而句柄的重要性主要在于,如果没有句柄就无法进行操作内核对象。每生成一个新的内核对象,句柄表中就会多一个索引。

④多进程共享一个内核对象

image-20230122110336845

存在多个进程使用同一个内核对象,而这样会在各自的句柄表中生成新的一个索引,但具体的索引值可能不尽相同。(句柄表是一个私有的概念,是私有的一张表,每一张表只对当前的进程有意义)

内核对象被一个进程使用,就会有一个句柄索引,当多个进程使用内核对象,当句柄关闭,某个进程不再使用内核对象,内核对象的进程就会减少,如果还有进程使用,就不会消亡,直到没有一个进程使用,内核对象就会消亡。(所谓的一个计数器)

而线程这个结构体不同,如果想要将线程这个结构体关闭,必须要先关闭线程(即代码执行完毕),再将内核对象的计数器置为零,两个缺一不可。(利用句柄来操作内核对象 CloseHandle,进行关闭打开的一系列操作)

⑤句柄是否“可以”被继承

image-20230122111748876

其实可以具物化的想象这个句柄表实际上是三列,一列是索引值,一列是内核对象的地址,一列是句柄表示是否可以被继承的数值。

举个例子:例如 CreateEvent 这个进程

HANDLE CreateEvent(
LPSECURITY ATTRIBUTES lpEventAttributes, // SD
BOOL hManualReset, // reset type
BOOL bInitialstate, //initial state
LPCTSTR IpName //object name
);

主要是第一个参数用于判断是否继承,如果不继承就设置为 NULL , 如果是需要继承就需要创建一个结构体对象。而第一个参数是一个指向另一个结构体的指针。

typedef struct _SECURITY_ATTRIBUTES {  
DWORD nLength;  
LPVOID lpSecurityDescriptor;  
BOOL bInheritHandle;
} SECURITY_ATTRIBUTES,  *PSECURITY_ATTRIBUTES,  *LPSECURITY_ATTRIBUTES;

我们想要设置句柄表能够继承的话,就需要创建一个对象。并设置初始化。

SECURITY_ATTRIBUTES sa;

ZeroMemory(&sa,sizeof(SECURITY_ATTRIBUTES));
sa.nlength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE;

CreateEvent(sa,FALSE,FALSE);//所以如果想继承的话,就将这个对象传进第一个参数。

这样的话,子进程就能继承父进程的句柄表。虽然不是完成得到,因为可能只有部分设置了可继承。但是如果子进程结构体中的成员 (不继承句柄),就不论父进程中的句柄表是否可以被继承,都不影响,子进程的句柄表。

 

⑥句柄是否“允许”被继承

image-20230122200225211

于是我们就可以得到两种共享内核的解决方式,第一种就是OPEN函数来得到(同一个内核对象),第二种就是通过继承的方式。(实际上多数线程和进程都属于是内核对象,多数不继承父进程的句柄表,而当作为父进程的时候,多数将句柄表继承下去。)

8、进程相关API

① ID 与句柄

image-20230122202801420

我们已经知道一个概念就是,如果我们利用 CreateProcess 函数创建了一个进程,将会给我们返回了四个数据。分别是进程编号,进程句柄,线程编号,线程句柄。句柄的一个概念是:当前进程私有的一个表中的索引(即为句柄表)。句柄都属于私有的,对于别的进程没有意义。

进程 ID 我们通常称为 PID (Process ID),句柄表这个表存储了这个进程打开了或者创建了的所有的内核对象。操作系统也有对应的一张表,称为全局句柄表,整个操作系统只有一份。其中包含了所有正在运行的进程和线程。和进程拥有的句柄表是相同的,也是包含一个索引和一个具体的地址。

进程句柄(hProcess)指的是,当前进程的句柄表所包含的索引,进程 ID(Process ID) 指的就是当前进程对应在全局句柄表中的索引。二者的区别就是进程句柄只对当前进程有意义,进程 ID 对于其他的进程也有意义。(相当于全局变量和局部变量的关系)

PID 是唯一的,一个进程(线程)对应的是一个 ID。但是有旧进程的消亡和新进程的增加,进程 ID 的特性就是唯一但并非不变。(唯一但非永远不变)

②以挂起的形式创建进程

BOOL CreateProcess(
LPCTSTR IpApplicationName, // name of executable module
LPTSTR IpCommandLine, // command line string
LPSECURITY_ATTRIBUTES IpProcessAttributes, //SD
LPSECURITY_ATTRIBUTES IpThreadAttributes, //SD
BOOL blnheritHandles, // handle inheritance option
DWORD dwCreationFlags, // creation flags
LPVOID IpEnvironment, // new environment block
LPCTSTR IpCurrentDirectory, // current directory name
LPSTARTUPINFO IpStartupInfo,// startup information
LPPROCESS_INFORMATION IpProcessInformation // process information
I

dwCreationFlags 这个成员也有许多的值,而我们最关键的一个就是 CREATE_SUSPENDED ,即为以挂起的形式创建一个进程。

创建进程的过程分为:先有一个 exe,映射 exe文件,创建内核对象(EPROCESS),映射系统DLL(htdll.dll),创建线程内核对象 ETHREAD,系统启动线程(映射DLL,线程开始执行)。

如果我们以挂起的形式创建线程的话,进程的创建过程就会发生变化:

<1>任何进程都是别的进程创建的:CreateProcess() <2> 进程的创建过程 1、映射EXE文件 2、创建内核对象EPROCESS 3、映射系统DLL(ntdll.dll) 4、创建线程内核对象ETHREAD 5、系统启动线程(一个进程最少都有一个线程,用于执行程序)//如果是以挂起的系统就不会启动线程,会在恢复之后再继续执行 映射DLL(ntdll.LdrlnitializeThunk) 线程开始执行

所以我们将进程挂起的话,我们就可以在启动线程之前(即为进程启动之前),实现我们想要实现的操作。然后我们再将线程恢复,利用 ResumeThread函数即可。

③模块目录与工作目录

char strModule[256];
GetModuleFileName(NULL,strModule, 256); //模块目录

char strWork[1000];
int i=1000;
GetCurrentDirectory(1000,buf); //工作目录

printf("模块目录: %sIn工作目录: %s In",strModule,strWork);

IpCurrentDirectory 这个成员,称为当前进程的工作目录。 GetModuleFileName这个 API 即指得到模块路径,你的exe在什么位置,得到的路径就是什么位置。其实较为固定的。 GetCurrentDirectory这个API 即为得到工作路径,这个路径是可以被修改的。因为这个工作路径是创建这个进程的人决定的。我们可以在 CreateProcess 中的进程目录进行自我修改,如果设置为NULL ,会自动的使用父进程目录作为当前目录。(可以理解为相对路径和绝对路径)

④其他进程相关 API:

获取进程 PID: GetCurrentProcessId 获取进程句柄: GetCurrentProcess 获取命令行: GetCommandLine 获取启动信息: GetStartupInfo 遍历进程 ID: EnumProcesses 快照 : CreateToolhelp32Snapshot(当调用这个函数,可以将当前系统中的重要信息保存下来,就可以分析当前模块、进程和线程)

内容来源于网络如有侵权请私信删除
你还没有登录,请先登录注册
  • 还没有人评论,欢迎说说您的想法!