Windows 内核对象
内核对象概述
- Windows操作系统分为Ring3和Ring0层
- Ring3层是全开放的, 无监管的
Ring0层是有监管的
- 是
操作系统及软件所运行的地方 - 在软件运行的时候, 我们真是的操作都是在Ring0发生的
- Ring3层可使用Ring0层的内核对象
- 通过
WinAPI向Ring0层发送请求 - 当Ring0层检测到这是一个合理的请求时
- 内核对象会被Ring0层进行改变
- 所以API调用成功了, Ring3层成功操作了Ring0层的内核对象
- 它的本质, 就是
向Ring0层发送请求
- 是
通过
句柄可以操作Ring0层指定的内核对象- 但是句柄不代表Ring0层的某个具体的内核对象
- 句柄的值是不透明的, 无人知道它代表了什么意思
句柄是属于当前进程的
可以通过WinObj进行观察
使用计数
- 每一个内核对象中, 都会有一个
使用计数 - 内核对象是属于操作系统的
- 进程可以使用任意一个内核对象
- 但是进程
不能决定任何一个内核对象的生死 内核对象的生死却是由进程来决定
- 当内核对象没有意义的时候, 操作系统会清理内核对象以节约资源
- 但是Ring0和Ring3又无法交互
- 所以
使用计数出现了 - 使用计数会在
使用的时候+1, 在不使用的时候-1 - 当
使用计数为0时, 操作系统会对内核对象进行回收 - 回收不是
实时的, 是当系统空闲的时候, 优先来清理的
- 当A进程Create一个内核对象后, 这个内核对象就和A进程没有必然的关系了
B进程也可以使用这个内核对象
- 比如, A和B两个进程都通过CreateFile打开同一个文件设备
- 那么这个文件设备的内核对象是被
连接成一个的 - 当A进程销毁后, B进程还在, 那么这时候销毁内核对象是不合理的
- 所以通过
使用计数来决定是否销毁内核对象
- 使用计数使得内核对象可以被多个进程所拥有, 并且能正确的释放
安全性设置
- 每个内核对象也会有一个
安全性设置 - 通常我们将之设置为NULL
但是它也可以被设置成其他几种的值
- 它可以决定哪些用户可以拥有这个内核对象的权限
可以设置Create内核对象后的安全性, 决定何时能够进行什么样的操作
- 另外的进程在想使用内核对象的时候, 就必须按照我规定的安全性来进行
- 比如CreateFile的时候设置的共享读写, 独占读写等等
- 可以决定内核对象在那些情况下是安全的
句柄表以及句柄的本质
我们就用文件设备的内核句柄来看
#include <Windows.h>
int main()
{
HANDLE hFileOne = CreateFile(TEXT("one.txt),
GENERIC_READ,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
HANDLE hFileTwo = CreateFile(TEXT("two.txt),
GENERIC_READ,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
CloseHandle(hFileOne);
CloseHandle(hFileTwo);
return 0;
}通过调试我们发现, hFileOne == 0xBC, hFileTwo == 0xC0
注: 每次观察都会不一样, 请自主实际操作
我们突然发现, 这两个句柄的值是连续的, hFileOne + 0x4 == hFileTwo
句柄就是一个数值, 它是如何代表内核对象的?
- 我们都知道, 进程也是内核对象
- 进程也实际存在于Ring0层, Ring3层只是一个"镜像"
- 进程内核对象中, 拥有一个
句柄表 它是一个结构体, 它包括
- 索引
- 指向内核对象的指针, 这是一个指向真实内核对象的指针
- 标识
- 索引就是给我们使用的
句柄
- 当我们通过API来CreateFile的时候, 会有一个File的内核对象
- 这个内核对象的地址会被赋予进程内核对象的句柄表中
然后
生成索引- 索引从0开始, 以4递增
- 所以句柄是属于进程的
- 句柄是不能跨进程的, 但是内核对象是可以跨进程的
- 每个进程都需要重新通过Create或者Open等方式来重新获得内核对象
- 所以, 句柄不是唯一的
- 句柄只在当前进程中有效
深入理解句柄表和索引
我们在来看进程启动的过程
- 进程启动后, 会有自己的空间
- 同时, 在进程内核对象中也会开辟一片空间, 存放句柄表
然后启动我们的 主线程
- 在CreateThread的时候, 就会去进程句柄表中找空白的地方
- 在空白区域, 将主线程内核对象的地址放入句柄表, 并生成对应的索引
- 所有的索引都一直存在, 只是找到一个空白索引, 将主线程内核对象地址放入当前所有所代表的空间中
我们将上边的例子在拿过来:
#include <Windows.h>
int main()
{
HANDLE hFileOne = CreateFile(TEXT("one.txt),
GENERIC_READ,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
CloseHandle(hFileOne);
HANDLE hFileTwo = CreateFile(TEXT("two.txt),
GENERIC_READ,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
CloseHandle(hFileTwo);
return 0;
}注意, 现在是 将hFileOne关闭后, 在创建hFileTwo
那么, 我们可以看到, hFileOne == 0xB4, hFileTwo == 0xB4
句柄表的使用, 就是去找空白索引, 然后进行填充值
我们在Ring3层拿到的句柄, 其实是没有什么意义的一个索引
句柄表存在的意义
在CloseHandle的时候
- 它会去进程的
句柄表中找到索引对应的内核对象 - 找到后给
内核对象发送一个使用技术递减的指令 然后返回
- 找到了对应的索引, 返回TRUE
- 没找到则返回FALSE
当我们Create了内核对象后, 忘记了使用CloseHandle
就会造成内核对象的泄露
当有了句柄表之后, 我的进程内核对象就会知道我当前使用了哪些内核对象
在进程死亡的时候, 就会通过句柄表对使用到的内核对象进行一一的关闭
这样就避免了内核对象的泄露
句柄表可以正确的处理使用过的内核对象从而避免泄露
这就是句柄表存在的意义
可通过ProcessExplorer进行观察进程拥有的句柄数
所用工具下载
未完待续...
如有错误,请提出指正!谢谢.
本文由 花心胡萝卜 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: 2017-06-30 at 03:16 am

