Windows保护模式下的段机制(PART2:系统段介绍)

1. 概述

PART1那篇文章基本上已经把段机制交待清楚了,这一部分主要是介绍在PART1里没有介绍的系统段。
我们知道段描述符里的S位为1就是代码段或数据段,如果为0就是系统段。

2. 调用门

2.1 调用门介绍

之前介绍过长调用,想通过长调用提升CPL只能通过调用门,调用门是门描述符的一种,保存在GDT中,Windows系统虽然没有使用调用门,但是为了演示提升CPL,我们可以手动构造一个调用门。大概的步骤如下:
1. 使用指令CALL CS:EIP
2. 分解CS的值,然后查GDT表,在Index位置处我们手动构造一个调用门描述符
3. 在调用门描述符中保存着一个代码段的段选择子(Segment Selector),指向的段描述符Base+调用门描述符中的偏移(Offset inSegment)就是真正要执行的地址
4. 经过调用门的处理以后CPL会变成0
门描述符的结构如下:

上图可以看出S=0,Type=1100,1100就指代调用门,Offset inSegment指代要执行的代码偏移,具体的用法在上面的第3步已经说了。
Segment Selector的值决定了长调用是否提权,如果RPL为0代表提权,RPL为3不提权。

2.2 调用门实例

(要进行以下操作步骤,WinDbg首先要设置好双机调试)

构造一个调用门:
1. 在WinDbg中使用r gdtr查看系统GDT表,显示它的地址是0x8003f000
2. 使用dq 8003f000显示GDT表内容
3. 使用eq 8003f048 0040ec00`00081030将构造好的调用门写入到地址0x8003f048处(调用门需要根据门描述符的结构来构造,如果你不知道这个调用门是怎么构造的,你将它转成2进制,再跟门描述符表中的成员进行对照就会明白的)

构造好调用门以后,使用长调用的方式访问GDT中的调用门,就可以实现权限的提升。

int main()
{
	char buff[6];
	*(DWORD *)&buff[0] = 0x12345678;
	*(WORD *)&buff[4] = 0x48;//赋值给高2个字节的CS段寄存器,0x48是段选择子
	_asm
	{
		call fword ptr[buff] ;//6个字节的指针,高2个字节是CS,低4个字节是IP,可以忽略低4个字节
	}
	getchar();
	return 0;
}

最后再通过调用门描述符中的偏移处的代码验证是否提权成功(要先找到裸函数在内存中的地址,然后填进构造好的调用门描述符中作为它的偏移,用前面说的方式将GDT表中的门描述符修改成0040ec00 000810d0)。

void __declspec(naked) GetRegister()
{
	_asm
	{
		int 3
		retf 
	}
}

运行程序,程序会中断在函数GetRegister()的int 3处,在WinDbg中注意观察SS、ESP、CS的值,会发现在这时已经改变了,分析它的选择子发现CPL已经变成0了,权限成功提升。

权限提升以后就可以访问高2G的内存了,我们将GetRegister()重写如下:

BYTE GDT[6] = {0};
DWORD dwH2GValue;
void __declspec(naked) GetRegister()
{
	_asm
	{
		pushad
		pushfd
			mov eax,0x8003f00c
			mov ebx,[eax]
			mov dwH2GValue,ebx
			sgdt GDT;
		popfd
		popad
		
		retf
	}
}
void PrintRegister()
{
	DWORD GDT_ADDR = *(PDWORD)(&GDT[2]);
	WORD GDT_LIMIT = *(PWORD)(&GDT[0]);
	printf("%x %x %x\n",dwH2GValue,GDT_ADDR,GDT_LIMIT);
}

3. 中断门

Windows系统大量使用中断门,主要应用有:
1. 老的CPU在使用系统调用时要从3环进入0环,使用的就是中断门
2. int 3就是执行中断门的指令

中断门保存在IDT中,IDT是由多个描述符组成的,跟GDT不同的是,IDT的第一个元素不是NULL,IDT中全部都是门描述符,它们分别是任务门描述符、中断门描述符、陷阱门描述符。
在WinDbg中查看IDT的基址使用r idtr指令,查看IDT的长度使用r idtl。
中断门描述符结构如下图所示:

中断门的调用可以直接使用INT 0x20这样的指令,0x20代表IDT表中的下标,下标处的中断门描述符决定了是否提权和接下来要执行的代码偏移。

4. 陷阱门

陷阱门跟中断门基本是一样的,主要的区别是中断门执行时,IF位会被清零,但陷阱门不会。IF位是EFLAGS寄存器里下标为9的位,IF位为0的话程序不会再接收可屏蔽中断,什么是可屏蔽中断?即可以被屏蔽的中断,什么意思?举个例子,键盘是通过向CPU发送中断来让系统感知的,但是这类中断是可以被屏蔽的,屏蔽了以后按键就不会再被接收了。

5.TSS段

之前说过,当CS的CPL改变时,SS也会随之改变,ESP跟着也会产生变化,那么这两个的值是从哪里来的呢?答案是TSS,即任务状态段。

需要注意的是TSS、TSS段描述符、TR寄存器是三个不同的结构

5.1 TSS

TSS是一块内存,大小104字节,它不在CPU中,它在内存中,它的结构如下:

TSS虽然叫任务段,Intel最初设计它的时候希望用于任务切换,但是Windows并没有采用,TSS在Windows中只是用于换掉一堆寄存器。

5.2 TR寄存器

CPU中有一个TR寄存器,它是段寄存器的一种,它的选择子保存了它在GDT表中的位置,通过加载TSS段描述符到TR寄存器中给寄存器赋值,当系统需要访问TSS的时候,系统会通过TR寄存器中的Base和Limit获取到TSS的位置和大小。

5.3 TSS段描述符

TSS段描述符是系统段的一种,它的结构跟其他段描述符差别不大

通过LTR指令可以将TSS段描述符加载到TR寄存器,但是LTR指令只能在0环使用,使用STR指令可以读TR寄存器,但是只能读TR寄存器可见的16位选择子。

5.4 修改TSS

我们可以通过LTR指令修改TR寄存器,修改了TR寄存器就可以修改TSS,但是LTR指令只能在0环使用。
我们知道使用JMP FAR访问一个代码段的时候,改变的是CS和IP,当JMP FAR访问的是一个TSS段的时候,改变的就是TR寄存器了,比如说JMP 0x48:0x12345,0x48是段选择子,如果这个段选择子内部的Index处是一个TSS段描述符,系统就会将这个描述符加载到TR寄存器中。

6.任务门

通过任务门可以访问TSS,任务门描述符如下图:

任务门中保存了一个TSS段的选择子,这个选择子用来找到GDT表中的TSS段描述符。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值