访问违例问题
在介绍内容前,先看两个程序
两个程序的差异仅仅在于段选择子的不同,结果则是一个访问成功另一个访问违例了。分别对两个段选择子进行拆分:
1 | 0023 = 0000 0000 00100 0 11 RPL:3 Index : 4 |
1 | 002B = 0000 0000 00101 0 11 RPL:3 Index : 5 |
可以看出,两个段选择子的差别仅在Index的不同,也就是指向的段描述符不同,再来看看两个段选择子对应的段描述符
注意:由于Index是从0开始算的(和数组一样),所以对应的实际上是表中第五个和第六个段描述符。
根据之前段描述符属性的内容,来查看属性位,分别为:
1 | Attr: CFF3 DPL:3 |
1 | Attr: 008B DPL:0 |
可以发现,两个段描述符的DPL不同,之前在段选择子的篇章中提到过,在将段选择子指向的段描述符加载到段寄存器时,一定要保证数值上(RPL<=DPL),上述例子中,出现访问违例的那一例的段选择子RPL>DPL。那为什么RPL>DPL就会出错呢?下面来逐步解析。
CPU分级
先来了解一下CPU分级
CPU共划分了4个等级,Ring0~Ring3,其中Ring3级别最低,Ring0级别最高。这个分级是CPU划分的,并不是操作系统所划分的,操作系统只是使用了这个分级,其中Windows系统只用了Ring3和Ring0两个等级,分别表示应用级和系统级。应用级的程序往往不能访问系统级,所以保护模式不仅仅是防止段的胡乱访问,还保证了程序在相应的级别稳定的运行。
CPL
Current Privilege Level,当前特权级,之前段选择子篇章中讲过,段选择子的后2位的值为RPL,而CPL指的CS或SS中的段选择子的后2位(CS/SS的后两位一定是相同的,所以无论用哪个作为CPL都一样),也就是说CS或SS的RPL就是当前程序的CPL。
下面来查看两个CPL:
随便拖一个程序进入OD,根据CS/SS可以算出当前程序的CPL为3。因为这些程序都是Ring3的程序。
按Ctrl+Break在Windbg中断下,查看寄存器,可以发现当前CPL为0。因为Windbg在调试系统时执行的都是内核函数,所以处在Ring0。
DPL
Descriptor Privilege Level:描述符特权级别。在段描述符属性那一篇中解析DPL时提到过,在段描述符高4字节的第13~14位就是DPL,那么DPL到底有什么用呢?和CPL有什么关系呢?
DPL存储在段描述符中,规定了访问该段所需要的特权级别是什么。
通俗的理解:如果你想访问我,那么你应该具备什么特权。例如:
1 | mov ds, ax |
如果ax指向的段DPL = 0 但当前程序的CPL = 3 这行指令是不会成功的,因为CPL = 3是应用层,权限较低,是不能访问DPL = 0的系统层的;这也是为什么之前的例子中会出现访问违例。
RPL
Request Privilege Level:请求特权级别
有小盆友可能会问了,既然已经有CPL和DPL,那只要CPL <= DPL,不就可以访问了,还需要RPL干什么?
比如当前程序处于0环,也就是说CPL=0
1 | mov ax,000B //1011 RPL = 3 |
这个程序是会执行失败的,虽然满足了CPL <= DPL,但是RPL > DPL。
网上看到一个例子很形象,这里拿过来用:中国官员分为6级国家主席1、总理2、省长3、市长4、县长5、乡长6,假设我是当前进程,级别总理(CPL=2),我去某城市(DPL=4)考察,我用省长的级别(RPL=3 这样也能吓死他们:-)) 去访问,可以吧;但是如果我用县长的级别(RPL=5),人家就不理咱了(你看看电视上的微服私访,呵呵)。
这个例子很形象,所以说数据段的权限检查,要保证 CPL <= DPL 并且 RPL <= DPL (数值上的比较)
总结
- CPL CPU当前的权限级别
- DPL 如果你想访问我,你应该具备什么样的权限
- RPL 用什么权限去访问一个段
本篇主要讲解的是数据段的段权限检查,代码段和系统段描述符中的检查方式并不一样,后面会再讲述
参考文章:https://blog.csdn.net/better0332/article/details/3416749