保护模式第六讲-IDT表-中断门 陷阱门 任务门

保护模式第六讲-IDT表-中断门 陷阱门 任务门

一丶IDT表

之前所说 GDT表 中存储了一些段描述符. 比如有调用门 段描述符. 代码段段描述符. 数据段段描述符 TSS段段描述符

GDTR记录的就是GDT表的首地址. GDTL是他这个数组的长度

那么同样. IDT表也是 记录在 IDTR 以及 IDTL 两个寄存器中

其中IDT表中 只能存储 中断门段描述符表 陷阱门段描述符表 任务门段描述符表

其中 中断门段描述符表 陷阱们段描述符表 其实都跟调用门用法一样. 只不过有些许不同.

1.1 中断门段描述符表

看一下inter手册.将中断门描述符表抠出来.

着重看一下 s位与TYpe位.

其中中断门的0-4位必须为0. 不使用了. 而且8-12位都是固定的了.

跟调用门唯一不同的就是 s与type位了. 其它都是一样的.

所以我们还可以根据调用门的相关知识. 构造一个中断门. 并且提权.

其中在第12位哪里有一个D. 其实这个是1的意思.

所以如果判断是不是一个中断门 可以通过

p = 1

s = 0

type = 1110 来确定一个中断门

1.2 中断门的Call调用流程流程图

观看上图可以得知

1.首先寻址到中断门 ,然后从中断门中分别取出记录的 offset(函数偏移) + 代码段段选择子

2.然后根据代码段段选择子. 去GDT表或者LDT表中查询 代码段描述符

3.从代码段描述符中取出记录的 Base(基地址)

4.取出的基地址与中断门中记录额函数偏移(offset)相加得到一个真正的函数地址.

5.进行调用.

1.3 中断门的调用以及返回

中断门 是由int xxx汇编指令来进行触发的. 进而去IDT表中中寻找中断门段描述符. 与 调用门不同的是. 当返回的时候. 比如使用 (IRET / IRETD)来进行返回

int xxx xxx是索引. 是IDT表中的索引. IDT.base + xxx * 8 = 实际的中断门所在的位置.

也可以理解为 IDT[xxx -1]

例如 int 3 查找的就是 IDT[2] 索引 数组从下标从0开始 0 1 2 正好查找的是第三项

调用门是 RETF

中断门是 IRET /IRETD

RETF的本质就是 调用门当发生权限切换的时候.堆栈会保存 SS ESP CS EIP(返回地址)

RETF本质就是将这些堆栈值进行恢复. 并不是说调用门非要使用RETF才可以. 你如果自己进行POP也是可以的.

中断门的堆栈会保存 SS ESP EFLAGS CS EIP(返回地址) 所以我们使用IRET来进行返回.

1.4 中断门的构造与代码

构造一个中断门

0000EE~00080000

根据代码中的函数地址则可以构造为如下

0040EE0000081030** 也可以构造成 **00408E0000081030 如果为8 代表P位有效. DPL = 00 S = 0 说明权限很高. 此时我们ring3就不能访问了.

构造完毕之后写入到IDT表中.

如下;

r idtr
dq address L40 显示40项 以8字节显示
eq 未使用的地址 0040EE00`00081030

显示了100项发现下面都使用了. 而10 - 40中 只给了一个段选择子就是08 我们随便找一个位置写入

以上图为例子,写入位置是 截图位置

eq 80b95530 0040EE00`00081030

写入完成则可以运行起来了.

  • 代码调用

    我们写入的IDT表 是第39项. 如果以下标来寻址 则是 39 - 1 = 38

    所以我们代码中写位 int 38即可. 注意40是10进制的. 如果你想写为16进制

    那么转为16进制则是 int 0x26

    代码如下

    // 11.cpp : Defines the entry point for the console application.
    //
    
    #include <stdio.h>
    #include <WINDOWS.H>
    #include <STDLIB.H>
    
    
    DWORD x;
    DWORD y;
    DWORD z;
    
    __declspec(naked) void run()
    {
    	__asm 
    	{
    	
    		int 3;
    		IRETD;
    	}
    }
    
    void printXyz()
    {
    	printf("%d,%d,%d \r\n",x,y,z);
    }
    int main(int argc, char* argv[])
    {
    
    	__asm
    	{
    	
    		int 38;
    	}
    
    	printXyz();
    	system("pause");
    	return 0;
    }
    
    
    

    堆栈调用前

代码执行后

我们可以发现

保存了 EIP(返回地址) cs EFlags esp ss 这就是中断门的堆栈

二丶陷阱门

2.1 陷阱门段描述符

