TSS不是寄存器,就是一段内存 共有104个字节
TSS包括当前执行任务的重要信息(如所有的通用寄存器,段寄存器,标志寄存器,CR3等)
TSS作用
TSS的意义就在于可以同时换掉“一堆”寄存器,实现任务的切换。
任务是对于CPU而言的,对于操作系统而言则为线程,因为每个进程都至少有一个线程,进程是空间实体,真正执行代码的是线程。
CPU如何寻找TSS
TR段寄存器
既然为段寄存器,则肯定有相对应的段描述符进行加载,即TR段寄存器的段描述符在GDT(全局描述符表)中。
这里总结三个概念
TSS描述符 TR寄存器 GDT
TR寄存器中的Base指向TSS在内存中的位置,Limit表示TSS的大小
TSS段描述符
如果Type 为 1001 (9)的时候说明这个段描述符没有加载到TR寄存器中
如果Type 为 1011 (B)的时候说明这个段描述符加载到TR寄存器中
TR寄存器读写
将TSS段描述符加载到TR寄存器当中
LTR
用LTR指令去装载的话,仅仅是改变TR寄存器的值,而实际指向的TSS并没有改变
LTR只能在0环进行使用,即只能在系统层使用
加载后TSS段描述符会状态位发生改变
读TR寄存器
STR
用STR去读的话,只读TR的16位,即选择子部分
6.修改TR寄存器:
用JMP去访问一个代码段时,改变的是CS和EIP
JMP 0x48:0x123456 如果0x48是代码段,则CS->0x48 EIP->0x123456
JMP 0x48:0x123456 如果0x48是TSS段描述符,则先修改TR寄存器,再用TR.Base
指向的TSS中的值去修改其他寄存器中的值
实验思路
- 编写测试入口函数
- 构造TSS
- 设计TSS段描述符并安装
一、构造TSS:
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}; //定义一个堆栈切换的 TSS tss = {// tss的地址根据执行代码自己组合 0x00000000,//link (DWORD)st,//esp0 0x00000010,//ss0 0x00000000,//esp1 0x00000000,//ss1 0x00000000,//esp2 0x00000000,//ss2 0x00000000,//cr3 0x0040fad0,//eip 必填项,不然执行完后cpu不知道回来从哪开始执行 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 //IO权限位图 };
二、 准备TSS段描述符:
0040e900~7b400068 //加载的地址在你自己的VC6中看!
三、写入准备好的TSS段描述符到GDT空白位置
1: kd> r gdtr |
|
四.修改TR寄存器
CALL FAR或者JMP FAR
char buff[6]; *(DWORD*)&buff[0]= 0x12345678; *(DWORD*)&buff[4]= 0xC0; __asm { call fword ptd[buff] }