Windows保护模式学习笔记(一)—— 段寄存器GDT表

Windows保护模式学习笔记(一)—— 段寄存器&GDT表

    • 保护模式
    • 参考书籍:
    • 一、段寄存器
      • 段寄存器的结构
      • 段寄存器的读写
      • 段寄存器的属性
        • 1)探测Attribute:
        • 2)探测Base:
        • 3)探测Limit:
    • 二、GDT表与LDT表
      • GDT表
        • 1)段描述符
          • 段描述符的属性
          • 段描述符与段寄存器结构的对应关系
        • 2)段选择子
      • 加载段描述符到段寄存器
      • 段权限检查
        • 1) CPU分级概念
        • 2) 进程特权级别
          • 当前特权级(CPL)
          • 请求特权级(RPL)
        • 3)数据段的权限检查

保护模式

X86 CPU的三个模式:实模式保护模式虚拟8086模式

参考书籍:

《Intel白皮书第三卷》

一、段寄存器

什么是段寄存器?

当我们用汇编读写某一个地址时:

mov dword ptr ds:[0x123456], eax

我们真正读写的地址是:

ds.base + 0x123456

段寄存器有几个,有哪些?

有八个,分别是:

ES CS SS DS FS GS LDTR TR

段寄存器的结构

结构图表示:

段寄存器结构图
结构体表示:

struct SegMent
{WORD Selector;		// 段选择子 16位 可见WORD Atrributes;	// 段属性 16位 不可见DWORD Base			// 段起始地址 32位 不可见DWORD Limit			// 段大小 32位 不可见
}

段寄存器的读写

读:MOV AX,ES
写:MOV DS,AX
注意:段寄存器在的时候只读16位,但的时候会写入96位!
思考:如何证明向段寄存器写入时写了96位?

段寄存器的属性

属性图:
在这里插入图片描述
图中红色字体部分在不同环境中可能不同

1)探测Attribute:

在编辑器中尝试编译并执行以下代码:

int var = 0;
__asm
{mov ax,ss			// 此处不能为CS CS可读 可执行 但不可写mov ds,axmov dword ptr ds:[var],eax
}

编译器能成功编译上述代码,并且程序运行过程中没有报错


再在编辑器中尝试编译并执行以下代码:

int var = 0;
__asm
{mov ax,cs			// SS 改成了 CSmov ds,axmov dword ptr ds:[var],eax
}

编译器能成功编译上述代码,但程序运行过程中报错

上面的两个例子说明段寄存器的Attribute在写入时会被更改!

2)探测Base:

在编辑器中尝试编译并执行以下代码:

int var = 1;					
__asm					
{					mov ax,fs				mov gs,ax				mov eax,gs:[0]		// 不要使用DS 否则编译不过去mov dword ptr ds:[var],eax	// = mov edx,dword ptr ds:[0x7FFDF000]
}

编译器能成功编译上述代码,并且程序运行过程中没有报错

说明段寄存器的Base在写入时会被更改!

3)探测Limit:

int var = 1;					
__asm					
{					mov ax,fs				mov gs,ax				mov eax,gs:[0x1000]// 访问的地址相当于下面这行注释的代码 但DS的Limit是0xFFFFFFFF// mov eax,dword ptr ds:[0x7FFDF000+0x1000]mov dword ptr ds:[var],eax	
}

编译器能成功编译上述代码,但程序运行过程中报错
这是因为 FS 段寄存器的 Limit 为 0xFFF,而我们输入的段偏移为0x1000

思考:段寄存器在写入时,只给了16位,剩下的80位填什么?数据从哪里来?

二、GDT表与LDT表

GDT:全局描述符表
LDT :局部描述符表

当我们执行类似MOV DS, AX指令时,CPU会查表,根据AX的值来决定查找GDT还是LDT,查找表的什么位置,以及查出多少数据

GDT表

工具:WinDbg
WinDbg中查看GDT与LDT命令

gdtr是一个寄存器,存储了GDT表所在位置
gdtl也是一个寄存器,存储了GDT表的大小

在内存中查看GDT表
gdt1
第一排的数据为内存地址,红框中的数据才是真正的内存数据,即GDT表
我们已经知道如何查看GDT表了,但是如果想要看懂这张表,得先学习段描述符段选择子

1)段描述符

描述:GDT表中存储的元素称为段描述符
大小:每个段描述符占用空间为8个字节

