TR寄存器
说到TSS就不得不提TR寄存器了
TR段寄存器依然是指向一个段描述符,但是是一个比较特殊的描述符,我们称之为任务段描述符
TR寄存器的作用
书中表示栈的方式与OD表示的方式刚好相反
TR 寄存器中的selector查找GDT表后将段寄存器剩余内容填充,address刚好指向TSS表,而limit则是这张表的大小
TR寄存器的读写:(特权指令,只能在0环用)
- LTR (Load TR):写入TR寄存器,和往常一样写入selector,剩余的查GDT表填充 (LTR reg16 / men)
- STR (Save TR):读取TR寄存器的值并存储到 (STR reg16 / men)
任务段描述符
B (busy flag) : 当进入任务时设置为1,退出时设置为0。若是B为1时被调用,则会出异常
TSS (任务段)
TSS : Task-state segment (任务状态段)
这个表的主要作用就是用来进行任务切换。
任务可以是一个线程,也可以是一个进程。也就是说线程切换或者进程切换的时候使用这个表
- 顾名思义,从4-95位都是用于保存任务切换时当前任务寄存器的状态,当切换回来当前任务的时候填充寄存器的。
- 每个任务都有自己独立的CR3,当前只要知道这个一定要填就行
- Previous Task Link : 指向前一个TSS表的段选择子
- 每个任务都有自己的LDT表(局部描述符表),但不使用
- T (debug trap flag): 是表示当前的是否是debug状态,如果是1则调试断点被设置,我们写0
- IO Map Base Address : IO权限位图,本实验中不涉及这个。是一个基于TR.BaseAddress的偏移。IO权限和EFLAG中的12,13位也有关联,而权限位图中表示端口是否可用
TSS表每个任务一份,当进行任务切换的时候,我们只需要改变TR寄存器的值就能用上不同的表了
我们知道,函数调用的时候要把某些寄存器的值存起来,以便return回来的时候能够继续执行。
任务切换的时候也不例外,需要保存现场,所以需要找一个地方存放上下文(context),当任务切换回来时使用上下文替换寄存器,继续执行当前任务。
使用任务段修改寄存器的值
- 首先构造一个TSS
- 然后使用call far 或 jmp far调用这个TSS描述符
- 函数返回
首先写出函数
#include <windows.h>
int tss[] = {
0x00000000,//link
0x00000000, //esp0 1
0x00000010,//ss0 2
0x00000000,//esp1
0x00000000,//ss1
0x00000000,//esp2
0x00000000,//ss2
0x00000000,//cr3 7
0x00000000,//eip 8 func addrs
0x00000000,//eflags
0x11111111,//eax
0x22222222,//ecx
0x33333333,//edx
0x44444444,//ebx
0x00000000, //esp 14
0x00000000,//ebp
0x00000000,//esi
0x00000000,//edi
0x00000023,//es
0x00000008,//cs 19
0x00000010,//ss
0x00000023,//ds
0x00000030,//fs
0x00000000,//gs
0x00000000,//ldt
0x20ac0000 // IO map
};
BYTE stack[0x100];
void __declspec(naked) func(){
_asm{
pushf
int 3
popf
iretd
}
}
int main(){
BYTE callAddrs[6] = {0x00, 0x00, 0x00, 0x00, 0x48, 0x00};
memset(stack, 0, 0x100); //init stack
printf("TSS addrs: %x\n", tss);
printf("input CR3:\n");
scanf("%x", &tss[7]); //cr3
tss[8] = (DWORD)&func; //eip
tss[14] = stack + 0x100; //esp 一定要设置堆栈,否则蓝屏
_asm{
call fword ptr[callAddrs]
}
}
获取TSS地址,并准备填入CR3
找一个位置写入TSS段描述符
eq 8003f048 0000e942`9a300068
执行!process 0 0
,然后复制CR3,输入到程序中去
运行后,程序在windbg上断下来了
千万别单步,直接 go
单步会修改EFLAG的NT位,会蓝屏
NT == 1 //iretd使用上一个 TSS 返回
NT == 0 //iretd使用 堆栈 返回
使用了TSS进入,自然要使用TSS返回,所以NT位改变了就会蓝屏
jmp far 和 call far 的不同
jmp far和call far调用的TSS,EFLAG是不同的
#include <windows.h>
int tss[] = {
0x00000000,//link
0x00000000, //esp0 1 stack0
0x00000010,//ss0 2
0x00000000,//esp1
0x00000000,//ss1
0x00000000,//esp2
0x00000000,//ss2
0x00000000,//cr3 7
0x00000000,//eip 8 func addrs
0x00000000,//eflags
0x11111111,//eax
0x22222222,//ecx
0x33333333,//edx
0x44444444,//ebx
0x00000000, //esp 14 stack3
0x00000000,//ebp
0x00000000,//esi
0x00000000,//edi
0x00000023,//es
0x00000008,//cs 19
0x00000010,//ss
0x00000023,//ds
0x00000030,//fs
0x00000000,//gs
0x00000000,//ldt
0x20ac0000 // IO map
};
BYTE tss2[0x68];
DWORD jmpEflag;
DWORD callEflag;
BYTE jmpBack[6];
BYTE stack[0x100];
void __declspec(naked) funcCall(){
_asm{
pushad
pushf
//int 3 //打开注释可以断下调试
mov eax, [esp];//刚push过EFLAGS
mov callEflag, eax //保存EFLAGS
popf
popad
iretd
}
}
void __declspec(naked) funcjmp(){
_asm{
pushad
pushf
//int 3
mov eax, [esp];
mov jmpEflag, eax //保存EFLAGS
popf
popad
//即使NT现在是0,在这里iretd也会直接蓝屏
//因为堆栈中并没有供iretd返回的 SS:ESP EFLAGS CS:EIP 结构
jmp fword ptr[jmpBack]
}
}
int main(){
//C语言只能在头部把变量全都申请了
BYTE callAddrs[6] = {0x00, 0x00, 0x00, 0x00, 0x48, 0x00};
WORD valTR;
memset(stack, 0, 0x100); //init stack
printf("TSS addrs: %x\n", tss);
printf("input CR3:\n");
scanf("%x", &tss[7]); //cr3
tss[8] = (DWORD)&funcCall; //eip
tss[14] = stack + 0x100; //esp
memcpy(tss2, tss, 0x68);//备份TSS
_asm{
call fword ptr[callAddrs]
}
memcpy(tss, tss2, 0x68);//这里是怕之前的TSS被覆盖了,所以使用备份重新填一次
tss[8] = (DWORD)&funcjmp;//修改TSS的跳转位置
_asm{
str valTR //保存当前TR,跳回使用
}
*(WORD*)(&jmpBack[4]) = valTR;//重新构造一个跳回的fword
_asm{
jmp fword ptr[callAddrs]
}
printf("call eflag: %x\n", callEflag);
printf("jump eflag: %x\n", jmpEflag);
system("pause");
}
上面代码的执行结果
4002 = 100 0000 0000 0010,第14位是NT位
这里也算是写出了 jmp far 跳回来的方法了
拓展
如果limit大于了0x68会发生什么?
- 书中写了:如果limit < 0x67 调用将会报错,但是如果limit > 0x67系统在使用任务段的时候懒得检查。除非检查IO权限位图,才会检查limit与权限位图偏移之间的关系。
这就是intel提供的任务切换方案,也只能是他自己的一厢情愿。windows和linux都没有使用intel制定的方案,只有在权限切换时才会用到SS:ESP这两个值,其他的并没有使用。
3环:
跳转到0环:
3环转到0环的时候,堆栈的SS:ESP从何而来,就是从这张表里查的。
系统把返回时用到的CS,EIP,EFLAGS,SS,ESP都放入堆栈了,所以windows操作系统并没有完全使用这张表,至少跳回去的时候,我们可以直接在堆栈里把值取了。
系统不用,不代表我们不能用。