一、10-10-12二级映射
下图是101012二级映射结构:
CR3寄存器存放物理地址,指向的是页目录表(PDT)
页目录表(PDT)大小是4KB=4096字节,每个成员(PDE)占4字节,所以共有1024个
PDE指向页表(PTE),PTE指向物理页
PDE和PTE后六位都是属性
PTE特征:
1.可以不指向物理页
2.多个PTE可以指向同一个物理页
3.一个PTE只能指向一个物理页
二、读写NULL指针
正常编程中,不能读写NULL,原因是NULL指针没有对应的物理页,因此,只要我们让NULL指针最终映射到一块可读写的物理页,就可以用NULL去读写数据了。
代码短小精悍,也很好理解,关键步骤都在windbg里操作。下面先让程序跑起来,停在getchar()处。
// PDE_PTE.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
int _tmain(int argc, _TCHAR* argv[])
{
int x = 1; // 让NULL指向x所在的物理页
printf("x的地址:%x\n", &x);
getchar(); // 在windbg里修改NULL 的PTE
// NULL不能读写,原因是PTE为0,即没有分配物理页
// 所以只要给NULL的PTE分配物理页,就能读写NULL了
printf("NULL地址数据:%x\n",*(int*)NULL);
*(int*)NULL = 0x112233;
printf("NULL地址数据:%x\n",*(int*)NULL); // 0x112233
return 0;
}
然后,在windbg里按照上一节课的做法,找到 x 的物理地址。
拆分x的地址 0x0012ff60
0000000000 0x0
0100101111 0x12f
111101100000 0xf60
!process 0 0 查到CR3=1915d000,CR3的值是PDT表的基址。
!dd 1915d000+4*0 查到x对应的PDE也就是PDT[0]=07842067,属性清零后是 07842000,这个值是x的PTT的基址。
!dd 07842000+4*12f 查x的PTE也就是PTT[12f]=0ce53067,属性清零后就得到x的物理页基址 0ce53000
!dd 0ce53000+f60 以dword形式查看x的内存
NULL = 0
拆分得:
0000000000
0000000000
000000000000
下标全是0。
CR3是一样的,一个进程只有一个,CR3=1915d000
找NULL的PDE,!dd 1915d000,因为下标都是0,所以NULL 的PDE 和 x的PDE是一样的,不用改,都是 07842067。
所以 07842000 就是NULL的PTT,然后NULL的第二个下标也是0,所以 !dd 07842000 就能查到PTT[0] 即 NULL的PTE,值是0.
用!ed指令改写成x的PTE 0ce53067
!ed 7842000 0ce53067
现在x和NULL的PTE值相同,NULL和x处于同一个物理页,物理页基址是 0ce53000,但是物理地址仍然是不同的,因为x在物理页内的偏移是 0xf60,而NULL的偏移是0。
但是没有关系,NULL已经指向了一块可用的物理页了,现在可以对NULL进行读写了
三、为变量x再映射一个线性地址,并通过这个新的地址读取x的值
方法是调用 VirtualAlloc 申请一个物理页,得到一个指针p,拆分地址,将p后12位改成和x的后12位相同,这样就构造好了一个新的线性地址。
解释一下为什么要VirtualAlloc,目的是确保有一块可用的物理页,并且和x的物理页不是同一个;而修改p的后12位,目的是让p的物理页偏移和x一样。
接下来,只需要修改p的PDE和PTE,使得p和x指向同一个物理页即可。
// PDE_PTE.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <Windows.h>
int _tmain(int argc, _TCHAR* argv[])
{
int x = 0x1;
printf("x的地址:%x\n", &x);
// 申请一个新的物理页,将p的PTE和物理页内偏移改成和x一样
int p = (int)VirtualAlloc(NULL,0x1000,MEM_COMMIT,PAGE_READWRITE);
int p_bak = p;
memset((int*)p,0,0x1000);
p &= 0xFFFFF000;
p |= ((int)&x & 0x00000FFF);
printf("新的线性地址:%x\n", p);
getchar(); // 在windbg里修改 p 的 PTE
// 用新的线性地址读x
printf("*addr:%x\n",*(int*)p); // 0x1
// 用新的线性地址写x
*(int*)p = 0x112233;
printf("x:%x\n",x); // 0x112233
getchar();
VirtualFree((int*)p_bak,0x1000,MEM_DECOMMIT);
return 0;
}
然后将p的PTE改成和x的PTE一样,这个过程步骤繁琐:
拆分x:
0x0012ff60
0000000000 0x0
0100101111 0x12f
111101100000 0xf60
拆分p:
0x003b0f60
0000000000 0x0
1110110000 0x3b0
111101100000 0xf60
查CR3:
!process 0 0
CR3 = 0ef6f000
查PDEx
!dd 0ef6f000
PDEx = 09f43067
查PTEx
!dd 09f43000+4*12f
PTEx = 0f461047
查PTEp
!dd 09f43000+4*3b0
改PTEp
!ed 9f43ec0 0f461047
执行剩余的代码:
四、10-10-12分页模式物理内存能够识别的最多范围是多少
页目录表有1024项,页表有1024项,物理页有4KB
寻址范围:1024 * 1024 * 4096 = 4GB
五、如何判断2个线性地址是否在同一个物理页?
看线性地址前20位,只要相同就在同一个物理页。
因为前20位确定了PDE 和 PTE,PTE相同一定是同一个物理页。