任务段

前言:任务段的学习笔记

我这笔记里面记录的TSS段指的就是那个104个字节大小的内存!!!

TSS任务段一个核只有一个,但是TSS段描述符有多个,一个TSS段描述符对应一个任务

前言

在调用门、中断门与陷阱门中,一旦出现权限切换,那么就会有堆栈的切换。而且,由于CS的CPL发生改变,也导致了SS也必须要切换。

切换时,会有新的ESP和SS(CS是由中断门或者调用门指定)这2个值从哪里来的呢?之前老师也有提到过说在讲TSS的时候就知道了,这里学习的就是TSS,也就是任务段

切换的时候新的ESP和SS就是从TSS任务段中来的

什么是TSS任务段

TSS 全称task state segment,是指在操作系统进程管理的过程中,任务(进程)切换时的任务现场信息。

TSS在任务切换过程中起着重要作用,通过它实现任务的挂起和恢复。所谓任务切换是指,挂起当前正在执行的任务,恢复或启动另一任务的执行。

在任务切换过程中,首先,处理器中各寄存器的当前值被自动保存到TR(任务寄存器)所指定的TSS中;然后,下一任务的TSS的选择子被装入TR;最后,从TR所指定的TSS中取出各寄存器的值送到处理器的各寄存器中。由此可见,通过在TSS中保存任务现场各寄存器状态的完整映象,实现任务的切换。

还需要知道的点:

1、TSS是CPU设计的东西,与操作系统无关。

2、一个CPU只有一个TSS,TSS存在的意义在于让一个CPU可以同时执行多个任务。但是操作系统(这里了解到的有linux和windows)并没有使用TSS来进行任务切换,而是直接将任务切换所需要保存的寄存器直接存到了堆栈里。

总结下作用就两点:

1、通过TSS任务段实现单CPU多任务切换

2、TSS任务段能够实现一次性保存/替换许多寄存器

如何观察TSS段描述符/TSS任务段/TR段寄存器

如何读取TR段寄存器?

  • windbg指令r tr,如下图所示,可以看到tr的段选择子为0x28

如果找到TSS段描述符?

  • 可以知道从TR段寄存器中进行寻找,如果TR段寄存器的段选择子所指向对应的GDT表中的段描述符,该描述符就是TSS段描述符

上面已经通过r tr,读出来了TR段寄存器的段 选择子0x28,这里通过0x28 -> 0010 1000 ,那么可以知道GDT表中索引值为5的地方就是对应的TSS段描述符

如何找到TSS任务段?

-根据下面的图可以看到,通过TSS段描述符中的base和limit就能够找到对应的TSS段的位置

下面来介绍如何找到TSS段的位置,这里需要先学习TSS段描述符的结构

TSS段描述符

如下图是TSS段描述符,这里的具体找到对应的位置,可以看到还是基于段描述符的结构,就是TYPE域有点不一样,其他的都一样的,那么TSS任务段的地址就是BASE的地址,然后这个limit则指明了这个TSS任务段的结构的大小

注意:

1、图中的TYPE中B表示的意思是刚创建时应为0,任务开始执行,挂起时为1,由硬件管理,防止切换任务切到自己,所以在创建的时候TYPE域应该是1001 -> 9

2、TSS描述符DPL必须为0,只有CPL为0能调用;

我这里的话TSS段描述符是80008b04 200020ab,那么base就是80042000,limit就是020ab

找出GDT表中存储的所有TSS段描述符

X

接着继续学习TSS任务段的结构

TSS任务段的结构

TSS任务段是一块内存,内存结构如下图所示,该结构的大小104字节

previous link:指向上一个TSS段,也就是上一个执行任务的TSS段的结构

ss0:在进入0环之后,0环所使用的ss的地址,之前我们通过提权进入0环的时候,TSS提供的ss也就是这个,以此类推ss1(1环提供) ss2(2环提供)

esp0:在进入0环之后,0环所使用的esp的地址,之前我们通过提权进入0环的时候,TSS提供的esp也就是这个,以此类推esp1(1环提供) esp2(2环提供)

LDT Segment Selector:存储到ldtr段寄存器,一个TSS对应一个LDT表,其中ldtr存储了base limit attribute seletor,其中base是LDT表的地址,limit是LDT表的大小,但是这个在windows中没有使用,了解即可

