第七章 Windows内核基础
7.1 内核理论基础
windows的启动过程
- 启动自检阶段(BIOS命令、检查硬件)
- 初始化启动阶段(加载启动盘,将主引导记录载入内存)
- Boot加载(设置内存模式)
- 检测和配置硬件阶段
- 内核加载(首先加载Ntoskrml.exe和HAL)
- 启动会话管理器(smss.exe,是windows系统中创建的第一个用户模式进程)
传统方式:BIOS+MBR
新的方式:UEFI+GPT
R0与R3通信
当应用程序调用一个有关 I/0的 API(例如 WriteFile)时,实际上这个API被封装在应用层的某个 DLL库(例如kemel32.d 和 user32.dl)文件中。而 dll动态库中的函数的更底层的函数包含在 ntdll.dll 文件中,也就是会调用在 ntdll.dll 中的 Native API函数。ntdll.dll中的Natvie API函数是成对出现的,分别以“Nt”和“Zw”开头(例如ZwCreateFile、NtCreateFile )。在 ntdll.dll 中,它们本质上是一样的,只是名字不同。
当 kernel32.dll 中的 API通过 ntdll.dll 执行时,会完成参数的检查工作,再调用一个中断(int2Eh或者SysEnter指令),从R3层进入R0层。在内核ntoskrnl.exe中有一个SSDT,里面存放了与 ntdll.dll中对应的 SSDT系统服务处理函数,即内核态的 Nt*系列函数,它们与ntdll.dll 中的函数一一对应。
在调用用户模式 Nt* API时,不会改变 Previous Mode 的状态;在调用 Zw*API时,会将 Previous Mode 改为内核态。因此,在进行Kernel Mode Driver 开发时,使用 Zw* 系列 API可以避免额外的参数列表检查,从而提高效率,也就是当通过int 2EH(WindowsXP以前)或者SYSENTER(WindowsXP及以后版本;在 AMD中为syscall)的 KiFastCallEntry()例程时,将要调用的函数所对应的服务号(也就是在 SSDT数组中的索引值)存放到寄存器EAX中,再根据存放在EAX中的索引值在SSDT数组中调用指定的服务(Nt*系列函数)。
在这个过程中,应用层的命令和数据会被系统的 I0管理器封装在一个叫作 IRP的结构中。之后,IRP会将 R3发下来的数据和命令逐层发送给下层的驱动创建的设备对象进行处理,完成对应的功能。
内核主要由各种驱动组成,驱动加载后,会生成相应的设备对象,并可以选择向R3提供一个可供访问和打开的符号链接。
内核函数
7.2 内核重要数据结构
应用层的进程、线程、文件、驱动模块、事件、信号量等对象或者打开的句柄在内核中都有与之对应的内核对象。
分类:Dispatcher对象(KPROCESS、KTHREAD)、I/O对象、其他对象(EPROCESS、ETHREAD)。
EPROCESS用于在内核中管理进程的各种信息,每个进程都对应于一个EPROCESS结构,用于记录进程执行期间的各种数据。所有进程的 EPROCESS 内核结构都被放入一个双向链表,R3在枚举系统进程的时候,通过遍历这个链表获得了进程的列表。因此,有的 Rootkit 会试图将自己进程的 EPROCESS 结构从这个链表中摘掉,从而达到隐藏自己的目的。
ETHREAD结构中,第1个成员就是线程对象KTHREAD成员,所有的ETHREAD结构也被放在一个双向链表里进行管理。EPROCESS 和 ETHREAD结构都是通过双向循环链表组织管理的。一个EPROCESS结构中包含了一个KPROCESS结构,而在一个KPROCESS结构中又有一个指向ETHRAD结构的指针。在ETHREAD结构中,又包含了 KTHREAD 结构成员。
SSDT (系统服务描述符表)用于处理应用层通过 kernel32.dll 下发的各个 API操作请求。ntdll.dll 中的 API是一个简单的包装函数,当 kemel32.dll 中的 API通过 ntdl.dll 时,会先完成对参数的检查,再调用一个中断(int 2Eh或者 SysEnter指令),从而实现从R3层进人R0层,并将要调用的服务号(也就是 SSDT 数组中的索引号 index值)存放到寄存器 EAX中,最后根据存放在EAX中的索引值在 SSDT数组中调用指定的服务(Nt*系列函数 )
TEB(线程环境块)在应用层中,进程中的每个线程(系统线程除外)都有一个自己的TEB。一个进程的所有 TEB 都存放在从0x7FFDE000开始的线性内存中,每4KB为一个完整的TEB。
7.3 内核调试基础
配置WinXP的boot.ini
配置windbg的参数