avatar

Catalog
段描述符与段选择子

在探究段寄存器属性时,注意到,段寄存器在的时候,只读了16位,但是的时候会写入96位。那么,段寄存器是如何做到写入96位的呢?今天就要研究两个新的概念:段描述符段选择子

基础知识

Windbg指令

Windbg是在调试Windows系统内核时常用的一个调试器,之后也会多次用到;通过Windbg可以实现主机对虚拟机上的Windows系统进行双机调试。搭建双机调试环境可以参考此贴:https://blog.csdn.net/q1007729991/article/details/52710390

这里,简单介绍一下,在研究段描述符和段选择子所需用到的几个Windbg指令:

命令 含义
r 查看和修改寄存器
dd 以4字节分隔,显示指定内存区域的数据内容
dq 以8字节分隔,显示指定内存区域的数据内容

第二个d和q分别是dword和qword的意思,第一个d是一个查看内存的指令,以后会详细说明。

汇编基础

这里补充一点汇编基础,如何在确保,给一个拥有6个元素的char型数组赋值时,确保元素所在高位或者低位呢?

通过观察反汇编可以看出,0x78处在 [ebp-8]的位置上,距离ebp相对较远;0x12处在[ebp-5]的位置上,距离ebp相对较近,两者差了4个字节,我们可以假象在地址空间中的位置如下表:

地址
0x12ff40(随便取个值) 0x78
0x12ff41 0x56
0x12ff42 0x34
0x12ff43 0x12
0x12ff44 0x23
0x12ff45 0x00

而Windows操作系统是小端模式,也就是高字节保存在高地址中;例如0x12在0x12345678这个数里属于高字节,0x12所在的地址位0x12ff43相对于0x78位于高地址,所以在赋值时,需要把0x12放到高地址中,根据小端模式在内存中的排列可知,若想确定一个实际值为0x12345678的数,在内存的排列大概是”78563412“这种形势,因此在赋值时按照如下方式:

这里还有一个坑是,赋值时不要加’ ‘,因为一个字节的数不止一个字符,不能放到单引号里。

GDT,LDT

GDT和LDT分别指全局描述符表局部描述符表。由于Windows系统没有使用LDT表,所以可以忽略这个表。而GDT表,表里存储的就是段描述符

了解GDT表,需要先知道这个表有多大,存在哪里。这时需要借助一个寄存器gdtr,这是个48位的寄存器,其中32位存的是GDT表的位置,16位存的是GDT表的大小;可以通过以下指令进行查询。

由图可知,当前虚拟机中的操作系统,gdt表位于0x8003f000的位置,大小是0x03ff,也就是说从0x8003f000~0x8003f3ff这段内存中,存放着gdt表。

段描述符

当执行以下语句时:

Code
1
mov ds, ax

CPU会去查表,根据ax的值决定查看GDT表还是LDT表,以及查找表的什么位置,查出哪些数据

首先查看一下GDT表,由于段描述符大小是8字节/64位,所以采用dq指令进行查看。

这里查看了GDT表0x80个字节大小的内存,一个段描述符的大小是8字节,所以显示了16个段描述符。

接下来看一下段描述符表的结构

可以发现,在段描述符中,有着Base,Limit,还有各种Attribute,这些就是从段描述符中查找的数据,并写入段寄存器剩下的80位里。那么有了GDT表和段描述符,那么究竟该选择哪一个段描述符的数据写入段寄存器呢?这就涉及到另一个结构:段选择子

段选择子

“段选择子是一个16位的段描述符,该描述符指向了定义该段的段描述符”。

这句话怎么理解,怎么又是16位的段描述符,又是GDT表的段描述符?首先,段的Base,Limit以及Attribute都是由GDT表的段描述符来决定的,那么到底是由哪个段描述符来决定的?为了确定这个段描述符,引入了段选择子这个结构,段选择子,指向了GDT表中某一个段描述符,这样就可以把该段描述符的数据写入到段寄存器内了。所以说,段选择子,是一个段描述符的描述符。下面是段选择子的结构。

由图,结构非常简单,各个位的含义也比较好理解。这里有个小g巧,段选择子一共16位,由于Windows没有使用LDT表,所以TI位永远是0。请求特权级别一般也只有0和3,所以段选择子最后4位的值只有4种组合:0000, 0011, 1000, 1011

加载段描述符至段寄存器

除了MOV指令,我们还可以使用LES、LSS、LDS、LFS、LGS指令修改寄存器。

CS不能通过上述的指令进行修改,CS为代码段,CS的改变会导致EIP的改变,要改CS,必须要保证CS与EIP一起改,以后的文章会说到。

Code
1
les ecx,fword ptr ds:[buffer]          //取buffer高2个字节给es,低4个字节给ecx

这里的buffer是一个地址,存了6个字节的数,例如定义buffer为一个6个元素的char型数组。这里的fword指的是三字,也就是6个字节

注意:在数值上需要要求RPL <= DPL,RPL也就是段选择子低2位,而DPL可以在段描述符结构中找到,位于高4字节的13~14位,原因之后会说明。

代码实现如下,实际上就是把ss的段选择子,写到buffer的高字节里,然后再通过les指令完成写入。

参考教程: https://www.bilibili.com/video/av68700135?p=9

Author: cataLoc
Link: http://cataloc.gitee.io/blog/2020/03/08/%E6%AE%B5%E6%8F%8F%E8%BF%B0%E7%AC%A6%E4%B8%8E%E6%AE%B5%E9%80%89%E6%8B%A9%E5%AD%90/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
    微信
  • 支付寶
    支付寶