保护模式入门
保护模式入门
段寄存器
一共有8个:ES、CS、SS、DS、FS、GS、LDTR、TR。每个段寄存器结构如下:
1 | Base 32bit |
读写方法:用mov
时,mov ax,es
只能读16位可见部分,mov ds,ax
能写96位。读写LDTR
用sldt
或lldt
,读写tr
用str
或ltr
。
成员如下,其中GS在Windows中不用:
段寄存器 | Selector | Attribute | Base | Limit |
---|---|---|---|---|
ES | 0023 | RW | 0 | 0xFFFFFFFF |
CS | 001B | RX | 0 | 0xFFFFFFFF |
SS | 0023 | RW | 0 | 0xFFFFFFFF |
DS | 0023 | RW | 0 | 0xFFFFFFFF |
FS | 003B | RW | 0x7FFDE000 | 0xFFF |
在调试时,单步调试触发单步调试异常进入内核,内核会把GS清零。段寄存器在内联汇编实现时编译器可能会瞎改。
段描述符
分为全局描述符表GDT和局部描述符表LDT,但后者Windows不用。CPU有个寄存器GDTR存储GDT的位置和大小:
1 | struct GDTR { |
用WinDBG读GDT:
1 | r gdtr ;读GDT地址 |
段选择子
指向定义该段段描述符的结构:
1 | RPL 2bit 请求特权级别 |
段描述符
注意Intel的图全是从下到上,从右往左:(。注意WinDBG读取数据时可能用XXXXXXXX`XXXXXXXX表示后4字节为低位,前4字节为高位,每4字节从右往左是从高到低。
1 | 0: |
P:为1段描述符有效,为0段描述符无效。
Base:被分到仨地方了,拼起来就是完整的。
Limit&G:俩Limit要拼起来用。当G为0时,在Limit前补000即为最终Limit结果,范围即为1B~1MB;当G为1时,在Limit后补FFF即为最终Limit结果,范围即为4KB~4GB。
S:为1表示代码段或数据段描述符,为0表示系统段描述符。
DPL:描述符特权级别,访问该段所需权限。
AVL:指示是否可供系统软件使用。
TYPE
段描述符的这个域太复杂了,单独拎出来讲讲。
当S为1时:
当第11位为0时,8-10位分别为A、W、E属性,说明描述符类型为Data;当第11位为1时,8-10位分别为A、R、C属性,说明描述符类型为Code。
属性:A访问位、E向下拓展位、R可读位、W可写位、C一致位。
当C为1表示一致代码段,为0时非一致代码段。
当E为1时表示向下拓展。向下拓展时Base到Base+Limit间区域无效,其余有效。向上拓展时Base到Base+Limit间区域有效,其余无效。
当S为0时:后面遇到再讲吧。
D/B
对于CS段来说,为1采用32位寻址方式,为0采用16位寻址方式。
对于SS段来说,为1时隐式堆栈访问指令用32位堆栈指针寄存器ESP,位0时使用16位堆栈指针寄存器SP。这里隐式堆栈访问指令为PUSH
、POP
、CALL
等。
对于向下拓展来说,这里称Base到Base+Limit间为向上拓展有效,4G中其他部分为向下拓展有效。当为1时向下拓展有效只能为前低64K空间。
段权限检查
CPL表示CPU当前权限级别,DPL表示访问该段所需权限,RPL表示所用权限。
对于一致代码段/共享段:内核态不能访问用户态数据,但用户态可访问内核态;对于非一致代码段:用户态与内核态不能互相访问,只能同级访问。
数据段权限检查:权限上CPL<=DPL && RPL<=DPL
。
代码段权限检查:对于非一致代码段要求权限上CPL==DPL && RPL<=DPL
,对于一致代码段要求权限上CPL>=DPL
。
代码跨段跳转
除CS外,其他段寄存器都可以通过MOV
、LES
、LSS
、LDS
、LFS
、LGS
进行修改,CS不能直接修改,可以用JMP FAR
、CALL FAR
、RETF
、INT
、IRETED
等同时修改CS和EIP。段间跳转用长跳转JMP FAR
就够了。
实例
例如代码JMP 0x20:0x004183D7
。
将0x20按照段选择子差分,TI=0查GDT表,Index=4找到对应段描述符。
权限检查,略。
加载段描述符到CS寄存器。
CPU将CS.Base+Offset
写入EIP后跳转到要执行的CS:EIP
处,段间跳转结束。
门
调用门
短调用就是常见的CALL
指令。长调用指令为CALL CS:EIP
,其中CS指向调用门段选择子,EIP废弃,CS一换EIP和SS配套的得跟着一块儿换。
调用门结构与普通段描述符很相似:
1 | 0: 段选择子 段偏移 即为调用后EIP位置 |
当DPL小于CPL需要提权。
长调用不提权:发生改变的寄存器有ESP、EIP、CS,当CALL
时先压入调用者CS再压入返回地址,RET
同理。
长调用提权:发生改变的寄存器有ESP、EIP、CS和SS,当CALL
时依次压入调用者SS、ESP、CS,最后压入返回地址,RET
同理。
大致步骤:根据CS查GDT,找到对应段描述符(应该是个调用门),进行权限检查并把段描述符中另一个代码段的段选择子加载到CS中,选择子指向段的Base+Offset即为真正要执行的地址。用RETF
返回。
中断门
IDT包含3种门描述符:任务门描述符、中断门描述符、陷阱们描述符,每个描述符8字节,WinDBG下读IDT首地址和大小:
1 | r idtr |
中断门结构:
1 | 0: |
指令格式为INT n
,n为中断门索引号。
执行步骤:无权限切换时依次压入EFLAG、CS、EIP,有权限切换时最先多压入SS;索引到IDT,中断门索引号即为IDT表项下标,没有RPL只需权限校验CPL;用IRET
或IRETD
指令返回。
陷阱门
跟中断门相似,就是要把IF位置零。