内核理论基础
特权级别
现代计算机的CPU设计中有四个特权级别:R0、R1、R2、R3
内核运行在R0(拥有最高权限),用户程序运行在R3
例如:Windows XP体系结构图中
Hardware Abstraction Layer(硬件抽象层):用于提供硬件的低级接口
Windows XP的执行体是NTOSKRNL.EXE的上层
ntdll.dll:暴露给用户态的接口,通过win32API进行调用
Windows R3与R0通信
API调用的结构关系:
- 结构关系:用户态中调用的底层API(如I/O操作),其封装一般在kernel32.dll或user32.dll中,这两个dll含有指向ntdll.dll的指针
ntdll.dll中的Native API函数是成对出现的,分别以Nt、Zw开头。
在用户态下,Nt和Zw的API本质上是一样的,没什么区别
在内核态中,Nt开头的函数是真正执行功能的函数。Zw函数则需要通过KiSystemService最终找到Nt函数
从用户态到内核态的大体过程如下:
-
kernel32.dll中的API通过ntdll.dll执行时,完成参数检测工作
-
调用中断从R3进入R0(比如
int 2Eh
或者SysEnter
指令)
R0和R3下调用Nt*和Zw*API的情况分析:
- 从用户模式调用Nt*和Zw* API,连接的是ntdll.lib:
二者没有区别,都是通过设置系统服务表中的索引和在栈中设置参数,经有syscall指令进入内核态(Windows 2000 下使用 int 0x2e
指令中断),最终由KiSystemService
跳转到KiServiceTable
对应的系统服务例程中,R3进入R0时,代码会严格检查参数
- 从内核模式下调用Nt*和Zw*API,连接ntoskrnl.lib
关于Previous Mode:如果从用户模式调用Native API,则Previous Mode是用户态;如果从内核模式调用Native API,则Previous Mode,则Previous Mode为内核态。Native API会根据Previous Mode是否为用户态,来确定是否要进行严格的检查。
驱动
内核主要由各种驱动(在磁盘上是.sys
文件)组成,这些驱动有的是Windows系统自带的(例如ntfs.sys
、tcpip.sys
、win32k.sysc
)等,当驱动加载后,会生成对应的设备对象, 并可以选择向R3
提供一个可供访问和打开的符号链接。常见的盘符C、D、E等其实都是文件系统驱动创建的设备对象的符号链接,对应的符号链接分别是\??\C:\
、\??\D:\
、\??\E:\
,应用程序可以根据这个符号链接名通过调用CreatFile
接口获得句柄,程序就可以调用应用层函数与内核驱动进行通信。
内核驱动模块
Windows内核驱动模块是内核的重要组成部分,既有微软自己开发的内核驱动,也有第三方开发的内核驱动;既有硬件的驱动,也有软件的驱动。
内核在磁盘上的文件拓展名为.sys
,是PE文件
此部分开发需要搭建内核驱动开发环境,后续一定。。
内核数据结构
内核对象
Windows内核中使用一种很重要的数据结构管理机制,称为内核对象
应用层的进程、线程、文件、驱动模块、事件、信号量等对象或者打开的句柄在内核中都有对应的内核对象
- 内核对象的一般结构:一般分为对象头和对象体两部分。
类型
Dispatcher
对象
此类对象在对象体开始位置一般有共享的数据结构DISPATCHER_HEADER
typedef struct _DISPATCHER_HEADER {
UCHAR Type; // DISP_TYPE_*
UCHAR Absolute;
UCHAR Size;
UCHAR Inserted;
LONG SignalState;
LIST_ENTRY WaitListHead;
}
DISPATCHER_HEADER,
*PDISPATCHER_HEADER,
**PPDISPATCHER_HEADER;
特性:这些对象都是可等待的(这些内核对象可以作为参数传给内核的KeWaitForSingleObject()
和KeWaitForMultipleObjects()
函数,以及应用层的WaitForSingleObject()
和WaitForMultipleObjects()
函数)
I/O
对象
I/O
对象在对象体开始位置并未放置DISPATCHER_HEADER
结构,常见的I/O
对象有DEVICE_OBJECT
、DRIVER_OBJECT
、FILE_OBJECT
、IRP
、VPB
、KPROFILE
等
- 其他对象
这里着重记录下EPROCESS
和ETHREAD
这两个内核对象
EPROCESS:内核态下的进程控制块
每个进程都对应于一个EPROCESS
结构,关键的数据结构:
所有进程的EPROCESS都会被放入一个双向链表中,R3中可以调用API访问到内核进程的列表。
ETHREAD:内核态下的线程管理对象
- EPROCESS、KPROCESS、ETHREAD、KTHREAD结构的关系:
SSDT
SSDT(System Service Descriptor Table):系统服务描述表符,在内核中的名称为
KeServiceDescriptorTable
,该表已通过内核ntoskrnl.exe导出
SSDT在API调用时的处理
kernel32.dll中的API通过ntdll.dll时,会完成对参数的检查,再调用一个中断(int 2E 或者 SysEnter指令),从而实现从R3进入R0
然后通过SSDT数组找到对应的服务(具体是将服务号index放入eax寄存器中),然后调用指定的服务(Nt*
系列函数)
SSDT表的结构体
#pragma pack(1)
typedef struct ServiceDescriptorEntry
{
unsigned int *ServiceTableBase;
unsigned int *ServiceCounterTableBase;
unsigned int NumberOfServices;
unsigned char *ParamTableBase;
} ServiceDescriptorTableEntry_t,
*PServiceDescriptorTableEntry_t;
#pragma pack()
TEB
TEB:线程环境块。用于用户模式下。
进程中的每个线程都有一个自己的TEB(系统线程除外)。一个进程的所有TEB都存放在从
0x7FFDE000
开始的线性内存中,每4KB为一个完整的TEB。
PEB
PEB:进程环境块。用于用户模式下。
线程环境块和进程环境块的关系如下:(x86)