为了更方便观察,在WinDbg中使用dq命令查看GDT表:
gdt2
段描述符结构图:
段描述符
段描述符高位在前低位在后
例如GDT表的第二项:00cf9b00`0000ffff
00cf9b00对应结构图的高四字节(上面一行),0000ffff对应结构图的低四字节(下面一行)

段描述符的属性

P位
P = 1:段描述符有效
P = 0:段描述符无效

段描述符加载时,首先看P位是否为1

G位
G=0:段寄存器的Limit元素单位为字节,最大值为0x000FFFFF
G=1:段寄存器的Limit元素单位为4KB,最大值为0xFFFFFFFF

S位
S = 1:段描述符为代码段数据段描述符
S = 0:段描述符为系统段描述符

Type域
S = 1时,即段描述符为代码段或数组段描述符时,Type域结构图如下:
Type域-代码段&数据段
第11位为0:段描述符为数据段描述符
第11位为1:段描述符为代码段描述符
A位:若该代码段/数据段未被访问过,则值为0,否则为1
W位:若为1,表示该段可写
E位:若为0,则向上拓展,若为1,则向下拓展
图例:
拓展方向
向上拓展:有效范围为fs.Base ~ fs.Base+Limit
向下拓展:有效范围除了fs.Base ~ fs.Base+Limit

R位:若为1,表示该段可读
C位:一致位。若为1,则是一致代码段;若为0,则是非一致代码段


S = 0时,即段描述符为系统段描述符时,Type域结构图如下:
Type域-系统段描述符
D\B位
情况1:对CS段的影响

D=1:采用32位寻址方式
D=0:采用16位寻址方式

情况2:对SS段的影响

D=1:隐式堆栈访问指令(如:PUSH POP CALL)使用32位堆栈指针寄存器ESP
D=0:隐式堆栈访问指令(如:PUSH POP CALL)使用16位堆栈指针寄存器SP

情况3:向下拓展的数据段

D=1:段上限为4GB
D=0:段上限为64KB

D_B位情况3
DPL
描述:

DPL存储在段描述符中,规定了访问所在段描述符所需要的特权级别是多少
DPL数值越大,访问所在段描述符所需要的权限越低
注意:在Windows中,DPL只会出现两种情况,要么全为0,要么全为1

例:
若AX指向的段描述符的DPL=0,但当前程序的CPL=3,那么这条指令是不会成功的!

段描述符与段寄存器结构的对应关系

Attribute:位于段描述符高四字节的第8-23位
Base:由三部分组成
第一部分:位于段描述符高四字节的第24-31位
第二部分:位于段描述符高四字节的第0-7位
第三部分:位于段描述符低四字节的第16-31位
Limit:由两部分组成
第一部分:位于段描述符高四字节的第16-19位
第二部分:位于段描述符低四字节的第0-15位

2)段选择子

描述:

段选择子是一个16位的段描述符,该描述符指向了定义该段的段描述符

段选择子结构图:
段选择子
字段说明
RPL:请求特权级别
TI:TI=0 查GDT表;TI=1 查LDT表
Index:处理器将索引值乘以8在加上GDT或者LDT的基地址,就是要加载的段描述符

加载段描述符到段寄存器

除了MOV指令,还可以使用LES、LSS、LDS、LFS、LGS指令修改段寄存器
注意:不存在LCS指令,因为CS不可写

例:

char buffer[6];					
__asm							
{			les ecx,fword ptr ds:[buffer] //高2个字节给es,低四个字节给ecx	
}

实验:为buffer赋值,并成功执行以上代码
注意:RPL<=DPL(在数值上)

段权限检查

1) CPU分级概念

CPU分级

平时我们称应用程序为3环,系统程序为0环,前面这句话只与CPU有关,与操作系统无关

思考:如何判断某个程序处于哪一环?

2) 进程特权级别

当前特权级(CPL)

描述:

段寄存器 CS 的后两位比特位称为当前特权级
注意:段选择子SS和CS的后两位比特位相同

如:
→ CS = 0x001B
→ 0x001B = 二进制:0000 0000 0001 1011
→ 二进制:11 = 十进制:3
→ 因此:当前进程处于3环

请求特权级(RPL)

描述:

RPL是段选择子结构中的一部分
RPL是针对段选择子而言的,每个段的选择子都有自己的RPL
RPL表示用什么权限去访问一个段

例:

MOV AX,0008
MOV DS,AX
与
MOV AX,000B
MOV DS,AX
指向的是同一个段描述符,但RPL不同

3)数据段的权限检查

检查:CPL<= DPL并且 RPL<= DPL(数值上的比较)

例:

当CPL = 0时执行以下指令:MOV AX,000B			// RPL=3,请求权限为3MOV DS,AX			// 假设ax指向的段描述符的DPL=0
上述指令虽然满足了CPL<=DPL,但RPL>DPL,因此执行失败

注意:代码段和系统端描述符的检查方式不一样

思考:既然已经有CPL(当前特权级别)了,为什么还要有RPL(请求特权级别)
回答:我们本可以用“读写”的权限去打开一个文件,但为了避免出错,有些时候我们使用“只读”的权限去打开

Published by

风君子

独自遨游何稽首 揭天掀地慰生平