? 这一点主要是了解下. 我们很多时候都听别人说 ring3 ring0 其实就是 CPU的等级划分.
不同的级别可以执行不同的 特权指令. 比如 IN OUT 等指令.在16位 实模式下就可以直接执行.
而保护模式下就不让你执行了. 原因就是 CPU分了等级了. 一共四个等级. ring3 - ring 0 而操作系统只使用了 ring3 与 ring0 所以 ring3就是应用程序. ring0 就是内核程序. 应用程序不可以执行特权指令
内核程序可以执行特权指令. 请注意. CPU是有4个等级的. ring3 ring 2 ring 1 ring0 操作系统使用了两个. 也就是微软使用了两个. 所以不要搞混. 我们要知道 特权级别是CPU提供. 微软只是使用而已.
根据inter手册图表示
CPL
CPL 是在CS 与 SS 段寄存器中的 bit 0位与 bit1位 记住是CS段寄存器. 它来表示我们的程序的当前特权级别. 也就是说 表示你是 运行在ring3的权限. 还是 ring0的权限.
RPL bit 0 bit1
RPL是段选择子的特权级别. 举个例子.看如下代码
mov ds,ax
ax里面是一个值.我们知道这个值是一个 段选择子 拆分开来可以是索引. 根据这个索引去GDT表中寻找对应的段描述符 然后段描述符 赋值给ds. 但是我们有没有想过. CPU执行这条指令的时候.为什么要去查.难道不该限制吗.不管是ring3 还是 ring0都可以查表吗. 不是这样的. RPL就是一个限制. 表示你想请求的特权级别. 当你请求的特权级别不满足.那么CPU肯定不会执行这条指令的.
那么不满足什么那. 就是我们下面所说的DPL
DPL 高13 14位
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相等或者小于 那么 就可以访问. 如果大于 那么久拒绝你访问
三种特权级别的表示方法
CPL bit0 bit1 == 1 表示 ring3应用 也就是CPL = 3 代表你是处于ring3的程序
CPL bit0 bit1 == 0 表示 ring0 程序. 运行在ring0中.
CS/SS CPL在同一时间特权都是一样的.X86规定的.也就是说 CS = 3 那么SS 也是3 CS = 0 SS 也是0
?
? RPL 选择子 RPL特选级别 要么是0 要么是3 也就是 RPL 要么两位都是0 要么都是1
? DPL DPL == 0 级别权限很高.
? DPL = 3 级别权限低. 只要 CPL RPL <= 3都可以访问.
CPL RPL DPL我么知道是啥了.那么我么可以进行模拟.
DPL针对数据段的权限检查
CPL <= DPL 且 RPL <= DPL 才可以执行指令成功
举个例子
CS = 8 代表当前特权级别是ring0
mov ax,0xF 段选择子为 1111 二进制 RPL是 3
mov dx,ax 执行指令操作
执行指令操作有. 优先与CPL比.然后在于RPL比.
根据指令我们知道 我们是在内核层执行的特权指令. mov dx,ax
首先检查CPL CPL = 0 DPL = 0 CPL<DPL 成立. 所以代表当前指令可以执行
继续比较
RPL = 3 DPL = 0 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下也是一样的.
一致代码段与非一致代码段的介绍
一致代码段: 可以理解为是共享段.数据是共享的.可以允许低权限去访问
非一直代码段: 如果不想让低权限访问.想对这一块加限制.那么可以用非一致代码段修饰
修饰的本质还是 校验特权位
非一致: 要求CPL == DPL 且 RPL<= DPL 才可以访问
一致: CPL>=DPL 则可以. cpl > 则代表权限是低的.
JMP far指令执行流程
截图如下
汇编指令如下
jmp far 0x0020:A50000
当CPU执行这条指令的时候会经过五个步骤
1.段选择子拆分查表
cpu 首先会将段选择进程拆分. 如我们的汇编的段选择子 0x0020
拆分为:
二进制: 0000 0000 0010 0000
RPL = 00
TI = 0
index = 4
2.查表获取段描述符
CPU则会根据 TI 判断查询是否是GDT表. 如果是查询GDT表. 然后根据index 去GDT表中查找对应的段描述符. 但是请注意,当我们查询的是 代码段 调用门 TSS任务段 任务门 的时候才能继续跳转
进行权限检查
查表之后当然首先第一步要进行权限检查.看是否你能访问我. 是否能访问段描述符 . 此时段描述符里面有DPL记录. 记录着段描述符的权限.
? 一致代码段的情况下: 当CPL== DPL 且 RPL <= DPL 的时候.才能访问 数据段. 我们知道 权限要么是0 要么是3
二进制分别对应的是 00 11 CPL 与RPL DPL都是两位表示. 而数字越低权限越高. 所以我们说的非一致代码段的情况下. 当前运行的特权级别(CPL) 权限必须与 段描述符权限相等(DPL) 否则不让你访问. 第一步通过之后. 然后判断
段选择子 (RPL) 是否 与DPL权限相当. 如果不是也不让你访问.
4.加载段描述符
当前三不执行过后. 此时CPU 才会真正的将 段描述符加载到CS段寄存器中
5.代码执行
我们说过 jmp far 执行要跨段必须修改 CS与EIP才可以. 我们CS已经通过第四步获取到了. 那么EIP的填写便是
将 cs.base + offset 填写到EIP中. 最后执行 cs:eip的指令. 这样我们流程才会结束
比如我们的 指令为
jmp far 0x0020:A50000
CS.base 已经通过段描述符获取了(第四步) 那么偏移就是 A50000 cs.base + A50000 然后进行代码执行
当我们使用 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指令来进行跳转 模拟段操作.当这条指令执行的时候就会执行我们的五步步骤
显然这条指令是可以执行成功了. 如果执行失败就会跑飞. 跳转到异常处理位置.
4.段权限检查
上面我们伪造的代码段描述符 = 00cffb00~0000ffff
其结果如下 我们直接洗DPL所在的高32位即可. 如下
kd> .formats 0x00cffb00
Evaluate expression:
Hex: 00cffb00
Decimal: 13630208
Octal: 00063775400
Binary: 00000000 11001111 11 11(DPL) 1011 00000000
Chars: ....
Time: Mon Jun 08 02:10:08 1970
Float: low 1.91e-038 high 0
Double: 6.73422e-317
通过解析.我们的DPL = 二进制的11 十六进制的3 我们构造的段选择子的权限也是11 所以执行我们指令的时候
权限通过. 可以进行执行.
但是我们可以尝试以下.将段选择子的RPL修改为0特权看一下.
代码段选择子改为 0x78 经过尝试也可以进行访问的. 原因就是 权限检查的时候 RPL <= DPL 既然DPL = 3那么就代表 权限小于或者等于我就可以访问了. RPL = 0 代表高权限 DPL = 3 代表地权限 搞特权是肯定能访问低特权描述符的.
尝试将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等
原文:https://www.cnblogs.com/iBinary/p/13246172.html