Windows XP 段保护(二)

ʕ •ᴥ•ʔ ɔ:

1 段权限检查

  1. 根据段选择子找到段描述符之后,将段描述符加载到段寄存器之前会进行权限检查(是否将该段描述符加载到段寄存器中)。
  2. Windows没有使用R1、R2
  3. CS、SS段寄存器的低2位存储的就是CPL(RPL是段选择子的低2位,CPL特指CS、SS低2位),当前程序的特权级别。Windows中只能为0x00或0x11

G位:决定limit的大小

  • 1:limit = 0xFFFFFFFF
  • 0:limit =0xFFFFF

D/B位:地址空间的大小

  • 1:4GB(FFFFFFFF
  • 0:64K(FFFF

RPL:段选择子的权限(我以什么样的特权去访问你)
DPL:段的权限(你要访问我,你应该具有什么特权)
CPL:CPU当前的权限级别(CPL特指CS、SS低2位)

数据段(DS)的权限检查和CS、SS段不同。

1.1 段权限检查

段权限检查步骤:

1、段选择子拆分

2、查表得到段描述符

3、权限检查

4、加载段描述符到段寄存器

5、代码执行EIP

数据段权限检查(满足条件,才能成功访问数据段。):

CPL <= DPL 并且 RPL <= DPL (数值上的比较)

代码段权限检查:

  • 非一致代码段:CPL = DPL 并且 RPL <= DPL
  • 一致代码段:CPL >= DPL

1.2 练习

<1> 在3环能加载的数据段有哪些?
3环CPL=3,只能加载DPL=3的数据段。

<2> 在0环能加载的数据段有哪些?
0环CPL=0,满足RPL<=DPL的数据段都可以加载。

<3> 详细描述这下面代码的执行过程:
mov ax,0x23
mov ds,ax
段选择子是0x0023,RPL=11b=3,属于最低权限,只能访问DPL=3的数据段。而CPL则无影响,不管是0环还是3环,都满足CPL<=RPL,只要RPL满足,CPL也一定满足。

当执行 mov ds,ax 时,CPU先解析段选择子0023,然后去GDT表找段描述符,检查段描述符P位是否有效,然后检查S位,确认是数据段或代码段,然后检查TYPE域确认是数据段,然后看DPL是否等于3.只要上述条件都满足,则mov指令执行成功,只要有一条不满足,mov失败。

2 断间跳转JMP FAR

3环执行JMP FAR并不能提权(CPL不改变)。

可参看:《(8)JMP FAR段间跳转》《深耕保护模式(二)》

要点回顾:

段寄存器:ES、CS、SS、DS、FS、GS、LDTR、TR
段寄存器读写:除CS外,其他的段寄存器都可以通过MOV,LES,LSS,LDS,LFS,LGS,LTR(仅在0环之行)指令进行修改。

CS为什么不可以直接修改呢?

CS为代码段,CS的改变意味着EIP的改变,改变CS的同时必须修改EIP和SS,SS改变就必须修改堆栈,所以我们无法使用上面的指令来进行修改。

段间跳转,有两种情况,即要跳转的段是一致代码段还是非一致代码段(参见代码段type域)。

同时修改CS与EIP的指令:

JMP FAR / CALL FAR / RETF / INT /IRETED

只改变EIP的指令:

JMP / CALL / JCC / RET

一致代码段:也就是共享的段,要求:CPL >= DPL。

  • 特权级高的程序不允许访问特权级低的数据:核心态不允许访问用户态的数据
  • 特权级低的程序可以访问到特权级高的数据,但特权级不会改变:用户态还是用户态

非一致代码段:普通代码段(Windows使用),要求:CPL = DPL 并且 RPL <= DPL。

  • 只允许同级访问
  • 绝对禁止不同级别的访问:核心态不是用户态,用户态也不是核心态

代码间的跳转(段间跳转 非调用门之类的) 执行流程

JMP 0x20:0x004183D7 CPU如何执行这行代码?

(1)段选择子拆分

0x20 对应二进制形式 0000 0000 0010 0000

  • RPL = 00
  • TI = 0
  • Index = 4

(2) 查表得到段描述符

TI = 0 所以查GDT表

Index = 4 找到对应的段描述符,并不是所有的段描述符都可以跳转。

四种情况可以跳转:代码段、调用门、TSS任务段、任务门

(3)权限检查

如果是非一致代码段,要求:CPL = DPL 并且 RPL <= DPL。

如果是一致代码段,要求:CPL >= DPL。

简要说明什么是一致代码段什么是非一致代码段。

一致代码段又称共享代码段。假设操作系统有一段代码是提供了某些通用功能,这段代码并不会对内核产生影响,并希望这些功能能够被应用层(三环程序)直接使用,即可让一直代码段去修饰这块代码。这也是为什么一致代码段要求:CPL >= DPL(当前权限比描述权限低)就可以了。这段代码就是给低权限的应用使用的。

非一致代码段相反,严格控制权限。

(4)加载段描述符

通过上面的权限检查后,CPU会将段描述符加载到CS段寄存器中.

(5)代码执行

CPU将 CS.Base + Offset 的值写入EIP 然后执行CS:EIP处的代码,段间跳转结束.

注意:直接对代码段进行JMP 或者 CALL的操作,无论目标是一致代码段还是非一致代码段,CPL都不会发生改变.如果要提升CPL的权限,只能通过调用门

2.1 练习:非一致性代码段跳转

条件:非一致性代码段跳转,只允许同级访问:CPL=3的CPU只允许访问DPL=3的代码

要求分析:

(1)CPL=DPL=0x11,RPL<=DPL,则RPL可为0x00或0x11,TI=0,则选择子:0x–000/0x–011;

(2)因为要修改CS,所以段描述符的Type域应该是Code —- Type最高位为1,则Type为:0x1xxx;

(3)非一致性代码—-Type次高位为0,则Type为:0x10xx —- >=0x8;

(4)P、DPL、S(因为要修改CS,S=1)位分别为:0x1111 —- F。

18.png

段选择子分别为以下两种情况:

本练习以段选择子中RPL = 0x11为例

该描述符即指向一个非代码段,我们需要构造一个段选择子来找到该描述符,然后将该段描述符加载到CS寄存器,最后执行跳转。

步骤一:

根据段描述符构造段选择子:

(1)该段描述符为第四个,Index=3

(2)TI=0(查GDT)

(3)RPL=0x11,如果此例RPL=0x00,则修改CS时会发生错误。因为非一致性代码跳转不改变CPU权限CPL。

(4)段选择子=0x0011 0 11=0x1B

(5)Base=0,G=1则Limit=0xFFFFFFFF,则Offset可随意构造

(6)JMP FAR格式:CS:EIP=1B:0xxxxxxxx,跳转地址为:CS.Base+Offset

(7)如果执行成功,应该会修改CS和EIP

步骤二:

打开OD观察此时的CS=0x1B、EIP=0x00441EC如下图

19.png

步骤三:

为了能看到CS变化,则当前的段描述符的位置需要修改,可以将该段描述符复制到0x8003F048处,该处目前为0(P=0),如果复制到一个原先P=1的段描述符可能会卡死或者蓝屏。

20.png

此时段选择子Index=9 —- 0x1001 0 11=0x4B,在XP中,修改此时的EIP地址处:JMP FAR 004B:0x0044420C,单步F8看CS、EIP变化。

21.png

结论:

  1. 非一致性代码段JMP FAR跳转时,满足段权限检查:CPL=DPL=0x11,RPL<=DPL即能成功。

  2. 如果此例RPL=0x00,则修改CS时会发生错误。因为JMP FAR不改变CPU权限CPL。

  3. 所以3环的非一致性代码跳转成功条件:CPL=DPL=0x11,RPL<=DPL,RPL=0x11

2.2 练习:一致性代码段跳转

条件:只允许低权限的程序访问高权限的代码:CPL=3的CPU只允许访问DPL=0的代码

要求:

(1)目前CPL=3,则P=1,DPL=00,S=1(因为要修改CS。DATA/CODE),1001=9

(2)Type:Decimal=1(代码段),C=1(一致性代码),11xx>=0xC,由于此时的的CPL=0x11,因为不能修改特权级别,所以RPL必须RPL=0x11

(3)构造段选择子0x004B

直接构造一个:00cf9F00`0000ffff

22.png

重启一下OD,让CS复位如下图。

23.png

OD中EIP指向的地址构造:JMP FAR 004B:0x0044420D,F8单步看CS、EIP变化。

24.png

结论:

  1. 一致性代码段JMP FAR跳转时,满足段权限检查:CPL>DPL即能成功。

  2. 如果此例RPL=0x00,则修改CS时会发生错误。因为JMP FAR不改变CPU权限CPL。

  3. 所以3环的一致性代码跳转成功条件:CPL>DPL,RPL=0x11

2.3 总结

1、为了对数据进行保护,普通代码段是禁止不同级别进行访问的。用户态的代码不能访问内核的数据,同样,内核态的代码也不能访问用户态的数据.
2、如果想提供一些通用的功能,而且这些功能并不会破坏内核数据,那么可以选择一致代码段,这样低级别的程序可以在不提升CPL权限等级的情况下即可以访问.
3、如果想访问普通代码段,只有通过“调用门”等提示CPL权限,才能访问。

3 长调用与短调用

通过JMP FAR可以实现段间的跳转,如果要实现跨段的调用就必须要学习CALL FAR,也就是长调用。也叫代码跨段跳转CALL FAR。

CALL FAR 比JMP FAR要复杂,JMP并不影响堆栈,但CALL指令会影响。

  • JMP FAR:段间跳转,不能提权。
  • CALL FAR:跨段跳转,可以提权。

Windows只使用非一致性代码段,没有使用一致性代码段。

CPU提供了任务切换,但是Windows没有使用CPU提供的机制,而是自己用操作系统实现了线程切换。

3.1 短调用

指令格式:

1
CALL 立即数/寄存器/内存

堆栈变化:

25.png

3.2 长调用(不提权)

指令格式:

1
CALL CS:EIP(EIP是废弃的)

CALL FAR 和 RETF 一般是成对的,RETF 的执行流程(权限控制)和 CALL FAR 是一样的,所以下面的堆栈图我就不画 RETF 了。

长调用跨段不提权时:3环跳转到另一个3环代码段,不会切换堆栈

发生改变的寄存器:ESP EIP CS

无参数的堆栈变化:

26.png

3.3 长调用(提权)

提权,提的是CS寄存器的低两位CPL,而不是段选择子的低两位RPL。

指令格式:

1
CALL CS:EIP(EIP是废弃的)

提权无参数:

27.png

提权有参数:

28.png

  1. CALL CS:EIP EIP废弃不用,真正要跳转的地方是由段描述符装载进CS后的CS决定。
  2. 堆栈的变化:同一个线程从3环的堆栈切换到0环的堆栈
  3. 在段描述符加载进CS之前,会将CS保存到堆栈,方便RETF回来时恢复原来的CS。
  4. 跨段的意思就是修改CS,不提权:CS段描述符的DPL=CPL时,提权:CS段描述符的DPL < CPL时。
  5. RETF回去时也要进行权限检查。
  6. 不管提不提权,调用门描述符的DPL=0x11,如果不为3那么我们将无法访问到门描述符,敲门的资格都没有。

长调用的EIP是废弃的,所有信息都根据CS获取,这个CS是段选择子,指向GDT表中的一个特殊的段描述符,这个段描述符叫调用门

Windows中没有使用调用门,需要自己去构造调用门。

跨段并提权的调用:新的CS、EIP来自段描述符,但是新的R0下的SS、ESP来自TSS(可理解为一块内存)。

总结

  1. 跨段调用时,一旦有权限切换,就会切换堆栈(从R3的堆栈切换到R0的堆栈)。
  2. CS的权限一旦改变,SS的权限也要随着改变,CS与SS的等级必须一样。
  3. JMP FAR 只能跳转到同级非一致代码段,但CALL FAR可以通过调用门提权,提升CPL的权限。

SS与ESP从哪里来?参见TSS段。

4 调用门(无参)

指令格式:

1
CALL CS:EIP(EIP是废弃的)

执行步骤:

  1. 根据CS的值查GDT表,找到对应的段描述符,这个描述符是一个调用门。(S=0,Type=1100)
  2. 在调用门描述符中存储另一个代码段段的选择子。(具体看下图低四字节16到31位)
  3. 该选择子指向的段段.Base + 调用门偏移地址 = 就是真正要执行的地址。

为了能够访问不同特权级的代码段,处理器提供了一个特殊的描述符集合,叫做门描述符。以下是四种门描述符:

  • 调用门
  • 陷阱门
  • 中断门
  • 任务门

调用门段描述符结构:

29调用门描述符.png

30.png

需要注意的几点:

  1. GDT表中四种情况可以跳转:代码段、调用门、TSS任务段、任务门
  2. Windows中没有使用调用门,需要自己去构造调用门。
  3. 如果从3环使用调用门,则DPL要为3,如果不为3那么我们将无法访问到门描述符,敲门的资格都没有。
  4. ParamCount是传参用的,为参数个数。
  5. Segenment Selector是段选择子,指向要加载到CS的的段描述符,也就是要执行的段描述符,RPL=0x00/0x11。

4.1 调用门段权限检查

61.png

4.2 练习:使用调用门进行调试

本题要求:写代码实现一个调用门进行提权,并观察在3环和0环下的ESP、CS、SS段寄存器变化,观察3环进到0环时的堆栈。

先使用自己代码的段选择子找到调用门,然后根据调用门去找要调用的段描述符

调用门描述符的DPL必须=0x11,因为使用VC6做实验,低4字节高16位(段选择子)要指向一个0环的代码段描述符才能完成本实验。

要执行的程序入口地址 = Call_Gate.Offset ⊕ CS.Base

步骤:

  1. 构造调用门段描述符,0x00000000`00000000;(是DPL要为3,否则在3环无法访问到门描述符)

    • P=1,DPL=11,S=0,0x1110=0xE,0x0000E000`00000000;
    • 调用门Type=0x1100=0xC,0x0000EC00`00000000;
    • 构造选择子:调用门来执行提权,需要根据自身结构中隐含的段选择子去找一个DPL=00的CS段描述符,可构造0x0008/0x000B,0x0000EC00`00080000。剩下还需要构造一个偏移地址让CS去执行。
  2. 将段描述符写入GDT表中P=0的上面去,否则可能会卡死或蓝屏。

    31.png

  3. 构造偏移地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include "stdafx.h"
#include <windows.h>
void _declspec(naked) GetGdtRegister()
{
_asm
{
int 3 //此时的断点在0环
retf //不能是RET
}
}
int main(int argc, char* argv[])
{
char Buffer[6];

*(DWORD*)&Buffer[0] = 0x12345678; //eip随便填
*(WORD*)&Buffer[4] = 0x004B;
_asm
{
call fword ptr[Buffer] //call cs:eip
}
getchar();
return 0;
}

如图:

31_1.png

32.png

要执行的程序入口地址:0x00401020,则调用门段描述符:0x0040EC00`00081020

33.png

观察3环下:ESP=0x0012FF28,CS=0x001B,SS=0x0023。

注意:在XP中继续执行时如果不取消此时设置的断点的话,从0环返回时将会卡死(目前应该在0环调试状态下)。

此时,取消断点,F5执行,观察Windbg。

34.png

观察0环下:ESP=0xAA15ADD0,CS=0x0008,SS=0x0010。

注意:

  1. 函数GetGdtRegister()虽然是我们在3环写的,但是通过调用门去执行的时候,该函数的执行权限为0环。
  2. 提权后,通用寄存器的值不会发生改变
  3. 我们的代码虽然写的是三环程序的int3,但是由于这里权限已经提升,断点异常已经不再是三环程序处理(内核相比应用层具有优先处理权),应有内核层处理。这里直观的感受就是,0环调试器(windbg)断点了,vc6无法断点。

4.3 练习:在0环读取GDT表数据

既然已经提权到0环权限,那么我们就可以写只能在驱动开发中才能写的代码。

本题题目:读取gdt表打印、读取高2G内存中的值并打印。(三环权限是不能够读取高2G内存的,属于内核管理)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include "stdafx.h"
#include <windows.h>

DWORD dwH2GValue; // 存储高2G的数据
BYTE GDT[6] = {0};

// 该函数通过 CALL FAR 调用,使用调用门提权,拥有0环权限
void __declspec(naked) FunctionHas0CPL()
{
__asm
{
//int 3
pushad
pushfd
mov eax,0x8003f00C //读取高2G地址,GDT表的数据
mov ebx,[eax]
mov dwH2GValue,ebx
sgdt GDT; //读取GDTR寄存器(6字节)

popfd
popad

retf
}
}
void PrintRegister()
{
DWORD GDT_ADDR = *(PDWORD)(&GDT[2]);
WORD GDT_LIMIT=*(PWORD)(&GDT[0]);
printf("%08X %x %x\n",dwH2GValue,GDT_ADDR,GDT_LIMIT);
}
int main(int argc, char* argv[])
{
char buff[6] = {0,0,0,0,0x48,0};
__asm
{
call fword ptr [buff] // 长调用,使用调用门提权
}
PrintRegister();
getchar();
return 0;
}
  1. F9下断点,获取FunctionHas0CPL()函数的入口地址。

    35.png

    36.png

  2. 修改调用门段描述符。0040EC00`00081030

    37.png

  3. 将代码中的int 3注释后执行,否则无法正常从0环返回到3环。在XP的VC6中F5执行如下图。

    38.png

5 调用门(有参数)

有参数的调用门相比无参的调用门主要有两点变化:

  • 调用门段描述符带有参数的个数(如下图1)
  • 从3环堆栈切换到0环的堆栈时参数在栈中的位置(如下图2)。

39.png

28.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>

DWORD x,y,z;

// 该函数通过 CALL FAR 调用,使用调用门提权,拥有0环权限
void __declspec(naked) FunctionHas0CPL()
{
__asm
{
int 3
pushad
pushfd

// pushad 和 pushfd 使ESP减小了 0x24 个字节
// 原ESP+8就是参数1,+C就是参数2,+10就是参数3,详见堆栈图
// 如果这里还有疑问,可以在windbg的内存窗口中观察
mov eax,[esp+0x24+0x8+0x8] // 参数3
mov dword ptr ds:[x],eax
mov eax,[esp+0x24+0x8+0x4] // 参数2
mov dword ptr ds:[y],eax
mov eax,[esp+0x24+0x8+0x0] // 参数1
mov dword ptr ds:[z],eax

popfd
popad

retf 0xC // 注意堆栈平衡,写错蓝屏
}
}
int main(int argc, char* argv[])
{
char buff[6] = {0,0,0,0,0x48,0};
__asm
{
push 0x3
push 0x2
push 0x1
call fword ptr [buff] // 长调用,使用调用门提权
}
printf("%x %x %x\n",x,y,z);
getchar();
return 0;
}

41.png

40_1.png

  1. 构造调用门有参数的段描述符:0040EC03`00081020。

    42.png

  2. 回到XP中取消断点,F5执行,此时将会在断点在0环的int 3。观察堆栈和预期一样。

    43.png

    需要注释代码中的int 3才可以正常返回,执行结果如下图。

    44.png

6 调用门阶段性测试

关于0环的翻墙:3环的代码通过调用门执行0环权限下的代码后,RETF回来时通过修改返回地址跳到其他地方,即称为翻墙。

题目:

1、构造一个调用门,实现3环读取高2G内存。

2、在第一题的基础上进行修改,实现通过翻墙的方式返回到其他地址。

3、在第一题的基础上进行修改,在门中再建一个门跳转到其他地址。

要求:

代码正常执行不蓝屏。

6.1 CALL FAR翻墙

翻墙即不走原来的RETF,而是修改堆栈中保存的返回地址,跳转到其他地址。

本题以无参函数示例。

步骤:

  1. 构造2个函数,提权后将第一个函数的返回地址改为第二个函数的入口地址。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>

DWORD dwHigh2GValue;

// 该函数通过 CALL FAR 调用,使用调用门提权,拥有0环权限
void __declspec(naked) FunctionHas0CPL()
{
__asm
{
pushad
pushfd

// 读取了GDT表第二项的低4字节
mov eax,0x8003f008
mov eax,[eax]
mov dwHigh2GValue,eax

// 修改返回地址,跳转到Exit函数执行
mov eax,0x00401050
mov [esp+0x24],eax

popfd
popad

retf // 注意堆栈平衡,写错蓝屏
}
}

void Exit()
{
printf("bye!\n");
ExitProcess(0);
}

int main(int argc, char* argv[])
{
char buff[6] = {0,0,0,0,0x48,0};
__asm
{
call fword ptr [buff] // 长调用,使用调用门提权
}
printf("%08x\n",dwHigh2GValue);
getchar();
return 0;
}
  1. 如下图,第二个函数的入口地址是0x00401050,代码中修改如下。

    46.png

  2. 根据第一个函数的入口地址构造调用门段描述符,0040EC00`00081030

    45.png

    47.png

  3. 执行结果如下:

    48.png

6.2 双调用门

题目:通过调用门提权后,在第一个函数中再使用一个调用门去执行第二个函数。

注意:这里使用了2个调用门段描述符,第1个调用门段描述符的DPL=0x11,第2个调用门段描述符的DPL=0x00。因为第一个函数去调用第二个函数是在0环权限下,所以第二个调用门的DPL=0x00。

步骤:

  1. 构造2个函数,提权后进入第一个函数,在第一个函数中再使用调用门去执行第二个函数。

    第一个函数执行成功时,设置bFlag_1=1。

    第二个函数执行成功时,设置bFlag_2=1。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    #include <stdafx.h>
    #include <windows.h>
    #include <stdio.h>

    DWORD dwHigh2GValue;
    BYTE bFlag_1 = 0;
    BYTE bFlag_2 = 0;
    char Gate_1[6] = {0,0,0,0,0x48,0}; //CS_1:8003f048 0040EC00`00081030
    char Gate_2[6] = {0,0,0,0,0x90,0}; //CS_2:P=1,DPL=0x00,S=0,0x1000=8,调用门Type=0x1100=C
    //另一个门的CS的位置8003f090
    //CS_2:8003f090 00408C00`00081060

    // 该函数通过 CALL FAR 调用,使用调用门提权,拥有0环权限
    void __declspec(naked) FunctionHas0CPL_1()
    {
    __asm
    {
    //int 3 注意这里的断点在调试时可以用,但是在真正运行时不能
    //写到函数里,不然会导致程序崩溃
    pushad
    pushfd

    // 读取了GDT表第二项的低4字节
    mov eax,0x8003f008
    mov eax,[eax]
    mov dwHigh2GValue,eax

    // 修改返回地址,跳转到Exit函数执行
    //mov eax,0x401060
    //mov [esp+0x24],eax
    test eax,eax
    mov al,0x1
    mov bFlag_1,al //mov byte ptr ds:[bFlag_1],al

    call fword ptr ds:[Gate_2]

    popfd
    popad

    retf // 注意堆栈平衡,写错蓝屏
    }
    }

    void __declspec(naked) FunctionHas0CPL_2()
    {
    __asm
    {
    //int 3 注意这里的断点在调试时可以用,但是在真正运行时不能
    //写到函数里,不然会导致程序崩溃
    pushad
    pushfd

    test eax,eax
    mov al,0x1
    mov bFlag_2,al //mov byte ptr ds:[bFlag_2],al

    popfd
    popad

    retf // 注意堆栈平衡,写错蓝屏
    }
    }

    int main(int argc, char* argv[])
    {
    __asm
    {
    call fword ptr ds:[Gate_1] // 长调用,使用调用门提权
    }
    printf("%08x\nGate_1:%x,Gate_2:%x\n",dwHigh2GValue,bFlag_1,bFlag_2);
    getchar();
    return 0;
    }
  2. 获取两个函数的入口地址准备构造两个调用门的段描述符。

    49.png

  3. 构造2个调用门的段描述符,第一个放在一个P=0的0x8003F048。第二个放在P=0的0x8003F090,如下图。

    eq 8003F048 0040EC00`00081030

    eq 8003F090 00408C00`00081060(DPL=0x00)

    50.png

  4. 回到XP的VC6中F9取消断点后F5执行,结果如下图。

    51.png

Windows XP 段保护(一)

Windows XP 段保护(二)

Windows XP 段保护(三)