内核对象概述
在Windows内核中有一种很重要的数据结构管理机制,就是内核对象。引用层的进程、线程、文件、驱动模块、时间、信号量等对象或者打开的句柄在内核中都有之对应的内核结构对象。
如上图,一个Windows内核对象可以分为对象头和对象体两个部分。在对象头中至少有1个OBJECT_HEADER和对象的额外信息。对象体紧接着对象头中的OBJECT_HEADER。一个对象指针总是 指向对象体而不是对象头。如果要访问对象头,需要将对象体指针减去一个特定的偏移值,以获取OBJECT_HEADER结构,通过OBJECT_HEADER结构定位从而访问其他对象结构辅助信息。对象体内部一般会有一个type和一个size成员,用来表示对象的类型和大小。
Windows内核对象可以分为如下3中类型。
1. Dispatcher对象
这种对象在对象体开始位置放置了一个共享的公共数据结构DISPATCHER_HEADER,其结构代码如下。包含DISPATCHER_HEADER结构的内核对象的名字都以字母 K 开头,表明这是一个内核对象,例如KPROCESS、KTHREAD、KEVENT、KSEMAPHORE、KTIMER、KQUEUE、KMUTANT、KMUTEX,但以字母 K 开头的内核对象不一定是Dispatcher对象。包含DISPATCHER_HEADER结构的内核对象都是可以等待的,也就是说。这些恶和对象可以作为参数传给内核的KeWaitForSingleObject()和KeWaitForMultipleObjects()函数,以及应用层的WaitForSingleObject()和WaitForMultipleObjects()函数。
1 | typedef struct _DISPATCHER_HEADER{ |
2. I/O对象
I/O对象在对象体开始位置并未放置DISPATCHER_HEADER结构,但通常会放置一个与type和size有关的整型成员,以表示该内核对象的类型(例如文件内核对象的类型为26)和大小。常见的IO对象包括DEVICE_OBJECT、DRIVER_OBJECT、FILE_OBJECT、IRP、VPB、KPROFILE等。
3. 其他对象
除了Dispatcher对象和IO对象,剩下的都属于其他内核对象。其中有两个最常用的内核对象,分别是进程对象(EPROCESS)与线程对象(ETHREAD)。
EPROCESS
EPROCESS用于在内核中管理进程的各种信息,每个进程都对应于一个EPROCESS结构,用于记录进程执行期间的各种数据。尽管EPROCESS结构非常大,但他是一个不透明的结构(Opaque Structure),具体成员并未导出,随操作系统的变化而变化。因此,想查看EPROCESS结构中的成员,只能查阅网上资料或使用Windbg调试器加载内核符号后进行。
所有进程的EPROCESS内核结构都被放入一个双向链表,R3在枚举系统进程的时候,通过遍历这个链表获得了进程的列表。因此,有的Rootkit会试图将自己进程的EPROCESS结构从这个链表中摘掉,从而达到隐藏自己的目的。
EPROCESS结构中的一些关键数据:
1 | KPROCESS pcb; //进程的内核对象 |
调用线面的内核函数可以获得进程的EPROCESS结构。PsLookupProcessByProcessId函数的结构如下。
1 | NTSTATUS PsLookupProcessByProcessId( |
1 | PEPROCESS PsGetCurrentProcess( |
ETHREAD
ETHREAD结构式线程的内核管理对象,每个线程都有一个对应的ETHREAD结构。ETHREAD结构也是一个不透明的结构,具体成员你并未导出,而且会随着操作系统的版本的变化而变化。在ETHREAD结构中,第一个成员就是线程对象KTHREAD成员,所有的ETHREAD结构也被放在一个双向链表里进行管理。
ETHREAD结构中的一些成员信息:
1 | KTHREAD Tcb; //线程内核对象 |
EPROCESS、KPROCESS、ETHREAD、KTHREAD结构之间的关系图如下。可以看出,EPROCESS和ETHREAD结构都是通过双向链表组织管理的。一个EPROCESS结构中包含了一个KPROCESS结构,而在一个KPROCESS结构又有一个指向ETHREAD结构的指针。在ETHREAD结构中,又包含了KTHREAD结构成员。
EPROCESS、KPROCESS、ETHREAD、KTHREAD结构关系图