首页
社区
课程
招聘
[原创]Windows内核-句柄
发表于: 2022-3-25 23:26 18616

[原创]Windows内核-句柄

2022-3-25 23:26
18616

句柄

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

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

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

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

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

注:OS环境为Win7 SP1 32位

句柄表是一个多层结构,最多有三层,最少有一层。层数由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字段是句柄表信息中最关键的字段,前面写道该字段的低2位标识了句柄表的层数,除了该作用还有别的作用。

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

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

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

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

(参考WRK)

图片描述

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

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

图片描述

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

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

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

3 解析TableCode:

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

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

6 查看值:

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

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

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

图片描述

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

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

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

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

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

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

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

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

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

图片描述

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

这个结论有对也有错。

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

64位分为:

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

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

Process Security and Access Rights - Win32 apps | Microsoft Docs

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

和针对进程的访问掩码:

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

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

采用代码验证:

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

图片描述

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

图片描述

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

图片描述

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

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

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

001fffff`88175969

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

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

SetHandleInformation()函数官方文档 :

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

我的验证代码如下:

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

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

图片描述

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

接着查看调用后的:

图片描述

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

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

在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_AUDIT_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中可以找到有效证据:

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

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

​ 最后是全局句柄表中的句柄表项对应对象地址的是内核对象Body地址,而不是私有句柄表中的内核对象的对象头地址。
由于保存的是body地址,所以在内核中,根据进程或线程的唯一ID值,可以很快的找到对应的内核对象,例如以下API:

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

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

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

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

微软官方文档

借鉴博客:

私有句柄表(内核对象,并非用户对象),全局句柄表_寻梦&之璐的博客-CSDN博客

 
 
 
//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
*/
//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
*/
 
 
 
 
 
 
 
 
 
句柄值 含义
-1 代表当前进程
-2 代表当前线程
一个为4倍数的正数 句柄表中的索引
负数 其值的绝对值为System进程中的句柄值
 
 
 
 
 
 
kd> !process 0 0
PROCESS 86e97d20  SessionId: 1  Cid: 03e8    Peb: 7ffdf000  ParentCid: 0594
    DirBase: 07f70000  ObjectTable: a79b91c0  HandleCount:  72.
    Image: notepad++.exe
 
 
//    首地址为:a79b91c0
kd> !process 0 0
PROCESS 86e97d20  SessionId: 1  Cid: 03e8    Peb: 7ffdf000  ParentCid: 0594
    DirBase: 07f70000  ObjectTable: a79b91c0  HandleCount:  72.
    Image: notepad++.exe
 
 
//    首地址为:a79b91c0
kd> dt _HANDLE_TABLE  a79b91c0
ntdll!_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
kd> dt _HANDLE_TABLE  a79b91c0
ntdll!_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
0x8b4a0000 的前四位为:  0000 
    表明只有一层句柄表,
    清零后得到句柄表的首地址:
 
    0x8b4a0000
0x8b4a0000 的前四位为:  0000 
    表明只有一层句柄表,
    清零后得到句柄表的首地址:
 
    0x8b4a0000
4.1    如果只有一层就是TableCode前30位的值
 
4.2    如果有两层就需要先将句柄值除以512,看看占满了多少个最底层句柄表,然后将在TableCode中找到前面占满了的最底层句柄表的首地址的存放地址,再后面一个就是对应的最底层句柄表了。然后将句柄值-占满句柄表的个数*512等到在对应的最底层句柄表中句柄的偏移值,然后将该值*2得到句柄表项在句柄表中的偏移值。
 
4.3    4.2类似。
 
 
这里的情况就是4.1,直接可以得到对应表的地址为0x8b4a0000
4.1    如果只有一层就是TableCode前30位的值
 
4.2    如果有两层就需要先将句柄值除以512,看看占满了多少个最底层句柄表,然后将在TableCode中找到前面占满了的最底层句柄表的首地址的存放地址,再后面一个就是对应的最底层句柄表了。然后将句柄值-占满句柄表的个数*512等到在对应的最底层句柄表中句柄的偏移值,然后将该值*2得到句柄表项在句柄表中的偏移值。
 
4.3    4.2类似。
 
 
这里的情况就是4.1,直接可以得到对应表的地址为0x8b4a0000
句柄表中存放的是句柄项,句柄项是一个结构体里面包含了句柄值:
//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
句柄表中存放的是句柄项,句柄项是一个结构体里面包含了句柄值:
//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
//通过句柄表+偏移值的方式得到句柄表项中的对象地址,然后将后三位清零后得到对象的头地址,
//然后将头地址往下偏移就可以得到对象首地址
 
这里查看前面图中句柄值为0x28的内容:
kd> dq 0x8b4a0000+0x28*2
8b4a0050  000f01ff`87b3f329 000f037f`87b3ea41
8b4a0060  00020019`a7a133c9 00000001`a8795619
8b4a0070  00000804`86e6b0d9 00000804`86de5291
8b4a0080  00000804`88002da1 00000804`86de0b99
8b4a0090  00000804`87cb48d1 00000804`88002c39
8b4a00a0  00000804`86cee6f9 00000804`880bb271
8b4a00b0  00000804`86db1779 001f0001`87c722a9
8b4a00c0  001f0003`87bccf21 001f0001`87c46831
 
 
得到句柄表项中的对象值为87b3f329
 
将前三位清零后为:
    87b3f328
 
往下偏移0x18后为:
    87b3f340
 
查看对象内容:
 
kd> !object 87b3f340
Object: 87b3f340  Type: (866f67a0) Desktop
    ObjectHeader: 87b3f328 (new version)
    HandleCount: 12  PointerCount: 689
    Directory Object: 00000000  Name: Default
//通过句柄表+偏移值的方式得到句柄表项中的对象地址,然后将后三位清零后得到对象的头地址,
//然后将头地址往下偏移就可以得到对象首地址
 
这里查看前面图中句柄值为0x28的内容:
kd> dq 0x8b4a0000+0x28*2
8b4a0050  000f01ff`87b3f329 000f037f`87b3ea41
8b4a0060  00020019`a7a133c9 00000001`a8795619
8b4a0070  00000804`86e6b0d9 00000804`86de5291
8b4a0080  00000804`88002da1 00000804`86de0b99
8b4a0090  00000804`87cb48d1 00000804`88002c39
8b4a00a0  00000804`86cee6f9 00000804`880bb271
8b4a00b0  00000804`86db1779 001f0001`87c722a9
8b4a00c0  001f0003`87bccf21 001f0001`87c46831
 
 
得到句柄表项中的对象值为87b3f329
 
将前三位清零后为:

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2023-5-10 11:40 被SnA1lGo编辑 ,原因: 细节错误
收藏
免费 12
支持
分享
打赏 + 100.00雪花
打赏次数 1 雪花 + 100.00
 
赞赏  Editor   +100.00 2022/04/18 恭喜您获得“雪花”奖励,安全圈有你而精彩!
最新回复 (2)
雪    币: 4120
活跃值: (5822)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
2
例如:TableCode = 0x88884222; 其中的低二位为10B,也就是0x2,所以该句柄表就为两层结构。 不应该是三层?
2022-3-27 21:45
0
雪    币: 1604
活跃值: (1444)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
3
不好意思,已纠正
2022-3-28 10:28
0
游客
登录 | 注册 方可回帖
返回
//