内核对象学习

1.内核对象

当调用一些API函数时,Windows会返回一个结构体句柄,该结构体在内核地址中的某块内存,称为内核对象,内核对象的数据结构只能由操作系统访问和修改,用户要使用这些内核对象,需要使用Windows提供的函数,通过每个函数不同的功能实现操作内核对象

内核对象的句柄值(而非该对象本身)与当前进程相关,同一个句柄值在不同的进程中意义不同

2.使用计数

内核对象由操作系统管理和所有,当一个进程引用了一个内核对象,该内核对象的引用计数加一,当不同的进程引用了相同的内核对象,则该引用数会一直增加

(比如在两个进程中用CreateFileMapping调用了同一个文件)

在Windows操作系统中,CreateFileMapping 函数用于创建一个文件映射对象,该对象可以在不同进程之间共享内存。这允许不同的进程在共享的内存区域中读取和写入数据。

当多个进程使用 CreateFileMapping 函数创建文件映射对象,并且它们都打开同一个文件时,它们可以通过文件映射对象来共享数据。文件映射对象的作用是将磁盘上的文件映射到内存中,多个进程可以在这个内存区域中进行读写操作。

当一个进程取消对该对象的使用时(CloseHandle),操作系统将该引用对象减1,当引用计数为0时,操作系统才在内核地址中释放该内核对象。

3.安全性

内核对象用一个安全描述符(SD)来保护

  • SD详细介绍

    安全描述符(Security Descriptor,通常简称SD)是Windows操作系统中用于定义和控制对象(例如文件、目录、进程、线程等)访问权限的数据结构。安全描述符包含以下主要描述信息:

    1. 所有者(Owner):安全描述符中包括一个所有者SID(安全标识符),它标识了对象的所有者,即该对象的创建者或拥有者。所有者通常具有最高权限,可以更改对象的安全性描述符。
    2. 主要组(Primary Group):主要组SID标识了对象的主要组。主要组通常用于对对象进行分类,不像所有者那样拥有特殊权限。文件和目录通常继承其父目录的主要组。
    3. 自主访问控制列表(DACL,Discretionary Access Control List):DACL包含了一系列访问控制项(ACE,Access Control Entries),每个ACE定义了一个用户或组的安全标识符(SID)以及它们对对象的访问权限,如读取、写入、执行等。DACL决定了谁可以访问对象以及以何种方式访问它。
    4. 系统访问控制列表(SACL,System Access Control List):SACL也包含一系列ACE,但与DACL不同,SACL用于跟踪安全审核事件。ACE中指定了哪些操作会触发安全审计事件,并指定了何时记录这些事件以及将它们记录到何处(例如Windows事件日志)。
    5. 主要剩余标签(Primary Label):主要剩余标签是用于强制安全性的一个可选部分,通常用于多级安全(MLS)环境,例如军事和政府部门。它包括了针对标签属性的信息,如机密性级别和部门标签。

    总之,安全描述符包括了对象的所有者、主要组、DACL和SACL,它们一起定义了对象的访问控制策略和安全审计设置。这些信息帮助操作系统确定谁可以访问对象、以什么方式访问它以及何时记录相关的安全审计事件。安全描述符是Windows中重要的安全机制之一,用于确保对象的安全性。

对于安全性,每一个能够创建内核对象的API都有一个参数,该参数是指向SECURITY_ATTRIBUTES结构体的指针(该结构体为安全性描述)

