首页
社区
课程
招聘
[原创]保护模式学习笔记之段机制
发表于: 2021-11-27 17:35 16778

[原创]保护模式学习笔记之段机制

2021-11-27 17:35
16778

一.概念

内存是计算机系统的关键资源,程序必须被加载到内存中才可以被CPU所执行。程序运行过程中,也要使用内存来记录数据和动态信息。在一个多任务系统中,每个任务都需要使用内存资源,因此系统需要有一套机制来隔离不同任务所使用的内存。要使用这种隔离既安全又高效,那么硬件一级的支持是必须的。IA32 CPU提供了多种内存管理机制,这些机制为操作系统实现内存管理功能提供了硬件基础。

CPU的段机制提供了一种手段可以将系统的内存空间划分为一个个较小的受保护区域,其中每个区域称为一个段。每个段都有字节的起始地址(基地址),边界(limit),和访问权限等属性。当根据(段基址:段偏移)来获取程序的逻辑地址的时候,就需要将段寄存器中保存的基地址加上偏移地址才可以获得,比如ds:[0x12345678]这个地址,最终会获得的逻辑地址就是ds段寄存器中保存的基地址加上0x12345678。下图就是这一过程的说明

而段寄存器中的基地址在何处,如何被加载就需要理解段机制,要理解这一套机制,就需要理解IA-32 CPU的段寄存器,段选择子以及段描述符的内容。

二.段寄存器

为了减少地址转换时间和编码复杂度,处理器提供了可容纳多达6个段寄存器。每个段寄存器都支持一种特定类型的内存引用(代码、堆栈或数据)。对于几乎任何类型的程序执行,至少代码段(CS)、数据段(DS)和堆栈段(SS)寄存器必须加载有效的段选择器。处理器还提供了三个额外的数据段寄存器(ES、FS和GS),它们可用于为当前执行的程序(或任务)提供其他数据段。

要让程序访问段,段的段选择器必须已加载到其中一个段寄存器中。因此,尽管一个系统可以定义数千个分段,但只有6个分段可以立即使用。在程序执行期间,通过将其段选择器加载到这些寄存器中,可以提供其他段。

段寄存器的结构如下图所示

一个段寄存器以供有96位,其中的16位段选择子是可见的,其余的80位不可见的部分中包含着32位的基地址,32位的边界以及16位的段属性。当段寄存器加载16位的段选择子的时候,处理器还会从段描述符中加载不可见的80位的内容。

有两种加载段寄存器的方法:

  1. 直接加载指令,如MOV、POP、LDS、LES、LSS、LGS和LFS指令。这些指令可以显式地引用段寄存器

  2. 隐含的加载指令,如CALL、JMP和RET指令的远跳转版本、系统输入和系统退出指令,以及IRET、INTn、INTO和INT3指令。这些指令将CS寄存器(有时还有其他段寄存器)的内容作为其操作的附带部分进行更改

MOV指令可以将段寄存器的可见部分存储在通用寄存器中。

为了让程序正常运行,操作系统在程序加载进内存要执行之前都会初始化段寄存器的值,不同操作系统初始化的值是不一样的。下图就是在我的Win10系统中对段寄存器的情况:

这些段寄存器的各项值的内容如下:

段寄存器 段选择子 基地址 边界 访问权限
ES 0x002B 0x0 0xFFFFFFFF 可读,可写
CS 0x0023 0x0 0xFFFFFFFF 可读,可执行
SS 0x002B 0x0 0xFFFFFFFF 可读,可写
DS 0x002B 0x0 0xFFFFFFFF 可读,可写
FS 0x0053 0x7FFDE000 0x00000FFF 可读,可写
GS 0x002B 0x0 0xFFFFFFFF 可读,可写

可以看到这些段寄存器多数内容都一样,除了CS属性可执行不可写,FS的基地址和边界不同和其他段寄存器不同。由于可见的只有16位,所以要证明其他80位的存在,只能通过实验进行证明。

以下代码可以证明段寄存器中有32位的基地址

#include 
#include 

DWORD g_dwFlag = 0;

