在《TSS切换实验》中,我们已经完成了使用 call 模拟了 TSS 的第二个功能——替换一堆寄存器。上一篇中,提到了任务门同样可以完成这个功能。
本节主要通过设计任务门描述符以及INT指令来完成TSS的第二个功能。
思路
- 编写测试入口函数,把它的函数入口地址填写到 TSS 段中
- 构造 TSS 段
- 设计 TSS 段描述符,安装到 GDT 表
- 设计任务门,安装到 IDT 表
- 编写主函数,使用 INT 指令进入任务门
实验的难点在于 TSS 段,TSS 段描述符和任务门的构造。要想完成这个实验,必须要正确理解这三者之间的关系。
我们要把握这几个关键点:
- TSS 段是们于内存中的一段数据
- TSS 段描述符是描述 TSS 段的基址和大小,并且安装在 GDT 表中
- 任务门中嵌入了TSS段选择子,任务门安装在 IDT 表中
图1 TSS 段、TSS 段描述符和任务门间的关系
实验
编写测试入口函数,填写到 TSS 段中
因为之前写过相同的代码,这里就直接搬过来用了。
DWORD g_esp;
DWORD g_cs;
__declspec(naked) func() {//00401020
__asm {
mov g_esp, esp
mov eax, 0
mov ax, cs
mov g_cs, eax
iretd // 在 32 位系统下,这里写 iret 和 iretd 都是一样的
}
}
这个函数在我的编译里显示的地址是 0x00401020。你需要在你的编译器里调试观察它的地址是多少。
构造 TSS 段
图2 tss 段的地址,栈地址
char st[10] = {0}; // 0042ace4
TSS tss = {// 0x00427a30
0x00000000,//link
(DWORD)st,//esp0,这里是自己定义的栈地址
0x00000010,//ss0
0x00000000,//esp1
0x00000000,//ss1
0x00000000,//esp2
0x00000000,//ss2
0x00000000,//cr3
0x00401020,//eip,这里是上面的测试入口函数的地址。
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 // 这个位置暂时忽略,填这个值就行了
};
设计 TSS 段描述符,安装到 GDT 表
图2 中显示 tss 的地址为 0x00427a30,根据TSS段描述符构造规则,构造如下描述符。
0000e942`7a300068
使用下面的命令安装到 GDT 表。
eq 8003f048 0000e942`7a300068
因为在 8003f048 这个位置是空的,所以我选择安装到这里。你需要根据你的情况选择合适的位置安装。具体见图3和图4.
设计任务门,安装到 IDT 表
TSS段刚刚是安装在8003f048这个位置,它的选择子是 0x48,根据任务门描述符规则,设计任务门描述符如下。
0000e500`00480000
使用下面的命令安装到 IDT 表。
eq 8003f500 0000e500`00480000
因为在我的 IDT 表中,8003f500 这个位置是空的,所以我选择安装在这里。安装在这个位置的话,那么它在 IDT 表中的索引是是 0x20。我的 IDT 表基址是 8003f400.
图3 安装TSS段描述符和任务门描述符
图3 安装描述符后的GDT表和IDT表
编写主函数,使用 INT 指令进入任务门
- 代码
int main(int argc, char* argv[])
{
printf("please input cr3:\n");
scanf("%x", &(tss.cr3)); // 使用 !process 0 0 在 WinDbg 查看 DirBase
__asm {
int 0x20
}
// 不出意外,这里打印的值分别是 00000008, 0042ace4
printf("g_cs = %08x\ng_esp = %08x\n", g_cs, g_esp);
return 0;
}
- 运行结果
运行的时候需要注意,CR3 需要在 WinDbg 中查看。在 WinDbg 中使用命令 !process 0 0
来查看程序TaskGate.exe 的 DirBase,然后在控制台里输入后回车继续执行。
完整代码
// 文件:TaskGate.cpp
#include <windows.h>
#include "tss.h" // tss.h 文件请移步至第11篇《TSS切换实验》中查看。
char st[10] = {0}; // 0042ace4
DWORD g_esp;
DWORD g_cs;
TSS tss = {// 0x00427a30
0x00000000,//link
(DWORD)st,//esp0
0x00000010,//ss0
0x00000000,//esp1
0x00000000,//ss1
0x00000000,//esp2
0x00000000,//ss2
0x00000000,//cr3
0x00401020,//eip
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
};
__declspec(naked) func() {//00401020
__asm {
mov g_esp, esp
mov eax, 0
mov ax, cs
mov g_cs, eax
iretd
}
}
int main(int argc, char* argv[])
{
printf("please input cr3:\n");
scanf("%x", &(tss.cr3)); // 使用 !process 0 0 查看
__asm {
int 0x20
}
// 不出意外,这里打印的值分别是 00000008, 0042ace4
printf("g_cs = %08x\ng_esp = %08x\n", g_cs, g_esp);
return 0;
}
总结
本文主要使用任务门模拟了 TSS 的第二个用途。实验中涉及到了一个CR3的东西,大家暂时不用在意,照着做就行了,后面的文章讲会慢慢讲解。