当前位置:网站首页 > 黑客培训 > 正文

Windows内核-句柄

freebuffreebuf 2022-04-18 310 0

本文来源:

概述

进程的地址空间分为系统空间和用户空间,内核对象都保存在系统空间中,用户空间不能通过地址作为指针来引用它们,Windows使用句柄(handle)来对内核对象进行引用。看起来很小,但是涉及的内容很多。

句柄

所谓的句柄值其实是进程结构体中句柄表中的索引,通过该句柄值在句柄表中进行逻辑换算就可以变成内核对象的指针来进行操作。

因为是进程句柄表中的索引,所以句柄只在进程中有效。一个进程中的句柄值传递给另外一个进程后,句柄值将不再有效。这种表被称为私有句柄表或者进程句柄表。

在Windows中还有一种句柄表叫全局句柄表是Windows全局都可以使用的,和私有句柄表稍许区别。

在EPROCESS结构体中,对应的ObjectTable字段来包含进程的句柄表信息。

相关结构体:

注:OS环境为Win7 SP1 32位

//0x3c bytes (sizeof)struct _HANDLE_TABLE{    ULONG TableCode;                                                        //0x0    //指向句柄表的存储结构(非常重要的字段)    struct _EPROCESS* QuotaProcess;                                         //0x4    //句柄表的内存资源存储在此进程中    VOID* UniqueProcessId;                                                  //0x8    //创建进程的ID,用于回调函数    struct _EX_PUSH_LOCK HandleLock;                                        //0xc    //句柄表锁,仅在句柄表扩展时使用    struct _LIST_ENTRY HandleTableList;                                     //0x10    //所有的句柄表形成一个链表,该字段作为一个链表节点    //链表头为全局变量HandleTableListHead    struct _EX_PUSH_LOCK HandleContentionEvent;                             //0x18    //访问句柄时发生竞争,就通过该推锁进行等待    struct _HANDLE_TRACE_DEBUG_INFO* DebugInfo;                             //0x1c    //仅当使用调试句柄时才有意义    LONG ExtraInfoPages;                                                    //0x20    //审计信息所占用的页面数量    union    {        ULONG Flags;                                                        //0x24        //标志域        UCHAR StrictFIFO:1;                                                 //0x24           //是否使用队列的风格,FIFO先进先出,先释放的地方先使用。    };    ULONG FirstFreeHandle;                                                  //0x28    //当前句柄表中的空闲句柄表项的索引值    //句柄索引值按HANDLE_VALUE_INC逐个递增,在win7 sp1 32位中为4字节    struct _HANDLE_TABLE_ENTRY* LastFreeHandleEntry;                        //0x2c    //当前句柄表中最后一个空闲句柄表项的地址    ULONG HandleCount;                                                      //0x30    //正在使用的句柄表项数量    ULONG NextHandleNeedingPool;                                            //0x34    //下一次句柄表扩展的起始句柄索引,也就是下一个新的句柄表的首地址    ULONG HandleCountHighWatermark;                                         //0x38 }; //0x8 bytes (sizeof) struct _HANDLE_TABLE_ENTRY{    union    {        VOID* Object;                                                       //0x0        ULONG ObAttributes;                                                 //0x0        struct _HANDLE_TABLE_ENTRY_INFO* InfoTable;                         //0x0        ULONG Value;                                                        //0x0    };    union    {        ULONG GrantedAccess;                                                //0x4        struct        {            USHORT GrantedAccessIndex;                                      //0x4            USHORT CreatorBackTraceIndex;                                   //0x6        };        ULONG NextFreeTableEntry;                                           //0x4    };};/*该结构体后续再解释 目前只需知道该结构体的低32位到低2位保存的是内核对象的首地址    以下在结构体中的低地址的union的32-2位中保存着首地址    union    {        VOID* Object;                                                       //0x0        ULONG ObAttributes;                                                 //0x0        struct _HANDLE_TABLE_ENTRY_INFO* InfoTable;                         //0x0        ULONG Value;                                                        //0x0    };    例如:_handle_table_entry == 00000001`8812ad09    那么对应的对象首地址就为 8812ad09将低3位清零的结果:8812ad08*/ 

句柄表

句柄表是一个多层结构,最多有三层,最少有一层。层数由TableCode的低2位的值来判断,低2位为0时为1层,为1时为2层,为2时为3层。 

例如:TableCode = 0x88884222; 其中的低二位为10B,也就是0x2,所以该句柄表就为三层结构。

