x64windows内核句柄表PspCidTable原理与解析

全局句柄表详解

一丶句柄表

1.1 句柄表介绍

句柄表老生常谈的话题,里面存储了 进程线程的对象信息。 通过句柄表也可以遍历出隐藏的进程。也就是说全局句柄表里面存储的并不是句柄 而是进程EPROCESS 和线程 ETHREAD

1.2 定位句柄表

在内核中有一个变量,变量叫做 PspCidTable这个变量是未导出的变量,所以我们要使用特征码搜索。 自己定位一下(PsLookUpProcessByProcessId此函数就可以)

这个变量记录的就是句柄表 _HANDLE_TABLE 结构

windbg查看变量如下:

3: kd> dp PspCidTable
fffff800`5a223518  ffff888f`880067c0 ffffe18f`a3edc290
fffff800`5a223528  ffffe18f`a3fc1e80 00000000`00000002
fffff800`5a223538  00000000`00000000 00001000`00010000
fffff800`5a223548  00000000`00000140 00007100`00010110
fffff800`5a223558  00000000`00000000 00000000`00000000
fffff800`5a223568  00000000`00000000 00000000`00000000
fffff800`5a223578  0000002e`00000000 00000000`00000000
fffff800`5a223588  00000000`00000000 ffffe18f`a3fef0c0

第一项就是 我们要找的 _HANDLE_TABLE

将变量解析为结构体类型

0: kd> dt _HANDLE_TABLE ffff888f`880067c0
nt!_HANDLE_TABLE
   +0x000 NextHandleNeedingPool : 0x2000
   +0x004 ExtraInfoPages   : 0n0
   +0x008 TableCode        : 0xffff888f`8b9fe001
   +0x010 QuotaProcess     : (null) 
   +0x018 HandleTableList  : _LIST_ENTRY [ 0xffff888f`880067d8 - 0xffff888f`880067d8 ]
   +0x028 UniqueProcessId  : 0
   +0x02c Flags            : 1
   +0x02c StrictFIFO       : 0y1
   +0x02c EnableHandleExceptions : 0y0
   +0x02c Rundown          : 0y0
   +0x02c Duplicated       : 0y0
   +0x02c RaiseUMExceptionOnInvalidHandleClose : 0y0
   +0x030 HandleContentionEvent : _EX_PUSH_LOCK
   +0x038 HandleTableLock  : _EX_PUSH_LOCK
   +0x040 FreeLists        : [1] _HANDLE_TABLE_FREE_LIST
   +0x040 ActualEntry      : [32]  ""
   +0x060 DebugInfo        : (null) 

其中在这个表中最终要的成员就是 TableCode 他才是真正的 句柄信息表 记录着所有的句柄信息。 你可以理解为 这个成员就是一个数组的首地址。而里面的内容就是数组的成员项。

其中 _HANDLE_TABLE 定义如下:(各版本平台可能不一,请用Windbg自己DT查看一下)

//0x80 bytes (sizeof)
struct _HANDLE_TABLE
{
    ULONG NextHandleNeedingPool;                                            //0x0
    LONG ExtraInfoPages;                                                    //0x4
    volatile ULONG TableCode;        //全局对象数组首地址                                       //0x8
    struct _EPROCESS* QuotaProcess;                                         //0xc
    struct _LIST_ENTRY HandleTableList;                                     //0x10
    ULONG UniqueProcessId;                                                  //0x18
    union
    {
        ULONG Flags;                                                        //0x1c
        struct
        {
            UCHAR StrictFIFO:1;                                             //0x1c
            UCHAR EnableHandleExceptions:1;                                 //0x1c
            UCHAR Rundown:1;                                                //0x1c
            UCHAR Duplicated:1;                                             //0x1c
            UCHAR RaiseUMExceptionOnInvalidHandleClose:1;                   //0x1c
        };
    };
    struct _EX_PUSH_LOCK HandleContentionEvent;                             //0x20
    struct _EX_PUSH_LOCK HandleTableLock;                                   //0x24
    union
    {
        struct _HANDLE_TABLE_FREE_LIST FreeLists[1];                        //0x40
        struct
        {
            UCHAR ActualEntry[20];                                          //0x40
            struct _HANDLE_TRACE_DEBUG_INFO* DebugInfo;                     //0x54
        };
    };
}; 

可以自己逆向也可以通过下方链接查看。

