前言
在上一节讲解了段权限检查机制中,我们分析了特权级检查模型,即CPU如何利用CPL、RPL与DPL的数值关系来控制段选择子的加载。这是保护模式的第一道安全屏障。
然而,仅有特权级检查是不够的。一旦段寄存器加载成功,CPU还需要依据段描述符中的属性位来决定对该内存段的具体访问方式(读、写或执行)。本节将继续解析描述符高32位中的S位与Type域,定义了内存段的类型与访问权限。
一、S位与Type域的位置
在上一节中,我们已经熟悉了64位段描述符的碎片化布局。在本节中,我们只需要关注高32位中第8到12位的这一小块区域。
这两个字段决定了段的本质类型:
- S位 (Bit 12):System Flag(系统标志位)。
- Type域 (Bit 8-11):Segment Type(具体类型域)。
它们在描述符中的位置如下:

二、 S 位 (System Flag)
S 位(Bit 12)是描述符类型的首要分类标志。
-
S = 1:代码段或数据段 (Code or Data Segment) 。
- 表示该描述符指向实际的存储空间(代码、数据或堆栈)。
- 此时Type域用于定义读写执行权限。
-
S = 0:系统段 (System Segment) 。
- 表示该描述符指向系统定义的特殊结构(如TSS、LDT)或门描述符(如调用门、中断门)。
- 此时Type域用于定义具体的系统对象类型。
三、 当 S=1 时:Type域详解 (代码段与数据段)
当S=1时,Type域(Bit 8-11)由4个标志位组成,分别控制段的执行、扩展方向/一致性、读写权限以及访问状态。
1. 位定义概览
| Bit 11 (E) | Bit 10 | Bit 9 | Bit 8 (A) |
|---|---|---|---|
| Executable | Exp-Down / Conforming | R/W | Accessed |
-
Bit 11 (E) :执行位。
- 0 = 数据段 (Data Segment),不可执行。
- 1 = 代码段 (Code Segment),可执行。
-
Bit 8 (A) :访问位。
CPU每次将该段加载到段寄存器时,硬件自动置1。操作系统可利用此位实现虚拟内存的页面置换算法(如LRU)。
2. 数据段 (E=0)
当Bit 11 = 0时,该段为数据段。Bit 10和Bit 9的定义如下:
-
Bit 10 (ED - Expand Down) :扩展方向。
- 0 = 向上扩展 (Up):有效偏移量
[0, Limit]。常规数据段使用此属性。 - 1 = 向下扩展 (Down):有效偏移量为
[Limit+1, 0xFFFFFFFF](在4GB空间下)。通常用于堆栈段,以适应栈从高地址向低地址增长的特性。
- 0 = 向上扩展 (Up):有效偏移量
-
Bit 9 (W - Writeable) :写权限。
- 0 = 只读 (Read-Only)。
- 1 = 可读写 (Read/Write)。
常见配置:
- Type 1 (0001) :只读数据段,已访问。
- Type 3 (0011) :可读写数据段,已访问(Windows默认数据段配置)。
3. 代码段 (E=1)
当Bit 11 = 1时,该段为代码段。Bit 10和Bit 9的定义如下:
-
Bit 10 (C-Conforming) :一致性。
- 0 = 非一致代码段(Non-Conforming)。代码执行时CPL必须等于DPL(除非通过调用门)。这是Windows内核的主要配置。
- 1 = 一致代码段(Conforming)。允许低特权级(如Ring 3)直接跳转执行,执行期间CPL保持不变(不提权)。Windows几乎不用。
-
Bit 9 (R - Readable) :读权限。
- 0 = 只执行 (Execute-Only)。不可读取指令内容(防止反编译或读取常量,早期常用于防止程序自我扫描或防止代码被窃取)。
- 1 = 可执行可读 (Execute/Read)。
- 注意:在保护模式下,代码段始终不可写。**
常见配置:
- Type 11 (1011) :非一致、可读、可执行代码段,已访问。
四、当S=0时:Type域详解 (系统段)
当S=0时,Type域编码了具体的系统描述符类型。
根据Intel手册定义,32位模式下的关键系统段类型如下:
| Type (Hex) | 描述符类型 | 说明 |
|---|---|---|
| 2 | LDT | 局部描述符表 |
| 9 | TSS (Available) | 任务状态段(可用) |
| B | TSS (Busy) | 任务状态段(忙) |
| C | Call Gate | 调用门(用于跨特权级调用) |
| E | Interrupt Gate | 中断门(用于中断处理) |
| F | Trap Gate | 陷阱门(用于异常处理) |
五、 实验验证:构造并测试只读数据段
为了验证属性位的硬件控制机制,我们将通过WinDbg手动构造一个Type=1 (只读) 的段描述符,并编写代码尝试写入该段,观察CPU的异常行为。
1. WinDbg构造段描述符
首先,在内核调试器中构造目标描述符。
- 确定空闲GDT表项:
kd> r gdtr
gdtr=8003f000
kd> dq 8003f000+(9*8)

其中偏移0x48处为空(全0)。对应选择子为0x4B(Index=9, TI=0, RPL=3)。
- 构造描述符数据:
-
目标属性:Base=0, Limit=FFFFF(4GB), DPL=3, S=1, Type=1 (Read-Only, Accessed)。
-
Type=1 (二进制 0001) -> 数据段、向上扩展、只读、已访问。
-
高4字节计算:
00cff100(P=1, DPL=3, S=1, Type=1)。 -
低4字节计算:
0000ffff。
- 写入GDT
eq 8003f048 00cff100`0000ffff
- 验证属性

输出显示Data RO (Read-Only),构造成功。
2.代码实现
编写代码加载该选择子并尝试写入。
- 测试构造的描述符是否能够读取数据:
int main(int argc, char* argv[])
{
unsigned int target_var = 666;
unsigned int read_val = 0;
unsigned int *pAddr = &target_var;
printf("[Experiment] Testing Read-Only Segment (Selector 0x4B)\n");
printf("Target Address: 0x%08X\n", pAddr);
__asm
{
mov ax, 0x4B
mov es, ax
mov ecx, pAddr
//
mov eax, es:[ecx]
mov read_val, eax
}
system("pause");
return 0;
}
读取成功,结果如下:

2. 测试构造的描述符是否能够写入数据:
int main(int argc, char* argv[])
{
unsigned int target_var = 666;
unsigned int read_val = 0;
unsigned int *pAddr = &target_var;
printf("[Experiment] Testing Read-Only Segment (Selector 0x4B)\n");
printf("Target Address: 0x%08X\n", pAddr);
__asm
{
mov ax, 0x4B
mov es, ax
mov ecx, pAddr
//
mov eax, es:[ecx]
mov read_val, eax
}
printf("[Action] Now attempting to WRITE (Expect Crash)...\n");
__asm
{
mov ecx, pAddr
mov eax, 888
mov bx,0x4b
mov es,bx
// 只读段,触发0xC0000005异常
mov es:[ecx], eax
}
system("pause");
return 0;
}
触发写入异常,结果如下:

六、结论
CPU在执行写指令时,检查了ES段描述符的Type域。检测到W位为0(只读),因此判定操作非法,抛出保护性异常。这证实了段描述符属性位对内存访问的硬件级控制有效。

被折叠的 条评论
为什么被折叠?



