当前位置: 代码迷 >> 综合 >> [保护模式]任务段
  详细解决方案

[保护模式]任务段

热度:92   发布时间:2023-12-21 20:18:51.0

文章目录

    • TSS
      • 什么是TSS
      • TSS的作用
      • 如何找到TSS
      • TR寄存器读写
        • 将TSS段描述符加载到TR寄存器
      • 构造TSS段描述符
      • 修改TSS
        • JMP 访问代码段
        • JMP 访问任务段
        • JMP FAR和CALL FAR访问任务段的区别
    • TSS切换实验
        • 示例代码
        • 构造段描述符
        • 填充CR3

TSS

什么是TSS

TSS是一块内存,大小为104字节,结构如图所示:
在这里插入图片描述

结构体如下:

typedef struct TSS {
    DWORD link; // 保存前一个 TSS 段选择子,使用 call 指令切换寄存器的时候由CPU填写。// 这 6 个值是固定不变的,用于提权,CPU 切换栈的时候用DWORD esp0; // 保存 0 环栈指针DWORD ss0;  // 保存 0 环栈段选择子DWORD esp1; // 保存 1 环栈指针DWORD ss1;  // 保存 1 环栈段选择子DWORD esp2; // 保存 2 环栈指针DWORD ss2;  // 保存 2 环栈段选择子// 下面这些都是用来做切换寄存器值用的,切换寄存器的时候由CPU自动填写。DWORD cr3;DWORD eip;DWORD eflags;DWORD eax;DWORD ecx;DWORD edx;DWORD ebx;DWORD esp;DWORD ebp;DWORD esi;DWORD edi;DWORD es;DWORD cs;DWORD ss;DWORD ds;DWORD fs;DWORD gs;DWORD ldt;// 这个暂时忽略DWORD io_map;
} TSS;
  • Previous Task Link 前一个TSS的链接。通过这个字段可以找到上一个TSS
  • ESP0:零环的ESP
  • SS0:零环的SS
  • ESP1:1环的ESP
  • SS1:1环的SS
  • ESP2:2环的ESP
  • SS2:2环的SS
  • LDT Segment Seletor:LDT段选择子,保存了LDT表的基址和长度。 这个选择子对应的段描述符必须是系统段描述符。LDT段选择子的数量有多少个取决于TSS有多少个
  • IO Map Base Address:IO权限位图 与硬件相关 可忽略

TSS的作用

TSS是CPU设计的东西,与操作系统无关。一个CPU只有一个TSS,TSS存在的意义在于让一个CPU可以同时执行多个任务。但是操作系统并没有使用TSS来进行任务切换,而是直接将任务切换所需要保存的寄存器直接存到了堆栈里。

如何找到TSS

那么CPU如果想执行任务切换的话就必须先找到TSS。通过TR段寄存器。

在这里插入图片描述

TR寄存器读写

将TSS段描述符加载到TR寄存器

指令:LTR

说明:

  1. 用LTR指令去装载的话,仅仅是改变TR寄存器的值,并没有真正改变TSS
  2. LTR指令只能在系统层使用
  3. 加载后TSS段描述符状态位会发生改变

构造TSS段描述符

在这里插入图片描述