Vergilius Project | _HANDLE_TABLE

1.3 三层结构介绍

1.3.1 TableCode 详解

windows规定了 TableCode低两位是代表当前句柄表的层数,最多有三层,初始化默认就是一层,如果句柄不断增多那么就会有三层。

而最后一个掩码代表了当前是几层表结构. 0 1 2 分别代表了 1层结构 2层结构 三层结构

以上述地址为例(TableCode)

0xffff888f8b9fe001 后缀为1 那么代表它是一个两个结构.

一层结构

如果 TableCode & 3 = 0 那么就代表是一个一层表结构,简单理解为就是一个一维数组。 TableCode(抹掉低两位) 形成的指针 直接指向的就是 句柄表存储结构(_HANDLE_TABLE_ENRY)

而windows规定了一页4KB4096)个字节来存储这些信息。如果超过了就放入第二个一维数组中。所以 4096 /sizeof(_HANDLE_TABLE_ENTRY)= 256. 那么如果是一级句柄表,那么它的数组里面只能存放256_HANDLE_TABLE_ENTRY 也就是说按照windows +4方式管理进程的话,那么一个表内只能存储 256 / 4 = 40个进程

64位下的 _HANDLE_TABLE_ENTRY 大小是16个字节。32位则是八个字节

二层结构

如果值为1 那么就是二层结构, 二层结构你就认为他也是数组,只不过数组里面存放的内容是 一层结构数组的指针。

既然存储的是一维数组的指针,那么在64位的系统下一个指针是八个字节,那么句柄表存储是按照一页来存储的。那么 4096 / 8 = 512. 所以说二维数组里面能存放512项一维数组指针。在换算一下存储的进程来说. 1层表能存储40个进程 那么二级表能存储512 * 40 大小的进程(20480),所以一般我们的系统会用到二级表结构.但是很少(或者基本不会)使用到三层表.

实战:

比如我们调试的地址为 : 0xffff888f8b9fe001 用此值 & 3 = 1 那么代表我们就是一个二层结构。 那么我们的地址 0xffff888f8b9fe001去掉后面1位补零` 就是数组的起始地址。

windbg如下:

0: kd> dp 0xffff888f`8b9fe000  注意这个地址我们去掉了后面
ffff888f`8b9fe000  ffff888f`8802a000 ffff888f`8b9ff000
ffff888f`8b9fe010  ffff888f`8c0ff000 ffff888f`8ca15000
ffff888f`8b9fe020  ffff888f`8cf33000 ffff888f`8d667000
ffff888f`8b9fe030  ffff888f`8de74000 ffff888f`8ec47000
ffff888f`8b9fe040  00000000`00000000 00000000`00000000
ffff888f`8b9fe050  00000000`00000000 00000000`00000000
ffff888f`8b9fe060  00000000`00000000 00000000`00000000
ffff888f`8b9fe070  00000000`00000000 00000000`00000000

可以看到里面共有八项,这八项每一项都是一个一维数组指针。 第一项里面则是存储了 句柄表存储信息结构。也就是_HANDLE_TABLE_ENTRY

图如下:

三层结构

三层结构和二层结构一样,三层结构的话那么代表当前的TableCode指向的是一个三维数组指针,它能存储512项二维数组指针,而二维数组指针能存储 512 项一维数组指针,一维里面则存放了 句柄表信息结构 _HANDLE_TABLE_ENTRY (512 * 512 * 4 的进程,基本用不到.)

1.4 _HANDLE_TABLE_ENTRY 信息

这个结构才是我们真正记录句柄信息的结构,也就是说如果我们的 TableCode 是1级表的话,那么里面的内容都是 _HANDLE_TABLE_ENTRY . 如果是2级表.那么存的只是1级数组的指针.

结构如下:

0: kd> dt _HANDLE_TABLE_ENTRY
nt!_HANDLE_TABLE_ENTRY
   +0x000 VolatileLowValue : Int8B
   +0x000 LowValue         : Int8B
   +0x000 InfoTable        : Ptr64 _HANDLE_TABLE_ENTRY_INFO
   +0x008 HighValue        : Int8B
   +0x008 NextFreeHandleEntry : Ptr64 _HANDLE_TABLE_ENTRY
   +0x008 LeafHandleValue  : _EXHANDLE
   +0x000 RefCountField    : Int8B
   +0x000 Unlocked         : Pos 0, 1 Bit
   +0x000 RefCnt           : Pos 1, 16 Bits
   +0x000 Attributes       : Pos 17, 3 Bits
   +0x000 ObjectPointerBits : Pos 20, 44 Bits
   +0x008 GrantedAccessBits : Pos 0, 25 Bits
   +0x008 NoRightsUpgrade  : Pos 25, 1 Bit
   +0x008 Spare1           : Pos 26, 6 Bits
   +0x00c Spare2           : Uint4B

