第7章:Windows 内核基础(内核调试暂略)

操作系统分为两层:应用层和内核层。应用层通过系统调用进入内核,由系统底层完成相应的功能。

驱动程序可以以内核模块方式加载。

内核理论基础

权限级别

目前操作系统的特权层级只有两个 Ring 0 和 Ring 3 .核心态工作在 R0 级,用户态工作在 R3 级。

HAL 是一个可加载的核心模块 HAL.dll ,它为运行在 Win XP 上的硬件平台提供低级接口. Win XP 的执行体是 NTOKRNL.EXE 的上层,内核在其下层。用户导出且可以调用的函数接口在 NTDLL.dll 中。

内存空间布局

以 32 位操作系统为例,通过段选择符和偏移地址可以得到一个线性地址,页表机制将线性地址映射为物理地址.

 

64位系统因为可寻址的空间过于庞大,因此系统一般只会用 40 几位,同时存在很多空洞。

 

Windows 内核启动过程

1.BIOS 引导阶段

(1).启动自检阶段

打开电源时,计算机从 BIOS 中载入必要的指令,然后进行自检操作并进行硬件的初始化检查,同时在屏幕上显示信息。

(2).初始化启动阶段

BIOS 加载启动盘,将主引导记录(MBR)中的引导代码载入内存,并执行该代码。该段代码搜寻 MBR 中的分区表并找到活动分区,并将第一个扇区中的引导代码载入内存。此段代码检测当前使用的文件系统,查找 ntldr 文件,并启动它。BIOS 将控制权交给 ntldr ,由 ntldr (在 win7 中是 Bootmgr)完成操作系统的启动工作。

2. ntldr 引导阶段

(1) BOOT 加载阶段

①设置内存模式。 如果是 x86 处理器并且是 32 位操作系统,则设置为 32 位平坦模式;如果是 64 位则设置为 64 位内存模式。

②启动一个简单的文件系统,以定位 boot.ini 、ntoskrnl 、Hal 等启动文件

③读取 boot.ini 文件

(2) 检测和配置硬件阶段

检查和配置一些硬件设备:系统固件、总线和适配器、显示适配器、键盘、通信端口、磁盘等等。

(3)内核加载阶段

ntldr 首先加载 Windows 内核 Ntoskrnl.exe 和硬件抽象层(HAL)。HAL 会对底层硬件的特性进行隔离,为操作系统提供统一的接口。接下来,ntldr 从注册表的 CurrentControlSet 键下读取这台机器安装的驱动程序,然后依次加载驱动程序。之后,初始化底层设备驱动,在 Service 键下查找 Start 键值(可以为 0、1、2、3、4,数值越小启动越早)为 0 和 1 的设备驱动。Service_BOOT_START(0)表示内核刚刚初始化,加载的都是与系统核心有关的重要驱动程序(比如磁盘驱动程序);Service_System_Start(1) 稍晚一些;Service_Auto_Start(2)是从登录界面开始出现的,如果登录速度很快,则驱动未加载就以登入;3 表示需要的时候手动加载,4 表示禁止加载 。

3.内核引导阶段

(1)驱动程序加载完成,内核会启动会话管理器,即 smss.exe 程序,是 Windows 系统中第一个创建的用户模式进程,作用如下:

①.创建系统环境变量

②.加载 win32k。sys,它是 Windows 子系统的内核模式部分

③.启动 csrss.exe,它是 Windows 子系统的用户模式部分

④.启动 winlogon.exe 

⑤.创建虚拟内存页面文件

⑥.执行上次系统重启未完成的重命名工作(PendingFileRename)

(2) Windows 子系统启动的 winlogon.exe 系统服务提供对用户的登录和注销支持,作用如下:

①.启动服务子系统 services.exe ,也称服务控制管理器(SCM)

②.启动本地安全授权(LSA)过程——lsass.exe

③.显示登录界面

如果用户提供的信息正确,则登入。

 

Win 7 在加载 Bootmgr 后的启动方式:

 

 

传统的借助 BIOS 和 MBR 完成系统的引导和启动有以下不足:最多支持 2TB 的空间用于启动代码;必须从指定的扇区中读取启动代码(包含在 MBR 中),然后从活动分区中引导并启动 OS

新型的 UEFI(Unified Extensible Firmware Interface,统一可拓展固件接口),支持更大的地址空间,具备文件系统的支持能力,可以开发出在 UEFI 下运行的应用程序,然后把它们放到任意分区中运行。

Windows 安装程序因此可以直接放到 U 盘上对电脑进行安装。

