第七章 Windows 内核基础

7.1 内核基础理论

7.1.1 权限级别

系统内核层分为4个 Ring0 到Ring3,运行权限从Ring0到Ring3 依次降低

CPU设计之初计划让系统运行于R0,R1,R2运行驱动,R3运行应用程序。

但是操作系统Windows 及 Linux 偷懒没有使用R1 R2, 之后CPU也遵照这个惯例只保留了R0,及R3

不知道7 以及10 的内核是怎么个模式,感觉这种时时在更新的很麻烦…软件开发还要做适配…

7.1.2 内存空间布局

x86 CPU支持32位寻址,所以最大支持2的32次方,4GB的虚拟内存空间。

Windows的系统内存主要分为内核空间,以及应用层空间,各约占2GB

Windows内存逻辑地址分为两部分,段选择符和偏移地址(并不理解)

CPU 在进行地址翻译时,先通过分段机制计算出一个线性地址,再通过页表机制将线性地址映射到物理地址,从物理内存中读取数据或指令。 (并不理解)

Linux 下64位系统虚拟内存,支持48位(256TB),内存并不全部可用,存在大量空洞。

7.1.3 Windows与内核启动过程

1.启动自检

硬件初始化检查之类…

2.初始化启动 (XP)

加载主引导记录(MBR),启动过程由MBR执行,搜索分区表,找到活动分区,将第一扇区的引导代码载入内存,引导代码检测当前使用的文件系统,查找ntldr文件,ntldr 完成操作系统的启动工作。

win7 使用 Bootmgr

3.Boot 加载阶段

ntldr进行如下设置

1.设置内存模式

x86处理器,32位操作系统,设置为32-bit flat memory mode

64位处理器,64位操作系统,设置为64位内存模式

(不知道64位处理器,32位操作系统,会设置成什么模式…)

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

读取 启动文件

4. 检测和配置硬件阶段

5.内核加载阶段

ntldr首先加载 windows 内核Ntoskrnl 和硬件抽象层(HAL)

HAL对硬件底层特性进行隔离,为操作系统提供统一的调用接口。

Ntldr从注册表 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet中读取驱动程序并依次加载, 初始化底层设备驱动

在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services查找Start键值为0和1的设备驱动

Start的值可以为0,1,2,3,4, 数值越小,启动越早

0 表示内核刚刚初始化,此时加载的都是与系统核心有关的重要驱动,例如磁盘驱动

1 稍微晚一些

2 从登录界面开始加载

3 需要手动加载

4 禁止加载

6. Windows 的会话管理启动

驱动程序加载完成,内核会启动会话管理器,smss.exe, 第一个用户模式进程。

作用如下:

创建系统环境变量

加载win32k.sys ,windows 子系统内核模式 部分

启动 csrss.exe , windows 子系统 用户模式部分

启动winlogon.exe

创建虚拟内存页面文件

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

7.登录阶段

Windows 子系统启动的 winlogon 系统服务提供登录和注销的支持。

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

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

3.显示登陆界面

8.Win7 启动过程

BIOS自检后读取MBR,找到启动管理器 Bootmgr

Bootmgr寻找活动分区boot文件夹中的BCD文件,显示启动菜单

选择win7 操作系统后,bootmgr读取系统文件 windows\system32\winload.exe,并移交控制权

Winload 加载win7内核,硬件 服务加载桌面信息,启动整个win7系统

9.UEFI 和 GPT

BIOS MBR 具有局限性, 磁盘逻辑块地址(LBA)是32位的,最多支持2的32次方个扇区,每个扇区大小一般为512字节,最多支持2TB.

而且MBR最多支持4个主分区或3个主分区和一个扩展分区

UEFI LBA 为64位,具备文件系统的支持能力

GPT本身对分区数量没有限制,但是Windows实现时,限制在128个分区以内

(书中介绍的很简单,回头再查查资料补充一下)

7.1.4 Windows R3与 R0 通信

应用程序调用API(kernal32.exe 和 user32.dll), 转向调用 在Ntdll.dll 中的API。 Ntdll.dll 为 本机用户模式的终端。

在Ntdll.dll中 Native API的函数时成对出现的,Nt Zw开头,只是名字不同,指向的函数地址相同。

当API 通过ntdll.dll执行时,会完成参数的检测工作,再调用一个中断(int 2Eh 或SysEnter指令)从R3 进入R0。在内核ntoskrnl 中有一个SSDT,里面存放了与ntdll.dll 中对应的SSDT 系统服务处理函数,即内核态的Nt*函数

System Services Descriptor Table,中文为系统服务描述符表,ssdt表就是把ring3的Win32 API和ring0的内核API联系起来

1.从用户模式调用 Nt 和 Zw API 链接 ntdll.lib。

