如果文中有错误,还请指正
1. 段选择子
首先我们来看段选择子的构成
图片取自 intel白皮书三卷 Vol. 3A 3-7
上面的那些位到底代表着什么呢?
-
index : 这个表示的是一个指向GDT表的索,而GDT表中存放的就是我们所想看到的段描述符了 (因特尔没有使用LDT表),我将如何使用index查询GDT表放在了最后
原文中写道:这是一个指向GDT表或LDT表的索引 (GDT表的位置(base address)由GDTR寄存器存放,LDT表的位置(base address)由LDTR寄存器存放)
-
TI (table indicator flag) : 如图所示,TI == 0时,会到GDT表中查询段描述符,TI == 1时,到LDT表中查询 (windows 的TI永远都是0)
-
RPL (Requested Privilege Level) : 请求权限级别,下文DPL一同讲解
那么该如何拆分一个段选择子呢?
- 例1: 0x0023 = 0000 0000 0010 0011 B,稍微划分一下 0000 0000 00100 0 11,所以index = 100 B = 4,TI = 0,RPL = 11 B = 3
- 例2: 0x0008 = 0000 0000 0000 1000 B,index = 1, TI = 0,RPL = 0
- 例3: 0x002B = 0000 0000 0010 1011 B,index = 5, TI = 0,RPL = 3
2. 段描述符
先看这张图,这就是段描述符的样子啦
接下来会对这张图进行循序渐进地解释。
为了描述的连续性,我将一些次要的东西放在了最后
图片取自 intel白皮书三卷 Vol. 3A 3-10
struct 段寄存器 {
WORD selector;
WORD attibute;
DWORD base;
DWORD limit;
}
- Base Address : 一共32位,直接存放到段寄存器相应的Base段即可
- Segment Limit : ( 16 - 19 ) 位 + ( 0 - 15 ) 位,可表示位数为 0x0 - 0xFFFFF (共计20位,那么如何使用20位的 Segment Limit 来填充32位的 Limit 呢,请看G位)
- P (segment-present) : 这个描述符是否有效, P == 1有效, P == 0无效
如果P == 0那么这个段描述符就没有什么意义了,一个有效的描述符P一定是1哦
- G (granularity) : 用于填充32位的Limit
- 当G ==1时,Limit = 0xFFF + Segment Limit << 0xC
- 当G == 0时,Limit = 0x0 + Segment Limit.
我将如何填充段寄存器隐藏位写在了最后
> 举个例子 :
> - G = 1,Segment Limit = 0x12345
> Limit = 0xFFF + Segment Limit << 0xC = 0xFFF + 0x12345 << 0xC = 0x12345==FFF==
> - G = 0,Segment Limit = 0x12345
> Limit = 0x00000000 + Segment Limit = 0x00000000 + 0x12345 = 0x==000==12345
- type : 下面的表描述的就是type这四个字节了,看不懂的话可以看下面对于S位的解释
图片取自 intel白皮书 Vol. 3A 3-12 (不是我用那本,我用的书,图不太直观,所以我更喜欢上面这张)
type位是与S位息息相关的,下面仅仅写出了当 S == 0 时的 type 位含义
- D/B位
- DPL (descriptor privilege level) : 访问该段所需要的特权级别
注:CPL (current privilege level) : 当前特权级别 (CS寄存器中存放的低2位就是CPL,如:当前的CS寄存器中的值为 0x1B = 0001 1011B,所以CPL = 3)
mov ax, 0x000B //0000 0000 0000 1011 RPL = 3
mov ds, ax //ax指向的段描述符的DPL = 0
数值越大权限越低,windows分为 ring 0 和 ring 3,ring 0为系统层,ring 3为用户层
当且仅当CPL <= DPL && RPL <= DPL
时,语句才能正常执行,也就是说低权限不能访问高权限
AVL : Available and Reserved Bit (简单理解就是 保留位,通常设为0)
白皮书中写道:Bit 20 of the second doubleword of the segment descriptor is available for use by system software. (第二个双字的第20位段描述符可供系统软件使用)
L : 64位扩展使用,兼容模式下(32位程序)为0,L为1则D一定为0
(感兴趣可以查白皮书 Vol. 3A 3-13)
注解:
段选择子index的使用
- 打开虚拟机,并使用windbg进行调试
- 进入桌面后点击break按钮暂停调试
- 输入指令
dq gdtr L20
,其中dq指令表示显示内存地址 gdtr 开始后20个地址,并以8字节为一组
同样的,我们也可以输入r gdtr来查看gdtr寄存器里的值,这个值指向GDT表,所以我们可以使用dq 0x8003f000
来查看GDT表 (对应dq指令,我们还可以使用 dd, db 指令分别按查询dword查询和按byte查询)
- 我们选择一个段选择子0x2B作为演示,0x2B = 00101 0 11 B,其中index = 5,所以查GDT表中下标为5的一个8字节,
查GDT表填充段寄存器
我们沿用上面的index = 5,所以查到的段描述符为 80008b04`200020ab,然后查表,首先填充
attribute : 80 008b 04`200020ab,所以 attribute = 008b
base : 80 008b 04 ` 2000 20ab,所以 base = 80042000
limit : 80 0 0 8b04`2000 20ab,所以 limit = 000 020ab (G = 0)
index = 2, 00 c f 93 00 ` 0000 ffff
attribute : cf93,base : 00000000,limit : fffff fff (G = 1)
向上拓展与向下拓展
课件里的图片,搬过来用了
- 图片中左边的是向上拓展,代表的是只有红色部分的Limit是可访问的
- 图片中右边的是向下拓展,同样是只有红色部分能够访问,相当于除了Limit的地址是不能访问,其他地址都能访问,相当于对Limit取反操作的地址有效
一致代码段与非一致代码段
- 一致代码段:当前特权级别为低权限也可以直接访问高权限代码段,比如ring 3直接访问ring 0中的一致代码段,但高权限不允许访问低权限
条件:CPL >= DPL
- 非一致代码段:必须拥有相同等级的特权才能访问
条件:CPL == DPL && RPL <= DPL
隐式堆栈访问
当使用push, pop, call等操作时,会对寄存器进行一些修改的操作,例如push eax,相当于 sub esp, 4
和 mov [esp], eax
这里的esp并没有显式写出来