什么是内核对象

内核对象本质上就是内存中的一块内存 ,这块内存由操作系统进行管理和分配,任何应用程序都无法直接操作这块内存区域。至于内核对象的作用,我们暂且不说,这里只需要直到它是内存中的一块内存。

在内存中,内核对象的存储类似下图,进程中的每个内核对象都有自己的地址,并且内核对象有一个固定的数据结构。

每个内核对象的结构体如下:

typedef struct _OBJECT_HEADER {
LONG PointerCount;//引用计数,表示有多少指针引用该对象。
union {
LONG HandleCount;//句柄计数,表示有多少句柄引用该对象。
PVOID NextToFree;
};
PVOID Lock;//用于同步访问该对象的锁。
UCHAR TypeIndex;//对象类型的索引。
UCHAR TraceFlags;//跟踪标志,用于调试和跟踪对象的使用。
UCHAR InfoMask;//信息掩码,指示哪些信息可用。
UCHAR Flags;//对象的标志,指示对象的状态和属性。
union {
PVOID ObjectCreateInfo;//对象创建信息。
PVOID QuotaBlockCharged;//配额块信息。
};
PVOID SecurityDescriptor;//安全描述符,定义对象的安全属性。
QUAD Body;//对象的主体,包含对象的实际数据。
} OBJECT_HEADER, *POBJECT_HEADER;

简单一点说,对于内核对象,大家把他理解成存储在内存中的struct结构体数组,每个结构体都有自己的地址,这个地址叫做内核对象的地址。

_OBJECT_HEADER 是一个内部结构体,用于描述内核对象的元数据。这个结构体在 Windows 内核中定义,但并不直接暴露给用户模式应用程序。

句柄表

既然应用程序无法直接操作内核对象,那么应该如何访问和操作内核对象呢,这其中有个很重要的桥梁:句柄表。句柄表本质上也是内存中的一块内存,在每个进程启动的时候,操作系统会在进程的地址空间上开辟一块内存,用来保存句柄表,每个进程都有自己的一个句柄表。句柄表的基本结构如下图:

注意:句柄表中有多条句柄,每个句柄也有自己的数据结构,主要有四个字段,第一个字段索引(句柄值),这是直接暴露给我们的应用程序的,第二个字段内核对象地址,表示的是某个内核对象真正的地址,就是上面第一张图中描述的内核对象的地址,访问掩码和标识我们暂时不讨论,后面会做特殊说明。

从这张图,我们就可以看到,句柄表其实就是连接内核对象和应用程序之间的一个桥梁,因为句柄表中的第二列,内核对象地址存储了真正的内核对象地址。

遗憾的是,句柄表的内存也是由操作系统分配和管理的,应用程序依然无法直接操作句柄表的内存,那到底如何访问内核对象呢,请继续往下看。

查看进程的所有句柄

通过ProcessExplorer可以查看一个进程的所有句柄列表。打开ProcessExplorer,选中一个进程,然后选择下方Tab栏中的Handles,可以查看到当前进程的句柄列表。注意:ProcessExplorer最好用管理员权限打开,如果用普通权限用户打开的话,有些句柄列信息是看不到,比如说ShareFlags这一列。

句柄表有Type、Name、Handle、Address、Access、ObjectAddress、DecodeAccess、ShareFlags、Attributes几列,每列分别代表的意义如下:

1、Type:表示句柄的类型。文件、事件、互斥体、信号量、注册表、作业、线程、管道、文件映射等类型。    通过CreateThread 创建线程内核对象、CreateFileMapping创建文件映射内核对象、CreateMutex创建互斥体内核对象、CreateProcess创建进程内核对象、CreateEvent创建事件内核对象、CreateWaitableTimer创建时间等待期内核对象,还要其他的很多类似CreateXXX的函数用于创建不同类型的内核对象,这里不在举例了

2、Name:句柄名称,一般句柄都可以设置一个名称。在调用CreateXXX创建内核对象的时候,一般最后一个参数叫做pszName就是指定内核对象名称的。

