Windows保护模式学习笔记(五)—— 任务段&任务门
Windows保护模式学习笔记(五)—— 任务段&任务门
前言
一、学习自
滴水编程达人
中级班课程,官网:https://bcdaren.com
二、海东老师牛逼!
要点回顾
- 在调用门、中断门与陷阱门中,一旦出现权限切换,那么就会有堆栈的切换;而且,由于CS的CPL发生改变,也导致了SS也必须要切换
- 思考:切换时,会有新的ESP和SS(CS是由中断门或者调用门指定)这2个值从哪里来的呢?
答案:TSS (Task-state segment ):任务状态段
任务段
TSS (Task-state segment )
描述:
TSS是一块内存
大小:104字节
TSS存储了一堆寄存器的值
TSS结构图:
TSS的作用:
- Intel的设计思想:通过使用TSS以达到任务(线程)的切换
- 操作系统的设计思想:与Intel设计思想不同的是,Windows并没有根据Intel的设计思想来做,甚至Linux也没有这样做
TSS的本质:
- 不要把TSS与"任务切换"联系到一起
- TSS的意义就在于可以同时换掉"一堆"寄存器
CPU通过TR段寄存器寻找TSS
TR段寄存器
描述:
TR寄存器的值是当操作系统启动时,从TSS段描述符中加载出来的,TSS段描述符在GDT表中
TR.Base = TSS起始地址
TR.Limit = TSS大小
TR段寄存器的读写
一、将TSS段描述符加载到TR寄存器
指令:LTR
说明:
- 用LTR指令去装载的话 仅仅是改变TR寄存器的值(96位)
- 并没有真正改变TSS
- LTR指令只能在系统层使用
- 加载后TSS段描述符的状态位会发生改变
二、读TR寄存器
指令:STR
说明:如果用STR去读的话,只读了TR的16位,也就是段选择子
TSS段描述符
描述:
TSS段描述符是系统段描述符中的一种
结构图:
Type = 二进制1001:说明该TSS段描述符未被加载到TR段寄存器中
Type = 二进制1011:说明该TSS段描述符已被加载到TR段寄存器中
TSS、TSS段描述符、TR段寄存器关系示意图:
实验:加载自定义TSS
第一步:获取必要参数
在VC6中运行如下代码并在main函数头部设置中断:
#include <windows.h>
DWORD dwOK;
DWORD dwESP;
DWORD dwCS;
char trs[6]={0};
void __declspec(naked) func()
{
dwOK = 1;
__asm
{
//jmp回去
//jmp fword ptr trs;
//call回去
pushfd
int 3 //此处int3会将nt位置1,不会通过link,因此提前保存eflag,返回之前还原nt位
mov eax,esp
mov dwESP,eax
mov ax,cs
mov word ptr [dwCS],ax
popfd
iretd
}
}
int main(int argc, char* argv[])
{
char bu[0x10]; //12ff70
int iCr3;
printf("input CR3:\n");
scanf("%x", &iCr3);
DWORD iTss[0x68] = {
0x00000000, //link
0x00000000, //esp0 //(DWORD)bu
0x00000000, //ss0
0x00000000, //esp1
0x00000000, //ss1
0x00000000, //esp2
0x00000000, //ss2
(DWORD)iCr3, //cr3
0x0040DE50, //eip
0x00000000, //eflags
0x00000000, //eax
0x00000000, //ecx
0x00000000, //edx
0x00000000, //ebx
(DWORD)bu, //esp
0x00000000, //ebp
0x00000000, //esi
0x00000000, //edi
0x00000023, //es
0x00000008, //cs 0x0000001B
0x00000010, //ss 0x00000023
0x00000023, //ds
0x00000030, //fs
0x00000000, //gs
0x00000000, //dit
0x20ac0000};
char buff[6];
*(DWORD*)&buff[0] = 0x12345678;
*(WORD*)&buff[4] = 0xC0;
__asm
{
str ax;
mov rs,ax;
}
*(WORD*)&trs[4]=rs;
__asm
{
//jmp fword ptr buff;
call fword ptr[buff]
}
printf("ok=%d \t ESP=%x \t CS=%x \n", dwOK, dwESP, dwCS);
return 0;
}
进入反汇编窗口查看 func 函数起始地址,我这里是0x401020
将地址填入iTss数组注释为eip的地方,表示TSS切换后EIP的值
再通过 memory 窗口查看iTss数组所在地址,记下来备用
注意:代码中如有地方发生修改,需要先停止程序,重新编译
第二步:构造TSS段描述符
Offset in Segment 31:16 = 0x0000 // 暂定
G = 0
AVL = 0
Limit = 二进制:0000
P = 1
DPL = 二进制:11
Type = 二进制:1001
Segment Limit = 0068H // Intel规定TSS段描述符G=0时Limit必须大于或等于67H
Offset in Segment 15:00 = 0x0000 // 暂定
由上述参数构造出的门描述符为:0000E900`00000068
在第一步中,我们已经知道iTss数组所在地址为0x12FDCC
因此,TSS段描述符最终确定为:0000E912`FDCC
0068
第三步:将TSS段描述符写入GDT表
我写入的地址是8003f0c0,若写入其他地址,则需要修改buff数组的后两个字节
这时候先不要急着继续运行代码,先在WinDbg中输入命令:!process 0 0
获得当前进程的Cr3(我这里进程名叫TestDoor.exe,之前是用来做调用门的实验,TSS没有新建项目)
DirBase的值就是Cr3
第四步:解除中断,继续执行代码
输入上一步得到的Cr3,回车
WinDbg成功获得了中断信号
这时候看一下反汇编代码
可以确定正在执行func函数的代码
再看一下寄存器
eip、cs、ss都符合我们想要的结果
至此,TSS切换成功!
思考:TSS切换完成后,如何回到切换前的下一行继续执行?
任务门
描述:
任务门存在于IDT表
任务门中包含TSS段选择子
可以通过访问任务门达到切换TSS的目的
结构图:
任务门执行过程
- INT N(N为IDT表索引号)
- 系统通过用户指定的索引查找IDT表,找到对应的门描述符
- 门描述符若为任务门描述符,则根据任务门描述符中TSS段选择子查找GDT表,找到TSS段描述符
- 将TSS段描述符中的内容加载到TR段寄存器
- TR段寄存器通过Base和Limit找到TSS
- 使用TSS中的值修改寄存器
- IRETD返回
实验:通过任务门切换TSS
第一步:构造任务门描述符
任务门描述符结构图灰色部分默认填充为0
P = 1
DPL = 二进制:11
TSS Segment Selector = 0x00C3 // TSS段描述符选择子
由上述参数及默认参数构造出的门描述符为:0000e500`00C30000
第二步:将任务门描述符写入IDT表
第三步:构造TSS段描述符
TSS段描述符构造具体过程参照任务段实验部分,这里不再详解,只给出测试代码
代码如下:
#include <windows.h>
DWORD dwOK;
DWORD dwESP;
DWORD dwCS;
void __declspec(naked) func()
{
dwOK = 1;
__asm
{
mov eax,esp
mov dwESP,eax
mov ax,cs
mov word ptr [dwCS],ax
iretd
}
}
int main(int argc, char* argv[])
{
char bu[0x10]; //12ff70
int iCr3;
printf("input CR3:\n");
scanf("%x", &iCr3);
DWORD iTss[0x68] = {
0x00000000, //link
0x00000000, //esp0 //(DWORD)bu
0x00000000, //ss0
0x00000000, //esp1
0x00000000, //ss1
0x00000000, //esp2
0x00000000, //ss2
(DWORD)iCr3, //cr3
0x00401020, //eip
0x00000000, //eflags
0x00000000, //eax
0x00000000, //ecx
0x00000000, //edx
0x00000000, //ebx
(DWORD)bu, //esp
0x00000000, //ebp
0x00000000, //esi
0x00000000, //edi
0x00000023, //es
0x00000008, //cs 0x0000001B
0x00000010, //ss 0x00000023
0x00000023, //ds
0x00000030, //fs
0x00000000, //gs
0x00000000, //dit
0x20ac0000};
__asm
{
int 0x20
}
printf("ok=%d \t ESP=%x \t CS=%x \n", dwOK, dwESP, dwCS);
getchar();
return 0;
}
第四步:运行代码
需要输入Cr3,Cr3获取流程参照任务段实验部分,这里不再详解
运行结果:
TSS切换成功!
至此,我们已经学会通过CALL/JMP以及任务门来切换TSS
思考:既然已经可以直接访问任务段了,为什么还要有任务门?
答:任务段位主动调用,任务门位被动调用,例如int 8 练习:请进入一环,流畅运行