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规定了一页4KB
(4096
)个字节来存储这些信息。如果超过了就放入第二个一维数组中。所以 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 那么代表我们就是一个二层结构。 那么我们的地址 0xffff888f
8b9fe001去掉后面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
我们是取得 0XFFFFE18FA3E950
10 这个字节。
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
坚持两字,简单,轻便,但是真正的执行起来确实需要很长很长时间.当你把坚持两字当做你要走的路,那么你总会成功. 想学习,有问题请加群.群号:725864912(收费)群名称: 逆向学习小分队 群里有大量学习资源. 以及定期直播答疑.有一个良好的学习氛围. 涉及到外挂反外挂病毒 司法取证加解密 驱动过保护 VT 等技术,期待你的进入。
详情请点击链接查看置顶博客 https://www.cnblogs.com/iBinary/p/7572603.html
本文来自博客园,作者:iBinary,未经允许禁止转载 转载前可联系本人.对于爬虫人员来说如果发现保留起诉权力.https://www.cnblogs.com/iBinary/p/16281106.html
欢迎大家关注我的微信公众号.不定期的更新文章.更新技术. 关注公众号后请大家养成 不白嫖的习惯.欢迎大家赞赏. 也希望在看完公众号文章之后 不忘 点击 收藏 转发 以及点击在看功能.

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
2018-05-17 C语言第十讲,枚举类型简单说明
2018-05-17 C语言第九讲,结构体