【软件逆向-x64内核实验7.1补充-句柄表】此文章归类为:软件逆向。
全局句柄表是一个内核中存储进程线程内核对象引用的一个结构,句柄表是啥能干啥我就不说了,下面我们来看一下我们经常用的一个导出函数PsLookupProcessByProcessId
首先我们在导出表里找一下这个函数,跟进去
如果大家有xp下搜索全局句柄表的经验的话会记得在win732或xp时候我们要找的PspCidTable就在这个函数里被引用了,不过在我这个win10-22h2下面是如下的引用方式
跟进去
可以看到是在PspReferenceCidTableEntry这个函数中直接引用的全局句柄表结构体地址
我们也可以通过从PsCidTable反过来找引用位置来确定自己的特征码搜索位置和方式
根据上面的引用方式我们可以确定下来我们的一个搜索方法,这是最直观也最简单的搜索方法,方便大家理解这个过程
下面我们来了解一下全局句柄表的结构以及遍历方式
PspCidTable: 地址是一个结构体 HANDLE_TABLE—>TableCode指向一张全局句柄表。
TableCode:地址的低2位记录这个表是一级句柄表,还是二级或者三级;00表示一级,01表示二级,10表示三级;
如果是一级:那么TableCode指向的表直接是内核地址;
如果是二级:那么TableCode指向的是一个存储了句柄表地址的表,再由这个表指向的一个表这个表里存储的地址指向内核地址.
如果是三级:则一层一层下去。一般不会不存在三级。
下面我们到环境里找一个看看,我这边启动了一个dbgview他的pid是1824
我先来说一下win10-22h2和win7下的变化吧
在我这个环境上现在的句柄表是这样计算的
首先我们的pid是1824,跟win7时候一样我们拿1824/4 = 456(0x1c8)
456/256=1 456%256=200(0xc8)
说明我们在第二张表上的第c8个HANDLE_TABLE_ENTRY
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
1
: kd> dq PspCidTable
fffff801`
302fc5c8
ffffd08d`
38a1ed00
ffffe081`b22bc920
fffff801`
302fc5d8
ffffe081`b22b8f00
00000000
`
00000002
fffff801`
302fc5e8
00000000
`
00000000
00010000
`
00001000
fffff801`
302fc5f8
00000000
`
00000000
00009705
`
00000000
fffff801`
302fc608
00000000
`
00000000
00000000
`
00000000
fffff801`
302fc618
00000000
`
00000000
00000000
`
00000000
fffff801`
302fc628
00000000
`
00000000
00000000
`
00000000
fffff801`
302fc638
ffffe081`b22fdae0 fffff801`
30646000
1
: kd> dt _HANDLE_TABLE ffffd08d`
38a1ed00
ntdll!_HANDLE_TABLE
+
0x000
NextHandleNeedingPool :
0x3400
+
0x004
ExtraInfoPages :
0n0
+
0x008
TableCode :
0xffffd08d
`
3c3c6001
+
0x010
QuotaProcess : (null)
+
0x018
HandleTableList : _LIST_ENTRY [
0xffffd08d
`
38a1ed18
-
0xffffd08d
`
38a1ed18
]
+
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)
1
: kd> dq ffffd08d`
3c3c6000
ffffd08d`
3c3c6000
ffffd08d`
38ab2000
ffffd08d`
3c3c7000
ffffd08d`
3c3c6010
ffffd08d`
3c8cb000
ffffd08d`
3d1f9000
ffffd08d`
3c3c6020
ffffd08d`
3db2e000
ffffd08d`
3e47b000
ffffd08d`
3c3c6030
ffffd08d`
3f129000
ffffd08d`
3db2d000
ffffd08d`
3c3c6040
ffffd08d`
40efe000
ffffd08d`
460d1000
ffffd08d`
3c3c6050
ffffd08d`
40cff000
ffffd08d`
3d238000
ffffd08d`
3c3c6060
ffffd08d`
67cff000
00000000
`
00000000
ffffd08d`
3c3c6070
00000000
`
00000000
00000000
`
00000000
1
: kd> dt _HANDLE_TABLE_ENTRY ffffd08d`
3c3c7000
+
0x10
*
0xc8
ntdll!_HANDLE_TABLE_ENTRY
+
0x000
VolatileLowValue :
0n
-
2269328954387013655
+
0x000
LowValue :
0n
-
2269328954387013655
+
0x000
InfoTable :
0xe081b956
`e340dfe9 _HANDLE_TABLE_ENTRY_INFO
+
0x008
HighValue :
0n0
+
0x008
NextFreeHandleEntry : (null)
+
0x008
LeafHandleValue : _EXHANDLE
+
0x000
RefCountField :
0n
-
2269328954387013655
+
0x000
Unlocked :
0y1
+
0x000
RefCnt :
0y0110111111110100
(
0x6ff4
)
+
0x000
Attributes :
0y000
+
0x000
ObjectPointerBits :
0y11100000100000011011100101010110111000110100
(
0xe081b956e34
)
+
0x008
GrantedAccessBits :
0y0000000000000000000000000
(
0
)
+
0x008
NoRightsUpgrade :
0y0
+
0x008
Spare1 :
0y000000
(
0
)
+
0x00c
Spare2 :
0
|
可以看到ObjectPointerBits是0xe081b956e34
我们先是0xe081b956e34 << 4 = 0xe081b956e340
在0xe081b956e340 | 0xffff000000000000 = ffffe081b956e340
因为我们知道这是个process,所以直接dt一下看看0x5a8的位置
可以看到这就是我们的这个dbgview进程
下面我们用驱动代码的方式来实现一下这个过程
我这里只做了给了固定的pid解析到process对象,大家可以根据我的代码拓展一下写一个遍历全局句柄表的方法
注意:全局句柄表里不止存了进程还有线程对象,如何去区分他们两个可以根据OBJECT_HEADER来分辨
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
#pragma once
#include <ntifs.h>
#include <windef.h>
typedef struct _HANDLE_TABLE_ENTRY
{
union
{
LONG_PTR VolatileLowValue;
LONG_PTR LowValue;
PVOID InfoTable;
LONG_PTR RefCountField;
struct
{
ULONG_PTR Unlocked :
1
;
ULONG_PTR RefCnt :
16
;
ULONG_PTR Attributes :
3
;
ULONG_PTR ObjectPointerBits :
44
;
};
};
/
*
union
{
LONG_PTR HighValue;
struct _HANDLE_TABLE_ENTRY
*
NextFreeHandleEntry;
EXHANDLE LeafHandleValue;
struct
{
ULONG32 GrantedAccessBits :
25
;
ULONG32 NoRightsUpgrade :
1
;
ULONG32 Spare1 :
6
;
};
ULONG32 Spare2;
};
*
/
} HANDLE_TABLE_ENTRY,
*
PHANDLE_TABLE_ENTRY;
typedef struct _HANDLE_TABLE_FREE_LIST
{
ULONG_PTR FreeListLock;
PHANDLE_TABLE_ENTRY FirstFreeHandleEntry;
PHANDLE_TABLE_ENTRY lastFreeHandleEntry;
LONG32 HandleCount;
ULONG32 HighWaterMark;
ULONG32 Reserved[
8
];
} HANDLE_TABLE_FREE_LIST,
*
PHANDLE_TABLE_FREE_LIST;
typedef struct _HANDLE_TABLE
{
ULONG32 NextHandleNeedingPool;
LONG32 ExtraInfoPages;
ULONG_PTR TableCode;
PEPROCESS QuotaProcess;
LIST_ENTRY HandleTableList;
ULONG32 UniqueProcessId;
union
{
ULONG32 Flags;
struct
{
BOOLEAN StrictFIFO :
1
;
BOOLEAN EnableHandleExceptions :
1
;
BOOLEAN Rundown :
1
;
BOOLEAN Duplicated :
1
;
BOOLEAN RaiseUMExceptionOnInvalidHandleClose :
1
;
};
};
ULONG_PTR HandleContentionEvent;
ULONG_PTR HandleTableLock;
union
{
HANDLE_TABLE_FREE_LIST FreeLists[
1
];
BOOLEAN ActualEntry[
32
];
};
PVOID DebugInfo;
} HANDLE_TABLE,
*
PHANDLE_TABLE;
BOOLEAN TestLookupHandleTable(ULONG64 pid) {
PUCHAR lookupAddr
=
(PUCHAR)PsLookupProcessByProcessId;
PVOID E8Addr
=
NULL;
ULONG32 offset
=
0
;
for
(size_t i
=
0
; i <
0x100
; i
+
+
)
{
if
(lookupAddr[i]
=
=
0xe8
) {
E8Addr
=
&lookupAddr[i];
offset
=
*
(PULONG32)(&lookupAddr[i
+
1
]);
break
;
}
}
if
(E8Addr
=
=
NULL) {
return
FALSE;
}
PUCHAR PspReferenceCidTableEntryAddr
=
NULL;
PspReferenceCidTableEntryAddr
=
(PUCHAR)((ULONG64)E8Addr
+
4
+
offset);
PVOID X35Addr
=
NULL;
PLONG64 PsCidTable
=
NULL;
offset
=
0
;
for
(size_t i
=
0
; i <
0x150
; i
+
+
)
{
if
(PspReferenceCidTableEntryAddr[i]
=
=
0x4c
&&
PspReferenceCidTableEntryAddr[i
+
1
]
=
=
0x8b
&&
PspReferenceCidTableEntryAddr[i
+
2
]
=
=
0x35
) {
offset
=
*
(PULONG32)&PspReferenceCidTableEntryAddr[i
+
3
];
X35Addr
=
&PspReferenceCidTableEntryAddr[i
+
3
];
break
;
}
}
if
(offset
=
=
0
) {
return
FALSE;
}
PsCidTable
=
(PLONG64)((ULONG64)X35Addr
+
4
+
offset);
KdPrint((
"[PsCidTable Addr] %p\r\n"
, PsCidTable));
/
/
我们这里只做演示所以默认就是二级句柄表
PHANDLE_TABLE handleTable
=
PsCidTable[
0
];
PULONG64 tableArr
=
(PULONG64)((ULONG64)handleTable
-
>TableCode &
0xfffffffffffffff0
);
KdPrint((
"[HANDLE_TABLE_ENTRY Addr] %p\r\n"
, tableArr));
pid
/
=
4
;
int
pages
=
pid
/
256
;
int
index
=
pid
%
256
;
KdPrint((
"[attributes]pages:%d---index:%d\r\n"
, pages, index));
PHANDLE_TABLE_ENTRY handleTableEntry
=
(PHANDLE_TABLE_ENTRY)(tableArr[pages]
+
0x10
*
index);
KdPrint((
"[HANDLE_TABLE_ENTRY Addr] %p\r\n"
, handleTableEntry));
ULONG64 pointer
=
handleTableEntry
-
>ObjectPointerBits;
PUCHAR proc
=
((pointer <<
4
) |
0xffff000000000000
);
KdPrint((
"[process name] %s\r\n"
, (PCHAR)(proc
+
0x5a8
)));
return
TRUE;
}
|
全局句柄表和私有句柄表的结构体是共用的,所以说结构是一模一样的,只不过私有句柄表里存储的不只是进程线程还有其他的例如事件、文件、管道等等
既然是私有句柄表根据我们平时的编码过程可以理解,这个表一定是跟进程相关,甚至是存储到了进程对象中
大家可以回到我们之前那篇进程对象的文章里看下,在0x570的位置有个ObjectTable,这就是我们私有句柄表的地址
可以在环境里找个进程看一下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
!process
0
0
。。。。
PROCESS ffff9f8f09d29080
SessionId:
1
Cid:
1f28
Peb:
344ac2e000
ParentCid:
1084
DirBase:
0ad95000
ObjectTable: ffffb0871ab44d80 HandleCount:
194.
Image: dbgview64.exe
。。。。
1
: kd> dt _HANDLE_TABLE ffffb0871ab44d80
ntdll!_HANDLE_TABLE
+
0x000
NextHandleNeedingPool :
0x400
+
0x004
ExtraInfoPages :
0n0
+
0x008
TableCode :
0xffffb087
`
1a102000
+
0x010
QuotaProcess :
0xffff9f8f
`
09d29080
_EPROCESS
+
0x018
HandleTableList : _LIST_ENTRY [
0xffffb087
`
1ab4f558
-
0xffffb087
`
197b4918
]
+
0x028
UniqueProcessId :
0x1f28
+
0x02c
Flags :
0
+
0x02c
StrictFIFO :
0y0
+
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)
|
解析这个表的过程跟解析我们全局句柄表是一模一样的
大家可以参考我们解析全局句柄表的过程写一个函数来解析目标进程的私有句柄表
第二大家看一下HandleTableList的位置,我们全局句柄表的这个位置指向的是自己,这里则是一个链,大家可以根据这个链看一下能够否遍历所有的进程出来
具体代码我后面找个时间推到git上大家拉下去看吧,距离上一篇已经隔了几周了,这几周太忙了估计后面也不会像国庆时候那样有充足的时间来整理博文,不过所有的内容最终都会有的,我会慢慢找时间一点一点写
更多【软件逆向-x64内核实验7.1补充-句柄表】相关视频教程:www.yxfzedu.com