句柄

全局句柄表

在windows内核中一共有三种句柄表:全局句柄表、私有句柄表、内核句柄表。

全局句柄表中只记录两种东西,进程和线程

PID为4的倍数

x86系统的句柄占8字节 有四字节描述属性权限等信息

PID除4再乘8为全局句柄表索引

PspCidTable句柄表全局变量,结构为_HANDLE_TABLE

这里我随便找一个进程进行演示

image-20201215113723317

查看PspCidTable

image-20201215114141172

使用 _HANDLE_TABLE解析句柄表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1: kd> dt _HANDLE_TABLE 8aa010a8
ntdll!_HANDLE_TABLE
+0x000 TableCode : 0x9acf1001 //如果为0x9acf1000指向大小为4k的句柄 如果为0x9acf1001指向大小为4k的句柄目录表表中的每一项4字节成员都指向4k的句柄表
+0x004 QuotaProcess : (null) //所属进程
+0x008 UniqueProcessId : (null) //所属进程id
+0x00c HandleLock : _EX_PUSH_LOCK //锁
+0x010 HandleTableList : _LIST_ENTRY [ 0x8aa010b8 - 0x8aa010b8 ] //链表
+0x018 HandleContentionEvent : _EX_PUSH_LOCK
+0x01c DebugInfo : (null)
+0x020 ExtraInfoPages : 0n0
+0x024 Flags : 1
+0x024 StrictFIFO : 0y1
+0x028 FirstFreeHandle : 0xb1c
+0x02c LastFreeHandleEntry : 0x9acf2df0 _HANDLE_TABLE_ENTRY //正在释放的句柄索引实例
+0x030 HandleCount : 0x29c //句柄表中句柄的数量
+0x034 NextHandleNeedingPool : 0x1000
+0x038 HandleCountHighWatermark : 0x2ef

句柄表的大小为4k,每个句柄加上属性占8字节,所以每个句柄表只能存储512个句柄,3288除4得822,822-512=310,所以这个进程的句柄在第二个表中的第310项

image-20201215133600248

310的十六进制为136,每一项为8字节所以要乘8

image-20201215135630982

0x8778b381最后的1为权限,有137三种,去掉权限位就能使用_EPROCESS解析

image-20201215143607991

这里的每个句柄都包含这样一个对象,那么如何区分这个句柄是进程的还是线程的呢?

每一个内核对象都有一个对象头,结构为_OBJECT_HEADER

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0: kd> dt _OBJECT_HEADER -v
nt!_OBJECT_HEADER
struct _OBJECT_HEADER, 12 elements, 0x20 bytes
+0x000 PointerCount : Int4B
+0x004 HandleCount : Int4B
+0x004 NextToFree : Ptr32 to Void
+0x008 Lock : struct _EX_PUSH_LOCK, 7 elements, 0x4 bytes
+0x00c TypeIndex : UChar
+0x00d TraceFlags : UChar
+0x00e InfoMask : UChar
+0x00f Flags : UChar
+0x010 ObjectCreateInfo : Ptr32 to struct _OBJECT_CREATE_INFORMATION, 9 elements, 0x2c bytes
+0x010 QuotaBlockCharged : Ptr32 to Void
+0x014 SecurityDescriptor : Ptr32 to Void
+0x018 Body : struct _QUAD, 2 elements, 0x8 bytes

需要注意的是,这个结构中的实体在+0x18的位置+0x018 Body : _QUAD

所以查看的时候要减去0x18

1
2
3
4
5
6
7
8
9
10
11
12
13
14
0: kd> dt _OBJECT_HEADER 0x8778b380-18
nt!_OBJECT_HEADER
+0x000 PointerCount : 0n35 //引用计数 为0才释放内核对象
+0x004 HandleCount : 0n2 //句柄引用计数
+0x004 NextToFree : 0x00000002 Void
+0x008 Lock : _EX_PUSH_LOCK
+0x00c TypeIndex : 0x7 '' //对象是什么类型 通过这个就能知道对象是进程还是线程
+0x00d TraceFlags : 0 ''
+0x00e InfoMask : 0x8 ''
+0x00f Flags : 0 ''
+0x010 ObjectCreateInfo : 0x871ef940 _OBJECT_CREATE_INFORMATION
+0x010 QuotaBlockCharged : 0x871ef940 Void
+0x014 SecurityDescriptor : 0x97481f56 Void
+0x018 Body : _QUAD

查看ObTypeIndexTable对象类型数组

image-20201215151042034

找到OBJECT中对应的对象类型

image-20201215151341175

