文章目录
-
- 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
说明:
- 用LTR指令去装载的话,仅仅是改变TR寄存器的值,并没有真正改变TSS
- LTR指令只能在系统层使用
- 加载后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切换