roger 发表于 2022-7-4 14:26:19

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.libNt * 系列 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_Level:DPC (延迟过程)和更低的中断被屏蔽,不能访问分页内存。线程调度(因为线程调度是由时钟中断来保证的,因此该级别的中断就是调度中断)和 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 * index (x86)FuncAddr = (KeServiceDescriptorTable + 4 * index)* 16(即左移4位) (x64)
Shadow SSDT对应的表名是 KeServiceDescriptorTableShadow,是内核未导出的另一张表,也是一个 SSDT 结构体数组。包含 ntoskernl.exe 和 win32k.sys 服务函数,主要处理 User32.dll 和 GDI32.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 : 010 +0x070 CsrClientThread : (null) 11 +0x078 Win32ThreadInfo : 0xfffff900`c1df4c30 Void12 +0x080 User32Reserved : 013 +0x0e8 UserReserved : 014 +0x100 WOW32Reserved : (null) 15 +0x108 CurrentLocale : 0x804


FS 为段寄存器,当代码运行在 R3 级时,基地址即为当前线程的 TEB,所以也称 TEB 段。PEB 存在于用户地址空间中,记录了进程的相关信息,每个进程都有一个。
还有很多代码略。 微软的符号服务器国内无法访问BeingDebugged 用于表示该进程是否处于被调试状态,ProcessParameters 用于记录进程的参数信息
页: [1]
查看完整版本: Windows 内核基础(内核调试暂略)