保护模式篇之段描述符与段选择子,详细介绍段权限检查与代码跨段跳转。
此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。
练习及参考
如下是从虚拟机读取的GDT表的前18个段描述符,下面的实验均按照此进行练习。
8003f000 00000000`00000000 00cf9b00`0000ffff
8003f010 00cf9300`0000ffff 00cffb00`0000ffff
8003f020 00cff300`0000ffff 80008b04`200020ab
8003f030 ffc093df`f0000001 0040f300`00000fff
8003f040 0000f200`0400ffff 00000000`00000000
8003f050 80008955`22000068 80008955`22680068
8003f060 00009302`2f40ffff 0000920b`80003fff
8003f070 ff0092ff`700003ff 80009a40`0000ffff
8003f080 80009240`0000ffff 00009200`00000000
1️⃣ 练习读取GDT表的位置和长度,并显示GDT表前48个段描述符。
???? 点击查看答案 ????
r gdtr //读取GDT表地址
r gdtl //读取GDT表的大小
dq 8003f000 l30 //0x8003f000为GDT表的地址
2️⃣ 在给定的段描述符中,进行拆分练习(至少10个)。
???? 点击查看答案 ????
本答案仅提供一个样例,其余自行校验:
对于段描述符 0040f300`00000fff :
P:1
S:1
G:0
Type:3
DB:1
Base:0
Limit:FFF
DPL:3
3️⃣ 拆分如下段选择子。
002B 0023 0010 001B 003B
???? 点击查看答案 ????
002B:
RPL:3
TI:0
Index:5
0023:
RPL:3
TI:0
Index:4
0010:
RPL:0
TI:0
Index:2
001B:
RPL:3
TI:0
Index:3
003B:
RPL:3
TI:0
Index:7
4️⃣ 快速辨别给定段描述符是否可用以及段基址、段长(至少10个)。
5️⃣ 从给定段描述符,请按照下面的要求进行练习(全部):
- 快速找出所有数据段,并分析该段属性:只读、已访问、可读可写、拓展方向
- 快速找出所有代码段,并分析该段属性:只执行、可读可执行、已访问、一致代码
- 快速找出所有系统段,并分析属性
???? 技巧 ????
我拿出一个段描述符为例进行介绍,学会后可以自行找别的段描述符练习,如下图所示:
表示不同含义的位或十六进制数我都加以区分。由于直接标注在段描述符上非黑色数字通过结构示意图直接能对应上,故不再详细讲解。我们先看c
,它的二进制就是1100
,这个位置大于8
就说明G位有效。同理看9
,这个位置大于8
就说明段描述符有效;为奇数说明为代码段或者数据段,反之为系统段。其他的位不太常用于快速判断,规律可自行总结。
???? 点击查看答案 ????
1. 所有的数据段,分析属性自行查表:
00cf9300`0000ffff
00cff300`0000ffff
ffc093df`f0000001
0040f300`00000fff
0000f200`0400ffff
00009302`2f40ffff
0000920b`80003fff
ff0092ff`700003ff
80009240`0000ffff
2. 所有的代码段,分析属性自行查表:
00cf9b00`0000ffff
00cffb00`0000ffff
80009a40`0000ffff
2. 所有的系统段,分析属性自行查表:
80008b04`200020ab
80008955`22000068
80008955`22680068
6️⃣ 自行构造段选择子和段描述符,并用加载段描述符至段寄存器
中的代码模板和要求取得成功。如果有时间同样把LSS
、LDS
、LFS
、LGS
的实验也类比做了。
本题答案不唯一,合理并能够正常运行即可,注意小端存储。此题目有一个坑,自行踩坑。
???? 点击查看答案 ????
int main(int argc, char* argv[])
{
//索引为3的短描述符为:00cffb00`0000ffff
char buffer[6]={0xff,0xff,0xff,0xff,0x18,0x00};
_asm
{
les ecx,fword ptr ds:[buffer] //高2个字节给es,低四个字节给ecx
}
return 0;
}
7️⃣ 如何在调试器中快速判断程序在几环权限。
???? 点击查看答案 ????
根据CS或者SS,因为它存储的是段选择子,可以通过低两位直接判断。
8️⃣ 自学修改GDT表的相关知识,并思考如下问题。
r gdtr
dq 8003f090 00cffb00`0000ffff
r gdtr
8003f090
是GDT表中的一个段描述符的地址,更改后发现并没有更改,请思考为什么会这样。
???? 点击查看答案 ????
你看我之前写读取GDT表为什么用地址了吗?你再用地址dq
以下看看。
- CPL:CPU当前的权限级别
- DPL:如果你想访问我,你应该具备什么样的权限(CPL)
- RPL:用什么权限去访问一个段
RPL存在的意义:
举个例子,我们本可以用读写
的权限去打开一个文件,但为了避免出错,有些时候我们使用只读
的权限去打开。
对于一致代码段,也称为共享段:
- 特权级高的程序不允许访问特权级低的数据:核心态不允许访问用户态的数据
- 特权级低的程序可以访问到特权级高的数据,但特权级不会改变:用户态还是用户态
对于非一致代码段:
- 只允许同级访问
- 绝对禁止不同级别的访问:核心态不是用户态,用户态也不是核心态
数值上,CPL
<=DPL
且RPL
<=DPL
。同时满足上述条件才能通过。
下面的比较都是数值上的比较:
- 如果是非一致代码段,要求:
CPL
==DPL
且RPL
<=DPL
- 如果是一致代码段,要求:
CPL
>=DPL
代码跨段本质就是修改CS段寄存器。前面的教程介绍过段寄存器读写,除CS外,其他的段寄存器都可以通过MOV
/LES
/LSS
/LDS
/LFS
/LGS
指令进行修改。但是CS
为什么不可以直接修改呢?CS
的改变意味着EIP
的改变,改变CS
的同时必须修改EIP
,故我们无法使用上面的指令来进行修改,这个也是CPU不允许的。
段间跳转,有2种情况,即要跳转的段是一致代码段还是非一致代码段,它们不同做的权限检查就不同。
同时修改CS
与EIP
的指令如下:JMP FAR
/CALL FAR
/RETF
/INT
/IRETED
本篇只介绍段间跳转,故只使用JMP FAR
,即为长跳转。下面我举个示例来进行讲解:
CPU
如何执行这行代码JMP 0x20:0x004183D7
?
1️⃣ 段选择子拆分
0x20
对应二进制形式:0000 0000 0010 0000
- 解析结果:
- RPL = 0
- TI = 0
- Index = 4
2️⃣ 查表得到段描述符
TI=0
所以查GDT表,Index=4
找到对应的段描述符。注意四种情况可以跳转:代码段、调用门、TSS任务段、任务门。后面的几种将会在以后的教程详细讲解。
3️⃣ 权限检查
请参考本节的代码段的权限检查
4️⃣ 加载段描述符
通过上面的权限检查后,CPU会将段描述符加载到CS段寄存器中。
5️⃣ 代码执行
CPU
将CS.Base + Offset
的值写入EIP
然后跳转到将要执行的CS:EIP
处的代码,段间跳转结束。
本节练习直接对代码段进行
JMP
或者CALL
的操作,无论目标是一致代码段还是非一致代码段,CPL
都不会发生改变。如果要提升CPL
的权限,只能通过调用门。
本节的答案将会在下一节进行讲解,务必把本节练习做完后看下一个讲解内容。不要偷懒,实验是学习本教程的捷径。
俗话说得好,光说不练假把式,如下是本节相关的练习。如果练习没做好,就不要看下一节教程了,越到后面,不做练习的话容易夹生了,开始还明白,后来就真的一点都不明白了。本节练习比只有很少,请保质保量的完成。
1️⃣ 记住代码段间跳转的执行流程。
2️⃣ 自己实现一致代码段的段间跳转。
3️⃣ 自己实现非一致代码段的段间跳转。
下一篇保护模式篇——长调用与调用门、中断门、陷阱门
作者:寂静的羽夏