Windows x64内核

x64下的分段机制

前情回顾:

1. GDT(全局描述符表)

  • 分段机制: GDT是分段机制的核心部分。它定义了系统中所有的段描述符,供处理器在保护模式下使用。GDT中的描述符用于定义代码段、数据段以及系统段的基地址、段限长、访问权限等。
  • 功能:
    • 代码段和数据段: GDT包含代码段(代码段描述符)和数据段(数据段描述符)的定义,描述段的起始地址、段限长、段的特权级(DPL,Descriptor Privilege Level)、段类型等。
    • 系统段: 包括TSS(任务状态段)、LDT(局部描述符表)等的描述符,用于任务切换和其他系统级功能。

2. LDT(局部描述符表)

  • 分段机制: LDT也是分段机制的一部分,但它是GDT的一个补充。LDT允许每个进程或任务有自己独立的段描述符表,从而支持每个进程或任务使用不同的段设置。
  • 功能:
    • 局部描述符: LDT提供了局部段描述符,用于定义特定于某个任务或进程的代码段和数据段。每个LDT包含的描述符范围仅限于该LDT,而不是整个系统。
    • 任务隔离: 通过使用LDT,操作系统可以实现任务间的段隔离,允许不同任务使用不同的段配置,而不影响其他任务。

3. IDT(中断描述符表)

  • 分段机制: IDT不是分段机制的一部分。它专门用于处理中断和异常,定义中断服务例程(ISR)的地址和中断处理相关的信息。
  • 功能:
    • 中断向量: IDT包含中断向量和异常向量的描述符,指定每个中断或异常的处理程序的地址及其特权级。
    • 中断门、陷阱门和任务门: IDT条目可以是中断门、陷阱门或任务门,决定了在发生中断或异常时如何处理,特别是在特权级转换和堆栈切换时的行为。

分段机制的基本变化

  1. 在 64 位模式下(长模式),大多数段寄存器(如 CS, DS, ES, SS)的段基址被强制设置为 0,这意味着这些段寄存器的作用被弱化,不能用来做实际的内存分段。
  2. 在 64 位模式下,CS 段寄存器的作用是定义代码执行的特权级别(CPL),并设置 64 位模式的特定标志,而不是用于传统的分段基址计算。
  3. 段长度限制在 64 位模式下被忽略,代码和数据段的大小被假定为整个地址空间(即从 0 到 2^64-1)。

Code段(灰色属于无效)

1725193530829

数据段(灰色属于无效)

1725193566931

系统段

1725193630059

系统段(如GDT中的某些条目)仍然被使用

  • **Task-State Segment (TSS)**:尽管x64不使用硬件任务切换,TSS仍然用于保存中断处理期间的特定信息,例如指定的堆栈指针(在处理双重故障或其他异常时)。在x64系统中,每个处理器通常仍然有一个TSS段,尽管其功能已经简化。
    TSS在X64主要为每个特权级别(尤其是从用户模式切换到内核模式)提供了专用的堆栈指针(Rsp0,Rsp1,Rsp2,Rbp0,Rbp1,Rbp2), TSS中的IST(Interrupt Stack Table)机制提供了多个备用堆栈,用于在异常或中断处理时切换到安全的堆栈。这在处理某些关键中断时非常重要,以确保系统有一个可靠的执行环境。

    总结下来,TSS在x64只用于切换栈

  • **Local Descriptor Table (LDT)**:LDT在x64模式下基本被废弃了。在现代操作系统(包括Windows x64)中,LDT已经很少使用,主要是在一些极少数的向后兼容情况下才被需要。

  • **Global Descriptor Table (GDT)**:GDT仍然存在于x64模式下,主要用于定义代码段、数据段、TSS段以及其他系统段的描述符。然而,这些段的基址通常为0,限长为全64位地址范围,因此它们实际上是被扁平化处理的。

保留的段寄存器 FSGS

FSGS 寄存器在 64 位模式下仍然保留了一些特定的用途:

  • FSGS 寄存器可以通过 MSR(Model-Specific Registers)来设置基址,这些基址用于访问线程或进程特定的内存区域。
  • 在 Windows x64 系统中,GS 寄存器通常用于指向当前的进程控制块(KPRCB),而 FS 通常用于线程环境块(TEB)。这允许操作系统在多线程环境下快速访问线程或进程特定的数据。

1728106140110

IA32_EFER_MSR

img

索引0位表示SYSCALL/SYSRET这类指令是否启用,索引8位表示IA-32e模式是否启用,索引10位表示IA-32e模式是否处于处于活动状态,索引11位知识是否启用PAE分页的XD位是否有效。

