学习一下任务门相关的知识
从堆栈切换开始说起,不管是中断、陷入还是调用,提权之后 ESP 和 SS 都会被切换到对应权限的栈,那么必然有一个地方会维护这个栈所在的位置,其实就是使用任务段去维护的。
任务段
任务段介绍
任务状态段简称任务段,英文缩写为TSS
,Task-state segment
,任务段的结构体如下所示,大小为 104 字节。
观察结构体成员,可以很明显地看到有 SS2,ESP2,SS0,ESP0 等字段,没错,这就是保存相对应任务2环和0环的堆栈信息的。Intel
的设计 TSS
目的肯定主要就是实现任务切换。CPU
的任务在操作系统的方面就是线程。任务一切换,执行需要的环境就变了,即所有寄存器里面的值,需要保存供下一次切换到该任务的时候再换回去重新执行。但是事实上,线程切换并不走 TSS,而是操作系统自己实现了线程切换的逻辑[1],据说是因为 intel
自带的任务切换逻辑过慢[2]。
CPU
要找到 TSS
需要通过 TR
段寄存器,TR
也是一个内核寄存器,CPU
通过 TR
寄存器找到 TSS
的方式如下图所示:
可以看到,CPU
保存任务段选择子在 TR
寄存器中,将具体的任务段描述符保存在 GDT
表中。
任务段描述符的段描述符结构如下
其余位基本一样了,注意这里的 B 表示任务段是否被加载进 TR
寄存器中,B=0
表示没有被加载(available)。
读写 TR
寄存器指令
读写分别对应 Store
和 Load
操作,也就对应 STR
和 LTR
两个指令。
同样的,读指令是可以在三环下运行,但是只能读到任务段选择子。写指令只能在零环下运行,需要提供 96 位的数据去装载任务段描述符,且加载后会导致任务段描述符的 TYPE 发生改变(B
位从 0
变为 1
)。
任务门
还是先来看看任务门的结构:
很简单,具体字段也不赘述了。因为任务门是在 idt 表中的,所以必然是通过 int 指令去调用。
实验
先写一个代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
| #include "stdafx.h" #include <windows.h> #include <stdio.h>
char stack[10] = {0};
DWORD g_esp; DWORD g_cs; typedef struct TSS { DWORD link; DWORD esp0; DWORD ss0; DWORD esp1; DWORD ss1; DWORD esp2; DWORD ss2; 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; TSS tss = { 0x00000000, (DWORD)stack, 0x00000010, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00401000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, (DWORD)stack, 0x00000000, 0x00000000, 0x00000000, 0x00000023, 0x00000008, 0x00000010, 0x00000023, 0x00000030, 0x00000000, 0x00000000, 0x20ac0000 };
void __declspec(naked) func() { __asm { int 3 mov g_esp, esp mov eax, 0 mov ax, cs mov g_cs, eax iretd } } int main(int argc, char* argv[]) { printf("func=%x tss=%x stack=%x\n",func,&tss,stack); printf("please input cr3:\n"); scanf("%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); return 0; }
|
然后向 0x48 这个段描述符构造一个任务段,段描述符为 0000e940`5000ffff。
然后找到该进程 cr3 的值,这里 cr3 寄存器是保存程序页表的物理地址使用的,windbg中输入 !process 0 0
即可查看,dirbase 即是该值。
但是试了很多次,发现都会直接导致虚拟机关闭 or 蓝屏,这个进行不下去了可能得先一放下,下篇开始学分页。
参考文献