80x86 2-9-9-12分页

前言:80x86 2-9-9-12分页的学习笔记

在之前的课程中我们讲解了10-10-12分页方式,在这种分页方式下物理地址最多可达4GB。但随着硬件发展,4GB的物理地址范围已经无法满足要求,Intel在1996年就已经意识到这个问题了,所以设计了新的分页方式,也就是这节课要学习的2-9-9-12分页,又称为PAE(物理地址扩展)分页。

回顾下10-10-12的产生

首先10-10-12分页模式这个成品是基于设计者的想法而产生的,先要确定的肯定是页的大小也就是4kb大小,那么也就是2^12 -> 4KB大小

那么还剩下"10" "10" 接着又设计PDE和PTE,通过PTE的前20位再加上12位的偏移,那么也就是4G寻址范围(那当时的情况寻址能力已经能足够了),所以这里设计为PDE和PTE的大小都为4个字节,那么为什么PDE也需要4个字节呢?

原因就是为了迎合PTE,如果它不是4个字节的话,那么就没有前20位地址,如果PDE不是前20位基地址,那么它可能就找不到PTE的基地址,所以PDE的基地址也需要和PTE一样,所以"10","10"也就可以说明了

2-9-9-12的配置

之前的10-10-12的为/execute=optin,这里改为/noexecute=optin即可

2-9-9-12分页模式的产生

就是老师提出的问题,那么我有什么办法在32位cpu下如何能够提高物理地址的寻址范围呢?

这里就引入了2-9-9-12的分页模式,又被叫做为PAE物理地址扩展

1、页的大小是确定的,4KB不能随便改,所以低12位是可以确定的

2、如果想增大物理内存的访问范围,就需要增大PTE,增大多少了呢?需要找到64G,那么增大到36位,但是还需要考虑对齐的因素,所以对齐到8个字节

知识点:在Windows系统中,对齐大小有利于高效率的执行和搜索

3、同理PDI也是2的9次方 32 - 9 - 9 - 12 还差2位 所以就再做一级叫PDPI(页目录指针表)

知识点:这里需要让PDI也是2的9次方的原因,其实也是就是为了通过PDE同样可以定位到PTE,举个例子,此时2-9-9-12分页下,当前的PTE的前24位是4GB范围以外的,那么如果PDE不是跟PTE同样的结构,那么PDE的前24位就没有,也就是无法找到4GB范围以外,所以为了能够让PDE能够定位到4GB以为的PTE,所以也需要"9"

PDE(PS=1为大页的情况下)

当PS位为1的时候,该PDE指向的物理页是大页,35-21位是大页的物理地址,这样36位的物理地址的低21位为0,这就意味着页的大小为2MB,且都是2MB对齐。

PDE(PS=0)

2-9-9-12的9位,它是PDE,这个每项占8个字节,如下图所示

当PS=0时,第35-12位是页表基址,低12位补0,共36位。

PTE

2-9-9-12的9位,它是PTE,这个每项占8个字节,如下图所示

PTE中第35-12是物理页基址,24位,低12位补0物理页基址+12位的页内偏移指向具体数据

PDPTE(Page-Directory-Point-Table-Entry):

2-9-9-12最高的2位,它是页目录指针表项,这个每项占8个字节,如下图所示

整体的2-9-9-12架构

如下图所示

个人理解(不知道讲的对不对,仅供参考)

参考文章:https://www.cnblogs.com/kaleidopink/p/12672167.html

我感觉我自己还是没懂物理地址和一些知识点的概念,我自己的理解如下:

1、视频中学习的是,想要32位cpu下让物理内存能够识别的最多范围能够达到64G,那么是可以通过PAE物理地址拓展来实现的(其实本质也就是改变PTE的寻址位)?

我的理解就是改变了PTE的寻址位,让其能达到高36位的地址的寻址,那么也就能找到对应的64G的物理地址了

然后视频中有人提出来一个问题说 10-10-12的分页是 1024*1024*4096=4G,如果我让它再多一份PDT表的话,那么也就是2*1024*1024*4096=8G,那么它能找到的是不是就能有8G的物理地址了?

我觉得它说的是对的啊,但是我不知道是不是对的,我是这样理解的,这里多了一份PDT表的结果其实就是让寻址能力变大了,但是本质的问题没有解决,你的物理地址还是只有4G,所以说物理地址不变,你u寻址能力变大,那找到的物理地址还是只有4G,所以这个解决办法没有用

那么PAE它改变的是什么?我理解的就是增大了物理地址的范围,让其变成了64G,那么其实就是将PTE开刀了,让它变成8个字节大小,此时PTE的能找到的物理地址就变成了64G,所以这里的话才是真正让物理地址变大的原因,也就是物理地址的长度影响的

