Windows保护模式学习笔记(七)—— PDE&PTE
- Cr3
- PDE(页目录表项)
- PTE(页表项)
- 物理页的属性
- 10-10-12分页的补充
- 实验1:证明PTE的特征1
- 第一步:选择一个进程的Cr3
- 第二步:查看页表
- 实验2:通过修改页表使C语言能在0地址处读写
- 第一步:得到一个变量的地址
- 第二步:挂载PTE为0的物理页
- 第三步:继续运行程序
- 实验3:通过修改物理页属性使字符串常量可修改
- 第一步:运行程序
- 第二步:修改对应PTE的属性
- 第三步:继续运行程序
- 实验4:通过修改物理页属性使普通用户读取高2G内存
- 第一步:运行如下代码
- 第二步:修改PDE与PTE的U/S位
- 第三步:继续运行程序
Cr3
描述:
在所有的寄存器中,只有Cr3存储的是物理地址,其它寄存器存的都是线性地址
Cr3所存储的物理地址指向了一个页目录表(PDT)
在Windows中,一个页的大小通常为4KB,即一个页可以存储1024个页目录表项(PDE)
物理页结构图:
PDE(页目录表项)
描述:
页目录表(PDT)的每一项元素称为页目录表项(PDE)
每个页目录表项指向一个页表(PTT)
每个页表的大小为4KB,即一个页表可以存储1024个页表项(PTE)
PTE(页表项)
描述:
页表(PTT)的每一个元素称为页表项(PTE)
页表项(PTE)所指向的才是真正的物理页
特征:
- PTE可以指向一个物理页,也可以不指向物理页
- 多个PTE可以指向同一个物理页
- 一个PTE只能指向一个物理页
物理页的属性
物理页的属性
=PDE属性
& PTE属性
P位
:是否有效位
注意:当PDE或PTE中有一个的属性P=0时,物理页就是无效的
R/W位
:读写位
R/W=0:只读
R/W=1:可读可写
U/S位
:权限位
U/S=0:特权用户
U/S=1:普通用户
PS位
:PDE特有
PS == PageSize
PS=1:PDE直接指向物理页,低22位=页内偏移,偏移最大值为4MB,俗称"大页"
PS=0:PDE指向PTE
A位
:访问位
A=1:该PDE/PTE被访问过
A=0:该PDE/PTE未被访问过
D位
:脏位
D=1:该PDE/PTE被写过
D=0:该PDE/PTE未被写过
注:其他位等学完控制寄存器与TLB才能讲,本篇不讲
10-10-12分页的补充
为什么要按10-10-12分页:
- 一个物理页的大小为4096字节,即2的12次方,若要遍历整个物理页,则需要12个比特位
- 一个页表有1024个页表项,1024等于2的十次方,即需要10个比特位
- 页目录表项同理,也需要10个比特位
注:以下实验的分页方式都为10-10-12分页
实验1:证明PTE的特征1
第一步:选择一个进程的Cr3
我这里启动了一个记事本:notepad.exe
第二步:查看页表
查看当线性地址为0时,进程的页表
可以发现有许多页表项都为0,没有指向任何物理页
实验2:通过修改页表使C语言能在0地址处读写
第一步:得到一个变量的地址
运行代码如下:
#include <stdio.h>
#include <windows.h>int main(int argc, char *argv[])
{int x = 1;printf("x的地址:%x\n", &x);getchar();// 向0地址写入数据*(int*)0 = 123;// 从0地址读出数据printf("0地址的数据:", *(int*)0);getchar();return 0;
}
程序运行后,首先会输出x的地址
第二步:挂载PTE为0的物理页
使用WinDbg将虚拟机中断,将变量x所在的物理页挂载到线性地址0的PTE
第三步:继续运行程序
运行结果:
成功对0地址进行了读写,实验成功!
实验3:通过修改物理页属性使字符串常量可修改
第一步:运行程序
代码如下:
#include <stdio.h>
#include <windows.h>int main(int argc, char *argv[])
{char *str = "Hello World";printf("线性地址:%x", str);getchar(); // 让程序执行到这里//修改只读变量str[0] = 'M';printf("修改后的值:%s\n",str);return 0;
}
这时候得到了str的地址:
第二步:修改对应PTE的属性
第三步:继续运行程序
运行结果:
修改成功!
实验4:通过修改物理页属性使普通用户读取高2G内存
第一步:运行如下代码
#include <stdio.h>
#include <windows.h>int main(int argc, char *argv[])
{PDWORD p = (PDWORD)0x8003F00C;getchar(); // 让程序运行到这里printf("读取高2G内存:%x \n", *p);return 0;
}
第二步:修改PDE与PTE的U/S位
第三步:继续运行程序
运行结果如下:
成功读取了高2G的内存!