内核对象
回忆
我们了解到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;