使用_OBJECT_TYPE结构解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
0: kd> dt _OBJECT_TYPE 85cd9eb0
ntdll!_OBJECT_TYPE
+0x000 TypeList : _LIST_ENTRY [ 0x85cd9eb0 - 0x85cd9eb0 ]
+0x008 Name : _UNICODE_STRING "Process" //在这可以看到此object的类型为为进程
+0x010 DefaultObject : (null)
+0x014 Index : 0x7 ''
+0x018 TotalNumberOfObjects : 0x30
+0x01c TotalNumberOfHandles : 0xe1
+0x020 HighWaterNumberOfObjects : 0x34
+0x024 HighWaterNumberOfHandles : 0xf7
+0x028 TypeInfo : _OBJECT_TYPE_INITIALIZER
+0x078 TypeLock : _EX_PUSH_LOCK
+0x07c Key : 0x636f7250
+0x080 CallbackList : _LIST_ENTRY [ 0x85cd9f30 - 0x85cd9f30 ]

私有句柄表

windows给每个进程都分配了一个私有句柄表,用于打开其他进程的句柄。

私有句柄表在进程中的+0xf4的位置

image-20201216113002264

这里我使用explorer进程作为例子

image-20201216135344299

写个程序打开这个进程

1
2
3
4
5
6
7
8
9
10
11
#include<windows.h>
#include <stdio.h>

int main()
{
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 1720);
printf("hProcess = %d\r\n", hProcess);

system("pause");
return 0;
}

image-20201216143732188

在windbg中句柄表的全局变量已经写出来了,就不需要像上面一样在PspCidTable找了

image-20201216141924983

使用 _HANDLE_TABLE解析

image-20201216143605754

open返回的句柄为32,32除4得8

image-20201216145221306

9=1001 去掉后三位=1000=8

image-20201216151148343

但是直接这样看会发现ImageFileName是三个问号

image-20201216151444569

全局句柄表 每个元素就是对象

私有句柄表 每个元素是对象头

因为私有句柄表不是直接对象了,是对象头,全局句柄表才是直接是对象,所以这里要加上0x18

image-20201216151903236

权限

image-20201216194216640

后三位是权限为,尝试把1改为0

image-20201216153904447

尝试结束进程发现无法结束,因为在关闭程序的时候,如果没有主动去关闭句柄,系统就会自己把私有句柄表里的句柄关闭,

当他去关闭的时候,发现这个位已经置0,所以无法关闭

image-20201216154348360

在这我重新写一段代码,这次我主动关闭句柄

1
2
3
4
5
6
7
8
9
int main()
{
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 1720);
printf("hProcess = %d\r\n", hProcess);
system("pause");
CloseHandle(hProcess);
system("pause");
return 0;
}

重新打开

image-20201216160120970

同样把1改成0

image-20201216160751816

按回车关闭句柄后继续按回车发现进程无法正常退出

image-20201216161302470

测试把继承设为TRUE

1
2
3
4
5
6
7
8
9
10
11
#include<windows.h>
#include <stdio.h>
int main()
{
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, TRUE, 1720);
printf("hProcess = %d\r\n", hProcess);
system("pause");
CloseHandle(hProcess);
system("pause");
return 0;
}

可以看到最后一位变成了b

image-20201216162412537

这个继承是什么东西呢?

假如一个父进程创建了一个子进程,那么他们都拥有属于自己的一张私有句柄表。

1
2
3
4
5
6
7
8
9
10
11
12
BOOL CreateProcess(
LPCWSTR pszImageName,
LPCWSTR pszCmdLine,
LPSECURITY_ATTRIBUTES psaProcess,
LPSECURITY_ATTRIBUTES psaThread,
BOOL fInheritHandles,
DWORD fdwCreate,
LPVOID pvEnvironment,
LPWSTR pszCurDir,
LPSTARTUPINFOW psiStartInfo,
LPPROCESS_INFORMATION pProcInfo
);

BOOL fInheritHandles这个位就能设置是否可以继承父进程的句柄表,注意,这里的继承并不是共用一个表的指针,而是相当于深拷贝出来一个句柄表。但是,句柄表里面的每个句柄也能靠上面讲的这个权限位来规定自己是否能被别人继承。

A进程用OpenProcess打开B进程的时候,如果是否继承设置为TRUE,由这个A进程创建出来的子进程将继承该句柄。否则,进程将不会继承此句柄。

这里我测试创建一个进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<windows.h>
#include <stdio.h>

int main()
{

STARTUPINFOA lpStartupInfo = { 0 };
lpStartupInfo.cb = sizeof(STARTUPINFOA);
PROCESS_INFORMATION lpProcessInformation = { 0 };

HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, TRUE, 1720);
printf("hProcess = %d\r\n", hProcess);
system("pause");
CreateProcessA("1.exe", NULL, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &lpStartupInfo, &lpProcessInformation);
system("pause");
return 0;
}

image-20201216174448359

首先查看父进程的私有句柄表,可以看到OpenProcess出来的句柄为86c3253b

image-20201216174658613

再查看子进程的私有句柄表,可以看到是一样的

image-20201216175045340

References:

《牛逼的火哥》

《Windows Internals》