保护模式(段描述符与段选择子3)
要点回顾:
上一篇段属性探测2中,我知道了当写一个段寄存器的时候,只给了16位的数,但段寄存器有96位,那剩下的80位从哪里来的?(从GDT中查出来的),这16位数是随便写的吗?
GDT(Global Descriptor Table)全局描述符表和LDT(局部描述符表)
GDT是一块内存,是CPU设计中要求操作系统提供的一款内存。这块内存是操作系统在启动时填充的额。使用windgb的命令可以查看gdt表的位置。
gdtr寄存器(windbg伪寄存器,是windbg通过sgdt lgdt指令获取的,为了方便用户,才模拟了一个寄存器叫gdtr,实际是没有这个寄存器的):
存两个值,一个是GDT表在哪里,一个是GDT表有多大, 48位 有32位存储在哪里,16位存储大小
r gdtr r查看gdtr寄存器的前32位也就是位置
r gdtl r查看gdtr寄存器的后16位也就是大小 都查gdtr
dd xxxx 4个字节查看内存
dq xxxx 8个字节查看内存
dq xxxx Lnum 查看固定数量元素的内存
GDT表中存储着段描述符,每一个段描述符8个字节
当我们执行类似MOV DS,AX指令时,CPU会查表,根据AX的值来决定查找GDT还是LDT,查找表的什么位置,查出多少数据。
window并没有使用LDT,我们主要是查询的是gdtr,这里需要虚拟机和windbg调试环境。
用来查看gdtr寄存器的位置:
r gdtr 用来查看寄存器gdt(48位=32(表的起始地址)+16(表的大小))
r gdtl 用来查看表的大小
dd dd 8003f000 用来查看表的数据
用三环程序来读取gdtr的位置
int main()
{
unsigned char buff[6] = {0};
_asm
{
sgdt buf;
}
printf("%X,%X",*((unsigned int*)(&buf[2])),*((unsigned short*)(&buf[0])) )
}
2.段描述符:
我们都知道段寄存器有96位但是我们只给16位剩余的值就是从这张表里面查出来的数据放进去,每次需要查询8个字节(64位),因为GDT里面存放的每一个元素称为段描述符
当执行mov ds,ax这种对段寄存器的赋值时,不是简单的给可见部分赋值。也会为80位隐藏部分赋值。
因此cpu会查表来取那80位的数据,根据ax的值决定查找GDT还是LDT,查找表的什么位置,查多少数据
ax决定了gdt中的第几个段描述符,ax这种2字节的数据,称为段选择子。
因为段描述符占8个字节(64位),dq命令可以帮助我们一次读取8个字节的数据(低位在前,高位在后)
段选择子
当我们执行 MOV DS,AX指令时就是查找GDT这张表,查GDT表的那个位置由源操作数(AX)来决定,一旦查到它就会把段描述符8个字节放到段寄存器中,每个原操作数使用的段描述符都不一样所以我们要学习段选择子。
段选择子共有16位,该描述符指向了定义该段的描述符,这个数决定了去GDT/LDT表中查哪一个段描述符,(段选择子的RPL一定要<=对应段描述符的DPL)否则试图使用该选择子加载对应描述的行为将由于权限不足而失败
第0-1位:RPL(请求特权级别,段权限检查)
第2位:TI位 分为两种情况 TI=0 查GDT表 ,TI = 1查找LDT表(一般TI=0代表windows)windows没有使用LDT表
第3-15位:就是一个索引,我们到底查找那个段描述符就是由第3位到第15位
比如 段选择子 1B = 001B = 0000 0000 00001 1011(对这十六位按段选择子的位数进行拆分)
RPL = 11 =3(最后两位)
TI = 0(第最后末三位)(gdt表)
最后剩下的为:0000 0000 00001 1 = 3 查找的就是第三个字节的
加载段描述符至段寄存器
除了MOV指令,我们还可以使用LES、LSS、LDS、LFS、LGS指令修改寄存器(其中l为加载的意思)
CS不能通过上述的指令进行修改,CS为代码段,CS的改变会导致EIP的改变,要改变CS,必须保证CS和EIP一起改,后面再说。
char buffer[6];
__asm
{
les ecx,fword ptr ds:[buffer]//高两个字节给了es,低四个字节给ecx
}
注意RPL<=DPL(DPL在高四个字节的第13-14位上,一定要熟练段描述表)(在数值上)
总结
1.记住段描述符与段选择子(必须要记住)
2.使用LES、LDS等指令修改段寄存器
思考:段描述符共有64位,但是需要填充80位,怎么填呢
段寄存器(96位) = 16位的Selector+64位的段描述符+?