此结构其实由很多联合体构成,可以参考文档定义。

文档链接如下:

Vergilius Project | _HANDLE_TABLE_ENTRYVergilius Project | _HANDLE_TABLE_ENTRY

//0x8 bytes (sizeof)
union _HANDLE_TABLE_ENTRY
{
    volatile LONG VolatileLowValue;                                         //0x0
    LONG LowValue;                                                          //0x0
    struct
    {
        struct _HANDLE_TABLE_ENTRY_INFO* volatile InfoTable;                //0x0
    LONG HighValue;                                                         //0x4
    union _HANDLE_TABLE_ENTRY* NextFreeHandleEntry;                         //0x4
        struct _EXHANDLE LeafHandleValue;                                   //0x4
    };
    ULONG Unlocked:1;                                                       //0x0
    ULONG Attributes:2;                                                     //0x0
    struct
    {
        ULONG ObjectPointerBits:29;                                         //0x0
    LONG RefCountField;                                                     //0x4
    ULONG GrantedAccessBits:25;                                             //0x4
    ULONG ProtectFromClose:1;                                               //0x4
    ULONG NoRightsUpgrade:1;                                                //0x4
    };
    ULONG RefCnt:5;                                                         //0x4
}; 

其中这里面的 InfoTable定义的则是我们想要查看的对象信息。 这里还有一个很重要的成员. GrantedAccessBits 代表了当前句柄的权限.

windbg实战如下(接上面的windbg命令信息)