3、Handle:句柄值,这个就是传递给WindowsApi的句柄值,也就是上面句柄句柄表那张图中的第一列。

4、Access:表示句柄的访问权限,以下是不同数值的访问权限:

0x0012019F:对文件的读写访问权限。
0x00120189:对文件的只读访问权限。
0x001F01FF:对文件的完全访问权限。
0x00100000:对进程的查询信息权限。
0x001F0FFF:对进程的完全访问权限。
0x00100000:对线程的查询信息权限。
0x001F03FF:对线程的完全访问权限。

5、ObjectAddress:表示内核对象在内核地址空间中的真实地址。这个地址是内核对象的实际地址。也就是上面第一张图表示的内核对象地址。

6、Decoded Access:Access这一列解码后的访问权限说明,这一列是ProcessExplorer为了方便我们阅读,将二进制解读为字符串。实际内存并没有Decoded Access这一列。

7、ShareFlags:表示句柄的共享标志。

FILE_SHARE_READ (0x00000001):允许其他进程读取文件。
FILE_SHARE_WRITE (0x00000002):允许其他进程写入文件。
FILE_SHARE_DELETE (0x00000004):允许其他进程删除文件

8、 Attributes:表示句柄的属性,比如继承属性。

下面我们用CreateFile创建内核对象,然后在ProcessExplorer中查看我们创建的句柄信息。

#include <iostream>
#include <Windows.h> int main()
{
LPCWSTR fileName = L"example.txt"; // 定义安全属性,允许句柄继承
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL; // 使用默认安全描述符
sa.bInheritHandle = TRUE; // 允许句柄继承 // 创建文件并设置访问权限和共享模式
HANDLE hFile = CreateFile(
fileName, // 文件名
GENERIC_READ | GENERIC_WRITE, // 访问模式
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //共享模式
&sa, // 安全属性
CREATE_ALWAYS, // 创建选项
FILE_ATTRIBUTE_NORMAL, // 文件属性
NULL // 模板文件句柄
); // 关闭文件句柄
CloseHandle(hFile);
return 0;
}

CloseHandle(hFile);这一行打上断点,不要着急关闭句柄。运行控制台程序,在ProcessExplorer中会看到一个文件句柄如下:

上图可以清晰的看到,我们创建的句柄类型为File,句柄名称其实就是文件的路径,Handle表示文件句柄的句柄值,后续我们对文件的操作都要用到它,

然后是访问权限Access,我们在程序中设置的访问权限为:GENERIC_READ | GENERIC_WRITE,表示当前进程对文件有读写的权限,在Decoded Access这一列可以看到FILE_GENERIC_READ | FILE_GENERIC_WRITE标志已经成功被设置,对 于ShareFlags这一列,RWD分别代表Read、Write和Delete权限,如果没有设置对于权限,一条横线,因为我们在程序中设置了FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE权限,其他进程可以对该文件读写和删除操作,当然一般情况下,当我们打开这个文件的时候,尽量只给其他进程设置一个读权限。对于最后一个Attributes,Inherit表示句柄可以被继承,这个在主进程创建子进程的时候,如果主进程允许继承,那么这个句柄将会继承到子进程。这个属性是通过句柄的安全描述符来描述的。我们通过sa.bInheritHandle = TRUE; 设置句柄的继承性。当我们调用CloseHandle(hFile);关闭句柄之后,在ProcessExplorer界面上会发现,上图显示的句柄会立刻消失。

管理内核对象

每个内核对象结构体都有一个字段叫做引用计数PointerCount,当一个引用对象被创建的时候,这个计数被设置为1,代表创建这个内核对象的进程正在使用这个内核对象,当有其他进程通过类似OpenXXX函数打开这个内核对象的时候,该计数会递增1。当进程不在对这个内核感兴趣的时候,可以调用CloseHandle函数将内核对象的引用计数递减1,只有当内核对象中引用计数被递减到0的时候,操作系统才会清空该内核对象。所以对于 内核对象来说,当我们不再使用的时候,一定要记得释放。

