第3章 内核对象 -- Windows核心编程

第3章 内核对象 -- Windows核心编程

 

1、何为内核对象
      每个内核对象都只是一个内存块,它由操作系统内核分配,并只能由操作系统内核访问。这个内存块是一个数据结构,其成员维护着于对象相关的信息。少数成员(安全描述符何使用计数等)是所有对象都有的,但其他大多数成员都是不同类型的对象特有的。
      e.g. 访问令牌(access token)对象、事件对象、文件对象、文件映射对象、I/O完成端口对象、作业对象、邮件槽(mailsolt)对象、互斥量(mutex)对象、管道(pipe)对象等
      *可利用Sysintenals的WinObj(http://technet.microsoft.com/zh-cn/sysinternals/bb896657.aspx)工具查看内核对象类型的列表。
1.1、使用计数
      使用计数事所有内核对象类型都有的一个数据成员。初次创建对象事,其使用计数被设为1。如果另一进程获得对现有内核对象的访问后,使用计数会递增。进程终止运行后,操作系统内核将自动递减进程仍然打开的所有内核对象的使用计数。一旦对象的使用计数变为0,操作系统内核就会销毁该对象。
1.2、内核对象的安全性
      内核对象可以用一个安全描述(security descriptor, SD)符来保护。它描述了谁拥有对象;哪些组和用户被允许访问或使用此对象;哪些组何用户被拒绝访问此对象。
      用于创建内核对象的所有函数几乎都有指向一个SECURITY_ATTRIBUTES结构的指针
                      typedef struct _SECURITY_ATTRIBUTES { 
                          DWORD  nLength;                         //Specifies the size, in bytes, of this structure,一般为sizeof(此结构变量)
                          LPVOID nLength;                         //Specifies the size, in bytes, of this structure,一般为sizeof(此结构变量)
                          LPVOID nLength;                         //Specifies the size, in bytes, of this structure,一般为sizeof(此结构变量)
                          LPVOID nLength;                         //Specifies the size, in bytes, of this structure,一般为sizeof(此结构变量)
                          LPVOID nLength;                         //Specifies the size, in bytes, of this structure,一般为sizeof(此结构变量)
                          LPVOID nLength;                         //Specifies the size, in bytes, of this structure,一般为sizeof(此结构变量)
                          LPVOID nLength;                         //Specifies the size, in bytes, of this structure,一般为sizeof(此结构变量)
                          LPVOID nLength;                         //Specifies the size, in bytes, of this structure,一般为sizeof(此结构变量)
                          LPVOID lpSecurityDescriptor;       //和安全性有关的成员
                          BOOL   lpSecurityDescriptor;       //和安全性有关的成员
                          BOOL   lpSecurityDescriptor;       //和安全性有关的成员
                          BOOL   lpSecurityDescriptor;       //和安全性有关的成员
                          BOOL   lpSecurityDescriptor;       //和安全性有关的成员
                          BOOL   lpSecurityDescriptor;       //和安全性有关的成员
                          BOOL   lpSecurityDescriptor;       //和安全性有关的成员
                          BOOL   bInheritHandle
                     } SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES;    

2、进程内核对象句柄表
         进程的句柄表结构

索引

指向内核对象内存块的指针

访问掩码(包含标志位的一个DWORD

标志

1

0x?????????

0x?????????

0x?????????

2

0x?????????

0x?????????

0x?????????

2.1、创建一个内核对象
      用于创建内核对象的任何函数都会返回一个与进程相关的句柄,这个句柄可由同一个进程中运行的所有线程使用。由于句柄值实际事作为进程句柄表的索引来使用的,索引这些句柄是与当前这个进程相关的,无法供其他进程使用。
       调用函数来创建一个内核对象时,如果调用失败,多数返回的句柄值是0(NULL),但有几个函数会返回-1(也就是在Winbase.h中定义的INVALID_HANDLE_VALUE)。检查它们的返回值是,务必相当仔细。
2.2、关闭内核对象
   无论以什么方式创建内核对象,我们都要调用CloseHandle向系统表明我们已经结束使用对象。
         BOOL CloseHandle(
                 HANDLE
hObject   // handle to object
          );            
   在内部,该函数首先检查主调进程的句柄表,验证“传给函数的句柄值”标识的是“进程确实有权访问的一个对象”。
   1)如果句柄是有效的,系统就将获得内核对象的数据结构的地址,并将结构中的“使用计数”成员递减,如果使用计数变成0,内核对象将被销毁,并从内存中去除。
   2)如果句柄是无效的,
         如果进程是正常运行的,CloseHandle将返回FALSE,而GetLastError返回ERROR_INVALID_HANDLE。
         如果进程正在被调试,那么系统将抛出0xC0000008异常(“指定了无效的句柄”)。