int main()
{
	__asm
	{
		push gs
		mov ax, fs
		mov gs, ax
		mov eax, dword ptr gs:[0x0]
		mov dword ptr ds:[g_dwFlag], eax
		pop gs
	}
	printf("g_dwFlag=%d\n", g_dwFlag);

	return 0;
}

正常情况下,程序是无法对0地址的内容进行读写的,可是当将ds段寄存器的内容改成fs段寄存器的内容的时候,就可以实现对偏移为0的地址的读写,说明此时的段寄存器中包含了基地址,这样最终得到的地址才不是0地址

以下代码可以证明段寄存器中有32为的边界

#include 
#include 

DWORD g_dwFlag = 0;

int main()
{
	__asm
	{
		push ds
		mov ax, fs
		mov ds, ax
		mov eax, 1
		mov dword ptr ds:[g_dwFlag], eax
		pop ds
	}
	printf("g_dwFlag=%d\n", g_dwFlag);

	return 0;
}

由于将fs段寄存器的内容赋值到了ds寄存器中,此时ds的边界只到0xFFF,无法访问整个进程空间了,此时对全局变量的读写超出了范围就导致程序运行的崩溃

以下代码可以证明段寄存器中有16位的访问权限

#include 
#include 

DWORD g_dwFlag = 0;

int main()
{
	__asm
	{
	        push ds
		mov ax, cs
		mov ds, ax
		mov dword ptr ds:[g_dwFlag], 1
		pop ds
	}

	return 0;
}

根据上表可以知道CS段寄存器是不具有可写的属性的,将ds段寄存器内容修改为cs段寄存器内容以后,在对ds段的内存区域进行写入操作程序就会崩溃。可见,将cs段寄存器并不具有可写的属性

三.段选择子

96位的段寄存器中有16位可见部分是段选择子,处理器就是通过它去段描述符中加载不可见的80位加入到段寄存器中。所以段选择子并不直接指向段,而是指向定义段的段描述符,它结构如下图所示:

从图中可以知道,一个16位的段选择子分成了三个部分,这三部分的描述如下

名称 比特位 描述
RPL 0-1 请求者特权级别
TL 2

指定要查询的段描述

  • GDT:1

  • LDT:0

Index 3-15 在GDT或LDT中的8192个描述符中的一个。处理器将索引值乘以8(段描述符中的字节数),并将结果与GDT或LDT的基本地址(分别来自GDTR或LDTR寄存器)相加得到相应的段描述符地址

由此可知,处理器根据段选择子中的Index来获得需要的段描述符的地址,以此来加载隐藏的80位。

处理器不使用GDT的第一个条目。指向GDT的此条目的段选择器(即索引为0且TI标志设置为0的段选择器)被用作“空段选择器”。当段寄存器(除了CS或SS寄存器)加载了空选择器时,处理器不会产生异常。但是,当使用持有空选择器的段寄存器来访问内存时,它确实会产生一个异常。空选择器可用于初始化未使用的段寄存器。加载带有空段选择器的CS或SS寄存器会导致生成通用保护异常。

段选择器作为指针变量的一部分对应用程序可见,但选择器的值通常由链接编辑器或链接加载器分配或修改,而不是应用程序。

四.段描述符表

由上面内容可以知道,处理器要根据段选择子找到需要的段描述符,以此填充段选择子的隐藏的80位的内容,此时的段选择子就代表了一个段。但是,在一个多任务中通常会同时存在着很多个任务,每个任务会涉及多个段,每个段都需要一个段描述符,因此系统中会有很多段描述符,为了方便管理,系统用线性表来存放段描述符。根据用途不同,IA-32处理器有3中描述符表:全局描述符表(GDT),局部描述符表(LDT)和中断描述符表(IDT)。

GDT是全局的,一个系统中通常只有一个GDT,供系统中的所有程序和任务使用。LDT与任务有关,每个任务可以有一个LDT,也可也让多个任务共享一个LDT。IDT的数量是和处理器的数量相关的,系统通常会为每个CPU建立一个IDT。

