第三章:内核对象

来源:互联网 发布:微信淘宝助手破解版 编辑:程序博客网 时间:2024/05/18 01:04
 

1. 每个内核对象都只是一个内存块,它由操作系统内核分配,并只能由操作系统内核访问.这个内存块是一个数据结构,其成员维护着与对象相关的信息.少数成员(安全描述符和使用计数等)是所有对象都有的,但其他大多数成员都是不同类型的对象特有的.
  内核对象的所有者是操作系统内核,而非进程.换言之,如果我们的进程调用一个函数来创建一个内核对象,然后进程终止运行,则内核对象并不一定销毁.
2. 内核对象可以用一个安全描述符来保护.安全描述符描述了谁(通常是对象的创建者)拥有对象;哪些组和用户被允许访问或者使用此对象;哪些组和用户被拒绝访问此对象.安全描述符通常在编写服务器应用程序的时候使用.
    安全描述符的结构如下:
    typedef struct _SECURITY_ATTRIBUTES{
           DWORD    nLength; //结构体的所占字节大小

LPVOID lpSecurityDescriptor;//指向对象控制共享的安全描述符

BOOL bInheritHandle;//当一个进程被创建后返回指定的被继承句柄,如果值为TRUE,则这个新的进程继承这个句柄.

}SECURITY_ATTRIBUTES;

用简单的方式查看创建的对象是用户对象还是内核对象,只需查看创建这个对象的函数(几乎所有创建内核对象的函数都有一个允许我们指定安全属性的参数).

3.一个进程首次初始化的时候,其句柄表为空.当进程内的一个线程调用一个会创建内核对象的函数(比如CreateFileMapping),内核将为这个对象分配并初始化一个内存块.然后内核扫描进程的句柄表,查找一个空白的记录项.并对其初始化(说白了就是指针成员会被设置成内核对象的数据结构的内部内存地址,访问掩码将被设置成拥有完全访问权限,标志也会设置.

4. 系统用索引来表示内核对象的信息,保存在进程句柄表中的具体位置,要得到实际索引值,句柄值实际应该除以4.如果返回句柄无效,GetLastError就会返回(ERROR_INVALID_HANDLE)

  利用函数来创建一个内核对象时,如果调用失败,那么返回句柄值” 通常”为0.(CreateFile例外,它返回INVALID_HANDLE_VALUE).
  无论我们以什么方式创建内核对象,,我们都要调用CloseHandle向系统表明我们已经结束使用对象.如下:    BOOL  CloseHandle( HANDLE  hobject);

需注意的是:如果传给CloseHandle函数是一个无效的句柄,那么可能发生一下两种情况之一:
        1). 进程是正常运行的,CloseHandle将返回FALSE,GetLastError返回ERROR_INVALID_HANDLE,
        2).如果进程正在被调试,那么系统将抛出0xC0000008异常(“指定了无效句柄),便于我们调试.
   通常我们在创建一个内核对象时,我们会将相应的句柄保存到一个变量中.将这个变量作为参数调用CloseHandle函数后,还应该同时将这个变量设置为NULL.(这个有点类似于指针的使用,因为句柄实际上也是一个指针).,如果不设置为空,它将带来指针所包含的错误.

5. Windows支持的是”对象句柄的继承”;换言之,只有句柄才是可以继承的,对象本身是无法继承的.
  为了创建一个可继承的句柄,
    ① 父进程必须分配并初始化一个SECURITY_ATTRIBUTES结构(bInheritHandle的值为TRUE),并将这个结构的地址传给具体的Create函数(如果默认传递NULL则表明它是不可继承的). 

② 有父进程生成子进程(通过CreateProcess来完成)
BOOL CreateProcess( LPCTSTR lpApplicationName,//应用程序的名称, 

LPSTR lpCommandLine,//要执行的命令行

LPSECURITY_ATTRIBUTES lpProcessAttributes,//定义了进程的安全特性

LPSECURITY_ATTRIBUTES lpThreadAttributes,//定义了进程之主线程的安全特性

BOOL bInheritHandles,//是否允许当前进程中的所有句柄都被新建的子进程继承

DWORD dwCreationFlags,//创建标志

LPVOID lpEnvironment,//指向一个环境快的指针

LPCWSTR lpCurrentDirectory,//新进程的当前目录路径

LPSTARTUPINFOW lpStartupInfo,//创建进程时的附加信息

LPPROCESS_INFORMATION lpProcessInformation );//用于容纳新进程的进程和线程标识符

