Windows内核实验 ——— 非PAE分页

前言

之前我们所进行的都是关于段的,我们通过修改fs段来进行提权,再此之后我们将讨论一下关于Windows的页保护

实验内容

在开始之前我们需要将虚拟机设置为PAE模式

PAE:物理地址扩展

我们原来的启动配置文件boot.ini中将noexecute进行修改为execute

我们进行上述设置后相当于我们将页属性从不可执行设置为了可执行,数据保护是在段上完成,而页不对其进行操作

本次实验虚拟机的内存设置已经设置为256MB,方便我们对数据的观察

我们首先需要了解CPU如何获取物理地址,在Windows中会对每个进程开辟一个4G的虚拟内存空间,而CPU直接操作的是物理地址,那么我们如何通过这个虚拟地址来进行获取物理地址?在内核中存在有一个寄存器cr3,其是我们将虚拟地址转换为物理地址的关键

我们先了解一下段管理与页管理,分页式用于从虚拟地址到物理地址转换的结构,其关系如下图:

而对于cr3寄存器其指向了一个页目录表(DPT),每一个的页目录表又指向一个页表(PTT),通过页表来映射到对应的物理内存上

下面介绍几个Windbg的指令:

查看物理地址

1
!dx

!代表获取其物理地址

列出所有系统运行的进程

1
!process 0 0

进程挂载

1
.process addr

查页面属性

1
!pte addr

我们再次回到之前的 int 中断提权中去,编写一个简单的通过int 0x20来进行提权的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include<stdio.h>
#include<Windows.h>

DWORD g_cr3 = 0;
DWORD signFlag = 0x12345678;

void __declspec(naked) IdtEntry (){
__asm {
mov eax,cr3
mov g_cr3, eax
iretd
}
}

void crash() {
__asm {
int 0x20
}
}

int main() {
if ((DWORD)IdtEntry != 0x00401040) {
printf("Your addr is: %p\n", IdtEntry);
return 0;
}
crash();
printf("Cr3: %p\n", g_cr3);
printf("signFlag: %p\n", &signFlag);
system("pause");
}

我们编译上述代码丢到WinXP下运行,观察对应地址信息

运行前别忘了在中断向量表里添加对 int 20h 的处理

我们可以获取到相应的地址信息,接下来我们结合上面所提到的Windbg的相关指令来进行转换我们从cr3获取到singFlag的值位置

cr3 保存着进程的页目录表基址,切换进程cr3会改变

我们先获取一下系统活跃进程的信息:

因为上述中断提权的程序最后是执行了pause,意味着我们的signFlag仍然驻留在内存中,我们找到我们编写的程序:

我们可以看到下面有一个DirBase和我们上面的Cr3的值一样

对于上述的DirBase,其地址的最后12位(二进制下)为其属性值用来标识可读可写等,上图中则为0x3e0,前面的20位为其PDT表地址项,上图中为0x09cd8000,其指向的地址为页目录表的第一个单元:

需要注意我们后续找地址时需要去除后三位(16进制下),同时需要在后面添加 0 使其满足32位地址

我们想要获取一个数据的物理地址那么我们需要将其进行转换为二进制,按照10+10+12的形式进行划分地址

以上面0x004197B0为例,我们将其转换为二进制,我们可以利用windbg已有的功能完成

1
.formats xxx

xxx为需要转换的数,windbg会为我们转换为各种进制

获取到二进制后我们对其进行按照上述规则分割

1
2
3
4
# 00000000 01000001 10010111 10110000
00000000 01 # 0x1 PDE 表偏移 -- pdi
000001 1001 # 0x19 PTE 表的偏移 (每个表项占用4字节) -- pti
0111 10110000 # 0x7b0 页内偏移

我们已经拿到PDE表的第一项地址02fd4867,我们找到对应的PTE表项,计算公式为:Base + offset * 4,在此过程中则为0x02fd4867 + 0x19 * 4

我们查看该地址可以看到其指向的页表PTE位置

之后我们将其加上对应物理地址的偏移可以得到:0x99aa000 + 0x7b0

可以看到我们成功的找到了内存中我们的signFlag的物理地址

对于上述我们完成的过程Windbg中其实有一个指令可以帮我们进行转换

1
!vtop PFNOfPDE VA

PFNOfPDE为我们之前的cr3寄存器值也就是DirBase,我们填入该参数时注意要去掉最后三位(16进制下),VA则为我们之前需要求的signFlag的虚拟地址

可以看到其每一步的转换过程以及最后得到的物理地址和我们手动求解的地址一样

了解完上述内容后,我们回到之前提到的!pte指令,其功能为展示对应页表的属性,我们将我们的程序对应的页属性进行展示,如下图:

我们可以看到下面的其PDE表(页目录表)位置以及PTE表(页表)位置,其是如何计算的?

在内存中页表的基址为0xC0000000,当我们获取到一个页地址时,我们将其右移12位可以拿到pdi值,我们再左移2位则可以获取到pti值,获取到对应的值后我们加上基质可以得到对应的PTE的地址

1
addr of PTE = ((addr >> 12) << 2) + 0xC0000000

addr 为我们页的地址,我们也可以直接使用 Windbg 带的计算功能进行计算

可以看到计算的结果和上面!pte计算出的PTE地址一样

总结

操控一个进程的内存,我们首先需要访问其 cr3,切换到其虚拟内存中去找到我们需要操作的PTE位置,我们修改其低12位即可


Windows内核实验 ——— 非PAE分页
https://equinox-shame.github.io/2023/06/03/Windows内核实验 — 非PAE分页/
作者
梓曰
发布于
2023年6月3日
许可协议