GDTR和IDTR寄存器分别用来标识GDT和IDT的基地址和边界。这两个寄存器的格式是相同的,在32位模式下,长度是48位,高32位是基地址,低16位是边界。

LGDT和SGDT指令分别用来读取和设置GDTR寄存器。LIDT和SIDT指令分别用来读取和设置IDTR寄存器。操作系统在启动初期会建立GDT和IDT并初始化GDTR和IDTR寄存器。

当创建LDT时,GDT已经准备好,因此,LDT被创建为一种特殊的系统段,其段描述符被放在GDT表中。GDT表本身只是一个数据结构,没有对应的段描述符。

想要知道这两个寄存器中保存的内容,可以使用WinDbg的r命令来查看

kd> r gdtr
gdtr=8003f000
kd> r gdtl
gdtl=000003ff
kd> r idtr
idtr=8003f400
kd> r idtl
idtl=000007ff

从上面的gdtl值可以知道这个GDT的边界是0x3FF(1023),总长度是1024字节(1KB),由于每个段描述符占8个字节,所以一共有1024/8=128个表项。IDT的长度是2KB,共有256个表项。

当然也可也使用下面的代码来获得对应的内容

#include 
#include 

#pragma pack(1)
typedef struct _GDT
{
	WORD wLimit;
	DWORD dwBase;
}GDT;

typedef struct _IDT
{
	WORD wLimit;
	DWORD dwBase;
}IDT;
#pragma pack()


int main()
{
	GDT gdt = { 0 };
	IDT idt = { 0 };

	_asm
	{
		sgdt gdt
		sidt idt
	}

	printf("GDT base=0x%08x limit=0x%04x\n", gdt.dwBase, gdt.wLimit);
	printf("IDT base=0x%08x limit=0x%04x\n", idt.dwBase, idt.wLimit);
	system("pause");

	return 0;
}

可以看到输出和WinDbg的输出是一样的

五.段描述符

在保护模式下,每个内存段都有一个段描述符,段描述符是GDT或LDT中的一种数据结构,它为处理器提供段的大小和位置,以及访问控制和状态信息。段描述符通常由编译器、链接器、加载器或操作系统或执行程序创建,但而不是应用程序。下图是段描述符的一般格式,可以看到一个段描述符占8个字节,分为高32位和低32位。

段描述符最基本的内容是这个段的基地址和边界。基地址是以4个字节表示(字节2,3,4和7),它可以是4GB线性地址空间中的任意地址(0x00000000~0xFFFFFFFF)。边界是用20个比特位表示(字节0,1和字节6的低4位),其单位由粒度位(字节6的最高位)决定,当G=0时,段边界的单位是1字节,当G=1时是4KB。因此一个段的最大边界值是(2^20-1),最大长度是2^20*4KB=4GB。

在段描述符中一共只有20位来表示边,最大为0xFFFFF,而要表示32位的边界值就需要使用到G位

  • G位为0:在0xFFFFF高位补3个0x0,此时边界就是0x000FFFFF

  • G位为1:在0xFFFFF低位补3个0xF,此时边界就是0xFFFFFFFF

下表介绍了段描述符的其他各个域的含义

域简称 全称 含义
S 系统(System) S=0代表该描述符是一个系统段,S=1代表该描述符是代码段,数据段或者堆栈段
P 存在(Present)

指示该段是否存在于内存中。如果P=0,则当将指向段描述符的段选择器加载到段寄存器中时,处理器将生成段不存在异常。内存管理软件可以使用此标志来控制哪些段实际加载到物理内存中。它除了提供分页外,还提供了一个管理虚拟内存的控件。

图3-9显示了当P=0时段描述符的格式。当P=0,操作系统或执行人员可以自由使用标记为“可用”的位置来存储自己的数据。

DPL 描述符特权级(Descriptor Privilege Level) 这两位定义了该段的特权级别(0~3),简单来说,仅当要访问该段的程序的特权级别(CPL)等于或高于这个段的级时CPU才允许其访问,否则便会抛出保护性异常(GPF)。
D/B Default/Big

对于代码段,该位表示的是这个代码段默认的位数(Default Bit)

  • D=0:16位代码段

  • D=1:32位代码段

