参考文档:3.4、3.5节
INTEL技术白皮书.pdf

1.段选择子

  • 段选择子(Segment Selector)的结构是16位的,它的结构如下:

image.png

  • RPL(Requested Privilege Level):请求访问者所使用的权限级别,从0到3级。
  • TI(Table Indicator):描述符表索引位。当 TI=0 时,从GDT查找;当TI=1时,从LDT查找。
  • Index(Descriptor Index):这是Descriptor在GDT/LDT中的序号,根据TI的值在相应的描述表中查找descriptor。
    • 使用VS2013进行试验,可以看到CS段的段选择子CS:001B,如下图所示:

段选择子.png

  • 先将0x001B转化为2进制,然后根据段选择子的结构找出各个部分的含义,如下图所示:TI=0,表示段描述符索引在GDT表中查询,然后段描述符的Index=3。

image.png

  • WinDbg命令:
    • “r 寄存器”查看寄存器中的内容。
    • “db address”查看给定地址的内存空间内容,使用单字节查看。dw,双字节查看。dd,4字节查看。dq,8字节查看。d命令默认单字节查看。
  • 使用命令r gdtr查看GDT表的地址,r gdtl查看GDT表的长度:

image.png

  • 查看GDT表的内容,d命令默认单字节查看,db也表示单字节查看:

image.png
image.png

  • 查看GDT表内容,dw表示双字节查看:

image.png

  • 查看GDT表内容,dd表示4字节查看:

image.png

  • 查看GDT表内容,dq表示8字节查看:

image.png

  • 从GDT表中找到CS:001B的段描述符,索引是3,即查找GDT[3]的位置,内容为:00cffb00`0000ffff:

image.png

2.段描述符的结构

  • 上面从CS段的段选择子的结构中找到了CS段的段描述符,它是存放在GDT表中的GDT[3]的位置,查看到CS的段描述符为:00cffb00`0000ffff。
  • 从Intel白皮书中可以查询到段描述符的结构:

image.png

  • 将CS段的段描述符按照白皮书中的段描述符结构进行拆解如下:

image.png

2.1 Type域

  • 当段描述符中的S标志位为1时,该描述符为代码段或者数据段的描述符。
  • Type域的最高位(段描述符第二个双字的第11位),决定该描述符为数据段描述符(为0)或者代码段描述符(为1)

image.png

  • 对于数据段而言:Type域的低3位(第8、9、10位)被解释为:标识该描述符是否已经被访问过(A),是否可写(W),扩展方向(E)。
  • 对于代码段而言:Type域的低3位(第8、9、10位)被解释为:标识该描述符是否已经被访问过(A),可读位(R),一致位(C)。
    • Type域的数据位定义如下图所示:

image.png

  • 从上面提取出的CS段的Type域为二进制的1011,所以此CS段的权限为可执行可读可访问。

image.png

2.2 G(Granularity)位

  • 段描述符的Segment Limit字段(20位)限制了段的长度,根据标志位G(粒度)的不同,确定两种不同段长的计算单位,换句话说,G位确定了Segment Limit字段的计算单位:
    • G标志位为0时,段长增量的计算单位为字节,则该段的大小为1字节到1M字节。
    • G标志位为1时,段长增量的计算单位为页(4K字节),则该段的大小为4K字节到4G字节。

image.png

  • 上面的CS段的Limit字段为20个1,转化为16进制为0xFFFFF,由于G位为1,所以计算单位为页(4K字节=4096字节=0x1000字节),所以段的长度可以计算为:0xFFFFF * 0x1000 = 0xFFFFF000,由于计数是从0开始,所以还需要加上1个页,也就是0xFFFFF000 + 0x1000 = 0x100000000,又由于寻址是从0地址开始的,所以0x100000000 - 1 = 0xFFFFFFFF。就得到了CS段的段长为:0xFFFFFFFF。

image.png

2.3 P(Segment Present)位

  • P位主要的作用是标志该段是否在内存中:

    • 当P位为0时,表示该段不存在内存中,处理器会产生一个段不存在的异常(NP)。
    • 当P位为1时,表示该段在内存中,可以正常访问。

      2.4 S(Descriptor Type)位

  • S位是段描述符类型的标志:

    • 当S位为0时,表示该描述符是系统描述符。
    • 当S位为1时,表示该描述符是代码或者数据段描述符。

      2.5 D/B(Default Operation Size)位

  • D/B这个标志位的功能由段描述符所指向的段的类型决定:

    • 当描述符指向的是可执行的代码段,这个标志位被称为D标志,它指明该段中的指令所涉及的有效地址值的缺省位位数和操作符的缺省位位数。如果该标志为1,缺省为32位的地址, 32位或者8位的操作符;若为0,缺省为16位的地址, 16位或者8位的操作符。
    • 当描述符指向的是堆栈段,这个标志位被称为B标志,它为隐含的栈操作(如push, pop和call)确定栈指针值的位位数。如果该标志为1,则使用的是32位的栈指针,该指针放在32位的ESP寄存器中;若该标志为0,则使用的是放在16位SP寄存器中的16位的栈指针。如果该堆栈段为一个向下扩展的数据段,B标志位还确定了该堆栈段的地址上界。
    • 当描述符指向的是向下扩展的数据段,这个标志位被称为B标志,它确定了该段的地址上界。如果该标志位为1,段地址上界为0xFFFFFFFFH(4GB);若该标志位为0,段地址上界为0xFFFFH(64KB)。

      2.6 DPL(Descriptor Privilege Level)域

  • 指明该段的特权级。特权级从0~3, 0为最高特权级。DPL用来控制对该段的访问。