通过设置系统服务表中的索引和在栈中设置参数,经由SYSENTER 或syscall 指令进入内核态,并最终由KiSystemService跳转到KiServiceTable对应的系统服务例程。由于从用户模式进入内核模式,代码会严格检查参数。

2.从内核模式调用Nt* 和 Zw* API, 链接ntoskrnl.lib

Nt* 系列函数直接调用对应函数代码(真正执行函数的部分)

Zw*系列API 通过KiSystemService最终跳转到对应的函数代码处 (似乎是再带哦用Nt* 系API)

在调用用户模式Nt* API时, 不会改变Previous Mode的状态 调用Zw* API 时会将调用模式改为内核态,因此使用Zw*可避免额外的参数列表检查

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

有一说一不是很懂右面的东西..回头需要再查一查。

内核主要由各种驱动构成,驱动加载后,会产生对应的设备对象,并可以选择向R3提供一个可供访问和打开的符号链接,如C,D,E盘,对应的符号链接为 “\??\C:\” “\??\D:\” “\??\E:\” 等

应用层程序可以根据内核驱动的符号调用CreateFile()函数打开,在获得句柄之后,程序就可以调用应用层函数与内核函数进行通信了。

内核驱动执行了DeviceEntry()函数,就可以接受R3的通信请求了

在内核驱动中,专门有一组分发派遣函数来分别相应应用层的调用请求。

例如图中 DispatchCreate 对应CreateFile, IRP会把数据传给派遣函数,函数处理完IRP以后可以选择传递给下层驱动继续处理,或者停止。

7.1.5内核函数

调用内核函数需要注意他的IRQL (Interrupt Request Level) 内核在不同情况下,会运行在不同的IRQL上,因此必须调用符合该水平要求的内核函数。

7.1.6 内核驱动模块

编译好的驱动被加载执行过程

1.创建一个服务,在注册表Services下建立一个与驱动名称相关的键

2.对象管理器生成驱动对线,并传递给DriverEntry函数,执行DriverEntry函数(相当于R3程序的main函数)

3.创建控制设备对象

4.创建控制设备符号链接 (R3可见)

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

6.注册特定的分发派遣函数

7.其他初始化动作,例如Hook, 过滤,回调框架等的注册和初始化。

7.2 内核重要数据结构

7.2.1内核对象

内核对象是一种数据结构。应用层的进程 线程 文件 驱动模块 事件 信号量等对象或者打开的句柄都有与之对应的内核对象。

从图中可见,指针总是指向对象体,对象头需要用指针减去一定量来访问对象头

Windows 内核对象可分为三种类型

(1)Dispatcher 对象

包含DISPATCHER_HEADER结构的内核对象都以字母 K 开头 表明这是一个内核对象 如KPROCESS,KTHREAD,KEVENT…但以K开头的内核对象不一定是Dispatcher对象(把我看迷惑了… 是所有内核对象都是以K开头吗…以后待检验)

包含Dispatcher Header 的对象都是可以等待的,可以传给内核的KeWaitForSingleObject()函数等

(2)I/O 对象

I/O 对象在对象体开始位置并未放置 DISPATCHER_HEADER结构,但会放置一个与type 和size有关的整形成员,以表示内核对象的类型和大小。

(3)其他对象

除了上面两个其他都属于其他对象,常用的有进程对象(EPROCESS)和线程对象(ETHREAD)

每个进程都对应一个EPROCESS结构,结构很大且不透明

所有进程对象都被放入一个双向链表,R3在枚举系统进程时,会遍历链表得到进程链表,因此有的Rookit 会将自己的进程从链表中摘除,从而隐藏自己。

结构中的关键数据

NTSTATUS PsLookupProcessByProcessId(   // 通过进程ID拿到EPROCESS 结构
  HANDLE    ProcessId,
  PEPROCESS *Process
);

也可以用

PEPROCESS GetCurrentProcess();

ETHREAD 大致同EPROCESS,不透明,保存在双向链表里,重要成员如下

双向链表结构

7.2.2 SSDT

全称为 System Services Description Table, 系统服务描述表,在内核的名称为

KeServicesDescriptorTable,通过内核ntoskrnl导出(64位不导出)

kernal32.dll 中的API 通过 ntdll.dll时,会先完成参数的检查,再调用中断进入R0,将要调用的服务号(SSDT数组中索引的index 值)存放到EAX中,最后根据EAX中的值调用指定的Nt*服务。

SSDT表定义:

通过基地址以及index 可以得知指定的服务地址

x86 平台

FuncAddr = KeServiceDescriptorTable + 4 * index

x64 平台

FuncAddr = ([KeServiceDescriptorTable+index*4]>>4 +KeServiceDescriptorTable )