对于栈段,该位称为B(Big)标志。

  • B=1:使用32位堆栈指针(保存在ESP中)

  • B=0:使用16位堆栈指针(保存在SP中)

对于数据段,该位称为B(Big)标志,指定了段的上界。

  • B=1:段的上界是0xFFFFFFFF(4GB)

  • B=0:段的上界是0xFFFF(64KB)

Type 段类型 由S位来决定段类型,具体内容见下面的分析
L 64-bit代码段

用于描述IA-32e模式下的代码段

  • L=1:代码段包含64位代码

  • L=0:代码段包含兼容模型的代码

AVL Availible and reserved bits 供系统软件(操作系统)使用


1.代码和数据段描述符

当S=1的时候,段类型为代码段,数据段或者堆栈段,此时type各比特的值以及对应的含义如下图

根据上图可以知道,type的最高位来决定这是一个数据段还是代码段,当最高位为0,这就是一个数据段,当最高位为1这就是一个代码段。无论是对数据段还是代码段,最低位(A)都是表示是否访问过,如果是1则已被访问,如果是0则未被访问。

对于数据段来说:

第9位(W)表示该段是否可写,如果是1则该段可读可写,否则可读不可写。

第10位(E)表示该段的扩展方向,如果是1则表示向上扩展,否则表示向下扩展。

对于代码段来说:

第9位(R)表示该段是否可读,如果是1则该段可执行可读,否则该段可执行不可读。

第10位(C)表示该段是否是一致代码段,如果是1则表示是,否则就不是

对于向下扩展的栈数据段,段边界指定的是该段的最小偏移,B标志用来指定偏移的最大有效值(即上边界),当B=1时,最大偏移是0xFFFFFFFF,这样,如果limit=0,那么段总长度就是4GB(G=0),如果B=0,那么上边界便是0xFFFF(64KB)。

2.系统段

当S=0的时候,段类型是系统段,处理器可以识别以下类型的系统段描述:

  • 本地描述符表(LDT)段描述符

  • 任务状态段(TSS)描述符

  • 调用门描述符

  • 中断门描述符

  • 陷阱门描述符

  • 任务门描述符

这些描述符类型可分为两类:系统段描述符和门描述符。系统段描述符指向系统段(LDT和TSS段)。门描述符本身就是“门”,它保存指向代码段中的过程入口点(调用、中断和陷阱门)的指针,或者保存针对TSS(任务门)的段选择器

显示了系统段描述符和门描述符的类型字段的编码。请注意,IA-32e模式下的系统描述符为16字节,而不是8字节

不同的系统段有不同的特点和作用,详细内容后面再说,现在先做几个实验来熟悉上面的内容。

3.实验练习

就以系统默认加载的这几个段寄存器中的段选择子,也就是0x0023,0x001B,0x003B来进行实验。

a.0x0023

0x0023对应的二进制是0000000000100 0 11,此时RPL=11(0x3),意味着请求特权级为3。TI=0,意味着要去GDT表查询相应的段描述符。索引Index=0100(0x4),所以相应的段描述符在内存中的地址就等于GDT表基地址+0x04 * 8。

根据WinDbg的输出可以知道,GDT表的地址是0x8003F000。

kd> r gdtr
gdtr=8003f000

所以段选择子0x0023的段描述符的地址就是0x8003F000 + 0x4 * 8 = 0x8003F020

kd> dq 0x8003F000 + 0x4 * 8
8003f020  00cff300`0000ffff 80008b04`200020ab
8003f030  ffc093df`f0000001 0040f300`00000fff
8003f040  0000f200`0400ffff 00000000`00000000
8003f050  80008955`27000068 80008955`27680068
8003f060  00009302`2f40ffff 0000920b`80003fff
8003f070  ff0092ff`700003ff 80009a40`0000ffff
8003f080  80009240`0000ffff 00009200`00000000
8003f090  00000000`00000000 00000000`00000000

