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 @   iBinary  阅读(717)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
历史上的今天:
2018-05-17 C语言第十讲,枚举类型简单说明
2018-05-17 C语言第九讲,结构体
点击右上角即可分享
微信分享提示