参数: lpApplicationName:可设为NULL.此时应用程序的名称应该在lpCommandLine参数中出现
lpCommandLine:Windows查找文件的次序:
1)包含了父进程执行文件的目录
   (2)父进程当前的目录
   (3)由GetSystemDirectory返回的系统目录
   (4)仅适于windows NT16位系统目录
   (5)由GetWindowDirectory返回的Windows目录
   (6)由PATH环境变量指定的目录

lpProcessAttributes:0表示不允许继承的默认描述符
lpThreadAttributes:同上

bInheritHandles:Ture表示允许

dwCreationFlags:

CREATE_SEPARATE_WOW_VDM(仅适用于NT)

启动一个16位的Windows应用程序时,强迫它在自己的内存空间运行

CREATE_SHARED_WOW_VDM(仅适用于NT)

启动一个16位的Windows应用程序时,强迫它在共享的16位虚拟机(VM)内运行

CREATE_SUSPENDED

立即挂起新进程。除非调用了ResumeThread函数函数,否则它不会恢复运行

也可能是下述常数之一,用于指定优先级

IDLE_PRIORITY_CLASS

新进程应该有非常低的优先级——只有在系统空闲的时候才能运行。基本值是4

HIGH_PRIORITY_CLASS

新进程有非常高的优先级,它优先于大多数应用程序。基本值是13。注意尽量避免采用这个优先级

NORMAL_PRIORITY_CLASS

标准优先级。如进程位于前台,则基本值是9;如在后台,则优先值是7

说明:bInheritHandleFALSE表明我们不希望子进程继承父进程句柄表中的”可继承的句柄”.如果为TRUE,子进程就会继承父进程的”可继承的句柄” 值.传递TRUE时,操作系统会创建新的子进程,但不允许子进程立即执行它的代码.当然系统会为子进程创建一个新的、空白的进程句柄表---就像他为任意一个新的进程那样做.但是如果它的值为TRUE,此时系统还会多做一件事:它会遍历父进程的句柄表,对它的每一个记录项进行检查.凡是包含一个有效的”可继承的”项都会被完整的复制到子进程的句柄表.在子进程的句柄表中,复制项的位置与它在父进程句柄表中位置完全一样的.这也就意味着:在父进程和子进程中,对一个内核对象进行标识的句柄值完全是一样的.
  对象句柄的继承只会在生成子进程的时候发生.假如父进程后来又创建了新的内核对象,并同样将它们的句柄设置为可继承的句柄.那么正在运行的子进程是不会继承这些新的句柄的.
  对象句柄还有一个非常奇怪的特性:子进程并不知道自己继承了任何句柄.在子进程的文档中,应该指出当它从另一个进程生成时,希望获得对一个内核对象的访问权---只有在这种情况下,内核对象的句柄继承才是有用的.

6. SeHandleInformation改变内核对象句柄的继承标志:

BOOL SetHandleInformation( HANDLE  hObject,//标识了一个有效句柄

   DWORD   dwMask,//想更改的哪个或者哪些标致.如果想把那个标志一次性更改完毕,可以对这个标志执行一次按位或运算.

DWORD  dwFalgs)//希望把标志位设置成什么.
:HANDLE_FLAG_PROTECT_FROM_CLOSE标志告诉系统不允许关闭句柄.
如果想要得到一个句柄是否可以继承.可以调用:   

BOOL  GetHandleInformation(HANDLE hObject,

PDWORD  pdwFlags);

该函数将会在pdwFlags指向的DWORD中返回指定句柄的当前标志.

7.命名对象:Microsoft没有提供任何专门的机制来保证为内核对象指定的名称是唯一的.

Create*对应的函数Open*的执行过程:首先在同一个内核对象命名空间搜索,以查找一个匹配的对象.如果没有找到这个名称的一个内核对象,函数将返回NULL,GetLastError将返回2.如果名称相同但是类型不同,函数将返回NULL, GetLastError将返回6.如果名称类型都相同系统会检查请求的访问(通过dwDesiredAccess来指定)是否允许,就会更新主调进程的句柄表,并使用对象的使用计数递增.
  调用Create*函数和调用Open*函数的主要区别在于,如果对象不存在,Create*函数会创建它,Open*函数则不同,如果对象不存在,它只是简单地以调用失败告终.(这个机制和Windows的许多函数类似,例如注册表创建和打开函数.)然而为了防止不同的来源程序的命名内核对象名称不一致采用的是GUID,这其实和COM非常类似.

8.一个服务的命名内核对象始终位于全局命名空间内的.默认情况下,在终端服务中,应用程序自己的命名内核对象在会话的命名空间内.不过我们也可以强制把一个命名对象放入全局命名空间,具体的做法是在名称前面加上”Global\”前缀(当前会话可以是”Local\”)

原创粉丝点击