句柄与句柄表(数据结构,源码分析)

摘要:
句柄本身是进程句柄表中的一个结构,用于描述打开的操作。句柄值可以简单地视为句柄表中的索引,这不会影响理解。句柄表可以简单地视为一维数组。每个表项都是句柄、结构和句柄描述符。结构定义如下:typedefstruct_HANDLE_TABLE_ENTRY//句柄描述符{union{PVOIDObject;//是一个键字段。因此,句柄表基本上是句柄和目标对象之间的交叉引用表,句柄表中的每个表项都表示一个特定的连接。

0x01  句柄,句柄表概念

    任意进程,只要每打开一个对象,就会获得一个句柄,这个句柄用来标志对某个对象的一次打开,通过句柄,可以直接找到对应的内核对象。句柄本身是进程的句柄表中的一个结构体,用来描述一次打开操作。句柄值则可以简单看做句柄表中的索引,并不影响理解。HANDLE的值可以简单的看做一个整形索引值。

    每个进程都有一个句柄表,用来记录本进程打开的所有内核对象。句柄表可以简单看做为一个一维数组,每个表项就是一个句柄,一个结构体,一个句柄描述符,其结构体定义如下:

typedef struct _HANDLE_TABLE_ENTRY  //句柄描述符
{
    union
    {
        PVOID Object;//关键字段。该句柄指向的内核对象(注意是其头部)
        ULONG_PTR ObAttributes;//关键字段。该句柄的属性
        PHANDLE_TABLE_ENTRY_INFO InfoTable;
        ULONG_PTR Value;//值(可见值本身是一个复合体),最低3位表示该句柄的属性(Value= Object | ObAttributes)
    };
    union
    {
        ULONG GrantedAccess;//关键字段。该句柄的访问权限
        struct
        {
            USHORT GrantedAccessIndex;
            USHORT CreatorBackTraceIndex;
        };
        LONG NextFreeTableEntry;//当本句柄是一个空闲表项时,用来链接到句柄表中下一个空闲表项
    };
} HANDLE_TABLE_ENTRY, *PHANDLE_TABLE_ENTRY;

  

    

  一 个 进 程 可 能 同 时 打 开 许 多 对 象 ,跟 许 多 对 象 建 立 连 接 , 还 可 以 跟 同 一 个 对 象 建 立 起 多 个 连 接 。 所 以 每 个 程 都 需 要 有 个 句 柄 表 用 来 记 录 , 维 持 这 些 连 接 。 所 以 , 句 柄 表 最 基 本 的 作
用 就 是 一 张 句 柄 与 目 标 对 象 之 间 的 对 照 表 , 而 句 柄 表 中 的 每 个 表 項 , 则 代 表 着 一 个 具 体 的 连 接 。 所 以 , 在 "进 程 控 制 控 制 *"EPROCESS#“中有 指 针 ObjectTable, 用 来指 向 本 进 程 向 柄 表。

  句柄表结构体定义:

typedef struct _HANDLE_TABLE    //句柄表描述符
{
    ULONG TableCode; //表的地址|表的层数(该字段的最后两位表示表的层数)
    PHANDLE_TABLE_ENTRY **Table; 
    PEPROCESS QuotaProcess;//所属进程
    PVOID UniqueProcessId; //所属进程的PID
    EX_PUSH_LOCK HandleTableLock[4];
    LIST_ENTRY HandleTableList;//用来挂入全局的句柄表链表(间接给出了系统中的进程列表)
    EX_PUSH_LOCK HandleContentionEvent;
    ERESOURCE HandleLock;
    LIST_ENTRY HandleTableList;
    KEVENT HandleContentionEvent;
    PHANDLE_TRACE_DEBUG_INFO DebugInfo;
    LONG ExtraInfoPages;
    ULONG FirstFree;//第一个空闲表项的索引位置
    ULONG LastFree;//最后一个空闲表项的索引位置
    ULONG NextHandleNeedingPool;//本句柄表本身占用的内存页数
    LONG HandleCount;//表中的有效句柄总数
    union
    {
        ULONG Flags;
        UCHAR StrictFIFO:1;
    };
} HANDLE_TABLE, *PHANDLE_TABLE;

  每当创建或打开了一个对象,要为之创建句柄并插入句柄表的时候,就为其准备一个临时的HANDLE_TABLE_ENTRY结构,使其指向这个对象的头部,然后通过EXCreateHandle将其“安装”句柄表中。而EXCreateHandle返回句柄,句柄的值表明了安装的位置。

  ExCreateHandle函数原型(关键代码):