而 GPT 只有基于 UEFI 平台的主板才支持 GPT 分区引导启动。

 

Windows R0 与 R3 通信

其它不需要 R0 和 R3 进行通信的 API 可以直接在 R3 层完成参数检测并执行指令。

 

(1) 从用户模式调用 Nt * 和 Zw * API ,连接 ntdll.lib

二者没有区别,都是通过设置 SSDT(系统服务表)中的索引中设置参数,经由 SYSENTER(或 syscall )指令进入内核态(而不是像 Windows 2000 中那样通过 int 2e 指令中断),由于是从用户模式进入内核模式,存在风险,因此会在进入内核态前进行严格的参数检查,最终由 KiSystemService 跳转到 KiServiceTable 对应的系统服务例程中。

(2)从内核模式调用 Nt*  和  ZW*  API,连接 ntoskrnl.lib

Nt * 系列 API 将直接调用对应的函数代码,而 Zw* 系列 API 则通过 KiSystemService 最终跳转到对应的函数代码处。

 

重要的是对内核Previous Mode 的改变:如果从用户模式调用 Native API(即 Nt * 和  Zw * 系列,CreateFile 等都是被封装后的 API ),则 Previous Mode 是用户态内核模式调用 Native API 则为内核态

Previous Mode 是用户态时,Native API  会进行额外的严格参数检查内核态时则不会

 

当从用户模式调用 Nt* 系列的 API 时,不会改变 Previous Mode 的状态,而从用户模式调用 Zw * API 时,会将其改为内核态。在进入内核态前的参数检查会被省去,因此使用 Zw * 系列 API 会减少额外的参数检查 。

而从用户模式调用 Zw* 系列的 API 不会对 Previous Mode 进行改变。

 

 

应用层命令和数据会被系统的 I/O 管理器封装在一个叫 IRP 的结构中,IRP 会将 R3 发下来的数据和命令逐层发给下层的驱动创建的设备对象进行处理。

内核主要由各种驱动(在磁盘上是.sys 文件)组成,有自带的也有第三方软件厂商提供的。

驱动加载后会生成对应的设备对象,并可以选择向 R3 提供一个可供访问和打开符号链接(C 盘等的盘符就是由系统驱动创建的,对应的符号链接名为 \??\C:\ )。

应用程序根据内核驱动符号链接名调用 CreateFile() 打开,获得一个句柄就可以调用应用层函数和内核驱动进行通信了,比如 ReadFile() 等。

 

 

内核驱动执行了 DriverEntry() 后会开启对相应应用层程序接收。API 被调用后会将传递给 API 的数据和命令封装为 IRP 并直接传递给相应的驱动分发派遣函数处理。分发派遣函数处理完后,驱动可以决定这个 IRP 是继续向下传递还是进行其它操作。

 

内核函数

 

内核函数在被调用的时候需要注意它的 IRQL (Interrupt Request Level,中断请求级别) 的要求。内核在不同的情况下会运行在不同的 IRQL 级别上,符合该级别的内核函数才能被调用。

数值越大,优先级越高

 

Passive_Level:IRQL 的最低级别没有被屏蔽的中断。在这个级别上,线程执行用户模式,可以访问分页内存,并对所有中断都作出响应

APC_Level(Asynchronous Procedure Call): 只有 APC 级别的中断屏蔽,可以访问分页内存。当有 APC 发生时,将处理器提升到 APC 级别,就能屏蔽其它 APC 。为了与 APC 同步,驱动程序可以手动提升到这个级别。分页调度管理就运行在这个级别上。例如:当正在执行 APC 时,处理器提升至这一级别,可以将其它的 call 屏蔽,保护它的完整独立执行。

Dispatch_LevelDPC (延迟过程)和更低的中断屏蔽,不能访问分页内存。线程调度(因为线程调度是由时钟中断来保证的,因此该级别的中断就是调度中断)和 DPC 例程运行在这个级别上。在该级别上,线程的运行将不受线程中断影响,直到代码运行的 IRQL 被提升为 Dispatch_Level ,期间如果发生缺页中断(APC_Level)等在 Dispatch_Level中断,将都被屏蔽,此时代码将无法正常运行

DIRQL(Device IRQL):代表值在 3 ~ 26 的一个范围。处于更高层的驱动程序通常不会使用 IRQL 级别。在该级别上,所有的中断都会被忽略。这是 IRQL 的最高级别(更上方的是硬件中断),通常使用它来判断设备优先级

 

 

 

内核驱动模块

 

 

 