Shadow SSDT 原理与SSDT类似,表明对应于KeServiceDescriptorShadow, 包含Ntoskrnel.exe和win32k.sys服务函数,主要处理莱斯 User32.dll 和GID32.dll的系统调用。

Shadow SSDT是未导出的 因此不能在自己的模块中导入和直接引用

所以要使用不同方式获取该表的地址及服务函数的索引号。

7.2.3 Thread Environment block 线程环境块

TEB 是应用层的结构,包含了一些系统频繁使用的线程相关的数据,进程中的每一个线程(除了系统进程)一个进程所有的TEB都存放在从0x7FFDE000开始的线性内存中,每4KB一个完整的TEB

1.TEB结构体

2.TEB访问

(1)NtCurrentTeb函数调用

从ntdll.dll中导出了一个NtcurrentTeb函数,该函数可以返回当前线程TEB结构体的地址

使用代码如下:

(2)FS寄存器访问

FS为段寄存器,当代码运行在R3级别,基地址即为当前线程环境块(TEB)所以该段也称为TEB段

7.2.4 PEB

1.PEB 访问

TEB中的ProcessEnvironmentBlock就是PEB结构的地址,其结构的0x30偏移处是一个指向PEB的指针。

PEB的0x2偏移处是一个Uchar成员,名叫BeingDebugged,进程被调试时为1,否则为0.

获取PEB访问有两种方法:

1.直接访问
2.通过TEB访问

在我看起来都一样…

2.PEB结构

查阅MSDN 得知的不完整的结构

从windbg得出的结构

7.3 内核调试基础

7.3.1使用Windbg搭建双机调试环境

1.在本机上安装windDbg

2.在Vmware上创建COM串口

3.设置虚拟机内部系统的调试环境 Vista及以后系统

C:\Windows\system32>bcdedit /dbgsettings serial baudrate:115200 debugport:1
操作成功完成。

C:\Windows\system32>bcdedit /copy {current} /d DebugEntry
已将该项成功复制到 {9faa7012-5e1e-11eb-b7c3-5c879c22492a}。


C:\Windows\system32>bcdedit /displayorder {current} {9faa7012-5e1e-11eb-b7c3-5c8
79c22492a}
操作成功完成。

C:\Windows\system32>bcdedit /debug {9faa7012-5e1e-11eb-b7c3-5c879c22492a} ON
操作成功完成。

4.在虚拟机外创建windbg快捷方式

命令如下

“C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\windbg.exe” -k com:port=\.\pipe\com_1,baud=11520,pipe

并运行windbg

5.启动虚拟机操作系统为调试启动

windbg的菜单项 Debug → Break 让系统中断,就可以下调试命令了。

7.3.2 加载内核驱动并设置符号表

x64 的系统的驱动需要数字签名,可以输入如下命令禁用数字签名检查

Microsoft Windows [版本 6.0.6002]
版权所有 (C) 2006 Microsoft Corporation。保留所有权利。

C:\Windows\system32>bcdedit.exe -set loadoptions DDISABLE_INTEGRITY_CHECKS
操作成功完成。

C:\Windows\system32>bcdedit.exe -set TESTSIGNING ON
操作成功完成。

C:\Windows\system32>

调试驱动时,可以用DbgPrint()来打印信息

Vista及以上版本需要在

计算机\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Debug 新建一个新值 DEFAULT: REG_DWORD: 0xFFFFFFFF 三个数据分别为名称,类型,值。重启系统就可以查看函数输出了

调试驱动的符号设置:File → Symbol File Path

符号分为两部分

srv*d:\symbolsxp*http://msdl.microsoft.com/download/symbols 是MS的符号,路径固定

第二个部分为我们要调试的内核驱动的符号路径

然后在Windbg中设置源代码的路径

File → Source Search Path

然后File Open Source File 打开源文件并设置断点,就可以运行调试了

调试内核模块同时调试R3 级应用程序。

!process 0 0 获取用户空间进程

.process p + 应用程序的 EPROCESS 地址,切换到应用程序的地址空间

.reload /f /user 重新加载 user PDB文件 (需要在Windbg里设置 R3级的符号和源代码)

使用非入侵式的方法切换进程空间

.process /i /p + 地址

设置应用层断点

bp user32!ExitProcess

这些回头需要弄个驱动试试…

7.3.3 SSDT 与 ShadowSSDT 的查看

1.SSDT的查看

kd> x nt!kes*des*table*

2. Shadow SSDT的查看

1.在虚拟机运行 mspaint.exe System 进程一般不会加载win32k.sys

2.运行 !process 0 0, //找到mspaint的EPROCESS 地址传给3 作为参数

3.运行 .process/p mspaint

4.运行 x nt!*kes*des*table

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