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; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
2020-02-21 反汇编:函数指针
2020-02-21 反汇编:指针数组和数组指针
2020-02-21 反汇编:字符串的3种表达方式