然后利用微软提供的 WDK 驱动开发工具包来编译,生成一个 .sys 文件,并将它加载到系统中运行。正式的内核驱动还需要:分发派遣函数Hook 技术或者过滤技术回调技术框架

驱动加载执行流程:

①在注册表的 Services 键下建立一个与驱动名称相关服务键,即 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\XXXX 。这个服务键规定了驱动的一些属性,比如启动的先后顺序等。

对象管理器生成驱动对象(DriveObject)并传递给 DriveEntry() 函数,并执行该函数。

创建控制设备对象

创建控制设备符号连接(R3 级可见)。

⑤如果是过滤驱动,则创建过滤设备对象绑定

注册特定的分发派遣函数

⑦其它初始化动作:Hook过滤(文件过滤、网络防火墙过滤等)、回调框架(注册表回调等)等的注册初始化

 

内核中重要的数据结构

 

内核对象

 

(1)Dispatcher  对象

这种对象在对象体的开始位置放置了一个共享的公共数据结构  DISPATCHER_HEADER ,其结构如下:

 

包含 DISPATCHER_HEADER 结构的内核对象的名字都以字母 K 开头,例如 KProcess 、KThread。但以 K 开头不一定是 Dispatcher 对象。

包含 DISPATCHER_HEADER 结构的内核对象都是可以等待的(Waitable),这些内核对象可以作为参数传给内核的 KWaitForSingle/MultipleObject() ,以及应用层的 WaitForSingle/MultipleObject() 函数。

(2)I/O 对象

对象体开始的位置没有放置 DISPATCHER_HEADER 结构,而是放置一个与 type 和 size 有关的整型成员,以表示内核对象的类型(文件内核对象的类型为26)和大小。

(3)其它对象

除了 Dispatcher 和 I/O 对象,剩下的都属于其它内核对象。其中有两个(进程对象线程对象)是常用的。

所有进程的 EProcess 内核结构都被放入一个双向链表,R3 在枚举系统进程的时候会遍历它。

 

两个内核函数可以获得该进程的 Eprocess 结构

     

EThread 结构是线程的内核管理对象,每一个线程都有对应的结构。

 

 

SSDT

全称为 System Service Descriptor Table(系统服务描述符表),在内核中的名称是 KeServiceDescriptorTable 。这个表通过 ntoskernl.exe 导出(x64 不导出)

 

FuncAddr = KeServiceDescriptorTable + 4 * indexx86

FuncAddr = (KeServiceDescriptorTable + 4 * index)* 16(即左移4位) (x64

 

Shadow SSDT  对应的表名是 KeServiceDescriptorTableShadow,是内核未导出的另一张表,也是一个 SSDT 结构体数组。包含 ntoskernl.exewin32k.sys 服务函数,主要处理 User32.dllGDI32.dll 的系统调用

 

 

 

PEB、TEB

进程中的每一个线程都有自己的 TEB ,一个进程的所有 TEB 都存放在 0x7FFDC000 (32 位)开始的线性内存中,每 4KB 作为一个完整的 TEB

 

进一步查看该内存中的信息

 1 ntdll!_TEB
 2 +0x000 NtTib : _NT_TIB
 3 +0x038 EnvironmentPointer : (null) 
 4 +0x040 ClientId : _CLIENT_ID
 5 +0x050 ActiveRpcHandle : (null) 
 6 +0x058 ThreadLocalStoragePointer : 0x00000000`002e3720 Void
 7 +0x060 ProcessEnvironmentBlock : 0x000007ff`fffdb000 _PEB
 8 +0x068 LastErrorValue : 0
 9 +0x06c CountOfOwnedCriticalSections : 0
10 +0x070 CsrClientThread : (null) 
11 +0x078 Win32ThreadInfo : 0xfffff900`c1df4c30 Void
12 +0x080 User32Reserved : [26] 0
13 +0x0e8 UserReserved : [5] 0
14 +0x100 WOW32Reserved : (null) 
15 +0x108 CurrentLocale : 0x804
// 更多代码略

FS 为段寄存器,当代码运行在 R3 级时,基地址即为当前线程的 TEB,所以也称 TEB 段。

PEB 存在于用户地址空间中,记录了进程的相关信息,每个进程都有一个。

 

还有很多代码略。 微软的符号服务器国内无法访问

BeingDebugged 用于表示该进程是否处于被调试状态,ProcessParameters 用于记录进程的参数信息

posted @ 2020-10-28 21:04  Rev_omi  阅读(858)  评论(0编辑  收藏  举报