内核对象

回忆

我们了解到CreateProcess()函数创建进程会有一个结构体LPPROCESS_INFORMATION lpProcessInformation,这个结构体会有进程和线程ID、句柄信息,那么什么是ID?什么是句柄?

内核对象

首先我们来了解一下内核对象,以后会经常与内核对象打交道,例如进程、线程、文件、互斥体、事件等等在
内核都有一个对应的结构体,这些结构体都由内核负责管理,所以我们都可以称之为内核对象(当我们创建一
个进程,在内核层(高2G)就会创建一个结构体EPROCESS...)。

记不住没关系,我们可以在MSDNLibrary中搜索CloseHandle****这个函数,它是用来关闭句柄的,暂时先不用管其原理,我们只要知道它所支持关闭就是内核对象。

什么是内核对象

像进程、线程、文件、事件等在内核都有一个对应的结构体,这些结构体由内核负责管理。我们管这样的对象叫做内核对象。

如何管理内核对象

当我们使用CreateProcess、CreateThread、CreateEvent、CreateFile创建时,会在内核层创建一个结构体,而我们如何管理这些结构体呢?或者说如何使用这些结构体呢?其实很好解决,我们可以通过内核结构地址来管理,但是这样做存在的问题:应用层很有可能操作不当导致修改内核结构体的地址,我们写应用层代码都知道访问一个不存在的内存地址就会报错,而如果访问到一个内核地址错误的,微软系统则直接会蓝屏,微软为了避免这种情况的发送,所以其不会将内核结构体的地址暴露给应用层,也就是说没法通过这种方式来直接管理。

访问内核地址错误会直接报成蓝屏

进程句柄表

每一个进程都有一个句柄表(防火墙:应用层和内核层隔离开来,避免应用层访问错误)


通过句柄就可以控制进程内核结构体,我们得到所谓句柄的值实际上就是句柄表里的一个索引
不是所有的内核对象都有句柄表,而是EPROCESS ,通过句柄值来对内核对象进行操作

多进程共享一个内核对象(OPENPROCESS )

如图所示,A进程通过CreateProcess函数创建了一个内核对象;B进程通过OpenProcess函数可以打开别人创建的一个进程,也就是可以操作其的内核对象;A进程想要操作内核对象就通过其对应的句柄表的句柄(索引)来操作;B进程操作这个内核对象也是通过它自己的句柄表的句柄(索引)来操作内核对象(需要注意的是:句柄表是一个私有的,句柄值就是进程自己句柄表的索引)

其中A的值为2 ,直到A的为0时就销毁了内核对象(除了线程)
在之前的例子中我们提到了CloseHandle这个函数是用来关闭进程、线程的,其实它的本质就是释放句柄,但是并不代表执行了这个函数,创建内核的对象就会彻底消失如上图所示内核对象存在一个计数器,目前是2,它的值根据调用A的次数来决定,它的值是根据调用A的次数来决定的,如果我们只是在A进程中执行了CloseHandle函数,内核对象并不会消失,因为B进程还在使用,而只用进程B也执行了CloseHandle函数,这个内核对象的计数器问问为0,就会关闭消失了。

如何关闭线程内核对象了



最后,注意,以上所述特性适合于除了线程以外的所有内核对象,创建进程同时也会创建线程,首先需要CloseHandle函数要让其计数器为0,其次需要有人将其关闭(只用线程关闭了,进程才会关闭如果想要真正的移除一个内核对象,你首先要终止这个线程然后关掉所有句柄的线程

#include "stdafx.h"
#include<windows.h>
/*
创建子进程
返回值:BOOL成功与失效
*/

BOOL CreateChildProcess(PTCHAR szChildProcssName, PTCHAR szCommandLine)
{      //启动信息
	STARTUPINFO si;
       //进程信息
	PROCESS_INFORMATION pi;

	ZeroMemory(&pi,sizeof(pi));
	ZeroMemory(&si,sizeof(si));
	if(!CreateProcess(
		szChildProcssName,//对象名称
		szCommandLine,//命令行
		NULL,//不继承进程句柄
		NULL,//不继承线程句柄
		FALSE,//不继承句柄
		0,//没有创建标志
		NULL,//使用父进程的环境变量
		NULL,//使用父进程目录作为当前目录,可以自己设置目录
		&si,//STARTUPINFOR结构体详细信息
		&pi//PROCESS_INFORMATION结构体进程信息
	))
	{
	printf("CreateChildProcess Error :%d \n",GetLastError());
	return FALSE;
	}
	//获取进程信息
	SuspendThread(pi.hThread);//挂起
	ResumeThread(pi.hThread);//释放

	//释放句柄
	CloseHandle(pi.hProcess); //将计数器的值减一 ,其中的参数为句柄的值,直到为0时关闭
	CloseHandle(pi.hThread);
	return TRUE;


}
int main(int argc, char* argv[])
{	
	TCHAR szApplicationName[] = TEXT("C:\\Program Files\\Internet Explorer\\iexplore.exe");
	CreateChildProcess(szApplicationName,NULL);

	return 0;
}

OpenProcess

如何判断是否为内核对象

带有这个参数的LPSECURITY_ATTRIBUTES lpThreadAttributes,都为内核对象

typedef struct _SECURITY_ATTRIBUTES {  
DWORD nLength;  //结构体长度
LPVOID lpSecurityDescriptor;//安全描述符号
BOOL bInheritHandle;//句柄是否被继承
} SECURITY_ATTRIBUTES,  *PSECURITY_ATTRIBUTES,  *LPSECURITY_ATTRIBUTES;

结构体体成员分别是1.结构体长度、2.安全描述符、3.句柄是否被继承
第一个成员我们见不怪了在windows设计都会有这样一个成员;
第二个安全描述符,这个对我们来说实际上没有任何意义,一般留空就行,默认它会遵循父进程的来,其主要作用就是描述谁创建了该对象,谁有访问权限、使用该对象的权限
第三个成员是我们重点需要关注的,因为其决定了句柄是否可以被继承,如下图所示,我们让CreateProcess函数创建的进程、线程句柄可以被继承。

句柄是否"可以"被继承


CreateEvent(NULL,FALSE,FALSE);//第一个参数是决定是否可以被继承,不希望继承时直接填空,希望继承要创建这个结构体
结构体如下

	SECURITY_ATTRIBUTES  sa;
	ZeroMemory(&sa,sizeof(SECURITY_ATTRIBUTES));
	sa.nLength = sizeof(SECURITY_ATTRIBUTES);
	sa.bInheritHandle = TURE;

句柄是否"允许"被继承(CreateProcess的第五个参数)

posted @ 2022-04-04 18:00  不会笑的孩子  阅读(118)  评论(0编辑  收藏  举报