typedef struct _SECURITY_ATTRIBUTES {
    DWORD nLength;
    LPVOID lpSecurityDescriptor;
    BOOL bInheritHandle;
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;

第一个参数可直接用sizeof()取大小,第二个参数是指向一个SD描述(大小不定),第三个参数与对象的继承有关,以后讨论

通常情况下,在该参数位置填NULL则表示该对象默认的安全性(该安全性由当前进程的安全令牌决定)

tip:查询进程的安全令牌

//获取目标进程句柄
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwProcessId);
//成功执行以下代码则可获得令牌的句柄,再通过一些API即可查询令牌的具体值
HANDLE hToken;
OpenProcessToken(hProcess, TOKEN_QUERY, &hToken);
  • 关于UAC特性

    UAC(User Account Control)是Windows操作系统中的一项安全特性,旨在提高系统的安全性,防止未经授权的更改和访问。UAC首次引入于Windows Vista,并在之后的Windows版本中继续存在。其主要功能是确保用户帐户以及系统管理员权限在必要时被分隔,以减小恶意软件和不正当行为的风险。

    以下是UAC的一些主要特性:

    1. 提升权限:当普通用户尝试执行需要管理员权限的操作时,UAC会弹出用户提示,请求管理员密码或确认。这有助于确保用户明确同意以管理员权限运行程序,而不是在没有明确许可的情况下。
    2. 限制管理员权限:即使用户以管理员身份登录,大部分时间也以标准用户权限运行。当需要管理员权限时,会触发权限提升,以防止误操作和减小潜在的危害。
    3. 虚拟化文件和注册表:对于以标准用户权限运行的应用程序,UAC可以通过虚拟化文件系统和注册表来模拟对系统目录和注册表的访问。这使得应用程序在没有管理员权限的情况下仍然能够运行。
    4. 保护核心系统文件:UAC确保核心系统文件不容易被未经授权的更改,减少了对系统完整性的威胁。
    5. 限制用户帐户:UAC可以将用户帐户限制到标准用户级别,从而减小受到恶意软件攻击的可能性。
    6. 应用程序兼容性:UAC提供了应用程序兼容性支持,允许较旧的应用程序在UAC环境下运行。

    UAC是Windows操作系统中的一项重要安全特性,它有助于减小系统被恶意软件攻击的风险,提高了用户和系统的安全性。用户应当遵循UAC提示,仅在明确需要时提升权限,以确保系统的稳定性和安全性。

3.1区分用户对象和内核对象

用户对象由用户创立,简单的区别方法是,看该对象创建的函数是否有关于安全性的描述(前文提到内核对象创建的API都有一个关于安全性描述的参数)

用户对象和内核对象是Windows操作系统中的两种不同类型的对象,它们具有不同的特性和使用方式:

  1. 用户对象
    • 受用户进程控制:用户对象是由用户级进程(应用程序)创建和管理的对象。这些对象通常由应用程序代码创建和维护,而不是由操作系统内核直接管理。
    • 句柄可传递给其他进程:用户对象的句柄可以传递给其他进程,允许不同进程之间共享对象或进行进程间通信(IPC)。
    • 用户模式对象:用户对象存在于用户模式,不涉及内核模式的操作,因此它们通常比内核对象更容易创建和使用。一些示例包括文件句柄、窗口句柄、套接字句柄等。
  2. 内核对象
    • 由内核控制:内核对象是由Windows内核创建和管理的对象。它们通常是由操作系统或内核驱动程序使用,用于操作系统的内部功能和管理。
    • 句柄不能跨越进程传递:内核对象的句柄通常不能直接传递给其他进程,因为它们与特定的进程上下文相关。如果需要在不同进程之间共享内核对象,通常需要使用机制如文件映射、命名对象或IPC机制。
    • 内核模式对象:内核对象存在于内核模式,这意味着它们需要更高的权限和更高的系统级访问权限来创建和管理。一些示例包括进程对象、线程对象、事件对象、文件对象等。

用户对象和内核对象在创建、管理、共享和访问方面存在显著差异。用户对象通常由应用程序创建和使用,而内核对象是由操作系统和内核驱动程序创建和使用,用于操作系统内部的核心功能。了解这些区别对于有效和安全地开发和维护Windows应用程序非常重要。

4.进程内核对象句柄表

句柄表(Handle Table)是操作系统内核中的一个数据结构,用于存储和管理句柄(Handles)。这个表中的条目用于跟踪和维护每个句柄的状态、类型和相关信息。句柄表不直接存储句柄的实际值,而是存储了有关句柄的元数据和信息。

每个句柄表的条目通常包括以下信息:

  • 句柄值(Handle Value):实际句柄值,用于引用内核对象或资源。
  • 句柄类型(Handle Type):标识句柄所代表的对象类型,如文件、进程、线程、事件、互斥体等。
  • 访问权限(Access Rights):指定句柄的访问权限,例如读取、写入、删除等。
  • 句柄状态(Handle State):标识句柄的状态,如打开、关闭、无效等。
  • 句柄所有者(Handle Owner):指定拥有该句柄的进程或线程。
  • 其他元数据信息:具体的句柄表条目可能包括其他有关句柄的信息,如引用计数等。