如何访问内核对象

Window提供了一些列的API来帮助我们操作内核对象。

假设我们要操作内核对象1(地址0x111),句柄表中有一个记录记录了对内核对象0x111的引用,其句柄之为1,内核对象地址0x111。这个时候,操作系统可以提供一个函数,比如叫做HandleObject,然后这个函数有一个参数叫做句柄值,我们把句柄值1传递给该函数,操作系统就可以找到句柄值1对应的内核对象的真实地址,然后进行操作。事实上,操作系统也确实是这样做的。

操作系统提供了一系列API,这些API几乎都会有一个句柄值的参数,这个参数就是句柄表中的第一列,操作系统通过这个参数,可以找到对应的内核对象地址,然后对内核对象进行相应的操作。

对于几乎所有的内核对象,windows都提供一个统一的操作模式,就是先调用系统API打开(一般是OpenXXX函数)内核对象或创建内核对象(一般是CreateXXX函数),OpenXXX和CreateXXX一般都会返回一个当前进程的句柄值,也就是上面句柄表中的第一列。让当前进程与目标对象之间建立起连接,然后再通过别的系统调用进行操作(这些操作内核对象的函数一般都需要一个句柄值的参数),最后通过调用系统API(一般是CloseHandle函数)关闭对象。实际上是关闭进程与目标对象的联系。

我们来简单总结一下应用程序操作内核对象的一般流程,这个流程基本上适用于所有的内核对象。

1、通过CreateXXX函数创建一个内核对象,并且得到句柄值。

2、通过OpenXXX函数打开一个内核对象,也是得到一个句柄值,当然这一步也可以省略,一般创建的时候,就可以打开内核对象。

3、调用对应的函数操作内核对象,我们假设函数叫做HandleObject,那么这个HandleObjet函数的原型大概是这样的。bool  HandleObject(HANDLE handle)。返回值表示是否操作成功,参数handle表示需要操作的句柄。操作系统会根据这个句柄从进程句柄表中找到内核对象的真实地址,然后进行操作。

4、调用CloseHandle函数,递减内核对象的引用计数。

Windows编程----内核对象竟然如此简单?的更多相关文章

  1. Windows核心编程&内核对象

    1. 一个进程在初始化时,系统将会他分配一个空的句柄表,这个句柄表仅供内核对象使用,不供用户对象和GDI对象使用.进程在首次 初始化时,该句柄表为空.句柄表是一个由数据结构组成的数组,包含一个内核对象 ...

  2. windows编程:画线,简单的碰撞检测,简单的帧率锁定

    #define WIN32_LEAN_AND_MEAN #include <windows.h> #include <windowsx.h> #include <mmsy ...

  3. windows编程之内核对象

          学好windows编程,理解内核对象还是至关重要的(●'◡'●).闲话不多说,下面先来了解一下关于内核对象的知识:       内核对象(kernel object):内核对象是用于管理进 ...

  4. windows核心编程---第三章 内核对象及句柄本质

      本章讨论的是相对抽象的概念,不涉及任何具体的内核对象的细节而是讨论所有内核对象的共有特性. 首先让我们来了解一下什么是内核对象.内核对象通过API来创建,每个内核对象是一个数据结构,它对应一块内存 ...

  5. 《windows核心编程系列》三谈谈内核对象及句柄的本质

    内核对象 本章讨论的是相对抽象的概念,不涉及任何具体的内核对象的细节而是讨论所有内核对象的共有特性. 首先让我们来了解一下什么是内核对象.内核对象通过API来创建,每个内核对象是一个数据结构,它对应一 ...

  6. 【Windows 操作系统】 内核对象|句柄

    内核对象简介 内核对象就是 一些数据结构该结构用来描述存储内核中的一个内存块中的数据信息.   内存块是一种数据结构,其中的数据成员负责维护该对象的相应信息,这个数据结构以及其中的数据成员只能由内核访 ...

  7. Windows内核对象

    1. 内核对象 Windows中每个内核对象都只是一个内存块,它由操作系统内核分配,并只能由操作系统内核进行访问,应用程序不能在内存中定位这些数据结构并直接更改其内容.这个内存块是一个数据结构,其成员 ...

  8. (转)WINDOWS内核对象

    WINDOWS内核对象 原文地址:http://blog.csdn.net/misterliwei/article/details/976988  支持原创 一.前言 Windows中有很多像进程对象 ...

  9. 内核对象&句柄&泄漏&检测

    今天看到这个问题如何评价王垠的 <讨厌的 C# IDisposable 接口>? - 王垠(人物),答案被歪到windows 内核对象和句柄,答案中谈的太浅显而且有误.翻出陈年老文章(此文 ...

  10. Windows中的对象

    来源  http://www.0xaa55.com/forum.php?mod=viewthread&tid=1401&extra=page%3D1  windows里常用句柄操作资源 ...

