文章系列:
重新认识Intel权限检查(一)
重新认识Intel权限检查(二)
普通的JMP和CALL无法更改CPL,使用调用门才能更改CPL,本章主要介绍这种特权级变化情况下的权限检查
原理
-
调用门工作流程
-
调用门描述符
两个关键数据结构是DPL和Segment Selector,DPL代表成功穿过此门调用Segment Selector指向的代码段的最低权限。
引入调用门使权限检查涉及更多要素,假设CPU想由代码A转移到代码B,涉及要素有:RPL,CPL,DPL_G(调用门的权限),RPL_G(调用门中选择子的权限),DPL_B(代码段B的权限)。 -
调用门流程
1) 如果CPL和DPL_B权限相同,没有特权级变化,就是执行普通的CALL指令,有参数先将参数入栈,然后当前CS、EIP入栈,从调用门中取出选择子和偏移,加载到CS和EIP,跳转到代码段B处执行指令。
2) 如果CPL和DPL_B权限不同,特权级升高,执行检查,特权级降低,抛出异常。检查通过后,特权级有变化需要切换堆栈,假设从ring3升高到ring0。保存当前SS和ESP寄存器值到临时内存,从TSS中取出ring0的堆栈指针加载到SS和ESP寄存器,将临时内存中保存的SS和ESP值入栈,完成堆栈切换。之后和普通CALL指令一样,参数、CS、EIP入栈,跳转,执行代码段B处的指令。如下图所示:
-
-
检查原则
- 一句话概括,只有特权级处于调用门和目标代码之间的代码,才能穿过调用门完成调用。调用门定义了权限下限,目标代码定义了权限上限
- 调用门中的选择子RPL_G不参与检查
- 调用门看做一个存放代码段地址的数据段,访问此门时需要遵循访问数据段的原则,即权限必须大于调用门
- CPU访问代码B看做访问代码段,需要遵循访问代码段原则:
对一致性代码段,CPL权限小于等于DPL_B
对非一致性代码段,使用JMP时CPL只能等于DPL_B,使用CALL时允许CPL小于等于DPL_B
实验结果
RPL = 2, CPL = 2,DPL_G = 2, RPL_G = 0/1/2/3,DPL_B = 0 CALL指令跳转成功
Selector = 0x3A => RPL = 2
CS = 0x12 => CPL=2
DPL_G = 2
调用门选择子Selector = 0xA => index = 1,目的代码段DPL_B是非一致代码段
跳转后CS = 0x8 => CPL = 0
同时堆栈指针有变化,说明特权级更改时堆栈也会切换。
-
RPL = 2, CPL = 2,DPL_G = 2, RPL_G = 0/1/2/3,DPL_B = 0 JMP指令跳转失败
-
RPL = 3, CPL = 2,DPL_G = 2, RPL_G = 0/1/2/3,DPL_B = 0 跳转失败,RPL权限不够访问调用门
-
RPL = 1, CPL = 2,DPL_G = 2, RPL_G = 0/1/2/3,DPL_B = 0 跳转成功,处于调用们和目标代码段权限之间,调用后CPL=0
-
RPL = 2, CPL = 2,DPL_G = 1, RPL_G = 0/1/2/3,DPL_B = 0 跳转失败,RPL,CPL权限不够访问调用门
-
RPL = 2, CPL = 2,DPL_G = 3, RPL_G = 0/1/2/3,DPL_B = 0 跳转成功
附:完整代码 my github intel CPU权限检查