Base 31:24:00
G:0 0 0 AVL:0 ->0
Limit19:16:0
Type:9(表示未被加载过 加载过后为B)
Base 23:16:00
Base Address 15:00:0000
Segment Limit 15:00:0068
XX00E9XX`XXXX0068
# Base Address指的是新的TSS的内存地址

修改TSS

在Ring3我们可以通过call far或者jmp far指令来修改TSS。

JMP 访问代码段

JMP 0x48 0x12345678

如果0x48是代码段,执行后CS=0x48 EIP=0x12345678

JMP 访问任务段

JMP 0x48 0x12345678

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

JMP FAR和CALL FAR访问任务段的区别

  • 当使用JMP FAR来实现任务切换时,TSS结构体中的Previous Task Link的值在任务切换完成之后为0,CPU不会为其赋值;如果使用CALL FAR来实现任务切换,Previous Task Link的值在任务切换完成之后会CPU会将其填充为原来的TSS段选择子
  • 当使用JMP FAR来实现任务切换时,EFLAGS寄存器中的NT位不变;当使用CALL FAR来实现任务切换时,EFLAGS寄存器中的NT位就会被置1(NT位会对iret指令产生影响 NT位如果为0,iret的值从堆栈中取(中断返回);如果NT位为1,会找TSS中的Previous Task Link进行返回)

TSS切换实验

示例代码

首先准备好需要切换的104个字节,并且赋上正确的值,示例代码如下

#include "pch.h"
#include<windows.h>
#include<stdio.h>typedef struct TSS {DWORD link; // 保存前一个 TSS 段选择子,使用 call 指令切换寄存器的时候由CPU填写。// 这 6 个值是固定不变的,用于提权,CPU 切换栈的时候用DWORD esp0; // 保存 0 环栈指针DWORD ss0;  // 保存 0 环栈段选择子DWORD esp1; // 保存 1 环栈指针DWORD ss1;  // 保存 1 环栈段选择子 DWORD esp2; // 保存 2 环栈指针DWORD ss2;  // 保存 2 环栈段选择子// 下面这些都是用来做切换寄存器值用的,切换寄存器的时候由CPU自动填写。DWORD cr3;DWORD eip;DWORD eflags;DWORD eax;DWORD ecx;DWORD edx;DWORD ebx;DWORD esp; DWORD ebp;DWORD esi;DWORD edi;DWORD es;DWORD cs;DWORD ss;DWORD ds;DWORD fs;DWORD gs;DWORD ldt;// 这个暂时忽略DWORD io_map;
} TSS;char st[10] = { 0 }; 
DWORD g_esp = 0;
DWORD g_cs = 0;TSS tss = {		0x00000000,//link(DWORD)st, //esp00x00000010,//ss00x00000000,//esp10x00000000,//ss10x00000000,//esp20x00000000,//ss20x00000000,//cr30x00401090,//eip-----填裸函数地址0x00000000,//eflags0x00000000,//eax0x00000000,//ecx0x00000000,//edx0x00000000,//ebx(DWORD)st, //esp0x00000000,//ebp0x00000000,//esi0x00000000,//edi0x00000023,//es  0x00000008,//cs  0x00000010,//ss0x00000023,//ds0x00000030,//fs0x00000000,//gs0x00000000,//ldt0x20ac0000
};void __declspec(naked) func() 
{//00401090__asm {int 3}
}
int main(int argc, char* argv[])
{printf("%p\n", func);printf("%x\n", &tss);printf("cr3:\n");scanf_s("%x", &(tss.cr3));char buffer[6] = { 0, 0, 0, 0, 0x48, 0 };__asm{call fword ptr[buffer]}printf("g_cs = %08x\ng_esp = %08x\n", g_cs, g_esp);getchar();return 0;
}

需要注意的是,我们需要将eip设置为指定的地址,让TSS切换完成之后,跳转到那个地址。这里将裸函数的地址打印出来之后替换掉了EIP。通过主函数的这句代码可以打印出要跳转的裸函数地址

printf("%p\n", func);

构造段描述符

将我们之前准备好的任务段描述符直接拿过来

XX00E9XX`XXXX0068

将地址设置为我们准备好的TSS结构体,通过下面的代码打印结构体地址

printf("%x\n", &tss);

构造好的段描述符如下:

0000E940`30180068

接着修改GDT表的段描述符

kd> eq 80b95048 0000E940`30180068

填充CR3

接着编译,放到虚拟机中执行,运行以后,在windbg中查看CR3寄存器的值

PROCESS 87065d40  SessionId: 1  Cid: 0cc0    Peb: 7ffd9000  ParentCid: 0534DirBase: 7f0e7560  ObjectTable: c1dcf198  HandleCount:   7.Image: KernelTest.exe

将前进程的页目录基址填充到CR3,接着运行程序 即可完成TSS切换

编译,放到虚拟机中执行,运行以后,在windbg中查看CR3寄存器的值

PROCESS 87065d40  SessionId: 1  Cid: 0cc0    Peb: 7ffd9000  ParentCid: 0534DirBase: 7f0e7560  ObjectTable: c1dcf198  HandleCount:   7.Image: KernelTest.exe

将前进程的页目录基址填充到CR3,接着运行程序 即可完成TSS切换