以上说法我不知道对不对,因为学习都是看视频,想问问题也不知道找谁问,自己理解就只能理解到这边...

在2-9-9-12分页模式下进行线性地址到物理地址的转换

代码测试,就需要创建一个变量即可,然后获取该变量的线性地址,找到该变量线性地址对应的物理地址即可

int main()
{
  int x;
  getchar();
  return 0;
}

x变量的拆分地址如下所示

0012ff7c
->
00
00 0000 000  *8 -> 0
1 0010 1111  *8 -> 0x978
f7c

!process 0 0,cr3为0x073402a0

接着!dq 073402a0,内容为 00000000 31959801(去掉了反引号)

因为PDPTE的第35-12 存储的是页目录表的基址,低12位补0,共36位,即页目录基址,!dq 031959000+8*0,通过这个找到PDE

PDE结构中当PS=0时,35-12位是页表基址,低12位补0,共36位

!dq 0319b8000+0x978

PTE结构中35-12是物理页基址,24位,低12位补0,物理页基址+12位的页内偏移指向具体数据

!dq 0319ce000+f7c

知识点:为什么*8,跟10-10-12中是*4的,这里*8的原因是一个PDE和PTE大小为8个字节

这里还可以通过!vtop 073402a0 0x12ff7c来进行校验,看自己是不是计算的准确,如下图所示

XD标志位(AMD中称为NX,即No Excetion)

PDE/PTE结构

段的属性有可读、可写和可执行

页的属性有可读、可写

当RET执行返回的时候,如果我修改堆栈里面的数据指向一个我提前准备好的数据(把数据当作代码来执行,漏洞都是依赖这点,比如SQL注入也是)

所以,Intel就做了硬件保护,做了一个不可执行位,XD=1时。那么你的软件溢出了也没有关系,即使你的EIP蹦到了危险的"数据区",也是不可以执行的!

在PAE分页模式下,PDE与PTE的最高位为XD/NX位

这个XD/NX标志位我不知道在哪,因为文档中也没有标出来,高地址的话就是默认的为保留地址,应该指的是PDE或者PTE最高位,也就是第63位的值!

XD标志位模拟代码执行

X

2-9-9-12分页0线性地址挂物理页

测试代码如下:

#include<windows.h>
#include<stdio.h>

int main()
{
	DWORD* pAddr = (DWORD*)VirtualAlloc(NULL, 0x1000, MEM_COMMIT, PAGE_READWRITE);
	memset((DWORD*)pAddr,0,0x1000);
	printf("VirtualAlloc addr: %x\n", pAddr);
	printf("test addr: %x\n", test);
	getchar();
	*(DWORD*)0 = 0x12345678;
	printf("null: %x", *(DWORD*)0);
	return 0;
}

修改页属性,实现应用层读写高2G内存地址

这里拿线性地址0x8003F00C来进行举例

0x8003F00C
->
10 *8 -> 0x10
0 0000 0000 *8 -> 0
0 0011 1111 *8 -> 0x1F8
00C

测试代码:

#include<stdio.h>
#include<windows.h>

int main()
{
	// 修改0x8003F00C页属性U/S,实现应用层读写高2G内存地址
	getchar();
	*(DWORD*)0x8003F00C = 0x12345678;
	printf("%x", *(DWORD*)0x8003F00C);
        getchar();
 	return 0;
}

这个测试有问题了,直接蓝屏了,我把G位设置为0也没有用,不知道是什么情况导致的,先放这里,知道了再来补上

我看了别人的文章,参考文章: https://blog.csdn.net/Kwansy/article/details/108965196 ,该作者修改的高2G的地址是8003f048

拆分8003f048

8003f048
->
10 *8 -> 0x10
0 0000 0000 *8 -> 0
0 0011 1111 *8 -> 1F8
048

可以发现,换了一个地址8003f048,就发现可以成功修改了,原因不太清楚,知道了再来补上

PDT页目录表基址0xC0600000

总结:PDE = 0xC0600000 + PDPTEI*0x1000 + PDI*0x8

关于2-9-9-12分页的MmIsAddressValid函数的逆向我放到了 https://www.cnblogs.com/zpchcbd/p/15915333.html 地址中了

然后这里通过windbg来进行验证相关的页目录基址,我在ida中找到的PDT的页目录为0xC0600000

拆分0xC0600000

11 *8 -> 0x18
0 0000 0011 *8 -> 0x18
0 0000 0000 *8 -> 0
000

然后再展示下0线性地址的PDE,如下所示,可以发现是一样的,所以0xC0600000是2-9-9-12页目录表的基址

