深入特权级转移

简介: 深入特权级转移

引言

  • 本章节需要自己通过实际实验来摸索 DPL、RPL、CPL 三者之间的关系,实验方式就是尝试改变不同的 DPL、RPL 的值,然后观察程序的运行状态及结果
  • 实验过程在这里就不一一记录了,建议自己摸索,这样才能较为深入的体会特权级相关知识

特权级转移小建议

  • 建议绝大多数情况下同一个类段的相关特权级保持一致,即如果代码段 DPL = 0,那么与之对应的栈段、数据段等的 DPL 也要设置为 0,又或者不要出现对应的 RPL != DPL 的情况
  • 我们在上一章节实现的 loader.asm 这个代码基础上做个小实验看一下
  • 仅将栈段描述符 STACK32_DESC 的特权级改为 3

; STACK32_DESC : Descriptor 0, TOP_OF_STACK32, DA_DRW + DA_32

STACK32_DESC : Descriptor 0, TOP_OF_STACK32, DA_DRW + DA_32 + DA_DPL3

  • 运行一下,程序并不能正确执行
  • 不是满足 CPL <= DPL 并且 RPL <= DPL 这个条件吗?怎么程序还是不能执行成功呢?
  • 原因就在于特权级 0 的代码段使用了特权级 3 的栈,CPU 肯定不会同意你这么干的
  • 所以绝大多数情况下相关特权级定义一致能够有效的避免很多错误

段的分类

  • 保护模式 中我们知道,描述符分系统描述符和非系统描述符
  • 系统段和非系统段的区分方式就是段描述符中的 S 位,系统描述符(S=0),非系统描述符(S=1)
  • 系统描述符包含 LDT,TSS 和各种门等
  • 非系统描述符其实说的就是代码段或数据段描述符
  • 对于代码段,又可以分为两类,一致性代码段和非一致性代码段
  • 那么怎么区分一致性代码段和非一致性代码段呢?
  • 见下图,段描述符中 TYPE 字段中的 C 位为 1,则为一致性代码段, C 位为 0,则为非一致性代码段
  • 说了等于没说,那么如何直观理解一致性代码段和非一致性代码段的区别的呢?
  • 在不借助调用门的情况下:
  • 非一致性代码段:代码段之间只能平级转移(CPL == DPL,RPL <= DPL)
  • 一致性代码段:支持低特权级代码段向高特权级代码段转移(CPL >= DPL),虽然可以成功转移到高特权级的代码段,但是当前特权级 CPL 不变
  • 注意:数据段只有一种,没有一致性和非一致性的区分,并且数据段不允许被低特权级的代码段访问

验证一致性代码段特性

  • 目的:验证一致性代码段是否支持低特权级代码段向高特权级代码段转移,当前特权级 CPL 是否发生改变
  • 首先我们实验所用的基础代码依旧是上面实验所用的基础代码:loader.asm
  • 原程序中 Task A(特权级 3)通过调用门跳转到特权级 0 执行打印,现在我们先把打印程序代码段属性改为一致性代码段即可
; FUNC_DESC : Descriptor 0, FUNC_SEG_LEN - 1, DA_C + DA_32
FUNC_DESC : Descriptor 0, FUNC_SEG_LEN - 1, DA_CCO + DA_32
  • Task A 中原调用门方式要改为 jmp 直接跳转
; call FUNC_PRINT_SELECTOR : 0
jmp FUNC_SELECTOR : printOffset
  • 实验结果,程序成功运行,现象与所期待的一致
  • 用反汇编方式断点调试,发现跳转后特权级并未改变,验证我们上面所说的理论

深入理解调用门

  • 调用门用于低特权级代码段向高特权级代码段转移
  • 调用门描述符的特权级低于或小于当前特权级(DPL(Gate) >= CPL)
  • 用图形的形式直观理解

  • 关于调用门的注意事项
  • 调用门支持特权级同级转移
  • 调用门同级转移被处理位普通函数调用或直接调用
  • call 通过调用门提升特权级,jmp 只能同级转移
  • 通过调用门降特权级返回(retf)时,CPU 会对目标代码段及栈段进行特权级检查;对相关段寄存器强制清零
  • 通过反汇编后断点调试,发现执行 retf 指令(特权级降低)后,相关段寄存器都被清零了,这是为什么呢?
  • 原因就是处理器为了内核数据的安全,当发生高特权级代码段向低特权级代码段转移时,CPU 会将这些寄存器全部自动清零,这样子低特权级程序就没有机会非法的访问内核数据了