当应用程序使用一个句柄时,操作系统会在句柄表中查找相应的句柄表条目,以检查句柄的状态、访问权限等信息,并执行相应的操作。句柄表的存在使操作系统能够有效地管理和保护句柄,以确保安全和资源的正确使用。每个进程都有自己的句柄表,用于管理其句柄。这也有助于实现句柄的继承、复制和关闭等操作。

5内核对象共享

Q: 不同进程间为何要共享内核对象?

A:

1.利用文件映射对象,可以使得进程间共享数据块,节省空间(使用同一块内核地址,而不是再开辟一个新的内核对象)

2.借助邮件槽或者命名管道,则在网络上不同计算机的进程可以相互发送数据块

3.互斥量和信号量和事件允许不同进程中的线程同步执行

以下介绍四种内核对象句柄的共享方法(前两种方法都隶属于进程句柄继承)

5.1 子进程继承父进程的句柄

为了实现这种方法,需满足以下条件

1.父进程创建内存对象时,需要明确该内存对象是可继承的

具体而言,就是在使用某个Create函数创建一个内核对象时,要设置安全性参数,

以CreateMutex为例,如果我们需要设置该API返回的句柄为可继承,则要设置第一个参数不为空

SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa); //去大小
sa.lpSecurityDescriptor = NULL;  //设置安全性为默认安全性
sa.bInheritHandle = TRUE; //就是为了设置这个参数为TRUE,才实现该API返回的句柄可继承

HANDLE hMutex = CreateMutex(&sa, FALSE, NULL);
//第一个参数即为指向该安全性结构体的指针
//该结构体中指明来=了该返回句柄可继承

2.在调用CreateProcess函数创建子进程时,要设置该子继承父进程的句柄

BOOL CreateProcess(
  LPCTSTR lpApplicationName, // 可执行文件的路径
  LPTSTR  lpCommandLine,    // 命令行参数
  LPSECURITY_ATTRIBUTES lpProcessAttributes, // 进程安全属性
  LPSECURITY_ATTRIBUTES lpThreadAttributes,  // 线程安全属性

  BOOL    bInheritHandles,   // 是否继承父进程句柄

  DWORD   dwCreationFlags,   // 创建标志
  LPVOID  lpEnvironment,     // 进程环境变量
  LPCTSTR lpCurrentDirectory, // 当前工作目录
  LPSTARTUPINFO lpStartupInfo, // 启动信息
  LPPROCESS_INFORMATION lpProcessInformation // 进程信息
);

设置bInheritHandles为TRUE, 则可实现该子进程继承父进程的可继承句柄

通过这种方法继承的句柄,在该子进程的句柄表中的位置与父进程一致,因此两个进程中句柄的值是相同的

5.2 使用SetHandleInformation函数,改变该句柄的继承标识

BOOL SetHandleInformation(
HANDLE hObject,  //需要设置句柄
DWORD dwMask,   //需要改变的标志
DWORD dwFlag    //将该标志设置为何值
);

对于第二个参数,有以下两个标志

		#define HANDLE_FLAG_INHER  0x00000001
		#define HANDLE_FLAG_PROTECT_FROM_CLOSE  0x00000002

Tip: 需要同时设置两个标志时,可以用按位或,|

看名称可知,如果要设置该句柄可继承,可用SetHandleInformation(obj, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) 则将该句柄设置为可继承

第二个宏告诉操作系统不允许关闭句柄,即无法用CloseHandle()关闭该句柄,设置方法与前者格式一致

5.3 为内核对象命名

对于某些内核对象的创建,可以在创建时为其传一个\0结尾的字符串,作为该内核对象的名称

如:

HANDLE CreateEvent(
  LPSECURITY_ATTRIBUTES lpEventAttributes,  // 安全属性,通常为 NULL
  BOOL                  bManualReset,        // 手动复位或自动复位
  BOOL                  bInitialState,       // 初始状态(有信号或无信号)

  LPCTSTR               lpName               // 事件对象的名称
);

当一个内核对象命名后。再使用CreateEvent函数创建一个同名的内核对象时,如果该内核对象的类型与已经存在的内核对象的类型相同(且该内核对象没有访问限制),则该函数直接在句柄表中设置指针指向同一块内存。

针对已经存在的有名称的内核对象,通常用Open*函数获得一个已经存在的内核对象

HANDLE OpenMutex(
  DWORD dwDesiredAccess,   // 访问权限
  BOOL  bInheritHandle,    // 继承标志
  LPCTSTR lpName           // 互斥体对象的名称
);