最底层结构中保存的内容才是实实在在需要的内容,叫做句柄项(_HANDLE_TABLE_ENTRY),往上的层数内容都是在当前层数的句柄表不够时新建的数组来保存之前的句柄表的首地址。类似于分页管理中的机制,通过好几个数组来嵌套,最终有效的,实实在在指向了页的起始地址的只有页表项。

句柄表产生流程:

注,以WRK中的代码为例:

在执行体中创建进程时会首先为新进程分配一个单层句柄表,然后并初始化。

句柄在句柄表中呈线性增加,当增加一个句柄时会在当前最后一个句柄表的数组中往后添加一个句柄表项。

接着随着进程中句柄数的增加,如果当前句柄表不够使用,就会扩展句柄表,以此由单层到2层最多到3层。Windows中进程的句柄是有限制的在WRK中最多有2的24次方个。类似于分页管理机制。

创建单层句柄表采用:ExCreateHandleTable()函数来完成,初始化句柄表采用ExpAllocateHandleTable()函数来完成,扩展句柄表采用ExpAllocateHandleTableEntrySlow()函数来完成。

以上函数在WRK中都有仔细记载。

TableCode字段

TableCode字段是句柄表信息中最关键的字段,前面写道该字段的低2位标识了句柄表的层数,除了该作用还有别的作用。

该字段的低两位清零后的地址为句柄表的最高层表的首地址。

句柄通过句柄表得到内核对象的流程:

一个有效的句柄有四种可能:

-1和-2的情况不需要详解,主要是句柄值为一个正常的4倍数的情况。

同时还要区分私有句柄表和全局句柄表,两者的内容有一点小小的区别。

通过私有句柄表:

(参考WRK)

通过公有句柄表:

私有句柄表和公有句柄表唯一的区别是句柄表项的不同,私有表的_HANDLE_TABLE_ENTRY内存放的是指向OBJECT_HEADETR对象头的首地址,而公有表中存放的是对象Body的首地址。

Windbg查看

私有句柄表

首先在虚拟机中采用Process Explorer来查看notepad++中的句柄内容:

然后根据句柄值来找到对应的内核对象的地址:

1、在Windbg中找到notepad++的句柄表_HANDLE_TABLE 结构体首地址:

kd> !process 0 0PROCESS 86e97d20  SessionId: 1  Cid: 03e8    Peb: 7ffdf000  ParentCid: 0594    DirBase: 07f70000  ObjectTable: a79b91c0  HandleCount:  72.    Image: notepad++.exe //    首地址为:a79b91c0 

2、通过_HANDLE_TABLE结构体得到TableCode内容:

kd> dt _HANDLE_TABLE  a79b91c0ntdll!_HANDLE_TABLE   +0x000 TableCode        : 0x8b4a0000   +0x004 QuotaProcess     : 0x86e97d20 _EPROCESS   +0x008 UniqueProcessId  : 0x000003e8 Void   +0x00c HandleLock       : _EX_PUSH_LOCK   +0x010 HandleTableList  : _LIST_ENTRY [ 0x84145e28 - 0xa87854f8 ]   +0x018 HandleContentionEvent : _EX_PUSH_LOCK   +0x01c DebugInfo        : (null)   +0x020 ExtraInfoPages   : 0n0   +0x024 Flags            : 0   +0x024 StrictFIFO       : 0y0   +0x028 FirstFreeHandle  : 0xcc   +0x02c LastFreeHandleEntry : 0x8b4a0ff8 _HANDLE_TABLE_ENTRY   +0x030 HandleCount      : 0x48   +0x034 NextHandleNeedingPool : 0x800   +0x038 HandleCountHighWatermark : 0x4b 

3、解析TableCode:

0x8b4a0000 的前四位为:  1000     表明只有一层句柄表,    清零后得到句柄表的首地址:     0x8b4a0000 

4、根据句柄表的层数和句柄值来得到句柄所在的最底层句柄表。

4.1    如果只有一层就是TableCode前30位的值 4.2    如果有两层就需要先将句柄值除以512,看看占满了多少个最底层句柄表,然后将在TableCode中找到前面占满了的最底层句柄表的首地址的存放地址,再后面一个就是对应的最底层句柄表了。然后将句柄值-占满句柄表的个数*512等到在对应的最底层句柄表中句柄的偏移值,然后将该值*2得到句柄表项在句柄表中的偏移值。 4.3    和4.2类似。 这里的情况就是4.1,直接可以得到对应表的地址为0x8b4a0000 