hObject   // handle to object
          );            
   在内部,该函数首先检查主调进程的句柄表,验证“传给函数的句柄值”标识的是“进程确实有权访问的一个对象”。
   1)如果句柄是有效的,系统就将获得内核对象的数据结构的地址,并将结构中的“使用计数”成员递减,如果使用计数变成0,内核对象将被销毁,并从内存中去除。
   2)如果句柄是无效的,
         如果进程是正常运行的,CloseHandle将返回FALSE,而GetLastError返回ERROR_INVALID_HANDLE。
         如果进程正在被调试,那么系统将抛出0xC0000008异常(“指定了无效的句柄”)。
   *检测内核对象泄露
       用Windows任务管理器,选择(查看)->(选择列),然后选择显示(句柄数)。便可在进程出监视任何一个应用程序的内核对象数了。
        使用Sysinternals提供的Process Explorer工具(http://technet.microsoft.com/zh-cn/sysinternals/bb896653.aspx),选择(View)->(Select Columns),选择(Handle),选中所有列标题。 在顶部选择想要检查的进程,按F5来获得一份最新的内核对象列表。 然后启动应用程序并开始指向一个待查的工作流。完成之后,再次按F5。在此期间生成的每个内核对象都显示为绿色

3、跨进程边界共享内核对象
      内核对象的句柄是与每一个进程相关的,是为确保健壮性(可靠性)和安全性。
3.1、使用对象句柄继承
   只有在进程之间有一个父-子关系的时候,才可以使用对象句柄继承。   
   首先,父进程必须向系统指出它希望这个对象的句柄是可继承的。
            e.g.

    SECURITY_ATTRIBUTES sa;
    sa.nLength 
=  siezof(sa);
    sa.lpSecurityDescriptor 
=  NULL;
    sa.bInheritHandle 
=  TRUE;         // Make the returned handle inheritable

    HANDLE hMutex 
=  CreateMutex( & sa, FALSE, NULL);
    然后,由父进程生成子进程,通过 CreateProcess函数完成    
BOOL CreateProcess(
  LPCTSTR lpApplicationName,
  LPTSTR lpCommandLine,
  LPSECURITY_ATTRIBUTES lpProcessAttributes,
  LPSECURITY_ATTRIBUTES lpThreadAttributes,
  BOOL bInheritHandles,            
// 通常情况下设为FALSE(表明我们不希望子进程继承父进程句柄表中的“可继承句柄”)
                                                      
// 如果传递TRUE,子进程就会继承父进程的“可继承句柄”的值。
                                                                
// (系统会遍历符进程的句柄表,对它的每一个记录项进行检查,凡是包含一个
                                                               
// 有效的“可继承句柄”的项,都会被完整地复制到子进程的句柄表)
  DWORD dwCreationFlags,
  LPVOID lpEnvironment,
  LPCTSTR lpCurrentDirectory,
  LPSTARTUPINFO lpStartupInfo,
  LPPROCESS_INFORMATION lpProcessInformation
);
        除了复制句柄表的记录项,系统还会递增内核对象的使用计数,引擎两个进程现在都在使用这个对象。
3.2改变句柄的标志
     1)父进程想控制哪些子进程能继承内核对象句柄。可以调用 SetHandleInformation函数来改变内核对象句柄的继承标志。
BOOL SetHandleInformation(
  HANDLE hObject,        
// 有效句柄标志
  DWORD dwMask,             // 想要更改那个或哪些标志
  DWORD dwFlags             // 希望把标志设为什么
);
   每个句柄都关联了两个标志:
#define  HANDLE_FLAG_INHERIT                                       0x00000001
#define  HANDLE_FLAG_PROTECT_FROM_CLOSE        0x00000002
   如果想打开一个内核对象句柄的继承标志
SetHandleInformation(hObj, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
   如果想关闭这个标志
SetHandleInformation(hObj, HANDLE_FLAG_INHERIT,  0 );
   如果告诉系统不允许关闭句柄
SetHandleInformation(hObj,,HANDLE_FLAG_PROTECT_FROM_CLOSE, HANDLE_FLAG_PROTECT_FROM_CLOSE);
CloseHandle(hObj);    
// 会引发异常
   2)使用 GetHandleInformation函数返回句柄标志
BOOL GetHandleInformation(
  HANDLE hObject,
  LPDWORD lpdwFlags
);
   e.g.检查句柄是否可继承
DWORD dwFlags;
GetHandleInformation(hObj, 
& dwFlags);
BOOL fHandleIsInheritable 
=  ( 0   !=  (dwFlags  &  HANDLE_FLAG_INHERIT));
3.3为对象命名
HANDLE CreateMutex(
  LPSECURITY_ATTRIBUTES lpMutexAttributes,
  BOOL bInitialOwner,
  LPCTSTR lpName
);

HANDLE CreateEvent(
  LPSECURITY_ATTRIBUTES lpEventAttributes,
  BOOL bManualReset,
  BOOL bInitialState,
  LPCTSTR lpName
);

HANDLE CreateEvent(
  LPSECURITY_ATTRIBUTES lpEventAttributes,
  BOOL bManualReset,
  BOOL bInitialState,
  LPCTSTR lpName
);
   所有这些函数的最后一个参数都是pszName。传入NULL,相当于向系统表明我们要创建一个未命名的(即匿名)内核对象。
   要根据对象名称来共享一个对象,我们必须为此对象指定一个名称。传入一个“以0为终止的名称字符串”的地址。
// 进程A
HANDLE hMutexProcessA  =  CreateMutex(NULL, FALSE, TEXT( " JeffMutex " ));

// 进程B
HANDLE hMutexProcessB  =  CreateMutex(NULL, FALSE, TEXT( " JeffMutex " ));
      当进程B调用CreateMutex时,系统会查看是否存在一个名为 "JeffMutex "("JeffMutex "的对象是一个互斥量对象)的内核对象。如果存在,接着检查对象的类型,和调用者是否拥有该对象的完全访问权限。 如果答案是肯定的,系统就会在进程B的句柄表中查找一个空白记录项,并将其初始化为指向现有的内核对象;否则,返回NULL(失败)。
       也可以调用Open*函数:

你可能感兴趣的:(第3章 内核对象 -- Windows核心编程)