段寄存器简介
段寄存器 | Selector | Attribute | Base | Limit |
---|---|---|---|---|
ES | ++0023++ | 可读可写 | 0 | 0xFFFFFFFF |
CS | ++001B++ | 可读可执行 | 0 | 0xFFFFFFFF |
SS | ++0023++ | 可读可写 | 0 | 0xFFFFFFFF |
DS | ++0023++ | 可读可写 | 0 | 0xFFFFFFFF |
FS | ++003B++ | 可读可写 | ++ 0x7FFDE000++ | 0xFFF |
GS | windows没有使用 | - | - | - |
注释:加了下划线可能会变!
探测Attribute存在
#include int "stdafx.h"
int main(int argc,
char* argu[])
{
__asm{
mov ax,ss;//cs不行,cs是不可写的
mov ds,ax
mov dword ptr ds:[var],eax;
}
}
return 0;
探测Base存在
int var = 1;
__asm
{
mov ax,fs
mov gs,ax
mov eax,gs:[0x1000]//读取不成功,因为fs大小是FFF //不要用DS 否则编译不过,这个不是真正0地址,而是关于基址的偏移
mov dword ptr ds:[var],eax
//mov edx,dword ptr ds:[0x7FFDF000]
}
探测Limit存在
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]
}
GDT(全局描述符表)LDT(局部描述符表)
当我们执行类似MOV DS,AX指令时,CPU会查表,根据AX的值来决定
查找GDT还是LDT,查找表的什么位置,查出多少数据.GDT是一张表,GDTR是个寄存器,存了GDT起始位置和有多少个元素。
段描述符与段选择子
段描述符
段选择子
段选择子是一个16位的段描述符,该描述符指向了定义该段的段描述符.
RPL:请求特权级别
TI:
TI=0 查GDT表
TI=1 查LDT表
Index:
处理器将索引值乘以8
在加上GDT或者LDT的
基地址,就是要加载的
段描述符
加载段描述符至段寄存器
除了MOV指令,我们还可以使用LES、LSS、LDS、LFS、LGS指令修改寄存器.
CS不能通过上述的指令进行修改,CS为代码段,CS的改变会导致EIP的改变,要改CS,必须要保证CS与EIP一起改,后面会讲.
char buffer[6];
__asm
{
les ecx,fword ptr ds:[buffer] //高2个字节给es,低四个字节给ecx
}
注意:RPL<=DPL(在数值上)
段描述符属性
P位
P = 1 段描述符有效
P = 0 段描述符无效
3、段描述符与段寄存器的对应关系
WORD Selector;//16位
WORD Atrribute;//16位
DWORD Base;//32位
DWORD Limit;//32位 FFFFFFFF
S位
S = 1 代码段或者数据段描述符
S = 0 系统段描述符
因为P,DPL,S位,Type要么是1001(访问本段权限0),要么是1111(访问本段权限3),所以GDT表里第五5要是9或者F才是代码段或者数据段。又因为Type与第一位为1是代码段,所以第六位要是大于8就是代码段。
E 向上就是LImit里有效,扩展方向向下是Limit取反才是有效的。
系统段描述符
DB位
情况一:对CS段的影响
D = 1 采用32位寻址方式
D = 0 采用16位寻址方式
前缀67 改变寻址方式
情况二:对SS段的影响
D = 1 隐式堆栈访问指令(如:PUSH POP CALL)使用32位堆栈指针寄存器ESP
D = 0 隐式堆栈访问指令(如:PUSH POP CALL) 使用16位堆栈指针寄存器SP
情况三:向下拓展的数据段
D = 1 段上线为4GB
D = 0 段上线为64KB
段权限检查
位置 | 用途 | |
---|---|---|
CPL | 存放在CS和SS段选择子的最后两位更其他无关 | 当前程序特权级 ,CPU处于哪一环 |
DPL | 存放在段描述符中 | 要访问该段需要什么权限才能访问 |
RPL | 存放在段选择子的最后两位 |
如何查看程序处于几环?
CPL(Current Privilege Level) :当前特权级
CS和SS中存储的段选择子后2位,要一致,另外跟这两个有关,跟其他的东西无关
DPL(Descriptor Privilege Level) 描述符特权级别
DPL存储在段描述符中,规定了访问该段所需要的特权级别是什么.
通俗的理解:
如果你想访问我,那么你应该具备什么特权.
举例说明:
mov DS,AX 如果AX指向的段DPL = 0 但当前程序的CPL = 3 这行指令是不会成功的!
RPL(Request Privilege Level) 请求特权级别
RPL是针对段选择子而言的,每个段的选择子都有自己的RPL
举例说明:
Mov ax,0008 与 Mov ax,000B//段选择子
Mov ds,ax Mov ds,ax//将段描述
指向的是同一个段描述符,但RPL是不一样的.
数据段的权限检查
参考如下代码:
比如当前程序处于0环,也就是说CPL=0
Mov ax,000B //1011 RPL = 3
Mov ds,ax //ax指向的段描述符的DPL = 0
数据段的权限检查:
CPL <= DPL 并且 RPL <= DPL (数值上的比较)
注意:
代码段和系统段描述符中的检查方式并不一样,具体参加后面课程.
总结:
CPL CPU当前的权限级别
DPL 如果你想访问我,你应该具备什么样的权限
RPL 用什么权限去访问一个段
为啥要有RPL?
我们本可以用“读写”的权限去打开一个文件,但为了避免出错,有些时候我们使用“只读”的权限去打开。
跨段跳转
代码间的跳转(段间跳转 非调用门之类的)
段间跳转,有2种情况,即要跳转的段是一致代码段还是非一致代码段
同时修改CS与EIP的指令
JMP FAR / CALL FAR / RETF / INT /IRETED
注意:
只改变EIP的指令
JMP / CALL / JCC / RET
JMP 0x20:0x004183D7 CPU如何执行这行代码?
(1) 段选择子拆分
0x20 对应二进制形式 0000 0000 0010 0000
RPL = 00
TI = 0
Index = 4
(2) 查表得到段描述符
TI = 0 所以查GDT表
Index = 4 找到对应的段描述符
四种情况可以跳转:代码段、调用门、TSS任务段、任务门
(3) 权限检查
如果是非一致代码段,要求:CPL == DPL 并且 RPL <= DPL
如果是一致代码段,要求:CPL >= DPL
(4) 加载段描述符
通过上面的权限检查后,CPU会将段描述符加载到CS段寄存器中.
(5) 代码执行
CPU将 CS.Base + Offset 的值写入EIP 然后执行CS:EIP处的代码,段间跳转结束.
总结:
对于一致代码段:也就是共享的段
- 特权级高的程序不允许访问特权级低的数据:核心态不允许访问用户态的数据
- 特权级低的程序可以访问到特权级高的数据,但特权级不会改变:用户态还是用户态
- 对于普通代码段:也就是非一致代码段
- 只允许同级访问
- 绝对禁止不同级别的访问:核心态不是用户态,用户态也不是核心态.
直接对代码段进行JMP 或者CALL的操作,无论目标是一致代码段还是非一致代码段,CPL都不会发生改变.如果要提升CPL的权限,只能通过调用门.
长调用与短调用
短调用
指令格式:CALL 立即数/寄存器/内存
调用门
调用门执行流程
指令格式:CALL CS:EIP(EIP是废弃的)
执行步骤:
1. 根据CS的值 查GDT表,找到对应的段描述符,这个描述符是一个调用门.
2. 在调用门描述符中存储另一个代码段段的选择子.
3. 这个调用门的选择子指向的段 段.Base +这个调用门的 偏移地址,就是真正要执行的地址.
构造一个调用门(无参,提权,不提权8的位置是1B)
0040EC00 000810D0放入gdt里没用到的位置。
构造一个带参数的调用门
调用门描述符:0040EC03 00081030
eq 8003f048 0040EC03`00081030
调用门总结:
1) 当通过门,权限不变的时候,只会PUSH两个值:CS 返回地址
新的CS的值由调用门决定
2) 当通过门,权限改变的时候,会PUSH四个值:SS ESP CS 返回地址 新的CS的值由调用门决定 新的SS和ESP由TSS提供
3) 通过门调用时,要执行哪行代码有调用门决定,但使用RETF返回时,由堆栈中压人的值决定,这就是说,进门时只能按指定路线走,出门时可以翻墙(只要改变堆栈里面的值就可以想去哪去哪)
4) 可不可以再建个门出去呢?也就是用Call 当然可以了 前门进 后门出
中断门
Windows没有使用调用门,但是使用了中断门:
<1> 系统调用
<2> 调试
IDT
IDT即中断描述符表,同GDT一样,IDT也是由一系列描述符组成的,每个描述符占8个字节。但要注意的是,IDT表中的第一个元素不是NULL。在windbg中查看IDT表的基址和长度:
老的CPU用的中断门,新的用的快速调用
IDT表都是系统段描述符,都不是空(调用门就有可能是代码数据段,和有空了)
中断门Type是1110,陷阱门是1111
陷阱门与中断门的区别
中断门执行时,将IF位清零,但陷阱门不会。
在调用门、中断门与陷阱门中,一旦出现权限切换,那么就会有堆栈的切换。而且,由于CS的CPL发生改变,也导致了SS也必须要切换。
切换时,会有新的ESP和SS(CS是由中断门或者调用门指定)这2个值从哪里来的呢?
答案:TSS (Task-state segment ),任务状态段.
TSS的结构
TSS是一块内存,不是寄存器不在CPU中,内存结构如下图,大小104字节
TSS的作用
Intel的设计思想
操作系统的设计思想
本质:
不要把TSS与“任务切换”联系到一起
TSS的意义就在于可以同时换掉”一堆”寄存器
CPU如何找到TSS呢? TR段寄存器
CPU找TSS直接在TR寄存器,TR的Base指向TSS,Limit是Tss有多大,TSS是操作系统启动时从GDT加载的。TSS是系统段的一种。所以S为是0,Type是1001,说明没有加载到TR寄存器中,1011时是已经加载到TR寄存器。
TR寄存器读写
- 将TSS段描述符加载到TR寄存器
指令:LTR
说明:
用LTR指令去装载的话 仅仅是改变TR寄存器的值(96位)
并没有真正改变TSS
LTR指令只能在系统层使用
加载后TSS段描述符会状态位会发生改变就是上面9变B - 读TR寄存器
1. 指令:STR
说明:如果用STR去读的话,只读了TR的16位 也就是选择子
注释
TSS是一块内存,大小104字节.通过TSS可以同时替换“一堆”寄存器,包括通用寄存器和段寄存器等.
CPU通过TR段寄存器来找到TSS.如果我们想用自己的TSS段来替换原来的寄存器,就要修改TR寄存器,TR寄存器的值又是来自TSS段描述符,那么我们接下来先构造一个段描述符。
修改TR寄存器
1) 在Ring0 我们可以通过LTR指令去修改TR寄存器
2) 在Ring3 我们可以通过CALL FAR 或者 JMP FAR指令来修改
用JMP去访问一个代码段的时候,改变的是CS和EIP :
JMP 0x48:0x123456 如果0x48是代码段
执行后:CS-->0x48 EIP-->0x123456
用JMP去访问一个任务段的时候:
如果0x48是TSS段描述符,先修改TR寄存器,在用TR.Base指向
的TSS中的值修改当前的寄存器
任务门描述符
Type为0101即5.
这是TSS段的描述符(在GDT表),任务门本身在IDT表
任务门执行过程:
INT N
查IDT表,找到中断门描述符
通过中断门描述符,查GDT表,找到任务段描述符
使用TSS段中的值修改寄存器
IRETD返回
参考资料:
[1]:滴水视频