image.png

3.修改段寄存器的Base

  • 首先,正常情况下的DS段和寻找到的val变量如下:

image.png

  • 修改DS段寄存器的Base的整体思路:首先使用WinDbg查看空的GDT表项和修改前的段描述符的值,并记录下空的该表项的Index,然后根据该空的表项的Index修改段选择子,以此来让段选择子定位到该空的GDT表项,再使用WinDbg命令修改该空的表项的值(按照段描述符的结构修改Base域的值)。
  • 使用WinDbg寻找空的GDT表项:看到GDT[9]处的段描述符为全0,可以使用,所以后续段选择子需要修改的GDT表项的Index的值应该设为9。修改前的DS段选择子为0x0023,初始的Index值为0x00100=4,看到DS段的段描述符修改前的值为:GDT[4] = 00cff300`0000ffff,如下图所示:

image.png

  • 修改DS段的段选择子:修改前的DS段选择子为0x0023,所以初始的Index值为0x00100=4,GDT[4] = 00cff300`0000ffff。修改Index的值为9=0x01001,所以修改后的段选择子为0x004B。

image.png

  • 使用WinDbg修改内存中存放的GDT表项:初始的DS段的段描述符值为:00cff3000000ffff,初始的Base为0x00000000,将初始的Base改变为0xf0000000,段描述符的结构如下图所示,要将Base的最高位改为f,所以将初始的段描述符的最后一位16进制改为f即可,则修改后的段描述符为:f0cff3000000ffff。然后通过WinDbg将GDT[9]修改为f0cff300`0000ffff,如下图所示:

image.png
image.png

  • 在汇编代码中修改DS段的段选择子:修改DS段的段选择子为0x004B,那么DS段就会通过段选择子的Index找到DS段在GDT表中的表项(GDT[Index],也就是DS段的段描述符),而DS段的段描述符已经被我们修改为了f0cff300`0000ffff,所以通过DS段的段描述符得到的Base不在是0x00000000,而是0xf0000000。整体过程如下图所示:

image.pngimage.png
image.png

4.修改段描述符的P位

  • 段描述符的P位标识当前段是否在内存中,也就是标识当前段是否可用,当将段描述符的P位修改从1改为0后,就会使当前段不能被使用。

image.png

  • 原理和流程和上一节中修改Base相同,首先使用WinDbg修改段描述符的值,将P位从1改为0。未修改前的DS段的段描述符为00cff300`0000ffff,按照上图P位的位置,将P位改为0后,DS段的段描述符为00cf7300`0000ffff。然后再将DS段的段选择子改为0x004B让DS段定位到我们修改后的段描述符(GDT[9]),实验截图如下:

image.png
image.png

5.修改段描述符的S位

  • S位用来该段描述符属于系统描述符(S位为0)还是代码或数据段描述符(S位为1),S位所在的段描述符结构的位置如下图所示:

image.png

  • 使用WinDbg修改DS段的段描述符:未修改前的DS段的段描述符为00cff300`0000ffff,按照上面S位所在的位置,将DS段的段描述符修改为:00cfe300`0000ffff

image.png

  • 修改DS段的段选择子定位到修改后的段描述符:将DS段的段选择子修改为0x004B。运行下面的代码,发现出现了错误,因为将DS段的段描述符的S位改为了0,所以该段描述符被标识为系统描述符,因此访问会发生错误。

image.png

6.修改段描述符的Type域

  • 段描述符的Type域所在的位置如下图所示:

image.png

  • Type域占了4位(第8、9、10、11位)的大小,各个位的作用如下图所示:

image.png

6.1修改Type域的W位

  • W位用来标识段是否可写:
    • 当W位为1时,该段可写;
    • 当W位为0时,该段不可写;
  • 未修改前的DS段的段描述符为:00cff300`0000ffff,此时的DS段的描述符的Type域的W位为1,所以是可以对DS段进行写操作的:

image.png

  • 按照W位在Type域中所在位置,DS段的W位修改为0,则修改后的DS段的段描述符为:00cff100`0000ffff

QQ截图20210527151239.png

  • 由于此时W位被置为了0,所以DS段不可写,再次执行对DS段的写操作时,会发生错误:

image.png

6.2修改Type域的A位

  • A位用来标识该段描述符是否已经被访问过:
    • 如果该段描述符被访问过,A位就会被置为1;
    • 如果该段描述符没有被访问过,A位就会为0;
  • 首先将DS段的段描述符Type域的A位修改为0:未修改前的DS段的段描述符为:00cff300`0000ffff,按照A位所在的位置修改后的段描述符为:00cff200`0000ffff。

image.png

  • 当通过代码访问DS段的段描述符后,DS段的段描述符的A位又会被置为1,于是又变成了00cff300`0000ffff

image.png
image.png

7.段权限

  • 段的特权级分为3种类型:当前特权级(CPL),描述符特权级(DPL),请求特权级(RPL)。

    7.1当前特权级(CPL)

  • CPL是当前正在运行的进程或任务的特权级。它存在CS和SS段寄存器的bit0和bit1位。通俗的来说,CPL与当前指令所在的代码段的特权级相等。

    7.2描述符特权级(DPL)

  • DPL是一个段或门的特权级。它存储在一个段或门的描述符的DPL域中,如下图所示。

image.png

7.3请求特权级(RPL)

  • 它存储在段选择符的bit0和bit1位,如下图所示:

image.png

To be continueing…