Windows x64内核学习笔记(四)—— 9-9-9-9-12分页

前言

在学习VT虚拟化技术EPT物理地址转换时,我们简单提到过9-9-9-9-12分页的概念,即支持48位物理地址转换。

9-9-9-9-12分页

描述:随着计算机技术的发展,64位系统逐渐占据主流地位,那么也就表示CPU的最大寻址范围为64位。但实际上,CPU只使用了其中的48位用于寻址,并使用9-9-9-9-12分页模式。即便如此,在未来较长一段时间里,48位寻址范围也足够大部分人的日常使用了。

9-9-9-9-12分页表示物理地址拥有四级页表,在Intel开发手册中,将这四级页表分别称为PML4E、PDPTE、PDE、PTE;但微软的命名方式略有不同,将这四级页表分别称为PXE、PPE、PDE、PTE,WinDbg中也是如此。

由于之后的大部分操作都是使用WinDbg完成的,因此我们在之后的笔记中都使用微软对四级页表的命名方式。

实验一:线性地址转物理地址

第一步:打开记事本
启动一个64位的notepad.exe,写入字符串“Hello World”。
在这里插入图片描述
第二步:定位线性地址
使用CE定位字符串“Hello World”的线性地址。
在这里插入图片描述
可以看到这里搜出了很多线性地址,怎么确定是哪一个呢?

可以在字符串后面再写入一些其他的字符,看看哪些线性地址的值会发生变化。
在这里插入图片描述
只剩下两个了,MSCTF.dll是微软用于提供文本服务的dll,那么真正的线性地址能确定是21BABF52920了。
第三步:拆分线性地址
将线性地址的二进制位拆分成9-9-9-9-12五组,可以借助WinDbg或计算器得到二进制。
在这里插入图片描述
拆分结果如下:

0 0000 0100			//004
0 0110 1110			//06e
1 0101 1111			//15f
1 0101 0010			//152
1001 0010 0000		//920

第四步:定位页表基址
首先我们需要通过notepad.exe进程的Cr3得到物理页的基址,即第一个PXE的地址。
在这里插入图片描述
在这里插入图片描述
第五步:定位PXE
在这里插入图片描述
注意:高位的0a00和低位的867是属性位,不包含在物理地址中。
第六步:定位PPE
在这里插入图片描述
第七步:定位PDE
在这里插入图片描述
第八步:定位PTE
在这里插入图片描述
第九步:定位物理页
在这里插入图片描述
成功定位到了记事本中的字符串文本,后面的"123"是之前残留的数据。

页表基址

思考:一个进程该如何访问自己的物理页呢?可以通过读取Cr3的值进行访问吗?
答案:不行,Cr3中保存的页表基址是物理地址,程序如果直接访问这个地址,虽然看上去值是一样的,但实际上访问的是一个线性地址,会被虚拟内存管理器解析成另一个地址。

实际上,操作系统会将当前进程的物理页映射在某个线性地址中,以供程序读取自己的页表内容。
在x86系统中,页表基址是固定的,位于0xC0000000,将这个线性地址进行解析,访问其物理页的内容,会发现从这个地址开始,里面保存的数据为当前程序的所有物理页地址。

而在x64系统中,页表基址不再是固定的值,而是每次系统启动后随机生成的。

可以在WinDbg中查看0地址对应的线性地址来确定当前的页表基址。
在这里插入图片描述
可以看到,当前系统的页表基址的线性地址为0xFFFF800000000000,注意,只有后48位才是有效地址。

其中,每个物理页占8个字节,例如,第一个物理页地址位于线性地址0xFFFF800000000000,第二个物理页地址位于线性地址0xFFFF800000000008,每个物理页中包含1024个字节的数据。

定位基址

描述:如果系统每次启动时,基址是随机分配的,那么该如何定位基址呢?其实方法有挺多的,大部分方式是通过提取特征码。

以周壑老师提供的方法为例,首先在WinDbg中定位内核模块的地址。
在这里插入图片描述
然后在内核模块中搜索与当前页表基址相同的值出现的位置,当前页表基址为0xFFFF800000000000
在这里插入图片描述
接着,在IDA中定位到数据所在的位置,可以看到是某行代码引用了这个值的硬编码。
在这里插入图片描述
在WinDbg中查看这段代码,能够识别到位于CcUnpinFileDataEx函数。
在这里插入图片描述
那么,由于系统每次启动时基址是不固定的,因此这些值也不可能是固定的硬编码,肯定是有“人”对这些值进行了修改,在需要使用时,可以通过固定的偏移量提取硬编码,从而得到页表基址,但要注意不同版本的内核文件的偏移量可能是不同的。

