段使用时相关的检查与调用门实验

85 篇文章 6 订阅
83 篇文章 10 订阅

段类型检查

加载段选择符进入段寄存器时候

  • CS只能存放可执行的选择符
  • 不可读可执行不能被加载到数据段寄存器
  • 只有可写的数据段才能加载到SS

段权限检查

当给段寄存器赋值,实际是从GDT中获取相应的段描述符加载到段寄存器的不可见部分。这个时候有个权限检查,有三个概念:

  • CPL:当前代码执行权限
  • DPL:存在段描述符中,描述访问本段内存需要的权限
  • RPL:存在于段寄存器加载时的段选择子中,描述了使用什么样的权限对目标进行访问

从数值上MAX(CPL,RPL)<DPL.

段内跳转不会产生权限检查(JMP,CALL,RET),段间会。

当S为0时:

  • 调用门:Type=12。
  • 中断门:Type=14。
  • 陷阱门:Type=15。
  • 任务门:Type=5。

调用门

调用门图片

调用门图片


调用门描述符中存储了一个代码段的段选择子。
指令格式:CALL CS:EIP(EIP废弃的)

  • 根据CS的值查GDT表,找到对应段描述符,之歌描述符是一个调用门。
  • 在调用门描述符中存储着另一个代码段的段选择子。
  • 选择子指向的段,段Base+偏移地址,就是真正要执行的地址。
    通过调用门可以原图,不过Windows并没有用调用门。

段寄存器一共有 96 位,其中16可见部分来源于段选择子的索引部分。剩下80位来源于 GDT表。++那GDT表64位是怎么表示80位的段描述符呢?++ 是有一部分是G位为零代表粒度是1字节,在段限长前面补12位000,如果G为1在段限长前面补FFF,FFF刚好4KB。(段限长就是FFFFF),所以当G=0,即粒度是1B时候,范围就是
2^00000000^到2^000FFFFF^即1B到4MB,当G=1,即粒度是1B时候,范围就是2^FFF00000^到2^FFFFFFFF^,即4KB到4GB。

中断门和陷阱门

除了GDT外,还有一个地方也存着门描述符,被称为IDT(中断描述符表)。
IDT中存着3种门描述符:

  • 中断门描述符
  • 陷阱门描述符
  • 任务门描述符

当S为0,type为1110是个中断门
中断门描述符存储着一个断码段选择子。
当S为0,type为1111是陷阱门

  • 陷阱门用于存放异常处理函数
  • 中断门用于存放中断处理函数地址
  • 中断门执行时候IF位会清零,屏蔽可屏蔽中断,陷阱门不会。

调用门提权实验

先确定一个事情,怎么提权?是要构造一个调用门描述符,然后CALL这个段的一个地址所以这个段的DPL得是3环的,所以,在构造的调用门描述符里,DPL位是3即11b,即3环就能访问本段的程序。然后这个调用门里的段选择子(就是13位可见的那部分个)是要执行的段,所以他的段选择子的RPL要是高权限,所以他的RPL应该是00,Ti也0,查GDT第二个就行所以索引是1,所以合起来是1000b,所以段选择子是8,所以要构造的调用门描述符里8,的作用是让其RPL是00为0,环以0环权限去访问,Ti为是0找GDT表,其他位置弄成如下
XXXXEC000008XXXX
下面的代码相关信息如下

  • 前后XXXX为偏移,比如你要执行的代码的偏移,即下文函数的入口,构造下面代码
  • 0x80b95500为任意一个内核空间(高地址空间),为了验证能不能提权,得到内核的信息。
  • retf为不仅像ret一样pop ip,还pop cs
  • 0x00,0x00,0x00,0x00,0x63,0x00 为CS:IP,因为内存以小端寸断存储。所以是这样,IP可以任意
  • fword为远跳,6字节的,用于段间跳转。
  • CS为0x63,即下面代码要CALL的段选择是0x63,是因为我们找了一个GDT里的空位为第12个位置,所以是1100,然后找GDT,权限是3环,所以段选择子是1100011b,所以这个是63
  • 注意vs要关闭随机基址,虚拟机要单核,比较好做这个实验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "stdafx.h"
#include <Windows.h>
int g_num = 0;
_declspec(naked) void fun() {
	//这里如果用了 int 3 那么回到3环的时候,会造成程序崩溃,因为回去的时候,FS会被置0;
	_asm {
		push eax;
		mov eax, DWORD ptr ds : [0x80b95500];
		mov g_num, eax;
		pop eax;
		retf;
	}
}

int main() {
	char buf[6] = { 0x00,0x00,0x00,0x00,0x63,0x00 };
	_asm {
		call fword ptr ds : [buf];
	}
	printf("%x", g_num);
	system("pause");
	return 0;

}

参考资料:

[1]:赵炯,《Linux内核完全剖析》,机械工业出版社. 4.3.4节
[2]:https://blog.csdn.net/q1007729991/article/details/52538080
[3]:李忠,《x86汇编语言:从实模式到保护模式》,电子工业出版社

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值