0: kd> dp ffff888f`8802a000  首先查看一维数组
ffff888f`8802a000  00000000`00000000 00000000`00000000
ffff888f`8802a010  e18fa3e9`5040ff47 00000000`00000000
ffff888f`8802a020  e18fa8ab`1080ffb9 00000000`00000000
ffff888f`8802a030  e18fa3f7`d080ffdf 00000000`00000000
ffff888f`8802a040  e18fa3f6`c080ffdf 00000000`00000000
ffff888f`8802a050  e18fa3ed`e080ffdf 00000000`00000000
ffff888f`8802a060  e18fa3f1`3080ffdf 00000000`00000000
ffff888f`8802a070  e18fa3fb`4140ffdf 00000000`00000000
0: kd> dt _HANDLE_TABLE_ENTRY ffff888f`8802a010   第一项没值,看第二项。(Pid=4)
nt!_HANDLE_TABLE_ENTRY
   +0x000 VolatileLowValue : 0n-2193354271036997817
   +0x000 LowValue         : 0n-2193354271036997817
   +0x000 InfoTable        : 0xe18fa3e9`5040ff47 _HANDLE_TABLE_ENTRY_INFO
   +0x008 HighValue        : 0n0
   +0x008 NextFreeHandleEntry : (null) 
   +0x008 LeafHandleValue  : _EXHANDLE
   +0x000 RefCountField    : 0n-2193354271036997817
   +0x000 Unlocked         : 0y1
   +0x000 RefCnt           : 0y0111111110100011 (0x7fa3)
   +0x000 Attributes       : 0y000
   +0x000 ObjectPointerBits : 0y11100001100011111010001111101001010100000100 (0xe18fa3e9504)
   +0x008 GrantedAccessBits : 0y0000000000000000000000000 (0)
   +0x008 NoRightsUpgrade  : 0y0
   +0x008 Spare1           : 0y000000 (0)
   +0x00c Spare2           : 0

查看

查看第二项得知 infoTable值为 0xe18fa3e95040ff47 也就是我们的对象信息.

此值是加密的我们需要解密。下面说如何解密

二丶句柄解密

2.1 Win10(64)版本解密

2.1.1 逆向 ExpLookupHandleTableEntry

此API根据句柄表和句柄值来查询我们想要的 _HANDLE_TABLE_ENTRY项的。 其实他本质就是根据三层结构来进行查表。

逆向结果如下:

unsigned __int64 __fastcall ExpLookupHandleTableEntry(_HANDLE_TABLE *a1, _EXHANDLE pExHandleHandle)
{
  unsigned __int64 tagBits; // rdx
  __int64 *TableCode; // r8

  tagBits = pExHandleHandle.Value & 0xFFFFFFFFFFFFFFFCui64;
  if ( tagBits >= a1->NextHandleNeedingPool )
    return 0i64;

  TableCode = (__int64 *)a1->TableCode;         // //Type Level
  if ( ((unsigned __int8)TableCode & 3) == 1 )
    return *(__int64 *)((char *)&TableCode[(tagBits >> 10) - 1] + 7) + 4 * (tagBits & 1023);

  if ( ((unsigned __int8)TableCode & 3) != 0 )
    return *(_QWORD *)(*(__int64 *)((char *)&TableCode[(tagBits >> 0x13) - 1] + 6) + 8 * ((tagBits >> 0xA) & 0x1FF))
         + 4 * (tagBits & 0x3FF);

  return (unsigned __int64)TableCode + 4 * tagBits;
}

看的不明白,下面是还原的代码。

typedef struct _EXHANDLE
{
    union
    {
        struct
        {
            ULONG TagBits : 2;
            ULONG Index : 30;
        };
        VOID *GenericHandleOverlay;
        ULONG Value;
    };
} EXHANDLE, *PEXHANDLE;

typedef struct _HANDLE_TABLE_ENTRY
{
    union
    {
        PVOID Object;
        ULONG ObAttributes;
        ULONG_PTR Value;
    };
    union
    {
        ACCESS_MASK GrantedAccess;
        LONG NextFreeTableEntry;
    };
} HANDLE_TABLE_ENTRY, *PHANDLE_TABLE_ENTRY;

// 0x80 bytes (sizeof)
typedef struct _HANDLE_TABLE
{
    ULONG NextHandleNeedingPool;
    LONG ExtraInfoPages;
    volatile ULONG TableCode;
} HANDLE_TABLE, *PHANDLE_TABLE;

PHANDLE_TABLE_ENTRY ExpLookupHandleTableEntry(
    IN PHANDLE_TABLE HandleTable,
    IN EXHANDLE Handle)

{
    ULONG_PTR i, j, k;
    ULONG_PTR CapturedTable;
    ULONG TableLevel;
    PHANDLE_TABLE_ENTRY Entry;
    const int LEVEL_CODE_MASK = 3;
    const int MIDLEVEL_THRESHOLD = 1024 * 512;
    const int LOWLEVEL_COUNT = 512;

    typedef HANDLE_TABLE_ENTRY *L1P;
    typedef volatile L1P *L2P;
    typedef volatile L2P *L3P;

    L1P TableLevel1;
    L2P TableLevel2;
    L3P TableLevel3;

    ULONG_PTR RemainingIndex;
    ULONG_PTR MaxHandle;
    ULONG_PTR Index;

    PAGED_CODE();
    Handle.TagBits = 0;
    Index = Handle.Index;

    MaxHandle = *(volatile ULONG *)&HandleTable->NextHandleNeedingPool;

    if (Handle.Value >= MaxHandle)
    {
        return NULL;
    }

    CapturedTable = *(volatile ULONG_PTR *)&HandleTable->TableCode;
    TableLevel = (ULONG)(CapturedTable & LEVEL_CODE_MASK);
    CapturedTable = CapturedTable & ~LEVEL_CODE_MASK;

    switch (TableLevel)
    {

    case 0:

        TableLevel1 = (L1P)CapturedTable;
        Entry = &(TableLevel1[Index]);
        break;

    case 1:
    {

        TableLevel2 = (L2P)CapturedTable;

        i = Index / LOWLEVEL_COUNT; // Calc table
        j = Index % LOWLEVEL_COUNT; // calc table offset

        Entry = &(TableLevel2[i][j]);
    }

    break;

    case 2:
    {
        TableLevel3 = (L3P)CapturedTable;

        i = Index / (MIDLEVEL_THRESHOLD);
        RemainingIndex = Index - i * MIDLEVEL_THRESHOLD;
        j = RemainingIndex / LOWLEVEL_COUNT;
        k = RemainingIndex % LOWLEVEL_COUNT;
        Entry = &(TableLevel3[i][j][k]);
    }
    break;

    default:
        _assume(0);
    }

    return Entry;
}

使用:

  PHANDLE_TABLE pPspCidTable = (PHANDLE_TABLE)0xffff888f880067c0; //写死了自己特征码寻找
    HANDLE handle = (HANDLE)4;
    ExpLookupHandleTableEntry(pPspCidTable, *(PEXHANDLE)&handle);
    return STATUS_SUCCESS;

返回的则是我们想要的句柄项结构。

这是参考BlockBone库的实现 如果想要稳定可以使用此方法

PHANDLE_TABLE_ENTRY ExpLookupHandleTableEntry( IN PHANDLE_TABLE HandleTable, IN EXHANDLE tHandle )
{
    ULONG_PTR TableCode = HandleTable->TableCode & 3;
    if (tHandle.Value >= HandleTable->NextHandleNeedingPool)
        return NULL;

    tHandle.Value &= 0xFFFFFFFFFFFFFFFC;

#if defined ( _WIN10_ )
    if (TableCode != 0)
    {
        if (TableCode == 1)
        {
            return (PHANDLE_TABLE_ENTRY)(*(ULONG_PTR*)(HandleTable->TableCode + 8 * (tHandle.Value >> 11) - 1) + 4 * (tHandle.Value & 0x7FC));
        }
        else
        {
            ULONG_PTR tmp = tHandle.Value >> 11;
            return (PHANDLE_TABLE_ENTRY)(*(ULONG_PTR*)(*(ULONG_PTR*)(HandleTable->TableCode + 8 * (tHandle.Value >> 21) - 2) + 8 * (tmp & 0x3FF)) + 4 * (tHandle.Value & 0x7FC));
        }
    }
    else
    {
        return (PHANDLE_TABLE_ENTRY)(HandleTable->TableCode + 4 * tHandle.Value);
    }
#elif defined ( _WIN7_ )
    ULONG_PTR Diff = HandleTable->TableCode - TableCode;

    if (TableCode != 0)
    {
        if (TableCode == 1)
        {
            return (PHANDLE_TABLE_ENTRY)(*(ULONG_PTR*)(Diff + ((tHandle.Value - tHandle.Value & 0x7FC) >> 9)) + 4 * (tHandle.Value & 0x7FC));
        }
        else
        {
            ULONG_PTR tmp = (tHandle.Value - tHandle.Value & 0x7FC) >> 9;
            return (PHANDLE_TABLE_ENTRY)(*(ULONG_PTR*)(*(ULONG_PTR*)(Diff + ((tHandle.Value - tmp - tmp & 0xFFF) >> 10)) + (tmp & 0xFFF)) + 4 * (tHandle.Value & 0x7FC));
        }
    }
    else
    {
        return (PHANDLE_TABLE_ENTRY)(Diff + 4 * tHandle.Value);
    }
#else
    if (TableCode != 0)
    {
        if (TableCode == 1)
        {
            return (PHANDLE_TABLE_ENTRY)(*(ULONG_PTR*)(HandleTable->TableCode + 8 * (tHandle.Value >> 10) - 1) + 4 * (tHandle.Value & 0x3FF));
        }
        else
        {
            ULONG_PTR tmp = tHandle.Value >> 10;
            return (PHANDLE_TABLE_ENTRY)(*(ULONG_PTR*)(*(ULONG_PTR*)(HandleTable->TableCode + 8 * (tHandle.Value >> 19) - 2) + 8 * (tmp & 0x1FF)) + 4 * (tHandle.Value & 0x3FF));
        }
    }
    else
    {
        return (PHANDLE_TABLE_ENTRY)(HandleTable->TableCode + 4 * tHandle.Value);
    }
#endif
}

2.1.2 解密 _HANDLE_TABLE_ENTRY

接1.4小结遗留问题,我们发现 _HANDLE_TABLE_ENTRY infoTable 是加密的,那么我们需要进行解密。如何解密其实这就是为什么要逆向2.1小节提供的API了。 因为windows系统内核也会使用它。只需要IDA 看看其交叉引用就知道了它是如何调用的,调用之后如何解密的。

随便查看一下交叉隐藏,下面就可以看到它的算法了。

算法如下:

(X >> 0X10)&0xFFFFFFFFFFFFFFF0ui64  == Object

那么根据我们 解析出的 InforTabe的值试试这个算法

(0xe18fa3e95040ff47 >> 0x10)&0xFFFFFFFFFFFFFFF0 == 0xFFFFE18FA3E95040

可以使用命令 !object 0xFFFFE18FA3E95040 查看是什么类型,在这里看到的是EPROCESS类型所以对其按照EPROCESS解析

0: kd> dt _EPROCESS 0XFFFFE18FA3E95040
nt!_EPROCESS
  ..........我已删除
  +0x450 ImageFileName    : [15]  "System"
  ......我已删除  

可以看到 一维数组中第一项的_HANDLE_TABLE_ENTRY 解密之后是 System进程

2.2 Win7(64)版本解密

Win764位下,获取到的值并不是加密的. 可以看到他判断了最后一位掩码. 所以说win7下的后值的后三位都是标志. 抹掉补零即可.

公式:

(X & FFFFFFFFFFFFFFF6) = object

以下是Windbg命令

0: kd> dp PspCidTable 
fffff800`04066988  fffff8a0`00004870 00000000`00000000
fffff800`04066998  ffffffff`80000020 00000000`00000101
fffff800`040669a8  ffffffff`80000308 ffffffff`80000024
fffff800`040669b8  00000000`00000000 00000000`00000113
fffff800`040669c8  00000000`00000000 00000000`00000000
fffff800`040669d8  fffff800`03fce650 00000000`00000000
fffff800`040669e8  00000000`00000000 00000000`00000000
fffff800`040669f8  00000000`00000000 00000000`00000008
0: kd> dt _HANDLE_TABLE fffff8a0`00004870
nt!_HANDLE_TABLE
   +0x000 TableCode        : 0xfffff8a0`00fdf001 //找到数组
   +0x008 QuotaProcess     : (null) 
   +0x010 UniqueProcessId  : (null) 
   +0x018 HandleLock       : _EX_PUSH_LOCK
   +0x020 HandleTableList  : _LIST_ENTRY [ 0xfffff8a0`00004890 - 0xfffff8a0`00004890 ]
   +0x030 HandleContentionEvent : _EX_PUSH_LOCK
   +0x038 DebugInfo        : (null) 
   +0x040 ExtraInfoPages   : 0n0
   +0x044 Flags            : 1
   +0x044 StrictFIFO       : 0y1
   +0x048 FirstFreeHandle  : 0xf70
   +0x050 LastFreeHandleEntry : 0xfffff8a0`02118bc0 _HANDLE_TABLE_ENTRY
   +0x058 HandleCount      : 0x308
   +0x05c NextHandleNeedingPool : 0x1000
   +0x060 HandleCountHighWatermark : 0x347
