1. 句柄表
1.1 句柄
- 什么是句柄(内核对象)
- 当一个进程创建或者打开一个内核对象时,将获得一个句柄,通过这个句柄可以访问内核对象,句柄其实是句柄表的下标。如:
HANDLE g_hMutex = ::CreateMutex(NULL,FALSE, "XYZ");
HANDLE g_hMutex = ::OpenMutex(MUTEX_ALL_ACCESS,FALSE, "XYZ");
HANDLE g_hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
HANDLE g_hThread = ::CreateThread(NULL, 0, Proc, NULL, 0, NULL);
1.2 句柄表
1.2.1 句柄表作用
- 为什么要有句柄
- 句柄存在的目的是为了避免在应用层直接修改内核对象。
- HANDLE g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
- 如果g_hEvent存储的就是EVENT内核对象的地址,那么就意味着我们可以在应用层修改这个地址,一旦指向了无效的内核内存地址就会蓝屏。
- 3环调用OpenProcess时,只是在句柄表中添加了一个句柄,将句柄表中的句柄返回,若调用了100次OpenProcess打开同一个进程,会得到100个不同的句柄,但是句柄表中,只是多了100个句柄,并且这100个句柄的内容最终都是指向同一个进程的地址,进程对象还是只有一个。
1.2.2 句柄表存储的值
- 句柄表中,实际上存储了8个字节的数据,真正的内核对象地址是:低四字节,且低四字节的bit[0],bit[1],bit[2]设置为0后的值
1.2.3 句柄表位置
- 句柄表也是一个结构体,结构体里面的TableCode才真正指向了句柄表
1.3 通过句柄表找内核对象
1.3.1 句柄表
- 句柄表中,每个句柄的值占8个字节,句柄除以4得到该句柄在句柄表中的下标
1.3.2 获取句柄表的值
- 1.OpenProcess获取句柄 假设得到值:684
- 684/4 = 该句柄在句柄表中的下标。句柄表中,每一个句柄的数值占8字节
- 2.获取当前进程的句柄表
- kd> !process 0 0 假设得到进程结构体值:86097da0
- kd> dt _EPROCESS 86097DA0 找到0xc4的_HANDLE_TABLE结构体,得到值为0xe1928c08
- kd> dt _HANDLE_TABLE e1928c08 找到句柄表TableCode,得到值为0xe1cf6000
- 3.通过句柄,找到句柄表对应该句柄的值
- ①句柄除以四得到下标,6BC/4 = 1A1
- ②句柄表+下标*8,得到:句柄在句柄表中存储的数值
- 根据1.2.2句柄表存储的值,可以得出:该内核对象(该进程结构体)地址是86050d48(二进制后3位置0)
1.3.3 句柄对应的内核对象
- 完整的内核对象
- 所有完整的内核对象都是以一个OBJECT_HEADER结构体开头的,真正的内核对象是存储在Body中的,_EPROCESS、_ETHREAD等结构体就是存储在Body里的
- 句柄表中的内核对象指向的是一个完整的内核对象
- kd> dt _EPROCESS 86050d48+0x18
1.3.4 句柄表反调试
- 当一个进程调试我自己的进程的时候,那么这个进程的句柄表中就一定会有我的进程地址,可以判断自己的进程是否被调试了,用作反调试手段
2. 全局句柄表
2.1 全局句柄表
- 每一个进程都有一个句柄表,这个句柄表是私有的,里面存储了当前进程所打开的所有句柄,除此之外,系统还有一个全局句柄表:PsdCidTable
- 1) 所有的进程和线程无论无论是否打开,都在这个表中。
- 2) 每个进程和线程都有一个唯一的编号:PID和CID 这两个值其实就是全局句柄表中的索引。
- 进程和线程的查询,主要是以下三个函数,按照给定的PID或CID从全局变量PspCidTable查找相应的进线程对象:
- PsLookupProcessThreadByCid()
- PsLookupProcessByProcessId()
- PsLookupThreadByThreadId()
- PspCidTable指向_HANDLE_HEADER结构体,里面的TableCode才是真正句柄表
2.2 全局句柄表结构
2.3 通过PID在PspCidTable找到内核对象
- 1.打开计算器,通过任务管理器获取计算器的PID:0x414
- 2.获取全局句柄表
- kd> dd PspCidCode 得到值:e1000858
- kd> dt _HANDLE_TABLE e1000858 得到TableCode值:10030000
- kd> dq 10030000+105*8
- 3.获取句柄表里面的值
- 86201781,二进制后3位置零,得到86201780
- 查看对应的进程结构体
- kd> dt _EPROCESS 86201780,全局句柄表指向的内核对象没有OBJECT_HEADER,不需要+0x18,+0x174,看进程名是不是计算器