修改段寄存器时的保护

处理器在变更段寄存器以及描述符高速缓存器时,会检查代入值的合法性,若不合法,则抛出异常。将段选择子送入段寄存器时,CPU 的固件会自动确认选择子和该选择子对应的段描述符的正确性。
对选择子的检查内容包括

  1. 选择子的索引是否超界 ,即对应的段描述符是否在 GDT 范围内。要求 索引号×8+7 <= 边界 。若超过边界,则产生异常中断 13,同时段寄存器的原值不变。

  2. 选择子的索引是否为 0 。在全局描述符表 & 段选择子概述一文中说过,GDT 的第 0 个描述符不可用。对于 DS,ES,FS,GS 这四个段寄存器,可以向其中加载索引为 0 的段选择子,CS,SS 段寄存器则不可。

    虽然能够加载索引为 0 的段选择子,但访问内存时就会出错并抛出中断。CPU 使用这种特殊的设计来保证系统安全。

对段描述符的检查内容包括

  1. 结合 S 位判断 TYPE 字段的有效性 。比如 0000 就是无效值。
  2. 检查描述符类型是否和段寄存器用途匹配 。段的类别检查规则如下:

    大概原则可以总结为:
    1)只有可执行的段才能加载进 CS;
    2)只有可读写的段才能加载进 SS;
    3)至少可读的段才能加载进 DS, GS, ES, FS;
    注意,可读的代码类似于 ROM,既可以用 CS 执行,也可被 DS,ES,FS,GS 当作数据访问
  3. 检查 P 位 。若 P=0 ,则表示该段虽然已经被定义,但并不在内存中,抛出异常中断11。应当定义相应的中断处理程序,抛出异常后,把该描述符对应的段从硬盘中调入内存,中断返回时,处理器再进行操作。

对代码段的保护

EIP 中装着下一条指令的偏移地址。在获取下一条指令前,CPU 会检查 EIP 的有效性,以防止执行超出范围之外的指令。检查方式为:0<=(EIP+指令长度-1)<=实际段界限 ,其中 实际段界限=(段界限+1)×粒度-1 。跨越边界的指令同样不允许执行,这种指令也已经被包含在此公式中。

对数据段的保护

同代码段类似,其检查方式为:0<=(EA+操作数的长度-1)<=实际段界限 。例如:

mov [0x2000],edx

其中 EA=0x2000 。

对栈段的保护

同上类似,其检查方式为:(ESP-操作数长度)>=实际段界限

你可能会在源代码中进行以下的栈段定义:
注意图中为偏移地址,不是物理地址
将 ESP 初始值设为 0x7c00 ,段界限设为 0x7A00 ,粒度设为 1 字节,扩展方向设为向下 (E位为1) ,则定义出 0x7C00~0x7A00 的栈段。虽然 ESP 不能低于 0x7A00 ,但它却能够一直 POP 从而高于 0x7C00 ,这仍然超过了我们指定的空间。最可怕的是,即使高于 0x7C00 ,CPU 也不会抛出异常,这很有可能导致我们悄无声息地改变其他内存空间中的重要数据或代码,从而使程序崩溃。正确的栈段定义应该采用如下方式:
将段描述符中的段基址设为 0x00007c00 ,段界限设为 0x000FFFFE ,粒度设为 4KB(即设 G 为1) ,初始 ESP 设为 0 。则实际段界限为 FFFFEFFF ,又因为 ESP 的最大值为 FFFFFFFF ,所以操作该段时,CPU 的检查规则为:

0xFFFFEFFFF<=(ESP-操作数长度)<=0xFFFFFFFF

访问内存时,物理地址=段基址+ESP ,所以该栈段的栈底 物理地址 为:

0x00007C00 + 0xFFFFF000 = 0X00006C00

该栈段的最高端地址为:

0x00007C00 + 0xFFFFFFFF = 0X00007BFF

从而,该栈段被限制在物理地址 0x00006C00 ~ 0x00007BFF 范围内,长为 512 字节 。这波操作实在 666 。稍加思考就能发现,上面的方法是 通过 ESP 本身的最大值限制来为栈段创造了一个上边界

注意,数据段和栈段的段基址可以相同 。比如,上面讲的栈的基地址设为 0x7c00 ,但其栈底元素所占内存为 0x7BFC~0x7BFF ;若讲数据段的基地址也设为 0x7C00 ,则起始数据的内存就是从 0x7C00 开始。

实际上,可以将数据段作为栈段,这种方式定义起来比较简单,但其中有些细节问题笔者还未搞清楚,搞明白后再更新。

利用段别名修改代码段

一般情况下,任何指令都不允许向代码段写入数据,而且,只有可读的代码才允许访问。但很多时候又需要对代码作一些修改,比如调试程序时需要加入断点指令 int3。此时,就不能使用原描述符来进行访问,而应该重新定义一个段描述符,并将其定义成可读写的段,这样就可以通过这个新定义的段描述符来修改之前的代码段。像这样,当多个不同的描述符指向同一个段时,把另外的描述符称为该段的别名。

如果两个程序想共享同一个内存区域,也可以采用此方式。

文章参考:《操作系统真相还原》《x86汇编语言:从实模式到保护模式》

文章作者: 极简
本文链接:
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 后端技术分享
自制操作系统
喜欢就支持一下吧