0: kd> dp 0xfffff8a0`00fdf000
fffff8a0`00fdf000  fffff8a0`00005000 fffff8a0`00fe0000
fffff8a0`00fdf010  fffff8a0`01593000 fffff8a0`02118000
fffff8a0`00fdf020  00000000`00000000 00000000`00000000
fffff8a0`00fdf030  00000000`00000000 00000000`00000000
fffff8a0`00fdf040  00000000`00000000 00000000`00000000
fffff8a0`00fdf050  00000000`00000000 00000000`00000000
fffff8a0`00fdf060  00000000`00000000 00000000`00000000
fffff8a0`00fdf070  00000000`00000000 00000000`00000000
0: kd> dp fffff8a0`00005000 //查看数组值
fffff8a0`00005000  00000000`00000000 000004cc`fffffffe
fffff8a0`00005010  fffffa80`18db0861 00000000`00000000
fffff8a0`00005020  fffffa80`18db02d1 00000000`00000000
fffff8a0`00005030  fffffa80`18e54041 00000000`00000000
fffff8a0`00005040  fffffa80`18dc7041 00000000`00000000
fffff8a0`00005050  fffffa80`18dc7b51 00000000`00000000
fffff8a0`00005060  fffffa80`18dc7661 fffffa80`00000000
fffff8a0`00005070  fffffa80`18dcfb51 fffffa80`00000000
0: kd> dt _HANDLE_TABLE_ENTRY fffff8a0`00005000 //查看第一项
nt!_HANDLE_TABLE_ENTRY
   +0x000 Object           : (null) //第一项为0并没有信息
   +0x000 ObAttributes     : 0
   +0x000 InfoTable        : (null) 
   +0x000 Value            : 0
   +0x008 GrantedAccess    : 0xfffffffe
   +0x008 GrantedAccessIndex : 0xfffe
   +0x00a CreatorBackTraceIndex : 0xffff
   +0x008 NextFreeTableEntry : 0xfffffffe
0: kd> dt _HANDLE_TABLE_ENTRY fffff8a0`00005010 //然后查看第二项(中间加了0)
nt!_HANDLE_TABLE_ENTRY
   +0x000 Object           : 0xfffffa80`18db0861 Void //定位到对象
   +0x000 ObAttributes     : 0x18db0861
   +0x000 InfoTable        : 0xfffffa80`18db0861 _HANDLE_TABLE_ENTRY_INFO
   +0x000 Value            : 0xfffffa80`18db0861
   +0x008 GrantedAccess    : 0
   +0x008 GrantedAccessIndex : 0
   +0x00a CreatorBackTraceIndex : 0
   +0x008 NextFreeTableEntry : 0
0: kd> !object 0xfffffa80`18db0860
Object: fffffa8018db0860  Type: (fffffa8018d3f910) Process
    ObjectHeader: fffffa8018db0830 (new version)
    HandleCount: 4  PointerCount: 159