在实际运用中,为了保证内核对象之间不重名,可以使用GUID作为内核对象的名称(GUID, 是一种全球范围内都不会重复的命名方式,能保证名称不重复)

5.3.1

与内核对象命名空间有关的Ddos攻击:

在 Windows 中,实现单实例应用程序的方法通常包括使用互斥量。应用程序在启动时尝试创建一个命名的互斥量,如果互斥量已存在,则表示另一个应用程序实例已经在运行,当前实例将退出。如果互斥量不存在,应用程序将创建它并继续运行,同时负责维护互斥量,以确保只有一个实例在运行。

当一个单实例运用程序之前就已经创建了相同的实例(内核对象名字相同),则后者将会变成“无实例”,因为该实例在创建时认为已经存在单实例在运行了,所以启动就退出

  • 概念介绍

    单实例:

    单实例(Single Instance)是一个应用程序设计模式,其目标是确保在计算机上只有一个应用程序实例在任何给定时刻运行。这通常用于避免应用程序多次启动或同时运行多个实例,特别是对于单一任务的应用程序。(与多开相对)

    互斥量:

    互斥量(Mutex)是一种同步原语,用于确保多个线程或进程之间对共享资源的互斥访问。互斥量允许线程或进程在访问临界区域(共享资源)时进行同步,以防止竞态条件和数据不一致。互斥量的主要特点是只允许一个线程或进程进入临界区域,其他线程或进程必须等待互斥量的释放才能访问该临界区域。

5.4复制对象句柄(DuplicateHandle)

DuplicateHandle 函数是 Windows API 中的一个函数,它用于复制或复制句柄(Handle)到一个新的目标进程或当前进程中。这可以用于允许一个进程在另一个进程中操作同一内核对象,或者在当前进程中创建另一个指向相同内核对象的句柄。

以下是 DuplicateHandle 函数的一般语法:

cCopy code
BOOL DuplicateHandle(
  HANDLE   hSourceProcessHandle,  // 源进程句柄
  HANDLE   hSourceHandle,        // 源句柄,来源于源进程
  HANDLE   hTargetProcessHandle,  // 目标进程句柄
  LPHANDLE lpTargetHandle,       // 新的目标句柄
  DWORD    dwDesiredAccess,      // 访问权限
  BOOL     bInheritHandle,       // 继承标志
  DWORD    dwOptions             // 选项
);

参数说明:

  • hSourceProcessHandle:源句柄所属的进程的句柄。
  • hSourceHandle:要复制的源句柄。
  • hTargetProcessHandle:目标进程的句柄,即新句柄将被创建或复制到该进程。
  • lpTargetHandle:用于接收新创建的目标句柄的指针。
  • dwDesiredAccess:新句柄的访问权限。这是一个标志,用于指定新句柄的权限,通常与源句柄的权限相同或更受限制。
  • bInheritHandle:一个布尔值,指示新句柄是否可以被目标进程中的子进程继承。
  • dwOptions:附加选项,通常设置为零。

DuplicateHandle 函数成功调用后,它将创建一个新的句柄,该句柄是源句柄的副本,并且可以在目标进程或当前进程中使用。这对于在进程间共享内核对象(如文件、事件、互斥体等)的句柄非常有用。

在多进程编程和进程间通信中,DuplicateHandle 函数通常用于允许不同进程之间访问和操作共享资源。

使用这种方法复制的内核对象,在目标进程句柄表中有记录,但是该目标进程并不知道自己有这个内核对象,需要使用一些进程间通信的手段告诉该进程这个内核对象的句柄(不能从控制台传,因为此时目标进程已经启动了)

以下演示两个进程之间句柄的复制

//假设当前进程为A, 另有一个进程B
HANDLE hObjInProcessA = CreateMutex(NULL, FALSE, NULL);
HANDLE hProcessB = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessIdB);

HANDLE hObjInProcessB; //此处用于填写在进程B中该句柄的值, 需要将该值通过某种方式传递给进程B

DuplicateHandle(GetProcess(), hObjInProcessA, hProcessB, hObjInProcessB,
 0, FALSE, DUPLICATE_SAME_ACCESS);
//**重点关注前四个参数**

DuplicateHandle也可用于设置目标进程的对内核对象的读写权限,维持代码健壮性

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值