在探究段寄存器属性时,注意到,段寄存器在读的时候,只读了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表。
段描述符
当执行以下语句时:
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一起改,以后的文章会说到。
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指令完成写入。