目录
这一点主要是了解下. 我们很多时候都听别人说 ring3 ring0 其实就是 CPU的等级划分.
不同的级别可以执行不同的 特权指令. 比如 IN OUT 等指令.在16位 实模式下就可以直接执行.
而保护模式下就不让你执行了. 原因就是 CPU分了等级了. 一共四个等级. ring3 - ring 0 而操作系统只使用了 ring3 与 ring0 所以 ring3就是应用程序. ring0 就是内核程序. 应用程序不可以执行特权指令
内核程序可以执行特权指令. 请注意. CPU是有4个等级的. ring3 ring 2 ring 1 ring0 操作系统使用了两个. 也就是微软使用了两个. 所以不要搞混. 我们要知道 特权级别是CPU提供. 微软只是使用而已.
根据inter手册图表示
RPL是段选择子的特权级别. 举个例子.看如下代码 mov ds,ax ax里面是一个值.我们知道这个值是一个 段选择子 拆分开来可以是索引. 根据这个索引去GDT表中寻找对应的段描述符 然后段描述符 赋值给ds. 但是我们有没有想过. CPU执行这条指令的时候.为什么要去查.难道不该限制吗.不管是ring3 还是 ring0都可以查表吗. 不是这样的. RPL就是一个限制. 表示你想请求的特权级别. 当你请求的特权级别不满足.那么CPU肯定不会执行这条指令的. 那么不满足什么那. 就是我们下面所说的DPL
DPL是描述段描述符的. 是指明了我们这个段描述符 你想访问我你需要什么权限. 也就是说我们的RPL想访问段描述符. 会跟 DPL做对比. <= DPL才可以访问. 进而操作这个段描述符.
还是以代码为例子
mov ds,ax
这条指令是执行在ring3的. 所以不用说 CS 的低两位 = 11 也就是3
但是我们知道 ax是一个段选择子.回去找GDT表中的段描述符. 而ax指向的这个段描述符的DPL正好是0 就是特权很高. 0是最高特权. 那么这条指令不会成功. 原因就是 CPL > DPL CPL必须<= DPL才能成功. 就是是一次段特权级别的 比较.
核心就是DPL 任何CPL RPL 都是为访问DPL(段描述符)来进行准备的. 如果不与DPL相等或者小于 那么 就可以访问. 如果大于 那么久拒绝你访问
RPL 选择子 RPL特选级别 要么是0 要么是3 也就是 RPL 要么两位都是0 要么都是1
DPL DPL == 0 级别权限很高.
DPL = 3 级别权限低. 只要 CPL RPL <= 3都可以访问.
CPL RPL DPL我么知道是啥了.那么我么可以进行模拟.
指令可以执行.但是 你访问这个段描述符的权限不足. 所以不让你访问.
代码跨段的原理是修改CS 寄存器. 但是CS寄存器比较特殊. 因为CS寄存器跟EIP是配套的. 如果CS改了. EIP没改. 那么就是非一致代码段. 然后EIP跳转的时候就会出错.
要想同时修改CS 与 EIP的话 有以下几条指令
JMP FAR / CALL FAR /RETF /INT /IRETED
长跳转 长Call 长返回 int指令 iRetEd指令
但是只修改EIP的话就很简单了. 指令如下
JMP /CALL /JCC /RET
这些都可以进行修改EIP. 包括x64下也是一样的.
JMP far指令执行流程
截图如下
汇编指令如下
jmp far 0x0020:A50000
当CPU执行这条指令的时候会经过五个步骤
一致代码段的情况下: 当CPL== DPL 且 RPL <= DPL 的时候.才能访问 数据段. 我们知道 权限要么是0 要么是3
二进制分别对应的是 00 11 CPL 与RPL DPL都是两位表示. 而数字越低权限越高. 所以我们说的非一致代码段的情况下. 当前运行的特权级别(CPL) 权限必须与 段描述符权限相等(DPL) 否则不让你访问. 第一步通过之后. 然后判断
段选择子 (RPL) 是否 与DPL权限相当. 如果不是也不让你访问.
当我们使用 JMP FAR指令的时候 CPU总会执行五步
1.拆分段选择子
2.通过拆分的段选择子 查找对应的表. 得出对应的段描述符
3.进行权限检查. CPL RPL DPL等检查.
4.加载段描述符
5.填写EIP 执行代码.
原理就是根据五步步骤来操作.
其实本质还是 构造段选择子. 段选择子指向一个你自己定义的段描述符.
段描述符里面可以设置权限等.
这个简单.根据段描述符.来找 我们上一篇已经说过了. s = 1 代表是代码段或者是数据段. 然后再根据s解析type来
具体的判断是代码段还是数据段.
其实简单的方法就是看 段描述符的 16进制表示的低五位来判断寻找即可. 或者根据讲解段描述符的时候写的代码来寻找.
查看段描述符
我们可以看到.s跟type是紧密相连的. 所以s和type可以看作是一个整体的二进制位. 并且这4位可以组成一个整体.
也就是一个16进制位 正好就是 高32位的第5个字节表示 表示了s与type. 而s = 1的时候代表是系统段或者数据段.
所以可以肯定如为1 那么组成的这个16进制位必然是大于8的.
举个例子. Windbg查看段描述符
r gdtr
dq gdtrbase
.formats gdtrbase[1]
我们直接查看的是第二项.看下它的高4个字节的第五位 00CF9 9就是一个16进制位. 包含了s与type 我们说过
s = 1那么必然会导致 s与type的组合肯定 > 8 根据我们解析的二进制数据我们看一下 看一下第五位
解析出来是 1001 1代表s = 1 后面的001代表的type解析 分别是 C RA 也就是code代码段 可以看一下上篇的
type解析与s解析.
那么确定了这个段是代码段. 那么我们就可以将这个代码段 拷贝到GDT表数组中要给新的地方. 最后构造段选择子
让其访问即可.
观察上图.我们看GDT的时候. 第一项是没有使用的. 我们将一个段描述符(代码段)拷贝到GDT表中第16项中.
代码段怎么找也是同上. 看s = 1还是0 确定是是代码或者数据段. 然后紧接着看s后面跟着的type值. 由type值确定是代码段还是数据段. 这里我们直接用GDT表中的第四项来进行操作
00cffb00~0000ffff
windbg如下
r gdtr
dq GDTbase
eq 地址 拷贝的内容
dq GDTBASE
图中遮挡的不要看.因为这是我实验之后没有修改回去而产生的错误项. 只需要看黑框一栏即可.
通过以上指令我们可以看到 将GDT表中的第四项,拷贝到了 第16项中.
下面就是构造段选择子 然后进行跨段实验
我们所添加的新项在 GDT表中的第16项. 我们知道段选择子的构成 所以按照段选择子进行构造
但是GDT表的我们说过本质是一个数组. 这个数组里面是8个字节大小.存储着段描述符.
所以我们下标要从0开始. 所有构造段选择子的时候就要按照下表的方式来.
16项 = 下表15
15 = 二进制 1111
RPL以及TI 构造位 011 RPL = 3
结合起来 =
1111 011
按照从右向左 4个字节分组. 构成16进制的段选择子
111 1011 = 7B
所以我们段选择子是0x7B
跨段实验
我们直接用上面搜说的 jmp far指令来进行跳转 模拟段操作.当这条指令执行的时候就会执行我们的五步步骤
显然这条指令是可以执行成功了. 如果执行失败就会跑飞. 跳转到异常处理位置.
尝试将DPL的11修改为00 然后重新执行代码跨段流程 从右边向左.从0开始都 读取到第13 14 DPL位 修改为00 0000 0000 1100 1111 11 11(DPL) 1011 0000 0000 0 0 C F 1001(9) B 0 0 0x00CF9B00~xxx 经过尝试是不可以进行访问了.且会跳到异常处理 就算段选择子的RPL = 0 也不可以. 因为我们的CPL特权级别不够. 而要修改CPL只能通过门来修改. 至此我们可以进行总结以及代码添加段描述符了. 并且进行测试 三丶总结 首先当访问段描述符的时候.操作系统会经过五个步骤 1.拆分段选择子 2.查表 3.DPL权限对比 4.通过之后加载段描述符 5.段描述符中的base+当前的偏移修改到EIP中进行执行 也经过windbg调试进行校验.发现确实可以伪造并且跳转. 注意一点的是 构造段选择子的index 是下标. 还要构建RPL TI等