【2021.03.24】任务段:下

要点回顾

  1. 前文提到,TSS、TSS段描述符以及TR段寄存器。
  2. TSS是一块内存,大小104字节。通过TSS可以同时替换"一堆"寄存器,包括通用寄存器和段寄存器等。
  3. CPU通过TR段寄存器来找到TSS。如果想用自己的TSS段描述符来替换原来的寄存器,就需要修改TR段寄存器,TR段寄存器的值又是来自TSS段描述符
  4. TR段寄存器的BASE指向了TSS(也就是这104个字节)在哪里,TR.LIMIT指定了当前的TSS的大小。
  5. TR段寄存器的值又是从GDT表加载的,GDT表中存储了某个所谓的TSS段描述符。在系统启动的时候把这个段描述符的数据加载到TR段寄存器中,通过TR段寄存器就可以找到TSS了。
  6. TSS是Intel在硬件的角度提供的任务切换的方式,但是操作系统在真正使用的时候可能并没有使用这种方式来切换任务或者切换线程,它们是一个概念。

TSS段描述符

注意:高4字节的第23位,也就是G位。

G位=0:代表limit界限单位是字节。G位=1:代表limit界限单位是4KB。

前文中的实验通常都是1,本文中为0。

为什么是0?因为LIMIT指向的TSS是以字节为单位的。

构造TSS段描述符

1.准备104个字节

希望切换以后的寄存器变成什么样就填什么。

2.准备好段描述符并写入GDT表中

3.修改TR段寄存器

  • 在R0可以通过LTR指令修改TR段寄存器。
  • 在R3可以通过 CALL FAR 或者 JMP FAR 指令修改。

用JMP去访问一个代码段的时候,改变的是CS和EIP:

JMP 0x48:0x123456,如果0x48是代码段,执行后:CS-->0x48、EIP-->0x123456。

用JMP去访问一个任务段的时候:

如果0x48是TSS段描述符,先修改TR段寄存器,再用TR.BASE指向的TSS中的值修改当前的寄存器。

代码

#include "stdafx.h"
#include <windows.h>

DWORD dwOK;
DWORD dwESP;
DWORD dwCS;

__declspec(naked)func()    //函数地址:0x00401020
{
    dwOK = 1;

    __asm
    {
        int 3        //程序一旦执行到此处会中断到windbg中
        mov eax,esp
        mov dwESP,eax
        mov ax,cs
        mov word ptr [dwCS],ax

        //切换回去的代码没写
        //因为使用CALL和JMP切换进来以后再回来的方式不一样
        //内部实现细节也完全不一样
    }        
}

//eq 8003f0c0 0000e912`fdcc0068
int main(int argc, char* argv[])
{
    char bu[0x10];        //0x12ff70
    int iCr3;

    printf("input CR3:\n");
    scanf("%x",&iCr3);    //通过windbg工具指令:!process 0 0 获取

    DWORD iTss[0x68] = 
    {
        0x00000000,    //link    当一次性切换一堆寄存器的时候,该地方存储的就是原104个字节的段选择子,该值不需要填充,当发生切换时由CPU自动完成

        //因为并不是中断门或调用门,故esp0、ss0,esp1、ss1,esp2、ss2可以填0
        0x00000000,    //esp0    (DWORD)bu    想进0环取esp0和ss0
        0x00000000,    //ss0
        0x00000000,    //esp1    想进1环取esp1和ss1    Windows并未使用1环,自己使用时可以相进就近
        0x00000000,    //ss1
        0x00000000,    //esp2    想进2环取esp2和ss2    Windows并未使用2环,自己使用时可以相进就近
        0x00000000,    //ss2

        (DWORD)iCr3,   //cr3    目前只需要知道它是个寄存器即可, 必须填写
        0x00401020,    //eip    下一次执行的代码位置, 必须填写

        0x00000000,    //eflags
        0x00000000,    //eax
        0x00000000,    //ecx
        0x00000000,    //edx
        0x00000000,    //ebx

        (DWORD)bu,     //esp    代码执行切换的时候, 一旦发生切换所有寄存器都会发生变化, EIP变了那么ESP也会发生变化, 上面定义的数组当成堆栈来用

        0x00000000,    //ebp
        0x00000000,    //esi
        0x00000000,    //edi

        0x00000023,    //es    无论3环还是0环都是23
        0x00000008,    //cs    0x0000001B    3环1B, 0环08
        0x00000010,    //ss    0x00000023    3环23, 0环10
        0x00000023,    //ds    无论3环还是0环都是23
        0x00000030,    //fs    0x0000003B    3环3B, 0环30
        0x00000000,    //gs    永远是0, window并未使用
        0x00000000,    //ldt
        0x20ac0000     //IO权限位图, Windows2000以后并未使用, 但结构中仍然存在
    };

    char buff[6];

    *(DWORD*)&buff[0] = 0x12345678;
    *(WORD*)&buff[4] = 0xC0;

    __asm
    {
        call fword ptr[buff]
    }

    printf("ok = %d, ESP = %x, CS = %x\n", dwOK, dwESP, dwCS);

    return 0;
}

实验

构建描述符

段描述符不见得是这个位置,按照自己机器中的去写。

找到CR3的值并写入

因为CR3的值是必须提供的。

1.运行

运行后要求输入CR3的值。

2.查看所有进程

3.找到CR3的值

11680280就是当前要用的程序的CR3的值。

4.输入CR3的值并按下回车

按下回车后回到windbg中发现程序已经断下。

断下以后单步执行一次,或几次。

可以看到断到了int3断点,且下面的代码也是程序中内联汇编的代码。

然后观察地址可以发现是0040102A,刚好是函数所在的地址。

证明通过切换的时候,代码就已经执行了,下面来看一下寄存器。

CR3改变了。

EIP:内联汇编中写的是401020,目前是401032。因为刚才断下后单步执行了几步,所以此处的EIP是没有问题的。

CS:8,SS:10,ESP:12ff70,还有FS、ES、DS等都指定了具体的值。

已经正确的切换成了指定的值。

由于后面切换回去的内联汇编代码没有写,所以继续执行的话会蓝屏。可以动手尝试一下使用CALL或JMP切换回去。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值