陷阱门同上,唯一不同的 段描述符中的第八位是不同的. 中断门为0 陷阱门为1

如果按照16进制来说. 一个是E 一个是F

陷阱门的构造 以及代码调用与中断门一样. 而且参数也不能有

0000EF00`00080000

2.2 陷阱门与中断门的不同

陷阱门与中断门唯一的不同就是 EFLAGS 位中的 IF位

中断门 -执行后 IF 设置为0

陷阱门 -执行后 - IF不变

inter手册中的 EFlags介绍

3.4.3 是介绍E flags的

IF 位跟中断有关. CPU 必须支持中断

IF = 1 响应可屏蔽中断

IF = 0 禁止可屏蔽中断

2.2.1 中断

CPU 必须支持中断 不管你当前执行了多少流程. 但是只要你操作键盘. 那么CPU就会第一时间响应

比如 win + D 显示桌面 win + L 锁屏

中断时基于硬件的.

可屏蔽中断 中 比如键盘. 键盘是可屏蔽中断

按电源. 电源属于不可屏蔽中断. 当我们拔掉电源之后,CPU并不是直接熄灭的. 而是有电容的.

此时不管你IF是什么.都会执行 int 2中断. 来进行一些收尾的动作.

看下inter手册怎么说

inter手册说. 中断和异常 是强制性执行流的转移 也就是不管你处于那个环境.只要有中断 和异常.都会强制执行的.换句话来讲就是 从当前流程强制转到另一个流程

中断是可以进行软件模拟的. 称为软中断. 也就是通过 int n 来进行模拟. 我们构造的中断门. 并且进行int n 模拟 就是模拟了一次软中断

inter手册所说. 任何可屏蔽中断 可以通过使用 interl 架构定义的中断向量(0`255) 也就是说有一个中断表. 大小是255项. 通过 E Flags 中的 if 位 可以进行屏蔽 所有可屏蔽的硬件中断

软件中断模拟的注意事项. inter手册所属. 0-255 我们都是可以通过 int n进行模拟的. 但是有一个注意的问题就是. 比如不可屏蔽中断. int 2. 如果我们用int 2来进行模拟 是不会产生真正的效果的 意思就是说.你执行int 2确实是执行了. 但是硬件上并没有激活. 也就是说你是假的. 真正的执行时硬件上的处理这块的地方也执行了.

inter手册所说 0-31中断向量是给 NMI中断使用的.也就是给CPU使用的. 剩下的32 - 255 才是

给用户使用的.

三丶任务段与任务门

3.1 TSS 学习.

3.1.1 TSS简介

通过中断门 陷阱门 以及调用们我们知道. 其实不管提权不提权. 本质就是 修改寄存器的值.

比如:

jmp cs:EIP          提权 修改的是 cs ss esp eip
call far            eip cs eflags esp ss 等等

TSS 主要就是为了替换寄存器的. 存在的意义就是任务切换. 在CPU 层面只有任务层面. 没有进程

没有线程等概念. TSS也是一个好设计. 设想在单核时代.没有TSS 根本不能多任务切换.

而有了TSS 则可以当A程序执行到一个地址. 保存地址处的 通用寄存器 段 以及控制寄存器等.

然后执行B. 当切换到A的时候.在还原回来进行继续执行. 如果对应操作系统就是线程的概念.

  • TR段寄存器(96位)

    TR寄存器是一个96位的段寄存器.保存了TSS的首地址

    LTR STR 相关汇编指令  L = load的意思 就是装载TR寄存器. 属于特权指令 STR是读,读出来也就是16位的段选择子. 没有特权要求
    

    TR既然是段寄存器.那么可见部分肯定还是只有段选择子. 所以还是去GDT查表.

    tr寄存器段选择自查找到段描述符

    段描述符.base = TSS首地址

    段描述符.limit = TSS这块内存的大小

    而我们查的表就是 TSS段描述符

    r tr
    dg xxx 解析段选择子
    

    段寄存器结构复习

    struct segment
    {
      WORD Selector, //段选择子  16位
      WORD Attribute,//段属性    16位
      DWORD Base,    //段基地址 32位
      DWORD Limit,   //段限长 32位
    }
    

    三者之间的关系

    TSS 属于是一块内存

    TSS段描述符. 指明了TSS在哪.

    TR寄存器.段寄存器. 指明了去GDT表中查找TSS段描述符

3.1.2 TSS内存结构

在inter手册 3A卷第七章可以看到TSS 内存结构. 主要保存了 通用寄存器 段寄存器.

而且一个TSS 对应一个LDT段寄存器

