最近一直没有更新博客,因为一直在想一个问题,内核对象这一章内容很多很重要,自己没有掌握好也没有把握写好这一章,最后还是决定能写多少写多少,一面写一面学,后续学到新的再更新吧;
《windows核心编程》提了几种内核对象:
访问令牌对象:与windows的安全性有关,目前不是很懂,了解后再写;
事件对象: Event对象,可跨进程同步; 由CreateEvent创建;
文件对象: File对象,比较常见; 由CreateFile创建;
文件映射对象: 通过文件映射可以方便的操作文件(如同文件数据就在内存上一样);由CreateFileMapping创建,WinObj可以看出系统创建的是Session对象;
I/O完成端口对象: 简称IOCP,经常用于套接字编程中;
作业对象: 不是很了解;
邮件槽对象: 可用于进程间同步,但很少用。目前还没有发现有其他的特殊用途;
互斥量对象: 常用于的进程同步;
管道对象: 常用于进程的数据传输,并且进程可以在不同的计算机上;
进程对象: 操作系统 利用此对象进行进程的管理工作
线程对象: 操作系统利用此对象进行线程的管理工作;
信号量对象: 常用于线程同步的内核对象,功能强大;
可等待的计时器对象:不是很了解,应该可以使用精度很高的Timer;
线程池对象: 没用过;
几乎所有的内核对象内部都有安全描述符(描述哪些成员可以访问以及何种访问权限)和使用计数(当前对象被几个成员同时使用着,如果为0,代表已没有成员使用了,该内核对象就没有存在的意义了,会自动销毁)两个成员;
使用计数:
为什么会有使用计数?
内核对象是操作系统而不是单个程序拥有的,这样的话可能就会出现多个程序使用同一个内核对象,使用计数值就表示了该内核对象被引用了多少次,当该内核对象被再次打开时计数值+1,当被Close时计数值-1,当计数值减为0时,操作系统自动销毁该内核对象;
(当进程终止时,操作系统会逐个Close掉句柄表中的所有句柄)
安全描述符:
安全描述符提供了谁拥有对象,谁可以访问对象,以何种方式访问(DACL)以及何种审查访问类型(SACL)等信息。一个用户A创建了对象ObjectA(File,Pipe等),但用户A希望用户B仅仅可以读不可以写ObjectA,这时候可以通过安全描述符完成这一需求;
安全描述符的结构体如下:
typedef struct _SECURITY_DESCRIPTOR
{
UCHAR Revision; //版本控制信息;
UCHAR Sbz1; //版本控制信息;
SECURITY_DESCRIPTOR_CONTROL Control; //unsigned short 类型,控制位;
PSID Owner; // 对象的拥有者SID, 可以更改安全描述符,而不管其他人对访问的锁定。
PSID Group; // Windows 通常忽略此参数(这是为了 POSIX 兼容性,但它现在已经退化了)
PACLSacl; //指定要对对象执行的审核的类型。如果发生了审核事件,会被存储到审核事件的日志中。
PACL Dacl; //这儿保存着对象的许可(允许谁访问对象,而拒绝谁)。
}SECURITY_DESCRIPTOR;
其中Dacl指向的是ACL的结构体:
typedef struct _ACL
{
UCHAR AclRevision; //版本控制信息;
UCHAR Sbz1; //版本控制信息;
USHORT AclSize; //(Bytes) ACL + ACEs+ Free Buffer 见winnt.h 注释;
USHORT AceCount; // ACE 个数 见winnt.h注释;
USHORT Sbz2; //版本控制信息;
} ACL;
typedef ACL *PACL;
我们可以看到这里有AceCount,但是没有发现AceList这个保存Ace信息的字段,我猜测Ace字段也保存到了这个结构体中只不过没有进行命名不让用户程序去索引,实现手段类似于char acelist[0],这些仅仅是猜测,如果想知道的话,可以跟踪GetAce或者AddAce的汇编代码就知道了;
ACE(访问控制项)需要关注的三个字段是AceType,Mask和SID,AceType包括allow(允许)/deny(拒绝)/audit(审计)/ alarm(警告);Mask表示允许或者拒绝的行为组合;SID表示关联的SID;
关于Token:
看完《深入解析windows操作系统》后再加;
当打开一个对象时,会从线程所拥有的令牌中(如果存在模仿令牌则使用模仿令牌,否则使用主令牌)获取用户名和用户所在组列表,与对象的安全描述符中的DACL比较,这个时候会发生如下情况之一:
1. 对象的DACL==Null,则线程拥有完全的访问权限。
2. 对象的DACL不为Null,但是AceCount==0(ACE,访问控制项),则拒绝任何线程访问。
3. 遍历DACL,找到跟令牌中用户或组一致的Ace,如果该Ace指明没有拥有制定的访问权限,则直接退出安全检查函数,并拒绝该线程访问。
4. 遍历DACL,没找到跟令牌中用户或组一致的Ace,并拒绝该线程访问。
5. 遍历DACL,找到跟令牌中用户或组一致的Ace,如果该Ace指明拥有制定的访问权限,则直接退出安全检查函数,并允许该线程访问。
安全描述符以及其他安全性的文章可参考:
http://blog.csdn.net/hjxyshell/article/details/38502933
http://blog.csdn.net/hjxyshell/article/details/38503387
http://www.doc88.com/p-382740626684.html
《深入解析windows操作系统》第八章
内核对象是由操作系统创建、访问、修改、销毁的内存块并且只能由内核访问,用户程序不应该去猜测内存块的内容进行访问和更改,用户程序如果想操作内核对象的话,应该使用Windows提供的一组函数;当用户程序调用函数创建(由内核执行创建的操作)或者打开一个内核对象时,函数会返回一个句柄值(handle),这个句柄标识了该内核对象,将该句柄传给对应函数可对内核对象进行操作; 32位程序下sizeof(handle) = 4; 64为程序下 sizeof(handle) = 64;
Notice:
1. handle是与进程相关的,所以句柄值只能在同一进程内使用,不能把该值传给另一进程使用访问,如果需要多进程共同访问同一内核对象,后面会介绍多个方法
2. handle的真实含义是handle/4 = handle对应内核对象在该进程句柄表的索引值,也证明1。但是句柄的含义并没有在windows的文档中公开,所以将来也有可能发生变化
其他注意项:
1. 有些windows API要求传入一些访问信息(Read,Write,Exec,All Access等)的参数,建议根据需要传入正确的访问参数而不是一味的传入All Access,在XP之前的系统中程序可能会运行正常,但是从Vista后可能就会失败,因为对于一个不是管理员的标准用户来说,索取All Access会给与拒绝。传入正确的访问参数有利于程序在各windows系统中正常运行;
2. 判别内核对象(Event,Mutex,Thread,Process等)与用户对象(Icon,Brush等)的一个方法是创建对象时是否传入了安全描述符的参数,传入了则为内核对象,否则为用户对象(不过书中写的是几乎所有创建内核对象的函数都会传入安全描述符,发现特殊内核对象再修改吧);
3. 调用完创建内核对象的函数后,一直要注意对返回句柄值的判断。因为有的会返回NULL有的会返回-1(INVALID_HANDLE_VALUE),应详细查看MSDN;
关于CloseHandle
CloseHandle就是移除Handle所标示的句柄表中的记录项,并且使对应的内核对象计数减1,如果内核对象计数变为了0,那么内核对象销毁,从内存中释放;
下面一个例子来解释普遍对于CloseHandle的一个误解
HANDLE hThread =CreateThread(…);
CloseHandle(hThread);
调用CloseHandle后线程就马上停止了吗?显然不是的,CloseHandle仅仅是移除了句柄表中的记录项,表示我对这个线程已经不管了,让它自己跑吧,什么时候运行完返回值是多少我都不在乎。如果不CloseHandle,那么通过调用WaitForSingleObject和GetExitCode可以知道线程什么时候运行完以及返回值是多少,一旦调用过CloseHandle,Handle值就是无效的了,把此Handle值赋给任何一个函数结果都是未知的;