Windows内核对象

内核对象概述

1550655557295

在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
2
3
4
5
6
7
8
9
10
11
typedef  struct  _DISPATCHER_HEADER{
UCHAR Type ; //DISP_TYPE_*
UCHAR Abso lu te ;
UCHAR Size ; //number of DWORDs
UCHAR Inserted ;
LONG
LIST_ENTRY WaitListHead ;
}
DISPATCHER_HEADER,
*PDISPATCHER_HEADER,
**PDISPATCHER_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
2
3
4
5
6
7
8
9
10
KPROCESS pcb;				//进程的内核对象
PVOID UniqueProcessId; //进程的PID
PVOID DebugPort; //调试端口,设置为0,禁止进程被调试
EX_FAST_REF Token; //进程的权限token
UCHAR ImageFileName[16]; //进程的名字,最长16字节
PPEB Peb; //进程的环境快
PEJOB Job; //进程的job
PVOID Win32Process;
LIST_ENTRY ActiveProccessLinks; //指向正在运行的系统进程列表
PHANDLE_TABLE ObjectTable; //进程的handle表

调用线面的内核函数可以获得进程的EPROCESS结构。PsLookupProcessByProcessId函数的结构如下。

1
2
3
4
5
NTSTATUS PsLookupProcessByProcessId(
//根据进程PID拿到进程的EPROCESS结构
IN HANDLE ProcessId,
OUT PEPROCESS *Process
);
1
2
3
4
PEPROCESS PsGetCurrentProcess(
//直接获取当前进程的EPROCESS结构
VOID
);

ETHREAD

ETHREAD结构式线程的内核管理对象,每个线程都有一个对应的ETHREAD结构。ETHREAD结构也是一个不透明的结构,具体成员你并未导出,而且会随着操作系统的版本的变化而变化。在ETHREAD结构中,第一个成员就是线程对象KTHREAD成员,所有的ETHREAD结构也被放在一个双向链表里进行管理。

ETHREAD结构中的一些成员信息:

1
2
KTHREAD Tcb;			//线程内核对象
CLIENT_ID Cid; //进程PID

EPROCESS、KPROCESS、ETHREAD、KTHREAD结构之间的关系图如下。可以看出,EPROCESS和ETHREAD结构都是通过双向链表组织管理的。一个EPROCESS结构中包含了一个KPROCESS结构,而在一个KPROCESS结构又有一个指向ETHREAD结构的指针。在ETHREAD结构中,又包含了KTHREAD结构成员。

1550658031014

​ EPROCESS、KPROCESS、ETHREAD、KTHREAD结构关系图

1571159984250

Author: YuanBi
Link: https://www.basicbit.cn/2017/06/13/2017-06-13-Windows内核对象/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.