MOV ds,0x5C   5c段选择子拆开就是 查询LDT表. 如果你在TSS够早了LDT.那么当任务切换的时候LDTR则会被赋值,当执行 0x5C的时候.CPU会去LDTR中找到到对应的段描述符进行操作

LDT TSS 一一对应, 只对单个任务有意义.

TSS 中的 第0位置处. Previous Task Link 是一个链表.指向了上一个TSS. TSS是在内存中的.

当我们中断门 陷阱门调用的时候. 切换堆栈的时候. esp ss 都是从TSS这块内存中拿到的.

这里有ESP0 SS0 等. 意思就是当你切换到0环的时候. 那么就是用ESP 0 如果是1环 就切换到ESP 1

以此类推

通过Windbg内核.可以得知TR寄存器保存的是TSS的段选择子. 通过TR 可以找到TSS段描述符.

然后通过TSS段描述符来寻找到 TSS内存所在的位置.

dg tr    解析出TSS段描述符结构
kd> dg tr
                                  P Si Gr Pr Lo
Sel    Base     Limit     Type    l ze an es ng Flags
---- -------- -------- ---------- - -- -- -- -- --------
0028 801e4000 000020ab TSS32 Busy 0 Nb By P  Nl 0000008b

db 801e4000 L0x68  可以查看TSS的内存

windbg如下.

TSS内存是从低到高的. 高地址最后一个4个字节保存的就是IO了.

3.1.3 TSS 段描述符

inter手册图片. 其实跟之前的段描述符是一样的. 唯一不同的是 type位.

这里 1001 在这里还有个B. inter手册说. 如果为B那么就代表TSS正在忙碌.

G位在这里为1表示字节

3.1.4 TSS下的寻址形式

TSS段描述符在64位下进行扩展了. 而且TSS在64位下已经不支持了. 但是还有保留

等讲解 x64保护模式与32位包括模式扩展的时候在进行详细讲解.

image-20200719235026995

1.先从任务段寄存器(TR)中得出段选择子(visible part位置)

2.查询GDT表.找到TSS段描述符

3.根据TSS中段描述符的描述的 BaseAddres(基地址) 以及limit 去TSS寄存器中进行查找

3.1.5 windows下的TSS使用

windows觉得TSS设计不好.只使用了TSS中的 esp 0 ss0等寄存器

3.2模拟TSS任务切换

1.了解 TR TSS 与 TSS段描述符之间的关系

TSS段描述符 存储在GDT表中. 其中 TSS的base 指向了一个TSS.也就是一块内存 大小为0x64(104)个字节

TR寄存器. 指向了 TSS内存. 但是TR得值.是从TSS段描述符中获取到的. (TR是一个段选择子)

也就是说我们要模拟TSS任务切换首先要做到如下.

1.构建TSS内存.大小为0x68个字节 怎么构建,参考104字节每个字节代表的意思 参考 3.1.2 TSS内存结构

2.构建TSS段描述符. 然后写入到GDT表中

3.利用LTR修改TR寄存器.来达到从TRR段描述符中获取TSS的内存.

4.使用远call 来进行内核与ring3的切换.参考模拟调用们权限. 或者jmp 也可以.