根据WinDbg输出可以知道,此时的段描述符为0x00 CFF3 00 0000 FFFF。根据段描述符的格式可以知道,此时的Base为0x00000000,对于高32位的8-23位的0xCFF3,对应的二进制是1 1 0 0 1111 1 11 1 0011,各个比特位的内容及含义如下:

名称 比特位 数值 含义
Type 8~11 0011(0x3) 可读可写已访问的向上拓展的数据段
S 12 1(0x1) 代码和数据段描述符
DPL 13~14 11(0x3) 该描述符特权级为3
P 15 1(0x1) 该段有效
SegLimit 16~19 1111(0xF) 边界
AVL 20 0(0x0) 供系统使用,忽略
L 21 0(0x0) 非IA-32模式下,忽略
D/B 22 1(0x1) 段上界为0xFFFFFFFF(4GB)
G 23 1(0x1) 段边界单位为4KB

由于段描述符第32位的低16位为0xFFFF且SegLimit为0xF且G=1,所以此时段边界是0xFFFFFFFF,该段在内存中有效,特权级为3,是一个可读可写且向上扩展的数据段。


b.0x001B

0x001B的二进制为 0000000000011 0 11,TI=0意味着查询GDT表,RPL=3意味着请求者特权级为3,索引Index=3,用上面的方法去WinDbg查看对应的段描述符

kd> dq 8003F000 + 0x3 * 8
8003f018  00cffb00`0000ffff 00cff300`0000ffff
8003f028  80008b04`200020ab ffc093df`f0000001
8003f038  0040f300`00000fff 0000f200`0400ffff
8003f048  00000000`00000000 80008955`27000068
8003f058  80008955`27680068 00009302`2f40ffff
8003f068  0000920b`80003fff ff0092ff`700003ff
8003f078  80009a40`0000ffff 80009240`0000ffff
8003f088  00009200`00000000 00000000`00000000

得到此时段描述符为0x00 CFFB 00 0000 FFFF。此时段的Base为0x00000000,对于高32位的8-23位的0xCFFB对应二进制是1 1 0 0 1111 1 11 1 1011,各个比特位的内容及含义如下

名称 比特位 数值 含义
Type 8~11 1011(0xB) 可读可执行已访问的非一致代码段
S 12 1(0x1) 代码和数据段描述符
DPL 13~14 11(0x3) 该描述符特权级为3
P 15 1(0x1) 该段有效
SegLimit 16~19 1111(0xF) 边界
AVL 20 0(0x0) 供系统使用,忽略
L 21 0(0x0) 非IA-32模式下,忽略
D/B 22 1(0x1) 执行的是32位的代码段
G 23 1(0x1) 段边界单位为4KB

由于段描述符低32位的低16位为0xFFFF且SegLimit为0xF且G=1,所以此时段边界为0xFFFFFFFF,该段在内存中有效,特权级为3,是一个可读可执行已访问的非一致代码段。

c.0x003B

0x003B的二进制为0000000000111 0 11,TI=1意味着查询的是GDT表,RPL=3意味着请求者特权级为3,索引为7,用上面的方法在WinDbg中得到输出如下所示

kd> dq 8003F000 + 0x7 * 8
8003f038  0040f300`00000fff 0000f200`0400ffff
8003f048  00000000`00000000 80008955`27000068
8003f058  80008955`27680068 00009302`2f40ffff
8003f068  0000920b`80003fff ff0092ff`700003ff
8003f078  80009a40`0000ffff 80009240`0000ffff
8003f088  00009200`00000000 00000000`00000000
8003f098  00000000`00000000 810089f8`33300068
8003f0a8  00000000`00000000 00000000`00000000

得到此时段描述符为0x00 40F3 00 0000 0FFF。此时的Base为0x00000000,对于高32位的8-23位的0x40F3对应的二进制是0 1 0 0 0000 1 11 1 0011,各个比特位的内容及含义如下

名称 比特位 数值 含义
Type 8~11 0011(0x3) 可读可写已访问的向上拓展的数据段
S 12 1(0x1) 代码和数据段描述符
DPL 13~14 11(0x3) 该描述符特权级为3
P 15 1(0x1) 该段有效
SegLimit 16~19 0000(0x0) 边界
AVL 20 0(0x0) 供系统使用,忽略
L 21 0(0x0) 非IA-32模式下,忽略
D/B 22 1(0x1) 段上界为0xFFFFFFFF(4GB)
G 23 0 段边界单位为1字节