0: kd> dt _EPROCESS fffffa8018db0860

nt!_EPROCESS
   .....
   +0x2e0 ImageFileName    : [15]  "System"
   .....


2.3 Win11(64)版本解密

逆向分析查看其交叉引用的位置,发现跟Win10一样

公式:

(X >> 0X10) & 0xFFFFFFFFFFFFFFF0i64;

三丶 _OBJECT_HEADER 结构

内核对象的每一个结构其开始位置都是 _OBJECT_HEADER 结构,我们就拿我们的EPROCESS来说。虽然我们解密出来直接使用 EPROCESS解析就能看到内容,但是其实它的头结构是

_OBJECT_HEADER

结构如下:

0: kd> dt _OBJECT_HEADER
nt!_OBJECT_HEADER
   +0x000 PointerCount     : Int8B
   +0x008 HandleCount      : Int8B
   +0x008 NextToFree       : Ptr64 Void
   +0x010 Lock             : _EX_PUSH_LOCK
   +0x018 TypeIndex        : UChar
   +0x019 TraceFlags       : UChar
   +0x019 DbgRefTrace      : Pos 0, 1 Bit
   +0x019 DbgTracePermanent : Pos 1, 1 Bit
   +0x01a InfoMask         : UChar
   +0x01b Flags            : UChar
   +0x01b NewObject        : Pos 0, 1 Bit
   +0x01b KernelObject     : Pos 1, 1 Bit
   +0x01b KernelOnlyAccess : Pos 2, 1 Bit
   +0x01b ExclusiveObject  : Pos 3, 1 Bit
   +0x01b PermanentObject  : Pos 4, 1 Bit
   +0x01b DefaultSecurityQuota : Pos 5, 1 Bit
   +0x01b SingleHandleEntry : Pos 6, 1 Bit
   +0x01b DeletedInline    : Pos 7, 1 Bit
   +0x01c Reserved         : Uint4B
   +0x020 ObjectCreateInfo : Ptr64 _OBJECT_CREATE_INFORMATION
   +0x020 QuotaBlockCharged : Ptr64 Void
   +0x028 SecurityDescriptor : Ptr64 Void
   +0x030 Body             : _QUAD  //指向实际的对象结构

