[保护模式]任务段

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, //esp0
	0x00000010,//ss0
	0x00000000,//esp1
	0x00000000,//ss1
	0x00000000,//esp2
	0x00000000,//ss2
	0x00000000,//cr3
	0x00401090,//eip-----填裸函数地址
	0x00000000,//eflags
	0x00000000,//eax
	0x00000000,//ecx
	0x00000000,//edx
	0x00000000,//ebx
	(DWORD)st, //esp
	0x00000000,//ebp
	0x00000000,//esi
	0x00000000,//edi
	0x00000023,//es  
	0x00000008,//cs  
	0x00000010,//ss
	0x00000023,//ds
	0x00000030,//fs
	0x00000000,//gs
	0x00000000,//ldt
	0x20ac0000
};

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: 0534
    DirBase: 7f0e7560  ObjectTable: c1dcf198  HandleCount:   7.
    Image: KernelTest.exe

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

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

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

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

发布了99 篇原创文章 · 获赞 89 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/qq_38474570/article/details/103394632