5、通过句柄得到句柄项在表中的偏移:

句柄表中存放的是句柄项,句柄项是一个结构体里面包含了句柄值://0x8 bytes (sizeof)struct _HANDLE_TABLE_ENTRY{    union    {        VOID* Object;                                                       //0x0        //指向句柄代表的对象        ULONG ObAttributes;                                                 //0x0        struct _HANDLE_TABLE_ENTRY_INFO* InfoTable;                         //0x0        ULONG Value;                                                        //0x0    };    union    {        ULONG GrantedAccess;                                                //0x4        struct        {            USHORT GrantedAccessIndex;                                      //0x4            USHORT CreatorBackTraceIndex;                                   //0x6        };        ULONG NextFreeTableEntry;                                           //0x4    };};     因为句柄表项结构大小为8字节,而句柄的大小为4字节,所以在得到句柄表中句柄表项的偏移时,还需要将对应句柄表的句柄值*2。    例如这里的句柄表值为0x28,那么对应到句柄表中的偏移为0x28*2 

6、查看值:

//通过句柄表+偏移值的方式得到句柄表项中的对象地址,然后将后三位清零后得到对象的头地址,//然后将头地址往下偏移就可以得到对象首地址 这里查看前面图中句柄值为0x28的内容:kd> dq 0x8b4a0000+0x28*28b4a0050  000f01ff`87b3f329 000f037f`87b3ea418b4a0060  00020019`a7a133c9 00000001`a87956198b4a0070  00000804`86e6b0d9 00000804`86de52918b4a0080  00000804`88002da1 00000804`86de0b998b4a0090  00000804`87cb48d1 00000804`88002c398b4a00a0  00000804`86cee6f9 00000804`880bb2718b4a00b0  00000804`86db1779 001f0001`87c722a98b4a00c0  001f0003`87bccf21 001f0001`87c46831 得到句柄表项中的对象值为87b3f329 将前三位清零后为:    87b3f328 往下偏移0x18后为:    87b3f340 查看对象内容: kd> !object 87b3f340Object: 87b3f340  Type: (866f67a0) Desktop    ObjectHeader: 87b3f328 (new version)    HandleCount: 12  PointerCount: 689    Directory Object: 00000000  Name: Default 

和前面Desktop内核对象对应的值一样。

公有句柄表:

公有句柄表和私有句柄表的区别很小,首先公有句柄表有一个全局变量PspCidTable来保存起始地址。

这里需要纠正一下Windows内核原理与实现一书中的内容:

在Win7中全局句柄表和system进程句柄表内容不同:

PROCESS 866d2900  SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000    DirBase: 00185000  ObjectTable: 8b401b28  HandleCount: 430.    Image: System System的句柄表地址为;8b401b28 kd> dd PspCidTable8418fd54  8b401080 00000000 80000020 00000101全局句柄表地位为:8b401080 

但是system进程的句柄表又有个全局变量ObpKernelHandleTable来表示,看着名字这个system进程的句柄表应该叫内核句柄表吧,猜测是给内核驱动使用的句柄表。

公有句柄表和私有句柄表的区别:

1、地址的区别:公有句柄表由PspCidTable全局变量来保存,私有地址表在进程中保存。

2、句柄表项的区别:公有句柄表项中的对象地址是对象body的首地址,而私有地址表的对象地址是对象的头的首地址。

查看公有句柄表:

和前面差不多,只是在查看内核对象时稍有区别:

kd> dd PspCidTable8414cd54  8b401080 00000000 80000020 00000101 kd> dt _handle_table 8b401080ntdll!_HANDLE_TABLE   +0x000 TableCode        : 0xa3aec001   +0x004 QuotaProcess     : (null)   +0x008 UniqueProcessId  : (null)   +0x00c HandleLock       : _EX_PUSH_LOCK   +0x010 HandleTableList  : _LIST_ENTRY [ 0x8b401090 - 0x8b401090 ]   +0x018 HandleContentionEvent : _EX_PUSH_LOCK   +0x01c DebugInfo        : (null)   +0x020 ExtraInfoPages   : 0n0   +0x024 Flags            : 1   +0x024 StrictFIFO       : 0y1   +0x028 FirstFreeHandle  : 0xa1c   +0x02c LastFreeHandleEntry : 0xa3ae4430 _HANDLE_TABLE_ENTRY   +0x030 HandleCount      : 0x272   +0x034 NextHandleNeedingPool : 0x1000   +0x038 HandleCountHighWatermark : 0x275 //TableCode后三位为1,说明有两层。 kd> dd 0xa3aec000a3aec000  8b404000 a3ae4000 00000000 00000000a3aec010  00000000 00000000 00000000 00000000a3aec020  00000000 00000000 00000000 00000000 //数组中只有前两个有值,说明最底层句柄表只有两个//查看第一个表的内容:kd> dq 8b4040008b404000  fffffffe`00000000 00000000`866d29018b404010  00000000`866d2629 00000000`866f1a098b404020  00000000`866f5c81 00000000`866e60218b404030  00000000`866e6d49 00000000`866e6a718b404040  00000000`866e6799 00000000`866e64c18b404050  00000000`866e7021 00000000`866e7d498b404060  00000000`866e7a71 00000000`866e77998b404070  00000000`866e74c1 00000000`866e8021 //随便选个句柄表项查看对应的内核对象:kd> dt _handle_table_entry 8b404050ntdll!_HANDLE_TABLE_ENTRY   +0x000 Object           : 0x866e7021 Void   +0x000 ObAttributes     : 0x866e7021   +0x000 InfoTable        : 0x866e7021 _HANDLE_TABLE_ENTRY_INFO   +0x000 Value            : 0x866e7021   +0x004 GrantedAccess    : 0   +0x004 GrantedAccessIndex : 0   +0x006 CreatorBackTraceIndex : 0   +0x004 NextFreeTableEntry : 0 //将object前三位清零查看对应的内核对象:kd> !object 0x866e7020Object: 866e7020  Type: (866d2de8) Thread    ObjectHeader: 866e7008 (new version)    HandleCount: 0  PointerCount: 1 

句柄的权限

句柄是拥有操作权限的,不然胡乱使用很恐怖的,稍微懂点就可以让你的Windows系统坏掉。

