Windows XP 页保护(二)
ʕ •ᴥ•ʔ ɔ:
1 2-9-9-12分页
2-9-9-12分页结构(PAE,物理地址扩展):
- 12:页的大小是确定的,4KB不能随便改,所以12确定了。
- 9:如果想增大物理内存的访问范围,就需要增大PTE,增大到36位,考虑对齐的因素,增加到8个字节。4KB = 512*8 = 1024*4,512 = 2^ 9。PTI
- 9:如果PTE所在物理地址超过4GB,则同样也需要一个超过32位的PDE来进行寻址,考虑内存对齐,则PDE也是8字节。PDI
- 2:2 = 32 -(12+9+9),还剩2位,所以就再做一级叫PDPI。
这里需要注意物理地址和线性地址:32位CPU的线性地址范围是32位,大小为4GB。在10-10-12分页模式下,物理地址和线性地址大小相等为4GB,那么在多进程情况下就需要不停的进行换物理页,为了节省换页的开销,Intel设计了2-9-9-12分页。但是Windows在这种分页模式下仅使用了36位来进行寻址方式,2^ 36 = 64GB。注意:
- 2-9-9-12分页模式下,线性地址范围还是4GB。
- 物理地址范围为64GB。
- 物理地址范围和内存条大小无关,有可能此时内存条大小仅为2GB。
- 物理地址大小由8字节的PTE中的36位来决定。
- PDE也是8字节是因为PTE所在的物理页的物理地址可能超过4GB,同时考虑内存对齐。
其中:CR3、物理页数据是以32位存放,PDPTE、PDE、PTE为64位。
1.1 开启2-9-9-12分页
- 10-10-12分页:将
C:\boot.ini
文件中的noexecute
改为execute
重启。 - 2-9-9-12分页:将
C:\boot.ini
文件中的execute
改为noexecute
重启。
1.2 PDPE(2)
由于CR3是32位的,所以PDPTE所在物理页的物理地址范围是0-4GB。
PDPT表(页目录指针表)是2-9-9-12分页模式下的线性地址的高两位,共组成四项PDPTE。PDPE结构如下:
每个PDPTE为8字节:
- 0~11:属性,也是PDE的偏移。
- 9~11:给操作系统用的,CPU不用。
- 12~35:PDT的基址,把低12位补0,0-35:共36位为PDT基址。
- 36~63:保留位,保留位并不意味着填0就行了,而是CPU要用的,但是我们不能用,并且CPU用了还不会告诉我们怎么用的。
1.3 PDE(9)
每个PDT表(页目录表)大小为4KB,共512个PTE,每个PTE为8字节。
PDE根据PS位来区分是大页还是小页:
- PS = 1:大页,剩下的9+12=21位,即2^ 21=2MB为 1 页。此时PDE中的G位才有意义:
- G = 1,表示全局页,是多个进程共享的,与TLB相关。
- G = 0,非全局页(独享页),如VirtualAlloc申请的内存。
- PS = 0,PDE指向页表,每个物理页大小4KB,此时G位是无效位,一直为0。
G=1,即为全局页,进程(CR3)切换时,TLB中的记录不会被刷新。
4KB物理页下:0-35:共36位为PTT基址
PDE大页、小页的结构图如下:(低36位为页表基址)
VirtualAlloc申请的内存是独享内存,仅当前进程可用
CreateFileMapping申请的是共享物理页,所有进程都可以使用,如放DLL的内存
1.4 PTE(9)
PTT表(页表),PTE结构如下:
PTE中35-12是物理页基址,24位,低12位补0:物理页基址+12位的页内偏移指向具体数据,共36位。具体的物理数据是按32位存放的。
1.5 XD标志位[63]
在PAE分页模式下,PDE与PTE的最高位为XD/NX位,AMD中称为NX位,即No Excetion。DEP就是PDE、PTE最高位X位。(DEP保护)段的属性有可读、可写和可执行。
页的属性有可读、可写。
所以,Intel就做了硬件保护(保护数据),做了一个不可执行位:
- XD = 1:该物理页数据不可执行。即使软件溢出了也没有关系,即使你的EIP蹦到了危险的“数据区”,也是不可以执行的!
1.6 PDPTE、PDE、PTE的属性
除了G位和XD位属性为OR外,其余属性为: PDPTE属性 & PDE属性 & PTE属性。
1.7 PDT、PTT表
2-9-9-12分页下的PDT、PTT表结构如下:
根据示意图,0xC0000000
是第一张页表的线性地址,0xC0600000
是第一张页目录表的线性地址。
PDT表共4张,每张4KB。
2-9-9-12:
PDPTI-PDI-PTI-OFFSET
公式:
PDE = 0xc0600000 + (PDPTI*4KB) + (PDI*8)
PTE = 0xc0000000 + (PDPTI*2MB) + (PDI*4KB) + (PTI*8)
更高效的公式(MmIsAddressValid是这么干的)
PDE = 0xc0600000 + ((addr >> 18) & 0x3ff8)
PTE = 0xc0000000 + ((addr >> 9) & 0x7ffff8)
1.8 练习:找一个线性地址对应的物理地址
1 |
|
- 开启2-9-9-12分页:将XP
C:\boot.ini
文件中的execute
改为noexecute
重启。 - 运行程序,线性地址为:0x0012FF7C,进行2-9-9-12拆分:
- 2:00 = 0x0 * 8 = 0x0
- 9:00 0000 000 = 0x0 * 8 = 0x0
- 9:1 0010 1111 = 0x12F * 8 = 0x978
- 12:0xF7C
- 在Windbg中使用
!process 0 0
获取当前进程CR3的值:0x06e40340(4字节物理地址)- 查PDPTE:
!dq 0x06e40340 + 0x0
:00000000`18403801。 - 查PDE:取PDPTE的低36位作为基址查PDE:0x018403801,然后将低12位属性清零,
!dq 0x018403000 + 0x0
:00000000`184f1867。 - 查PTE:取PDE的低36位作为基址查PTE:0x0184f1867,然后将低12位属性清零,
!dq 0x0184f1000 + 0x978
:80000000`1844c867。 - 查物理页中的数据:取PTE的低36位作为基址查物理页数据:0x01844c867,然后将低12位属性清零。因为物理页是4字节的:
!dd 0x01844c000 + 0xF7C
:12345678。
- 查PDPTE:
- 使用 !vtop 指令验证线性地址转换为物理地址是否正确。
!vtop CR3 线性地址
1.9 练习:给NULL挂上物理页
注意:如果NULL的PDE项不为0,则直接修改NULL的PTE即可。
1 |
|
运行程序获取变量dwNumber的线性地址:0x0012FF7C。
获取CR3的值
!process 0 0
:0x06e401c0。按2-9-9-12拆分线性地址NULL(0)、0x0012FF7C。
NULL:
!vtop 0x06e401c0 0x0
1
2
3
4
5X86VtoP: Virt 00000000, pagedir 6e401c0
X86VtoP: PAE PDPE 6e401c0 - 00000000`223d9801
X86VtoP: PAE PDE 223d9000 - 00000000`224c7867
X86VtoP: PAE PTE 224c7000 - 00000000`00000000
X86VtoP: PAE zero PTE0x0012FF7C:
!vtop 0x06e401c0 0x0012FF7C
1
2
3
4
5X86VtoP: Virt 0012ff7c, pagedir 6e401c0
X86VtoP: PAE PDPE 6e401c0 - 00000000`223d9801
X86VtoP: PAE PDE 223d9000 - 00000000`224c7867
X86VtoP: PAE PTE 224c7978 - 80000000`2235c867
X86VtoP: PAE Mapped phys 2235cf7c
可以看到NULL的PDE不为空,则直接修改PTE即可:由于
!eq
指令没法使用,则!ed 0x224c7000 0x2235c867
,!ed 0x224c7004 0x80000000
。此时再查看:
1
2
3
4
5
6kd> !vtop 0x06e401c0 0x0
X86VtoP: Virt 00000000, pagedir 6e401c0
X86VtoP: PAE PDPE 6e401c0 - 00000000223d9801
X86VtoP: PAE PDE 223d9000 - 00000000224c7867
X86VtoP: PAE PTE 224c7000 - 800000002235c867
X86VtoP: PAE Mapped phys 2235c000回到XP的VC6中继续执行代码。
1.a 练习:修改页属性读写高2G地址
除了G位和XD位属性为OR外,其余属性为: PDPTE属性 & PDE属性 & PTE属性。
要想在3环读写高2G地址,除了段中学到的提权外,还可以使用页的手段:改U/S位,和PTE的G位。
G位和TLB及PS位有关(PDE、PTE均包含G、PS位):
- PDE的PS = 0(小页4KB),G位在PDE中无效。
- 由于G位的属性是:PDE_G | PTE_G,当PTE的G = 1时,表示该物理页有缓存(使用了快表TLB),故要想使U/S修改后生效,要保证PTE中的G = 0。
题目:在3环写代码读写线性地址:0x8003f048。
1 |
|
运行程序,并使用
!process 0 0
获取CR3的值:0x06e401c0。拆分线性地址0x8003f048:
!vtop 0x06e401c0 0x8003f048
。1
2
3
4
5X86VtoP: Virt 8003f048, pagedir 6e401c0
X86VtoP: PAE PDPE 6e401d0 - 00000000`2556f801
X86VtoP: PAE PDE 2556f000 - 00000000`00b17163
X86VtoP: PAE PTE b171f8 - 00000000`0003f163
X86VtoP: PAE Mapped phys 3f048可以看到PDE、PTE下标2(低3位)的U/S为0(特权用户),需要修改为1。PTE下标8(低9位)G位为1,需要修改为0。
- 修改PDE:
!ed 0x2556f000 0x00b17167
。 - 修改PTE:
!ed 0x00b171f8 0x0003f067
。
- 修改PDE:
回到XP的VC6继续执行代码。
1.b 逆向分析函数MmIsAddressValid
内核函数MmIsAddressValid
的作用:验证一个线性地址是否有效。
在MSDN是这样说的:The MmIsAddressValid routine checks whether a page fault will occur for a read or write operation at a given virtual address.(MmIsAddressValid例程检查给定虚拟地址的读写操作是否会发生页面错误。)
找函数方法:
方法一:在windbg中输入 u MmIsAddressValid L41
或uf MmIsAddressValid
。
方法二:在C:\Windows\System32\ 中找到内核程序,用IDA分析。
- 2-9-9-12分页内核:ntkrnlpa.exe
- 10-10-12分页内核:ntoskrnl.exe
函数分析:
mov edi,edi
:正式分析前,先聊聊函数头的mov edi,edi
指令。这条指令看起来什么也没做,但是很多系统函数开头都有这条指令,why?其实这是为了实现对函数行为的动态修改(热补丁,Hot-Patching),且执行一条MOV指令比执行两条NOP指令花费更少的时间。可以一下这篇文章 函数开始处的MOV EDI, EDI的作用。align 8
:数据8字节对齐。bp-based frame
:使用 EBP 寻址。
1 | .text:0043C921 ; --------------------------------------------------------------------------- |
分析结果:
2-9-9-12:
PDPTI-PDI-PTI-OFFSET
公式:
PDE = 0xc0600000 + (PDPTI*4KB) + (PDI*8)
PTE = 0xc0000000 + (PDPTI*2MB) + (PDI*4KB) + (PTI*8)
更高效的公式(MmIsAddressValid是这么干的)
PDE = 0xc0600000 + ((addr >> 18) & 0x3ff8)
PTE = 0xc0000000 + ((addr >> 9) & 0x7ffff8)
1.c 练习:代码实现读写高2G地址
题目:在3环写代码读写线性地址:0x8003f048。
方法:通过构造调用门在0环修改线性地址0x8003f048对应PDE_U/S = 1、PTE_U/S = 1和PTE_G = 0。
1 |
|
注意:代码中如果不使用INVLPG dword ptr ds:[0x8003f048];
清空线性地址0x8003f048在TLB缓存的话,eq 8003f048 0040ec00·00081005
修改后到VC6中第一次运行时报错不能读写高2G内存(可不要退出程序再按F5继续执行即可成功),如果使用INVLPG清空0x8003f048在TLB缓存,则可直接运行成功。