任务门存在于IDT表中,下面是任务门的段描述符
下图是任务门触发的时候的执行过程
下面总体概述一下如何使用任务门来进行任务切换:
1.当中断发生时,处理器用中断号乘以8作为索引访问中断描述符表。当它发现这是一个任务门描述符时,就知道应当发起任务切换。
2.取出任务门描述符。
3.从任务门描述符中取出新任务的TSS选择子;
4.再用TSS选择子访问GDT,取出新任务的TSS描述符
5.在执行新任务前,处理器会把当前任务状态保存起来。也就是将当前任务的状态保存到TR寄存器指向的TSS状态段。
6.然后处理器访问新任务的状态段TSS,并从中恢复各个寄存器的内容,包括通用寄存器、标志寄存器EFLAGS、段寄存器、指令指针寄存器EIP、栈指针寄存器ESP,以及局部描述符表寄存器LDTR等。
7.最终任务寄存器TR指向新任务的TSS,而处理器开始新任务的执行。
8.一旦新任务开始执行,处理器固件会自动将其TSS描述符的B位置1,表示该任务状态为忙。
这里可能有个小问题,既然我们可以直接访问任务段了,为什么要有任务门呢?
一个“TSS描述符”指代了一个TSS结构,按理来说,这已经完全足够使用了,但是用于Intel允许在中断的时候也可以进行任务切换,这样,我们就可以把中断处理程序作为一个专门的任务,然而,中断描述符表中存放的只能是门描述符,而上面的“TSS描述符”并不是一种门描述符,因此,它不能被放在中断描述符表中,于是Intel又定义了一种“任务门”,它其实指向的是一个“TSS描述符”,但由于它是一种门描述符,因此,它可以被放在中断描述符表中,这样当发生中断的时候,CPU通过中断号查询中断描述符表,得到相应的门描述符,如果发现它是一个“任务门”,则通过它找到相应的“TSS描述符”,再通过相应的“TSS描述符”找到相应的“TSS结构”
NT flag in the EFLAGS register
NT(Nested Task):控制中断返回指令IRET,它宽度为1位。NT=0,用堆栈中保存的值恢复EFLAGS,CS和EIP从而实现中断返回;NT=1,则通过任务切换实现中断返回,这里也是我们之前说过代码里加入int3中断返回会卡死的问题。
任务门的作用
当CPU出现双重错误,其就会通过任务门,调到08到中断去。
下面来做一个任务门小实验
先构造一个任务门 假设TSS段在GTD 0x48位置
P DPL
0000 0000 0000 0000 1 11 00101 0000 0000
TSS Segment Selector
0000 0000 0100 1000 0000 0000 0000 0000
编译如下程序
#include <iostream>
#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;
// I/O 位图基地址域
DWORD io_map;
} TSS;
char st[10] = { 0 };
DWORD g_esp;
DWORD g_cs;
TSS tss = {
0x00000000,//link
(DWORD)st,//esp0
0x00000010,//ss0
0x00000000,//esp1
0x00000000,//ss1
0x00000000,//esp2
0x00000000,//ss2
0x00000000,//cr3
0x0040fad0,//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
};
__declspec(naked) void func() {
__asm {
mov g_esp, esp;
mov eax, 0;
mov ax, cs;
mov g_cs, eax;
iretd;
}
}
int main(int argc, char* argv[])
{
tss.eip = (DWORD)func;
printf("TSS:%x\n", &tss);
printf("func:%x\n", tss.eip);
printf("please input cr3:\n");
scanf("%x", &(tss.cr3));
__asm
{
int 0x20;
}
printf("g_cs = %08x\ng_esp = %08x\n", g_cs, g_esp);
getchar();
return 0;
}
根据显示的TSS位置构造任务段
Base 31:24 G AVL Limit19:16 P DPL Type Base 23:16
0000 0000 0 00 0 0000 1 11 0 1001 0100 0001
Base Address 15:00 Segment Limit 15:00
1110 0000 0000 0000 0000 0000 01101000
写入GDT表中
输入!process 0 0
找到相应进程cr3,记下,也就是DirBase
成功调用