HANDLE   ExCreateHandle(PHANDLE_TABLE HandleTable,   PHANDLE_TABLE_ENTRY HandleTableEntry)
{
    EXHANDLE Handle;
    NewEntry = ExpAllocateHandleTableEntry(HandleTable,&Handle);//在句柄表中找到一个空闲表项
   ... *NewEntry = *HandleTableEntry;//复制句柄表项
   ... return Handle.GenericHandleOverlay;//返回句柄值(也即空闲表项的索引位置) }

  

  ObpCreateHandle函数打开对象,获得句柄:

NTSTATUS
ObpCreateHandle(IN OB_OPEN_REASON OpenReason,//4种打开时机
                IN PVOID Object, //要打开的对象
                IN PACCESS_STATE AccessState, //句柄的访问权限
                IN ULONG HandleAttributes, //句柄的属性
                IN KPROCESSOR_MODE AccessMode,
                OUT PHANDLE ReturnedHandle) //返回的句柄值
{
    BOOLEAN AttachedToProcess = FALSE, KernelHandle = FALSE;
    NewEntry.Object = ObjectHeader;//关键。将该句柄指向对应的对象头

    if (HandleAttributes & OBJ_KERNEL_HANDLE)//如果用户要求创建一个全局型的内核句柄
    {
        HandleTable = ObpKernelHandleTable;//改用内核句柄表
        KernelHandle = TRUE;
        //将当前线程挂靠到system进程,也即修改当前的CR3,将页表换成system进程的页表
        if (PsGetCurrentProcess() != PsInitialSystemProcess)
        {
            KeStackAttachProcess(&PsInitialSystemProcess->Pcb, &ApcState);
            AttachedToProcess = TRUE;
        }
    }
    else
        HandleTable = PsGetCurrentProcess()->ObjectTable;//使用当前进程的句柄表
    

   //检查是否可以独占打开,检查权限,若各项检查通过才打开对象,递增句柄计数,调用对象的OpenProcedure等等工作
    Status = ObpIncrementHandleCount(Object,
                                     AccessState,
                                     AccessMode,
                                     HandleAttributes,
                                     PsGetCurrentProcess(),
                                     OpenReason);
    if (!NT_SUCCESS(Status))
        return Status;
    
    NewEntry.ObAttributes |= (HandleAttributes & OBJ_HANDLE_ATTRIBUTES);//填上句柄的属性

    DesiredAccess =AccessState->RemainingDesiredAccess|AccessState->PreviouslyGrantedAccess;
    GrantedAccess = DesiredAccess &(ObjectType->TypeInfo.ValidAccessMask);
    NewEntry.GrantedAccess = GrantedAccess;//填上句柄的属性
    Handle = ExCreateHandle(HandleTable, &NewEntry);//将句柄插入到句柄表中
    if (Handle)//if 插入成功
    {
        if (KernelHandle)
 Handle = ObMarkHandleAsKernelHandle(Handle);//将句柄值的最高位设为1,标记为内核句柄

        *ReturnedHandle = Handle;
        if (AttachedToProcess)
 KeUnstackDetachProcess(&ApcState);//撤销挂靠
        return STATUS_SUCCESS;
}
Else
{
…
       return STATUS_INSUFFICIENT_RESOURCES;
}
}

  

打开对象,以得到一个访问句柄。有四种打开时机:

1、  创建对象时就打开,如CreateFile在创建一个新文件时,就同时打开了那个文件对象

2、   显式打开,如OpenFile,OpenMutex,OpenProcess显式打开某个对象

3、   DuplicateHandle这个API间接打开对象,获得句柄

4、   子进程继承父进程句柄表中的句柄,也可看做是一种打开

在这四种情况下,都会调用这个函数来打开对象,得到一个句柄。OpenReason参数就是指打开原因、时机

注意句柄值的最高位为1,就表示这是一个内核全局句柄,可以在各个进程中通用。否则,一般的句柄,只能在对应的进程中有意义。

另外有两个特殊的伪句柄,他们并不表示‘索引’,而是一个简单的代号值

GetCurrentProcessHandle  返回的句柄值是-1

GetCurrentThreadHandle   返回的句柄值是-2

对这两个句柄要特殊处理。

 

句柄不光含有指向对象的指针,每个句柄都还有自己的访问权限与属性,这也是非常重要的。访问权限表示本次打开操作要求的、申请的并且最终得到的权限。句柄属性则表示本句柄是否可以继承,是否是独占打开的,是否是一个内核句柄等属性。

  

免责声明:文章转载自《句柄与句柄表(数据结构,源码分析)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Freeswitch 入门linux dialog详解(图形化shell)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

corefx 源码学习:NetworkStream.ReadAsync 是如何从 Socket 异步读取数据的

最近遇到 NetworkStream.ReadAsync 在 Linux 上高并发读取数据的问题,由此激发了阅读 corefx 中 System.Net.Sockets 实现源码(基于 corefx 2.2)的兴趣。 这篇随笔是阅读 NetworkStream.ReadAsync 相关源码的简单笔记,基于在 Linux 上运行的场景。  NetworkS...

Win7 VSCode 在线安装Rust语言及环境配置

睡前彻底解决在VSCode中,按F12不跳转到标准库源码的问题。 首先,如果装过离线版,卸载掉。 然后去官网下载 rustup-init.exe https://www.rust-lang.org/tools/install 下好后别急着安装 新建4个环境变量 CARGO_HOMEE:Rustcargo RUSTUP_HOMEE:Rust ustup RU...

Windows录音API学习笔记--转

Windows录音API学习笔记 结构体和函数信息  结构体 WAVEINCAPS 该结构描述了一个波形音频输入设备的能力。 typedef struct {     WORD      wMid; 用于波形音频输入设备的设备驱动程序制造商标识符。     WORD      wPid; 声音输入设备的产品识别码。     MMVERSION vDrive...

Java源码阅读-Integer(基于jdk1.8)

public final class Integer extends Number implements Comparable<Integer>   Integer 由final修饰了,所以该类不能够被继承,同时 Integer 继承了Number类,因此可以将Integer转换成 int 、double、float、long、by...

dubbo(2.5.3)源码之服务消费

消费端启动初始化过程:   消费端的代码解析也是从配置文件解析开始的,服务发布对应的<dubbo:service,解析xml的时候解析了一个ServiceBean,并且调用ServiceConfig进行服务的发布。服务的消费对应的<dubbo:reference,在初始化的过程中也解析了一个 ReferenceBean类去做处理。在bean加...

从源码中学习设计模式系列——单例模式序/反序列化以及反射攻击的问题(二)

一、前言 这篇文章是学习单例模式的第二篇,之前的文章一下子就给出来看起来很高大上的实现方法,但是这种模式还是存在漏洞的,具体有什么问题,大家可以停顿一会儿,思考一下。好了,不卖关子了,下面我们来看看每种单例模式存在的问题以及解决办法。 二、每种Singleton 模式的演进 模式一 public classLazySingleton {...