IO Map Base Address:IO权限位图 与硬件相关 了解即可,值是固定的

这里同样可以通过windbg指令来进行查看dt _KTSS

TR寄存器读写

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

指令:LTR

说明:用LTR指令去装载的话,仅仅是改变TR寄存器的值(96位),并没有真正改变TSS,LTR指令只能在系统层(0环特权指令)使用,加载后TSS段描述符会状态位会发生改变

  1. 读TR寄存器

指令:STR

说明:如果用STR去读的话,只读了TR的16位 也就是选择子

TSS切换练习

达到的效果就是 自己构建一个TSS段描述符和TSS任务段,然后让CPU进行相关操作实现将自己构建的TSS任务段进行替换原先的TSS任务段。

该练习的描述为"CPU通过TR段寄存器来找到TSS。如果我们想用自己的TSS段来替换原来的寄存器,就要修改TR寄存器,TR寄存器的值又是来自TSS段描述符"

这里先准备好一个TSS段描述符,这个TSS段描述符是放GDT表中进行存放的,这里构建的TSS段描述符的TYPE位1001(刚创建的时候TYPE位需要为9),构造的TSS段描述符为XX0XE9XX XXXX0068

然后继续看地址,到目前的话,TSS段描述符已经构造完了,接下来该TR段寄存器

让TR段寄存器的段选择子指向我们的TSS段描述符,这里需要改TR段寄存器的段选择子来指向GDT表中的TSS段描述符的索引

在Ring0 我们可以通过LTR指令去修改TR寄存器,在Ring3 我们可以通过CALL FAR 或者 JMP FAR指令来修改,所以这里的话两个实验都需要做

CALL FAR 无INT 3实验

需要注意的点:

1、如果通过call far进入的0环切换任务,此时不能使用int 3,因为通过call far调用门进入的0环切换任务EFLAG寄存器中的NT标志位,它会将其NT标志位设置为1,这个NT标志位的作用就是当任务切换的时候,这里的话就是iretd,它会切换到TSS结构体中的PTL(previous task link),而不是正常直接进行返回操作,但是如果你用了INT 3,那么这个NT标志位就会被清0,那么肯定就会触发异常操作,白皮书里面有说明,如下图所示

通过CALL FAR 调用TSS段描述符的时候,其过程就相当于默认将自己构造的TSS段描述符赋值给当前的TR段寄存器

我这里把tss作为全局变量事先进行打印,这个地址作为我们构造新的TSS任务段,printf("current tss addr: %X\n", &tss);,然后上面构造好的TSS段描述符放到GDT表中eq 8003f048 0000E940 D70F0068

在构建TSS结构的时候,0环堆栈我们自己需要位置,所以进入0环后,此时的esp0就是我们在tss中定义的堆栈位置,如果你定义在3环,那么此时0环用的堆栈位置就是在3环的位置

测试代码如下

#include <stdio.h>
#include <stdlib.h>
#include <Windows.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;

void __declspec(naked)  test()
{
	__asm
	{
		iretd;
	}
}

char stack[100]={0};
DWORD cr3;
DWORD testFunc;
TSS tss={0x0,0x0,0x0,0x0,0x0,0x0,0x0,
  0,
  0,
  0,0,0,0,0,
  ((DWORD)stack) - 100, //0环新的堆栈位置,我们自己来定义位置
  0,0,0,
  0x23,
  0x08,
  0x10,
  0x23,
  0x30,
  0,0,0x20ac0000
};
int main(int argc,char * argv[])
{
	printf("current tss addr: %X\n", &tss);
	printf("current testFunc: %p\n", test);
	printf("input testFunc: ");
	scanf("%X",&tss.eip);
    printf("input cr3: ");
    scanf("%X", &tss.cr3);

    char buf[6]={0,0,0,0,0x48,0};
    __asm
    {
		call fword ptr buf;
    }

    return 0;
}

可以看到成功的替换了寄存器然后进行执行

CALL FAR 有INT 3实验

#include <stdio.h>
#include <stdlib.h>
#include <Windows.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;

void __declspec(naked)  test()
{
	__asm
	{
		int 3;
		iretd;
	}
}