jmp cs:eip
call far cs:eip
  • 1.构造TSS内存

    char espBuffer[0x30] = { 0 }; //开辟空间构建TSS内存的栈顶
    	printf("Please Input Cr3 Value \r\n");
    	unsigned int Cr3Value = 0;
    	scanf("%d", &Cr3Value);
    	//构建TSS的内存
    	unsigned int TssMemory[0x64] = {
    	0x00000000,// Previous TaskLink 操作系统会给写入
    	0x00000000,// esp 0
    	0x00000000,// ss  0
    	0x00000000,// esp 1
    	0x00000000,// ss  1
    	0x00000000,// esp 2
    	0x00000000,// ss  2
    	(unsigned int)Cr3Value,
    	(unsigned int)EipFunction,// EIP              执行你函数的地址.EIP指向.这样才可以执行你的函数
    	0x00000000,// eflags 
    	0x12345678,// eax
    	0x87654321,// ecx
    	0x11223344,// edx
    	0x44332211,// ebx              寄存器随便给.便于调试的时候更直观的看到
    	(unsigned int)espBuffer,	// esp              栈顶的值.需要我们指定
    	0x00000000,// ebp              栈底的值
    	0x00000000,// esi
    	0x00000000,// edi
    	0x00000023,// es				段寄存器我们也要给 如果切换到内核则按照内核中的给.
    	0x00000008,// cs				0环的代码段选择子. 如果想要切换rign3就给ring3的. 可以windbg调试
    	0x00000010,// ss                同cs一样.ss与cs必须在同一代码段下
    	0x00000023,// ds
    	0x00000030,// fs
    	0x00000000,// gs
    	0x00000000,// LDT
    	0x20ac0000,// Io              这个根据Windbg调试得出来.看一下填写即可.
    	};
    	
    

    这里涉及到1个点

    如何查看 Cr3的值并且写入

    ​ 运行我们的模拟切换的程序. 此时会段在scanf位置. 在windbg下中断. 中断后. 输入 ! process 0 0 来查看所有进程.找到我们的进程.复制一下 Dirbase即可. 然后输入进去.

    • 构造TSS段描述符.

      构造很简单.根据上面所说的 TSS段描述符进行构造即可.

      这里则是将我们的TSS的内存的地址.构造进TSS段描述符.

      完整代码如下:

      // TSS.cpp : Defines the entry point for the console application.
      //
      
      #include <stdlib.h>
      #include <stdio.h>
      
      
      void __declspec(naked) EipFunction()
      {
      	__asm
      	{
      		int 3;   //便于内核调试器断下
      		push ebp;
      		mov ebp, esp;
      
      		
      	}
      }
      
      
      int main(int argc, char* argv[])
      {
      	char espBuffer[0x30] = { 0 }; //开辟空间构建TSS内存的栈顶
      	printf("Please Input Cr3 Value \r\n");
      	unsigned int Cr3Value = 0;
      	scanf("%d", &Cr3Value);
      	//构建TSS的内存
      	unsigned int TssMemory[0x64] = {
      	0x00000000,// Previous TaskLink 操作系统会给写入
      	0x00000000,// esp 0
      	0x00000000,// ss  0
      	0x00000000,// esp 1
      	0x00000000,// ss  1
      	0x00000000,// esp 2
      	0x00000000,// ss  2
      	(unsigned int)Cr3Value,
      	(unsigned int)EipFunction,// EIP              执行你函数的地址.EIP指向.这样才可以执行你的函数
      	0x00000000,// eflags 
      	0x12345678,// eax
      	0x87654321,// ecx
      	0x11223344,// edx
      	0x44332211,// ebx              寄存器随便给.便于调试的时候更直观的看到
      	(unsigned int)espBuffer,	// esp              栈顶的值.需要我们指定
      	0x00000000,// ebp              栈底的值
      	0x00000000,// esi
      	0x00000000,// edi
      	0x00000023,// es				段寄存器我们也要给 如果切换到内核则按照内核中的给.
      	0x00000008,// cs				0环的代码段选择子. 如果想要切换rign3就给ring3的. 可以windbg调试
      	0x00000010,// ss                同cs一样.ss与cs必须在同一代码段下
      	0x00000023,// ds
      	0x00000030,// fs
      	0x00000000,// gs
      	0x00000000,// LDT
      	0x20ac0000,// Io              这个根据Windbg调试得出来.看一下填写即可.
      	};
      
      	printf("TSS内存的地址为 : %p \r\n", TssMemory);     //输出一下TSS内存的地址. 便于构造段描述符.
      	getchar();
      	char FarAddress[6] = { 0 };//构造我们的远call 指令.同调用们一样
      	//call far 段选择子:eip 
      	*(unsigned int*)&FarAddress[0] = 0x00000000; //EIP在远call的时候不用.随便给.
      	*(unsigned short*)&FarAddress[4] = 0xC0;    //构造的TSS段在GDT表中的段选择子.
      	__asm
      	{
      		call fword ptr[FarAddress];
      	}
      
      	printf("over _ \r\n");
      	system("pause");
      	return 0;
      }
      
      

      假设我们的TSS内存地址为 0x0012FD84 那么构造的段描述符就为

      eq 8003f0c0 0000e912`FD800068
      

3.3 任务门

任务门如下

其实就是存储了一个 TSS的段描述符的段选择子.

任务门是存在中断表中的(IDT)而TSS段描述符是在GDT表中的.

任务门执行流程如下

  • 1.INT N指令来去IDT表中执行代码
  • 2.查询IDT表找到任务门描述符
  • 3.通过任务描述符表.查询GDT表.找到任务段描述符.
  • 4.使用TSS段中的值修改寄存器
  • 5.IRETD返回

所以要构造一个任务门 我们要构造一个TSS段. 还要构造一个任务门才可以.
如果是任务门跟以上构造TSS模拟任务切换一样.只不过我们要修改为 int n来进行调用了.
所以我们要修改IDT表. 而且代码的返回使用的是 IRETD指令

posted @ 2020-07-16 21:41  iBinary  阅读(1524)  评论(0编辑  收藏  举报