在我调试的电脑上结构占用是0x30字节。其中最后一项成员Body 才是真正的指向实际的内核对象的(比如EPROCESS)

所以如果我们想要查看实际的内核对象的OBJECT_HEADER结构,只需要将此对象结构 - sizeof(OBJECT_HEADER)结构即可。

拿上面的win10下的句柄对象,解密后的的EPROCESS地址举例,只需要-0x30即可看到实际的OBJECT_HEADER结构。

0: kd> dt _OBJECT_HEADER 0XFFFFE18FA3E95010
nt!_OBJECT_HEADER
   +0x000 PointerCount     : 0n196681
   +0x008 HandleCount      : 0n5
   +0x008 NextToFree       : 0x00000000`00000005 Void
   +0x010 Lock             : _EX_PUSH_LOCK
   +0x018 TypeIndex        : 0x9a ''  重要点
   +0x019 TraceFlags       : 0 ''
   +0x019 DbgRefTrace      : 0y0
   +0x019 DbgTracePermanent : 0y0
   +0x01a InfoMask         : 0 ''
   +0x01b Flags            : 0x2 ''
   +0x01b NewObject        : 0y0
   +0x01b KernelObject     : 0y1
   +0x01b KernelOnlyAccess : 0y0
   +0x01b ExclusiveObject  : 0y0
   +0x01b PermanentObject  : 0y0
   +0x01b DefaultSecurityQuota : 0y0
   +0x01b SingleHandleEntry : 0y0
   +0x01b DeletedInline    : 0y0
   +0x01c Reserved         : 0
   +0x020 ObjectCreateInfo : 0xfffff800`5a11c200 _OBJECT_CREATE_INFORMATION
   +0x020 QuotaBlockCharged : 0xfffff800`5a11c200 Void
   +0x028 SecurityDescriptor : 0xffff888f`88006c6e Void
   +0x030 Body             : _QUAD