(注:每个内核对象的句柄权限是有区别的,类似于对象头和对象的关系,这里以进程对象句柄举例。 

句柄的权限就保存在刚刚的句柄表项中:

struct _HANDLE_TABLE_ENTRY{    union    {        VOID* Object;                                                       //0x0        ULONG ObAttributes;                                                 //0x0        struct _HANDLE_TABLE_ENTRY_INFO* InfoTable;                         //0x0        ULONG Value;                                                        //0x0    };    union    {        ULONG GrantedAccess;                                                //0x4        struct        {            USHORT GrantedAccessIndex;                                      //0x4            USHORT CreatorBackTraceIndex;                                   //0x6        };        ULONG NextFreeTableEntry;                                           //0x4    };}; 

将该结构体划分为几个板块:

在有一些博客上是这样讲的:

(1)这一块共两个字节,低字节保留位恒为0,高字节是给SetHandleInformation这个函数用的,比如写成函数SetHandleInformation(Handle,HANDLE_FLAG_PROTECT_FROM_CLOSE,HANDLE_FLAG_PROTECT_FROM_CLOSE),那么这个位置将会被写入0x02;

HANDLE_FLAG_PROTECT_FROM_CLOSE宏的值为0x00000002,取最低字节,最终(1)这块是0x0200。

(2)这块是访问掩码,是给OpenProcess这个函数用的

OpenProcess(dwDesiredAccess,BInheritHandle,dwProcessId);具体的存的就是这个函数的第一个参数的值。

(3)和(4)这两块共计四个字节,其中bit0-bit2存的是这个句柄的属性,其中bit2 bit0 默认为0,1;bit1表示的函数是该句柄是否可继承;OpenProcess的第二个参数与bit1有关;

bit31-bit3则是存放的该内核对象在内核中的具体的地址。

原文链接:https://blog.csdn.net/csnn2019/article/details/113818969

这个结论有对也有错。

根据上述博客提供的结果再加上我的验证得出的正确结论如下:

64位分为:

(32-55bit)24位:

首先介绍这24位的原因是,它是通过OpenProcess函数来指定访问掩码的,这个函数用过比较熟练。

其中访问掩码的Microsoft官方文档:

Process Security and Access Rights - Win32 apps | Microsoft Docs

https://docs.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights

在官方文档中介绍了通用的访问掩码:

和针对进程的访问掩码:

其中将不通用访问掩码加起来可以得到 0xFFFF,然后将通用的访问掩码加起来得到 0x1F。

然后我查看了官方文档阐述了如果采用PROCESS_ALL_ACCESS得到的句柄的前32位值的大小为:0x001FFFFF。

采用代码验证:

#include #includeusing namespace std;int main(){    cout << "test" << endl;    DWORD tempId;    cin >> tempId;    auto tempHandle = OpenProcess(PROCESS_ALL_ACCESS, 0,tempId);    if (tempHandle == NULL)    {        cout << "打开进程失败" << endl;        return 0;    }    CloseHandle(tempHandle);    return 0;} 

将结果放到od里然后定位到if (tempHandle == NULL)语句中,因为这里可以直接看到handle值:

然后运行到这里,并输入进程的PID,这里我选择输入任务管理器中的notepad++的PID:

然后查看eax的值得到句柄值:

handle == ==eax ==== 0x24

然后用Windbg段下来后通过前面讲述的查看私有句柄表的方式得到该句柄对应的句柄表项:

kd> !process 0 0 PROCESS 86906d20  SessionId: 1  Cid: 053c    Peb: 7ffdf000  ParentCid: 0a3c    DirBase: 33575000  ObjectTable: a6caa668  HandleCount:   9.    Image: ApplicationTest1.exe    //你的程序名称 kd> dt _handle_table a6caa668ntdll!_HANDLE_TABLE   +0x000 TableCode        : 0x88608000   +0x004 QuotaProcess     : 0x86906d20 _EPROCESS   +0x008 UniqueProcessId  : 0x0000053c Void   +0x00c HandleLock       : _EX_PUSH_LOCK   +0x010 HandleTableList  : _LIST_ENTRY [ 0x8859c788 - 0x8842c8a8 ]   +0x018 HandleContentionEvent : _EX_PUSH_LOCK   +0x01c DebugInfo        : (null)   +0x020 ExtraInfoPages   : 0n0   +0x024 Flags            : 0   +0x024 StrictFIFO       : 0y0   +0x028 FirstFreeHandle  : 0x28   +0x02c LastFreeHandleEntry : 0x88608ff8 _HANDLE_TABLE_ENTRY   +0x030 HandleCount      : 9   +0x034 NextHandleNeedingPool : 0x800   +0x038 HandleCountHighWatermark : 0xa kd> dq 0x88608000+0x24*0x288608048  001fffff`88175969 0000002c`0000000088608058  00000030`00000000 00000034`0000000088608068  00000038`00000000 0000003c`0000000088608078  00000040`00000000 00000044`0000000088608088  00000048`00000000 0000004c`0000000088608098  00000050`00000000 00000054`00000000886080a8  00000058`00000000 0000005c`00000000886080b8  00000060`00000000 00000064`00000000 

可以看到对应的句柄表项为:

001fffff`88175969

所以32-55bit的值为1FFFFF和微软的文档是吻合的。(不要怀疑微软的文档)。

(56-64bit)8位:

这里的结果还要分为高四位和低四位,高四位的值始终为0,而低四位的值会因为SetHandleInformation()函数设置的HANDLE_FLAG_PROTECT_FROM_CLOSE标志位而改变。

SetHandleInformation()函数官方文档 :

SetHandleInformation function (handleapi.h) - Win32 apps | Microsoft Docs

https://docs.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-sethandleinformation?msclkid=cd41032bac1a11ecb388de747b53ec30

我的验证代码如下:

#include #includeusing namespace std;int main(){    cout << "test" << endl;    DWORD tempId;    cin >> tempId;    auto tempHandle = OpenProcess(0x0001, 0,tempId);    if (tempHandle == NULL)    {        cout << "打开进程失败" << endl;        return 0;    }    SetHandleInformation(tempHandle, HANDLE_FLAG_PROTECT_FROM_CLOSE, HANDLE_FLAG_PROTECT_FROM_CLOSE);    CloseHandle(tempHandle);    return 0;} 

这一次我在打开句柄时只赋值了一个0x0001的访问掩码,为了方便观察。

通用采用了前面的办法,然后停在了SetHandleInformation(tempHandle, HANDLE_FLAG_PROTECT_FROM_CLOSE, HANDLE_FLAG_PROTECT_FROM_CLOSE);函数这里:

然后查看调用前的句柄表项值:

kd> dq 0x8864a000+0x24*0x28864a048  00000001`88175969 0000002c`000000008864a058  00000030`00000000 00000034`000000008864a068  00000038`00000000 0000003c`000000008864a078  00000040`00000000 00000044`00000000 

接着查看调用后的:

kd> dq 0x8864a000+0x24*0x28864a048  02000001`88175969 0000002c`000000008864a058  00000030`00000000 00000034`00000000 

就从00000001变到了02000001,也就是56-64中的低4位从0变成了2。

(3-31bit)29位:

这个前面实验过很多次了,这里就不实验了。

(0-3bit)3位

在WRK中它的值如下:

#define OBJ_HANDLE_ATTRIBUTES (OBJ_PROTECT_CLOSE | OBJ_INHERIT | OBJ_AUDIT_OBJECT_CLOSE)

0bit位表示OBJ_AUDIT_OBJECT_CLOSE,

1bit位表示OBJ_INHERIT ,

2bit位表示OBJ_PROTECT_CLOSE 。

OBJ_ADIT_OBJECT_CLOSE已经被取缔为表示句柄表项的锁标志了,如果为1表示句柄表项被锁住了。(这个实验我暂时弄不出来)

OBJ_INHERIT :表示是否可以被该进程创建的子进程继承。

OBJ_PROTECT_CLOSE 指示关闭该对象时是否产生一个审计事件。(这个我也弄不出来)

OBJ_INHERIT可以在三个地方修改:

1:在打开句柄时函数的OpenProcess(dwDesiredAccess,BInheritHandle,dwProcessId),中第二个参数的指定。

2:采用SetHandleInformation(tempHandle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)来增加权限。

3:直接修改句柄表项的内容。

全局句柄表

前面介绍了私有句柄表,但是没有完整的解释私有句柄表的内容。

首先私有句柄表只包含进程和线程。进程有一个唯一ID,在EPROCESS中叫做UniqueProcessId,ETHREAD有一个_CLIENT_ID字段包含了线程的唯一ID和线程对应的进程ID。

这里的唯一ID是在创建进程和线程时通过ExCreateHandle函数在全局句柄表PspClidTable中创建的句柄索引值,此表也被叫做CID句柄表(Client ID handle table)。在WRK中可以找到有效证据:

NTSTATUSPspCreateProcess(    OUT PHANDLE ProcessHandle,    IN ACCESS_MASK DesiredAccess,    IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,    IN HANDLE ParentProcess OPTIONAL,    IN ULONG Flags,    IN HANDLE SectionHandle OPTIONAL,    IN HANDLE DebugPort OPTIONAL,    IN HANDLE ExceptionPort OPTIONAL,    IN ULONG JobMemberLevel    ){    ...    Process->UniqueProcessId = ExCreateHandle (PspCidTable, &CidEntry);    ...} 

所以PID和TID其实也是句柄值,只不过是对应的全局句柄表。所以进程的PID和线程的TID也是和进程一样都是4的倍数,至于0值,在windows中0值是给空闲进程留着的。

然后全局句柄表的首地址保存在全局变量PspClidTable中,至于原因得问Microsoft了。

最后是全局句柄表中的句柄表项对应对象地址的是内核对象Body地址,而不是私有句柄表中的内核对象的对象头地址。

由于保存的是body地址,所以在内核中,根据进程或线程的唯一ID值,可以很快的找到对应的内核对象,例如以下API:

别的就和私有句柄表没差了。

句柄小结

句柄是在应用层的一种内核对象的使用方式通常是和API一起使用,在内核中也可以使用,句柄要通过句柄表来和内核对象进行关联,句柄表又分为私有句柄表和公有句柄表。

当通过API+句柄的形式来使用内核对象时,操作系统通过句柄值来访问句柄表得到对应的句柄表项的内容,然后根据句柄表项的内容验证句柄的访问权限,后再进行API对应的操作来操作内核对象。

参考资料

《Windows内核原理与实现》 潘爱民

微软官方文档

借鉴博客

私有句柄表(内核对象,并非用户对象),全局句柄表_寻梦&之璐的博客-CSDN博客(https://blog.csdn.net/csnn2019/article/details/113818969?msclkid=41ffee33ab4f11ec96839b89637392ec

转载请注明来自网盾网络安全培训,本文标题:《Windows内核-句柄》

标签:句柄windows系统

关于我

欢迎关注微信公众号

关于我们

网络安全培训,黑客培训,渗透培训,ctf,攻防

标签列表