由于低32位的低16位为0x0FFF且SegLimit为0且G=0,所以此时的边界是0x00000FFF,该段在内存中有效,特权级为3,是一个可读可写已访问的向上拓展的数据段。

由此可以得出结论,当对段寄存器赋值的时候,处理器会根据段选择子中的TI会来决定是去GDT还是IDT表中查找相应的段描述符,随后根据索引到相应的表中找到对应的段描述符,在将段描述符中的内容赋值到段寄存器中,下图是对TI的不同的说明,注意这里的GDT表的第一项是没有使用的

可以使用mov,les,lss,lds,lfs,lgs指令修改ES,SS,DS,FS,GS,LDTR,TR这几个段寄存器,但不能修改CS段寄存器。原因是CS段寄存器的改变意味着EIP的改变,所以在修改CS段寄存器的时候需要同时修改EIP,所以无法使用那些指令来修改CS段寄存器,接下来就介绍几种修改CS段寄存器的方法。

六.修改CS段寄存器

上面说过,不能像修改其他段寄存器一样修改CS段寄存器,想要修改CS段寄存器需要通过以下的方法

  • 远跳转

  • 调用门

  • 中断门

  • 陷阱门

  • 任务段

  • 任务门

1.远跳转与长调用

jmp far指令(指令编码为0xEA),也称为远跳转指令,该指令的格式是jmp far cs:eip,其中EIP是随后的4个字节,而cs则是EIP随后的2个字节。也就是说对于0xEA 78563412 3000,其对应的指令是jmp far 0x0030:0x12345678。

而当程序运行这条指令的时候,会按照以下步骤进行:

  1. 将0x0030作为段选择子拆分,得到TI=0。

  2. 此时就要去GDT表中查找段描述符。

  3. 找到相应的段描述符以后,进行权限检查

  4. 通过了权限检查,CPU就会将段描述符中的内容加载到CS寄存器中

  5. CPU将CS寄存器中保存的基地址与偏移0x12345678相加就得到了要执行的代码的地址

通过第三步的权限检查分为两种情况:

  • 如果是非一致代码段,此时要求CPL 数值上等于DPL并且RPL 数值上小于等于 DPL。

  • 如果是一致代码段,此时要求CPL 数值上大于等于DPL。

也就是说,在一致代码段中,特权级低的代码(ring3)可以访问特权级高(ring0)的数据,而在一致代码段中,只能同样级别的代码段可以进行跳转。

接下来就通过几个跳转实验来验证上面的内容,要完成这些实验就需要在GDT表中找到P位为0的段描述符,这样才可以构造我们想要的段描述符来实现功能

kd> r gdtr
gdtr=8003f000
kd> dq 8003F000
8003f000  00000000`00000000 00cf9b00`0000ffff
8003f010  00cf9300`0000ffff 00cffb00`0000ffff
8003f020  00cff300`0000ffff 80008b04`200020ab
8003f030  ffc093df`f0000001 0040f300`00000fff
8003f040  0000f200`0400ffff 00000000`00000000
8003f050  80008955`1b800068 80008955`1be80068
8003f060  00009302`2f30ffff 0000920b`80003fff
8003f070  ff0092ff`700003ff 80009a40`0000ffff

由于p位是段描述符高32位中的第15位,所以要它为0,则需要低位开始第3个16进制小于8,由WinDbg的输出可以知道0x8003F048的位置的p位为0。

a.非一致代码段---DPL等于3

    接下来构造一致代码段,首先段描述符的基址和边界分别是0x0000000和0xFFFFFFFF,所以可以根据下表来构造需要的非一致代码段描述符