PTE to PXE

思考:如果已经得到了页表基址,即PTE的地址,那么,能否再得到PDE、PPE以及PXE的线性地址呢?
答案:可以,因为页表基址本身也是一个线性地址,也需要有人来管理,因此将页表基址本身当做一个线性地址,向右移动12位能够得到PDE基址对应的PTI,再乘以8(每个物理页地址占8个字节)就能够得到PDE基址在页表基址中的偏移量,然后将这个偏移量再加上页表基址就能得到PDE基址所在的线性地址。同理,也能够通过PDE基址得到PPE基址和PXE基址的线性地址。

计算公式:PAE = PTE_BASE + (Address >> 12) << 3;

实验二:通过页表基址定位各级页表的物理页

描述:以gdt基址为例,通过页表基址寻找保存其线性地址的各项页表的物理地址,此时页表基址为0xFFFF800000000000。
在这里插入图片描述

第一步:定位PTE的线性地址,并查看物理页的物理地址
在这里插入图片描述
第二步:定位PDE的线性地址,并查看PTE的物理地址
在这里插入图片描述
第三步:定位PPE的线性地址,并查看PDE的物理地址
在这里插入图片描述
第四步:定位PXE所在的线性地址,并查看PPE的物理地址
在这里插入图片描述
第五步:使用!pte指令进行验证
在这里插入图片描述

代码实现

#include "ntddk.h"

ULONG64 g_NT_BASE = 0xfffff80348aa4000;	// lm命令得到
ULONG64 g_PTE_BASE;
ULONG64 g_PDE_BASE;
ULONG64 g_PPE_BASE;
ULONG64 g_PXE_BASE;

//卸载函数
VOID DriverUnload(PDRIVER_OBJECT driver)
{
	DbgPrint("Driver Unload .\r\n");
}

ULONG64 GetPTEAddress(PVOID address)
{
	return (PULONG64)(g_PTE_BASE + ((((ULONG64)address & 0xffffffffffff) >> 12) << 3));
}

ULONG64 GetPDEAddress(PVOID address)
{
	return (PULONG64)(g_PDE_BASE + ((((ULONG64)address & 0xffffffffffff) >> 21) << 3));
}

ULONG64 GetPPEAddress(PVOID address)
{
	return (PULONG64)(g_PPE_BASE + ((((ULONG64)address & 0xffffffffffff) >> 30) << 3));
}

ULONG64 GetPXEAddress(PVOID address)
{
	return (PULONG64)(g_PXE_BASE + ((((ULONG64)address & 0xffffffffffff) >> 39) << 3));
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
	DbgPrint("Driver Load .\r\n");
	DriverObject->DriverUnload = DriverUnload;

	ULONG64 offset = 0xa4ad;		// 页表基址硬编码相对于内核文件基址的偏移量
	g_PTE_BASE = *(PULONG64)(g_NT_BASE + offset);
	g_PDE_BASE = GetPTEAddress(g_PTE_BASE);
	g_PPE_BASE = GetPTEAddress(g_PDE_BASE);
	g_PXE_BASE = GetPTEAddress(g_PPE_BASE);

	DbgPrint("g_PTE_BASE: %p \r\n", g_PTE_BASE);
	DbgPrint("g_PDE_BASE: %p \r\n", g_PDE_BASE);
	DbgPrint("g_PPE_BASE: %p \r\n", g_PPE_BASE);
	DbgPrint("g_PXE_BASE: %p \r\n", g_PXE_BASE);

	ULONG64 gdtr = 0xfffff8034b290fb0;

	DbgPrint("gdtr - PXE: %p \r\n", GetPXEAddress(gdtr));
	DbgPrint("gdtr - PPE: %p \r\n", GetPPEAddress(gdtr));
	DbgPrint("gdtr - PDE: %p \r\n", GetPDEAddress(gdtr));
	DbgPrint("gdtr - PTE: %p \r\n", GetPTEAddress(gdtr));

	return STATUS_SUCCESS;
}

运行结果
在这里插入图片描述
使用WinDbg进行验证
在这里插入图片描述

kd> r gdtr
gdtr=fffff8034b290fb0

在这里插入图片描述

参考资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值