3.1 Win7下解密 Typeindex

win764位下并没有加密.直接就是最终结果.

0: kd> dt _OBJECT_HEADER FFFFFA8018DB0830
nt!_OBJECT_HEADER
   +0x000 PointerCount     : 0n158
   +0x008 HandleCount      : 0n4
   +0x008 NextToFree       : 0x00000000`00000004 Void
   +0x010 Lock             : _EX_PUSH_LOCK
   +0x018 TypeIndex        : 0x7 '' //类型直接就是明文
   +0x019 TraceFlags       : 0 ''
   +0x01a InfoMask         : 0 ''
   +0x01b Flags            : 0x2 ''
   +0x020 ObjectCreateInfo : 0xfffff800`04043e40 _OBJECT_CREATE_INFORMATION
   +0x020 QuotaBlockCharged : 0xfffff800`04043e40 Void
   +0x028 SecurityDescriptor : 0xfffff8a0`00004592 Void
   +0x030 Body             : _QUAD

3.2 Win10下 解密 TypeIndex

TypeIndex代表的是句柄的类型,在每个系统都不一样,但是在这里其实也是加密的。
我们可以通过逆向未导出的API ObGetObjectType() 来进行分析,具体自己IDA分析一波。

解密方法是 为如下:

TypeIndex ^ ((OBJECT_HEADERADDR >> 8) & 0XFF ) ^ ObHeaderCookie

其中 typdeindex ((OBJECT_HEADERADDR >> 8) & 0XFF ) 我们都是已经知道的

((OBJECT_HEADERADDR >> 8) & 0XFF ) 其实本质就是取 OBJECT_HEADER的地址的倒数第二个字节。

例如上面的 0XFFFFE18FA3E95010 我们是取得 0XFFFFE18FA3E95010 这个字节。

ObHeaderCookie 是系统定义得它是一个字节。我们使用Windbg查看。

0: kd> db ObHeaderCookie
fffff800`5a22367c  cd 1c 93 fe 02 00 00 00-00 00 00 00 00 00 00 00  ................
fffff800`5a22368c  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
fffff800`5a22369c  4f a3 58 03 01 00 00 00-00 00 00 00 00 00 00 00  O.X.............
fffff800`5a2236ac  00 00 00 00 20 e8 e5 a3-8f e1 ff ff 00 00 00 00  .... ...........
fffff800`5a2236bc  00 00 00 00 00 00 00 00-00 00 00 00 00 2d 31 01  .............-1.
fffff800`5a2236cc  00 00 00 00 e0 0a ec a3-8f e1 ff ff 30 03 e5 a3  ............0...
fffff800`5a2236dc  8f e1 ff ff 40 9c 00 00-00 00 00 00 00 00 00 00  ....@...........
fffff800`5a2236ec  00 10 00 00 40 cb 09 88-8f 88 ff ff eb ac b0 09  ....@...........

那么套用解密公式则是

0x9a ^ 0X50 ^ 0Xcd = 7

使用PChunter查看

3.3 win11下解密Typeindex

逆向 ObGetObjectType 发现跟win10一样.所以猜测 win11是一样来解密的

具体需要可以自己求证.

3.4 其它文章推荐

(65)如何根据句柄从二级、三级结构句柄表中找到内核对象_hambaga的博客-CSDN博客

解析PspCidTable句柄表 (leanote.com)

通过句柄表实现反调试 - 『脱壳破解区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

posted @ 2022-05-17 15:45  iBinary  阅读(505)  评论(0编辑  收藏  举报