PTT页表基址0xC0000000

总结:PDE = 0xC0000000 + PDPTEI*512*512*8 + PDI*8*512 + PTI*8

拆分0xC0000000

11 *8 -> 0x18
0 0000 0000 *8 -> 0
0 0000 0000 *8 -> 0
000

0xC0000000对应的物理地址为如下所示

这里就比如拆第一个PDPE的第三个PDE的第一个PTE,正常拆出来是如下地址

PROCESS 86abada0  SessionId: 0  Cid: 05d8    Peb: 7ffdf000  ParentCid: 03cc
    DirBase: 073402c0  ObjectTable: e1e5a3d0  HandleCount:  13.
    Image: 2_9_9_12.exe

kd> !dq 073402c0
# 73402c0 00000000`0f27a801 00000000`1217b801
# 73402d0 00000000`0f17c801 00000000`12039801
# 73402e0 00000000`1032e801 00000000`0ffaf801
kd> !dq 0f27a000
# f27a000 00000000`0ee24867 00000000`0ee12867
# f27a010 00000000`0e6e3867 00000000`00000000
# f27a020 00000000`00000000 00000000`00000000
kd> !dq 0e6e3000
# e6e3000 80000000`0fc36025 00000000`0e9e0025
# e6e3010 00000000`0e7fd025 00000000`0e6be025
# e6e3020 00000000`0dfbf025 00000000`10600025

接着我们这里假设0xC0000000是页表的基地址,那么通过公式来进行计算PDE = 0xC0000000+PDPTEI*512*512*8+PDI*8*512+PTI*8

PDE = 0xC0000000 + 0*512*512*8 + 2*8*512 + 0*8为0xC0002000,这里拆分C0002000

C000 2000
->
11 -> 0x18
0 0000 0000 *8 -> 0
0 0000 0010 *8 -> 0x10
000

可以发现是这样的,那么0xc0000000是页表基址

PROCESS 86abada0  SessionId: 0  Cid: 05d8    Peb: 7ffdf000  ParentCid: 03cc
    DirBase: 073402c0  ObjectTable: e1e5a3d0  HandleCount:  13.
    Image: 2_9_9_12.exe

kd> !dq 073402c0+0x18
# 73402d8 00000000`12039801 00000000`1032e801
# 73402e8 00000000`0ffaf801 00000000`0fcf0801
kd> !dq 012039000+0
#12039000 00000000`0f27a863 00000000`1217b863
#12039010 00000000`0f17c863 00000000`12039863
kd> !dq 0f27a000+0x10
# f27a010 00000000`0e6e3867 00000000`00000000
# f27a020 00000000`00000000 00000000`00000000
kd> !dq 0e6e3000+0
# e6e3000 80000000`0fc36025 00000000`0e9e0025
# e6e3010 00000000`0e7fd025 00000000`0e6be025

代码实现2-9-9-12分页0线性地址挂物理页

这里我使用中断门来进行提权

eq 8003f500 0040EE00 00081005(省略了反引号)

测试代码如下


#include<stdio.h>
#include<windows.h>

DWORD iAddr;
DWORD* getPDEAddr(DWORD addr)
{
	return (DWORD*)(0xc0600000 + ((addr >> 18) & 0x3ff8));
}

DWORD* getPTEAddr(DWORD addr)
{
	return (DWORD*)(0xc0000000 + ((addr >> 9) & 0x7ffff8));
}

void _declspec(naked) test()
{

	__asm
	{
		int 3;
		push ebp;
		mov ebp, esp;
		sub esp,0x1000;
		pushad;
		pushfd;
		push fs;
	}

	*getPDEAddr(0) = *getPDEAddr(iAddr);
	*getPTEAddr(0) = *getPTEAddr(iAddr) & 0xFFFFFEFF;
	
	__asm
	{
		pop fs;
		popfd;
		popad;
		add esp,0x1000;
		mov esp,ebp;
		pop ebp;
		iretd;
	}
}

int main()
{
	iAddr = (DWORD)VirtualAlloc(NULL,0x1000,MEM_COMMIT,PAGE_READWRITE);
	memset((DWORD*)iAddr,0,0x1000);
	printf("VirtualAlloc addr: %x\n", iAddr);
	printf("test addr: %x\n", test);
	getchar();
	__asm
	{
		int 0x20;
	}
	// test r/w
	*(DWORD*)0 = 0x12345678;
	printf("%x\n", *(DWORD*)0);
	return 0;
}

posted @ 2022-02-21 00:44  zpchcbd  阅读(387)  评论(2编辑  收藏  举报