开篇

X86 CPU的3个模式:实模式(直接操作物理地址)、保护模式(操作虚拟内存)和虚拟8086模式(保护模式下的16位模拟)

X64:x86是intel推出的复杂指令集,后来amd拓展了X86指令集,称为称为x86-64,后更名为AMD 64,intel也出了自己的一套64位指令集,但是不向下兼容,所以就没了,继而移植了amd 64位指令集,称为intel 64(能够兼容x86指令集的)

段的机制

段寄存器

mov dword ptr ds:[0x123456],eax
实际操作的是 ds.base + 0x123456 地址

段寄存器的种类

ES(拓展段)、CS(代码段)、SS(堆栈段)、DS(数据段)、FS、GS、LDTR、TR 共8个段寄存器

段寄存器的读写

读只能读16位,写能写入96位
mov ax, ds    必须指定写入的地址为16位
mov ds, eax
除LDTR和TR外,其余段寄存器可用MOV指令进行读写

段寄存器的结构

upload successful

struct SegMent
{
    WORD Selecter,   16bit//可见部分 段选择子
    WROD Attribute,  16bit//属性  可读/可写/可执行
    DWORD Base,      16bit//Base  段的起始地址
    DWORD Limit      16bit//Limit 段的整体长度
};

upload successful

根据上图进行段寄存器的不可见部分探测

探测Attribute是否存在
#include <stdio.h>

int main(){
    __asm{
        mov ax, cs
        mov ds, ax                        //ds == cs
        mov dword ptr ds:[0x1000], eax    //对ds.base + 0x1000 地址进行操作就相当于对 cs.base + 0x1000 地址进行操作,但由于 cs 属性是不可写,所以编译出错。
    }
    return 0;
}
探测Base是否存在
#include <stdio.h>

int main(){
    __asm{
        mov ax, fs
        mov ds, ax                   //ds == fs
        mov dword ptr ds:[0], eax    //对ds.base + 0x0 地址进行操作就相当于对 fs.base + 0x0 地址进行操作
        //等同 mov dword ptr [0x7FFDE000], eax ,正常情况下,我们对0地址是不能读也不能写,这里可以编译通过,是因为我们对地址 0x0x7FFDE000 进行写操作。将fs换成其余的段寄存器,就会报错
    }
    return 0;
}
探测Limit是否存在
#include <stdio.h>

int main(){
    __asm{
        mov ax, fs
        mov ds, ax                        //ds == fs
        mov dword ptr ds:[0x1000], eax    //对ds.base + 0x1000 地址进行操作就相当于对 fs.base + 0x1000 地址进行操作,但由于 fs 支持的长度只是到0xFFF,0x1000 > 0xFFF,所以编译出错。
    }
    return 0;
}

段描述符与段选择子

GDT(全局描述符表)与LDT(本地描述符表)

windbg查看GDT表

r gdtr  //查看GDT表的位置
r gdtl  //查看GDT表的长度
dd 0x80808080(地址) //查看指定地址的内容(四字节查看)
dq 0x80808080(地址) //查看指定地址的内容(八字节查看)
dq 0x80808080(地址) L40 //查看指定地址的内容(八字节查看),显示40组

段选择子结构

upload successful

段描述符结构

upload successful

upload successful

upload successful

数据 E 拓展位
    根据D/B位可知段的上限,0为64kb,1为4GB。
    向上拓展
        ds.base + limit 的红色区域是可用的
    向下拓展
        出了红色部分,其余是可用的
代码 C 一致位
    非一致代码段
    一致代码段

段权限检查

CPU分为四个级别R0、R1、R2、R3(操作系统并没有使用R1、R2),R0运行内核记驱动程序,R3运行用户层程序。

CPL:当前特权级别

根据CS、SS的段选择子的RPL值可知道CPL的值

假设CS为0x001B(0000 0000 0001 1011)
RPL为3,所以CPL为3,所以应用程序运行在3环,是用户层程序。

数据段的权限检查
    检查CPL权限是否大于等于DPL,RPL权限是否大于等于DPL
    假设CPL = 0
    mov ax,000B
    mov ds,ax       //RPL = 3,指向的段描述符的DPL为0
    CPL权限与DPL相同,检测通过,RPL权限比DPL权限低,检测失败

代码段、系统描述符的权限检查与数据段不完全相同,后面会学习到。

段寄存器的操作

mov ds, ax //ds的值不能乱给,需要符合段选择子

除了mov指令还可以使用 LES,LSS,LDS,LFS,LGS指令修改段寄存器的值

没有LCS是因为更改CS涉及到更改EIP。

例:
    les ecx, fword ptr ds:[0x1000] //将ds.base + 0x1000 地址处的内容的高两个字节放入es,剩下四个字节放入ecx。fwrod(6字节)

假设ax = 0x001B(0000 0000 0001 1011)
所以:RPL = 3(请求特权级别为3,由11给出)
     所要查询的表是GDT表(由 0 给出)
     指定获取表中下标为3的段描述符(由0000 0000 0001 1给出,假设为 0x004F9AFF`0xFFFFFFFF)
     根据段描述符可得
         段基地址:0x00FFFFFF
        段极限:0x000FFFFF(G位为0,所以在前面填充12个0)
        段的属性:0x4F9A

回过头,在看一下段寄存器的结构

struct SegMent
{
    WORD Selecter,   16bit//可见部分 段选择子         指向的是段选择子结构
    WROD Attribute,  16bit//属性   可读/可写/可执行   指向的是段描述符结构的 G + D/B + L + AVL + 段界限0 + P + DPL + S + TYPE
    DWORD Base,      32bit//Base  段的起始地址       指向的是段描述符结构的 段基地址0 + 段基地址1 + 段基地址2
    DWORD Limit      32bit//Limit 段极限             指向的是段描述符结构的 段界限0 + 段界限1 合起来是20位,根据 G 位的值,若为0,则在其前面填充12位的0,最大就为0x000FFFFF,若为1,则在其后面填充12位的1,最大就为0xFFFFFFFF
};

虽然只能可见部分只有16bit,但是通过它去查找GDT/LDT表,就能获得其余80bit的值

总结

学习完这些,能够知道RPL、DPL、CPL分别只什么,怎么找到,段选择子与段描述符的结构以及每一位的作用,如何找到DGT/LDT表,能够按顺序背下ES、CS、SS、DS、FS、GS、LSTR、TR的名字,给一个段选择子能够分析出段的其余80bit的值。



保护机制      段的机制

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!