名称 比特位 数值 含义
Type 8~11 1011(0xB) 可读可执行已访问的非一致代码段
S 12 1(0x1) 代码或数据段描述符
DPL 13~14 11(0x3) 该描述符特权级为3
P 15 1(0x1) 该段有效
SegLimit 16~19 1111(0xF) 边界
AVL 20 0(0x0) 供系统使用,请忽略
L 21 0(0x0) 非IA-32模式下,请忽略
D/B 22 1(0x1) 执行的是32位的代码段
G 23 1(0x1) 段边界单位是4KB

由此可以构造出需要的段描述符高32位为0x00CFFB00,低32位为0x0000FFFF,接着在WinDbg中增加这一段描述符

可以看到在0x8003F048的位置成功添加了构造的段描述符。接下来就需要构造相应的段选择子,因为段描述符的地址是0x8003F048,相比于GDT表的基地址0x8003F000来说,它是第10个段描述符,所以此时的选择子的Index=9=1001,TI=0,RPL=11,最终得到的段选择子就是0x004B。

接下来就可以通过下面的代码来实现跳转

#include 
#include 

typedef struct _ADDRESS
{
	DWORD dwFuncAddr;
	WORD wSelector;
} ADDRESS;

void test()
{
	printf("jmp far success!\n");
}

int main()
{	
	ADDRESS addr = { 0 };

	addr.dwFuncAddr = (DWORD)test;
	addr.wSelector = 0x004B;
	
	__asm 
	{
		jmp fword ptr [addr]
	}
	printf("can not exec this!\n");

	return 0;
}

接下来就在OD中观察跳转前后CS段寄存器的变化,首先跳转前,此时的CS为0x001B。

跳转成功以后,此时的CS段寄存器就变成构造的0x004B了

b.非一致代码段---DPL等于0

此时需要更改段描述符高32位中的第13~14位为0,此时段描述符高32位为0x00CF9B00,低32位为0x0000FFFF,此时在WinDbg中通过命令修改相应的段描述符表的地址

此时再次在OD中运行上面的程序,在跳转前此时的CS依然是0x001B

这次在跳转,跳的不是我们写入的目的地址,CS段寄存器也没有发生改变,且根据OD提示的函数名可以知道,程序接下来要抛出异常,可想知道,这次的跳转失败了,接下来会执行异常程序的代码

c.一致代码段---DPL等于3

要构造一致代码段,只需要第8~11位的Type为1111(0xF)就可以,此时段描述符高32位为0x00CFFF00,低32位为0x0000FFFF,在WinDbg对相应位置进行修改:

继续使用上面的程序在OD中测试,跳转前,此时的CS等于0x001B


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2022-1-6 18:52 被1900编辑 ,原因:
收藏
免费 5
支持
分享
最新回复 (5)
雪    币: 708
活跃值: (994)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
2
作者大大,您好!来自小萌新的疑问:您在第六段第1部分的权限检查的解释中提到两次一致性代码段,但感觉前后有些矛盾,也可能是我个人理解有点问题,恳请您解释一下,谢谢!
2022-1-6 18:31
0
雪    币: 22413
活跃值: (25400)
能力值: ( LV15,RANK:910 )
在线值:
发帖
回帖
粉丝
3
PengLaiDoll 作者大大,您好!来自小萌新的疑问:您在第六段第1部分的权限检查的解释中提到两次一致性代码段,但感觉前后有些矛盾,也可能是我个人理解有点问题,恳请您解释一下,谢谢!
没错的,一致代码段可以ring3权限jmp far DLP=0权限的段描述符,非一致代码段不行
2022-1-6 20:03
0
雪    币: 708
活跃值: (994)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
4
1900 没错的,一致代码段可以ring3权限jmp far DLP=0权限的段描述符,非一致代码段不行
哦哦!是我个人理解错了,现在明白了,谢谢您!
2022-1-7 10:47
0
雪    币: 120
活跃值: (956)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
大佬,这里写反了 :
GDT:1
LDT:0

GDT是0
2023-9-26 17:56
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
直接对代码段进行JMP 或者 CALL的操作,无论目标是一致代码段还是非一致代码段,CPL都不会发生改变。 
大佬 萌新求问 这里用call far不可以改变CPL吗
2024-11-7 07:52
0
游客
登录 | 注册 方可回帖
返回