char stack[100]={0};
DWORD cr3;
DWORD testFunc;
TSS tss={0x0,0x0,0x0,0x0,0x0,0x0,0x0,
  0,
  0,
  0,0,0,0,0,
  ((DWORD)stack) - 100,
  0,0,0,
  0x23,
  0x08,
  0x10,
  0x23,
  0x30,
  0,0,0x20ac0000
};
int main(int argc,char * argv[])
{
	printf("current tss addr: %X\n", &tss);
	printf("current testFunc: %p\n", test);
	printf("input testFunc: ");
	scanf("%X",&tss.eip);
    printf("input cr3: ");
    scanf("%X", &tss.cr3);

    char buf[6]={0,0,0,0,0x48,0};
    __asm
    {
		call fword ptr buf;
    }

    return 0;
}

然后再用g,可以看到直接蓝屏了,所以需要注意这个NT标志位

JMP FAR实验

JMP FAR不影响EFLAG的NT标志位

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

  • JMP 0x48:0x123456 如果0x48是代码段

  • 执行后:CS-->0x48 EIP-->0x123456

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

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

这里挑选的GDT表的位置是如下,地址为0x8003f048

#include <stdio.h>
#include <Windows.h>
void __declspec(naked) test()
{
        __asm
        {
		int 3;
		iretd;
        }	
}

int main()
{

        char stack[100]={0};
        DWORD dwCr3;
        printf("your input cr3: ");
        scanf("%X",&dwCr3);

        DWORD tss[0x68]={
                0x0,
                0x0,
                0x0,
                0x0,
                0x0,
                0x0,
                0x0,
                cr3, // cr3,老师说切换进程的标识符
                (DWORD)test, // 进入0环之后的ESP0
                0,
                0,
                0,
                0,
                0,
                ((DWORD)stack) - 100,
                0,
                0,
                0,
                0x23, //ES
                0x08, //CS
                0x10, //SS
                0x23, //DS
                0x30, //FS
                0,
                0,
                0x20ac0000 // 默认写死的

        };

        char buf[6]={0,0,0,0,0x48,0};
        __asm
        {
                jmp fword ptr buf;
        }
        return 0;
}

构造完之后运行,发现直接蓝屏重启了

原因就是此时的iretd已经不适合我们了,因为我们走的是自己用的堆栈,最后的话默认的还是空堆栈,那么此时进行iretd,肯定会蓝屏,如下图所示

这里的话就是需要先保存TR段寄存器中保存的段选择子,通过STR事先保存,然后后面0环的执行完之后,手动再通过JMP段间跳转,跳回去即可模拟任务切换

#include <stdio.h>
#include <stdlib.h>
#include <Windows.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;

typedef struct seg{
	char a;
	char b;
	char c;
	char d;
	char e;
	char f;
} SEG;

DWORD dwHigh2GValue;
char stack[100]={0};
DWORD cr3;
DWORD testFunc;
WORD chCs;
SEG seg1 = {0,0,0,0,0,0};
SEG seg2 = {0,0,0,0,0x48,0};
char buffer1[6] = {0};
char buffer2[6]={0,0,0,0,0x48,0};

TSS tss =
{
	0x0,0x0,0x0,0x0,0x0,0x0,0x0,
	0,
	0,
	0,0,0,0,0,
	((DWORD)stack) - 100,
	0,0,0,
	0x23,
	0x08,
	0x10,
	0x23,
	0x30,
	0,0,0x20ac0000
};

void __declspec(naked) test()
{
	__asm
	{
		int 3;
		pushad;
		pushfd;
		mov eax, 0x8003f008;
		mov ebx,[eax];
		mov dwHigh2GValue, ebx;
		popfd;
		popad;
		jmp fword ptr buffer1;
	}
}

int main(int argc,char * argv[])
{
	__asm
	{
		STR chCs;
	}
	*((PDWORD)&buffer1+1) = chCs;
	printf("current tss addr: %X\n", &tss);
	printf("current testFunc: %p\n", test);
	printf("input testFunc: ");
	scanf("%X",&tss.eip);
    printf("input cr3: ");
    scanf("%X", &tss.cr3);
    __asm
    {
		jmp fword ptr buffer2;
    }
	printf("dwHigh2GValue: %d\n", dwHigh2GValue);
	printf("----ok----\n");
    return 0;
}

posted @ 2022-02-07 18:57  zpchcbd  阅读(819)  评论(0)    收藏  举报