随机推荐

  1. git学习之git reset命令

    Git版本恢复命令 reset命令有3种方式: git reset –mixed:此为默认方式,不带任何参数的git reset,即时这种方式,它回退到某个版本,只保留源码,回退commit和inde ...

  2. JVM简介—3.JVM的执行子系统

    大纲 1.Class文件结构 2.Class文件格式概述 3.Class文件格式详解 4.字节码指令 5.类的生命周期和初始化 6.类加载的全过程 7.类加载器 8.双亲委派模型 9.栈桢详解 11. ...

  3. React部署到线上Nginx环境中刷新页面后404解决方案

    我们需要在Nginx的配置文件中修改以下内容(通常Nginx配置文件位置为/etc/nginx/nginx.conf): server { # ... location / { # ... # 增加下 ...

  4. B站千万级长连接实时消息系统的架构设计与实践

    本文由哔哩哔哩资深开发工程师黄山成分享,原题"千万长连消息系统",本文进行了排版和内容优化等. 1.引言 在当今数字娱乐时代,弹幕已经成为直播平台上不可或缺的互动元素之一. 用户通 ...

  5. 解决 raw.githubusercontent.com 无法访问的问题

    解决 raw.githubusercontent.com 无法访问的问题 电信默认 DNS 直接遮蔽 github DNS 1: 61.139.2.69 DNS 2: 218.6.200.139 C: ...

  6. MySQL如果数据存在则更新,不存在则插入

    如果数据存在则更新,不存在则插入,MySQL有duplicate.replace into.replace三种方式如何更新数据? insert ignore into 又是如何插入数据的呢? 准备表和 ...

  7. 一款由 .NET 官方团队开源的电子商务系统 - eShop

    项目介绍 eShop是一款由.NET官方开源的,基于.NET Aspire构建的用于参考学习的服务架构电子商务系统,旨在展示如何利用.NET框架及其相关技术栈构建一个现代化的电子商务网站.该项目采用服 ...

  8. 让存储绿“翼”盎然,天翼云HBlock入选工信部目录!

    近日,中国电信天翼云的自研产品HBlock凭借"存储资源盘活技术"成功入选<国家工业和信息化领域节能降碳技术装备推荐目录(2024年版)>(以下简称<目录> ...

  9. USACO24DEC 2D Conveyer Belt S [ 蓝 ] [ 图论建模 ] [ Flood Fill ]

    2D Conveyer Belt:图论建模+洪水填充妙妙题.质量极高. 观察 首先面对这种不断往图里面加点或者边,且满足前面的答案可以由加边后推出的题,第一个想到的就得是倒序枚举加边过程的 trick ...

  10. AI-接入

    前言 前面已经申请了模型,并且通过测试已经可以访问使用了,本篇的接入还是使用Ollama,前面我们已经可以在命令行终端能够进行交互了,现在将AI接入到代码中: 准备 作为一名Neter这里使用的是.n ...