• 内核学习之调用门试验


    • 调用门实验

    目标: 使得一个3环程序能够读取出0环的数据

    步骤分析:

    1. 3环程序的cs段寄存器保存的是3环的CPL

    1. 3环程序的cs段寄存器不可见部分保存的段描述符,是一个DPL为3的段描述符

    1. 需要将3环的cs段寄存器的CPL改成0 , 将一个0环的代码段描述符加载到cs的不可见部分. 最后3环程序才有0环的特权等级去访问0环的数据.

    • 实现步骤:

    1. 在GDT表中手工构造一个调用门

    1. 在3环程序中加载这个调用门.(加载调用门是为了将0环的段描述符加载到3环的cs段寄存器.)

      2.1 要在环将一个描述符加载cs中必须满足以下要求:

      2.1.1 需要用到间接修改cs段寄存器的指令

      2.1.2 CPL<=DPL && RPL <= DPL,此处的DPL指的是调用门保存的DPL

      因此, 调用门的DPL必须是3

      2.1.3 由于门描述符在运行后, 会直接将门描述符中的段选择子加载cs中.

      因此, 可以将一个0环的代码段的段选择子保存调用门的段选择子部分

      2.1.4 加载之后, 需要转移到一个地址上执行代码,这个地址是在调用门的段内偏移部分来指定的.

    因此, 可以将一个函数的地址存放到这个位置.

    // 实现的主要代码
    int g_num;
    short g_ss;
    int g_esp;
    
    //通过调用门调用的函数
    void _declspec(naked) GateFun()
    {
        g_num = 100;
        _asm mov [ g_esp ] , esp;
        _asm mov ax , ss;
        _asm mov word ptr [g_ss],ax
        _asm retf;
    }
    
    int main()
    {
        printf( "调用门函数地址:%08X
    " , GateFun );
        printf( "切换的段选择子:%04X
    " , 8 );/*8是内核中的代码段选择子*/
    
        unsigned long long descript =
            createCallGateDescript(8/*8是内核中的代码段选择子*/ , ( unsigned int )GateFun , 0 );
    
    
        printf("请将这个段描述符写入到GDT[9]中: ");
        std::cout << std::hex <<std::uppercase<< std::setfill('0')<<std::setw(8)<< descript<<'
    ';
        system( "pause" );
    
    
        // 获取当前寄存器的值.
        _asm mov[ g_esp ] , esp;
        _asm mov ax , ss;
        _asm mov word ptr[ g_ss ] , ax
    
        printf( "调用前  esp=%08X, ss=%04X
    " , g_esp , g_ss );
    
        // 前4字节是EIP,后2字节是CS(0x004b)
        char buff[ ] = { 0,0,0,0,0x4b,00 };
    
        _asm call fword ptr ds:[buff];
    
        printf( "调用后  esp=%08X, ss=%04X
    " , g_esp , g_ss );
        printf( "g_num=%d
    " , g_num );
        system( "pause" );
    }
    1. 使用调用门之前,需要将调用门的描述符写进系统的GDT表中,使用WinDbg双机调试,获取GDT表的地址

      kd> rgdtr
    
      gdtr=80b95000
    1. 使用dq命令查看GDT表中哪些是空闲的(值为0是空闲的)

      kd> dq 80b95000
      ​
      80b95000  00000000`00000000 00cf9b00`0000ffff
      ​
      80b95010  00cf9300`0000ffff 00cffb00`0000ffff
      ​
      80b95020  00cff300`0000ffff 80008b1e`500020ab
      ​
      80b95030  84409313`ac003748 0040f300`00000fff
      ​
      80b95040  0000f200`0400ffff 00000000`00000000
      ​
      80b95050  84008913`80000068 84008913`80680068
      ​
      80b95060  00000000`00000000 00000000`00000000
      ​
      80b95070  800092b9`500003ff 00000000`00000000

       

       2. 构造一个可以在3环代码中使用的段选择子,构造规则:

      按照段选择子格式: | 描述符在表中的下标:13 | T1:0 | RPL:2 |

      由上面可以知道在GDT表中,第9项是空闲的,门描述符将保存在这个地方,则对应的段选择子:

    13位描述符表索引 1位:T1 2位:RPL
    十进制 9 0 3
    二进制 1001 0 0x11

      合并为: 0100 1011 十六进制为 4B

      前4字节是EIP,后2字节是CS,后两个字节是0x004B,此数值就是在第二步中构造出来的

    char buff[] = {0,0,0,0,0x4b,00};
    ​
    __asm call fword ptr ds:[buff]
    3. 把生成的程序放入虚拟机中运行 0119EC00~000811CC

     
    4. 根据步骤2中的信息可以得到,GDT[9]的地址为80b95000 + 6 * 8或者80b95040 + 8开始写入
    kd> eq 80b95048 0119EC00`000811CC

    5. 写入之后查看发现已经写入成功

    kd> dq 80b95000
    
    80b95000 0000000000000000 00cf9b000000ffff
    
    80b95010 00cf93000000ffff 00cffb000000ffff
    
    80b95020 00cff3000000ffff 80008b1e500020ab
    
    80b95030 84409313ac003748 0040f30000000fff
    
    80b95040 0000f2000400ffff 0119ec00000811cc
    
    80b95050 8400891380000068 8400891380680068
    
    80b95060 0000000000000000 0000000000000000
    
    80b95070 800092b9500003ff 0000000000000000

     6. 继续g回到虚拟机中往下查看,写入成功实验完成。

     

     

     

  • 相关阅读:
    解决“不是有效的win32应用程序”问题
    mysql 5.7 windows install
    Redis
    给 string 添加一个 GetInputStream 扩展方法
    定时任务为什么不用Timer
    怎样改动 VC6.0 4.0 2010 打印预览界面上的文字
    大数据时代的万象变化
    &lt;监听器模式&gt;在C++ 与 Java 之间实现的差异
    工厂模式之抽象工厂模式
    UIButton上字体的对齐方式
  • 原文地址:https://www.cnblogs.com/TJTO/p/11426864.html
Copyright © 2020-2023  润新知