IA32_FS_BASE

这个MSR寄存器保存的是fs的基址,由于是64位内核,直接读取的话该值是0

IA32_GS_BASE

这个MSR寄存器保存的是gs的基址,内核指向KPCR结构体(和32位的fs一样的作用),我们可以尝试以下:

IA32_KERNEL_GS_BASE

这个MSR的作用是作为GS基址的交换目标。说人话的话就是一个缓存,交换比较方便。我们从3环到0环。从0环到3环,GS内核指向KPCR,3环指向TEB,是如何做到呢?这个就是一个非常重要的寄存器.进0环的时候,把3环的交给它;出0环的时候, 把零环的交给它

分段机制的简化和页表的依赖性增加

在 64 位模式下,由于分段机制被极大简化,内存保护和管理更多依赖于分页机制(paging)。分页机制通过使用页表(Page Table)来管理内存,提供地址转换、内存保护(通过访问权限控制),以及内存隔离等功能。

  • 页表的层次增加:x64 架构使用了四级页表(PGD、PUD、PMD、PTE),允许管理更大的虚拟地址空间(高达 256 TB)。
  • 内存保护主要通过页表条目中的访问权限位来控制(如可读、可写、可执行等)。

特权级别和堆栈切换

虽然分段机制的角色在 64 位模式下被弱化,但特权级别(Privilege Level)依然通过 CS 段寄存器控制:

  • CS 寄存器的低 2 位(RPL - Requested Privilege Level)用于指示当前的特权级别(0-3),其中 0 是最高特权级别(内核模式),3 是最低特权级别(用户模式)。
  • 内核与用户模式之间的堆栈切换依然基于特权级别实现,但不再依赖于段选择子,而是依赖于任务状态段(TSS)中的堆栈指针。

x64的中断门

相比于X86,中断门扩展为16个字节

1725196011438

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <cstdint>

// 中断门描述符的结构体
struct InterruptGateDescriptor {
uint16_t OffsetLow; // 16位的偏移地址(低16位)
uint16_t Selector; // 选择子(段选择子)
uint8_t IST:3; //IST
uint8_t Reserved1:5; // 保留,必须为0
uint8_t TypeAttributes; // 描述符类型和属性
uint16_t OffsetMid; // 16位的偏移地址(中间16位)
uint32_t OffsetHigh; // 32位的偏移地址(高32位)
uint32_t Reserved2; // 保留,必须为0
} __attribute__((packed)); // 确保结构体不进行填充

x64的调用门

相比于x86,差不多只是多了个高地址

1725198052439

1
2
3
4
5
6
7
8
9
10
11
#include <cstdint>
// x64 调用门描述符结构体
struct CallGateDescriptor {
uint16_t OffsetLow; // 16位的偏移地址(低16位)
uint16_t Selector; // 段选择子(目标代码段选择子)
uint8_t Reserved; // 保留字段,必须为0
uint8_t TypeAttributes; // 描述符类型和属性
uint16_t OffsetMid; // 16位的偏移地址(中间16位)
uint32_t OffsetHigh; // 32位的偏移地址(高32位)
uint32_t Reserved2; // 保留字段,必须为0
} __attribute__((packed)); // 确保结构体按实际布局排列

64位MSR寄存器

在 x64 架构中,FS 寄存器通常用于指向用户模式下的线程环境块(TEB),而 GS 寄存器则被保留用于内核模式下的使用。这种分工有助于在 64 位系统中提供更清晰的模式划分和访问控制。

在 64 位模式下,分段机制的作用被弱化,GS 寄存器的基址通过 MSR(Model-Specific Register)进行设置,而不是通过段描述符。因此,dg gs 这样的命令在 64 位模式下不会显示 GS 段基址的信息,因为长模式下不再使用段描述符表来定义 GS 的基址。

**MSR_GS_BASE (0xC0000101)**:用于内核态,存储内核模式下GS寄存器的基地址,通常指向KPCR,用于访问处理器和线程相关的内核数据。

**MSR_KERNEL_GS_BASE (0xC0000102)**:用于用户态,存储切换回用户模式时GS寄存器的基地址,确保用户态程序能够正确访问TLS和其他基于GS的机制。

看MSR存的RIP 0xc0000082,叫做KiSystemCall64,进零环的指令变为Syscall,回用户模式SysRet

其余的0xc0000080存着标志寄存器 0xc0000081存着EIP,0xc0000100存着FS0

64位SSDT表

通过逆向可以发现,拿SSDT表已经不是从KTHREAD拿了

而且也不好Hook

后续说