IO 特权级

  • 在保护模式下,特权级检查保护机制不光体现在数据和代码的访问,还体现在 I/O 读写控制上
  • I/O 读写特权是由标志寄存器 eflags 中的 IOPL 位(bit12-bit13)和 TSS 中的 IO 位图决定的,它们用来指定执行IO 操作的最小特权级。I/O 相关的指令只有在当前特权级大于等于 IOPL 时才能执行。如果当前特权级小于 IOPL 时执行这些指令会引发处理器异常。这类指令有 in、out、cli、sti
  • CPL 为 0 的时候 I/O 操作是不受限制的
  • 做个小实验验证一下,我们在上一章节实现的 loader.asm 代码上 TASK A 中添加如下指令

mov al, 0x0A

out 0x20, al

  • 不用在乎这个指令是什么意义,知道这是 I/O 操作就可以了
  • 结果程序运行崩溃
...
(0).[16768112] [0x00002065] 0007:0000002d (unk. ctxt): out 0x20, al              ; e620
00016768112e[CPU0 ] exception(): 3rd (13) exception with no resolution, shutdown status is 00h, resetting
00016768112i[SYS  ] bx_pc_system_c::Reset(HARDWARE) called
00016768112i[CPU0 ] cpu hardware reset
  • 从打印提示中可以看出程序死在 out 指令处
  • 那么,如何才能操作 eflags 的 IOPL 位呢?
  • 可惜汇编中没有直接读写 eflags 寄存器的指令,不过可以使用如下指令迂回实现 eflags 读写操作
pushf
pop eax 
or eax, 0x3000 ; bit12-bit13:11b
push eax
popf
  • 解释一下,pushf 作用是将 eflags 寄存器入栈,再利用 pop eax 将刚刚入栈的 eflags 值弹出到 eax 寄存器中,然后改动 eax 的值,接下来 push eax 再将改动后的 eax 值入栈,最后再利用 popf 指令把改动过后的值弹出的 eflags 寄存器中
  • 接下来就将上面的指令写到 TASK A 代码中,发现还是报错了,而且通过reg 指令发现 eflags 中 IOPL 位还是 0,并没有改变,这又是怎么回事呢?
  • 只有在 0 特权下才能执行上面的指令。如果在其他特权级下执行此指令,处理器也不会引发异常,只是没任何反应
  • 于是我们再把指令复制到 CODE32_START 代码段(特权级为 0)下,这回程序运行一切正常了。Ctrl+C 强制退出程序,再用 reg 命令查看 IOPL=3
  • 最后提供一下实验完整代码:loader.asm
目录
相关文章
|
6月前
|
安全
特权级由低到高转移
特权级由低到高转移
64 0
|
2月前
|
负载均衡 网络协议 Unix
Nginx负载均衡与故障转移实践
Nginx通过ngx_http_upstream_module模块实现负载均衡与故障转移,适用于多服务器环境。利用`upstream`与`server`指令定义后端服务器组,通过`proxy_pass`将请求代理至这些服务器,实现请求分发。Nginx还提供了多种负载均衡策略,如轮询、权重分配、IP哈希等,并支持自定义故障转移逻辑,确保系统稳定性和高可用性。示例配置展示了如何定义负载均衡设备及状态,并应用到具体server配置中。
|
3月前
|
运维 监控 定位技术
故障转移和自动恢复
故障转移和自动恢复
|
3月前
|
存储 缓存 运维
无状态故障转移与有状态故障转移
【8月更文挑战第24天】
30 0
|
4月前
|
消息中间件 运维 监控
中间件故障转移主-备配置
【7月更文挑战第25天】
37 2
|
4月前
|
运维 监控 Kubernetes
中间件故障转移自动切换
【7月更文挑战第25天】
40 2
|
4月前
|
运维 监控 算法
中间件故障转移故障检测
【7月更文挑战第25天】
45 1
|
6月前
|
芯片
特权级由高到低转移
特权级由高到低转移
62 0
|
存储 运维 负载均衡
RH236配置IP故障转移--CTDB
RH236配置IP故障转移--CTDB
903 0
RH236配置IP故障转移--CTDB