驅動開發:核心解析記憶體四級頁表

2023-05-29 12:01:07

當今作業系統普遍採用64位元架構,CPU最大定址能力雖然達到了64位元,但其實僅僅只是用到了48位元進行定址,其記憶體管理採用了9-9-9-9-12的分頁模式,9-9-9-9-12分頁表示實體地址擁有四級頁表,微軟將這四級依次命名為PXE、PPE、PDE、PTE這四項。

關於記憶體管理和分頁模式,不同的作業系統和體系結構可能會有略微不同的實現方式。9-9-9-9-12的分頁模式是一種常見的分頁方案,其中實體地址被分成四級頁表:PXE(Page Directory Pointer Table Entry)、PPE(Page Directory Entry)、PDE(Page Table Entry)和PTE(Page Table Entry)。這種分頁模式可以支援大量的實體記憶體地址對映到虛擬記憶體地址空間中。每個級別的頁表都負責將虛擬地址對映到更具體的實體地址。通過這種層次化的頁表結構,作業系統可以更有效地管理和分配記憶體。

首先一個PTE管理1個分頁大小的記憶體也就是0x1000位元組,PTE結構的解析非常容易,開啟WinDBG輸入!PTE 0即可解析,如下所示,當前地址0位置處的PTE基址是FFFF898000000000,由於PTE的一個頁大小是0x1000所以當記憶體地址高於0x1000時將會切換到另一個頁中,如下FFFF898000000008則是另一個頁中的地址。

0: kd> !PTE 0
                                           VA 0000000000000000
PXE at FFFF89C4E2713000    PPE at FFFF89C4E2600000    PDE at FFFF89C4C0000000    PTE at FFFF898000000000
contains 8A0000000405F867  contains 0000000000000000
pfn 405f      ---DA--UW-V  not valid

0: kd> !PTE 0x1000
                                           VA 0000000000001000
PXE at FFFF89C4E2713000    PPE at FFFF89C4E2600000    PDE at FFFF89C4C0000000    PTE at FFFF898000000008
contains 8A0000000405F867  contains 0000000000000000
pfn 405f      ---DA--UW-V  not valid

由於PTE是動態變化的,找到該地址的關鍵就在於通過MmGetSystemRoutineAddress函數動態得到MmGetVirtualForPhysical的記憶體地址,然後向下掃描特徵尋找mov rdx,0FFFF8B0000000000h並將內部的地址提取出來。

這段程式碼完整版如下所示,程式碼可動態定位到PTE的記憶體地址,然後將其取出;

// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]

#include <ntifs.h>
#include <ntstrsafe.h>

// 指定記憶體區域的特徵碼掃描
PVOID SearchMemory(PVOID pStartAddress, PVOID pEndAddress, PUCHAR pMemoryData, ULONG ulMemoryDataSize)
{
	PVOID pAddress = NULL;
	PUCHAR i = NULL;
	ULONG m = 0;

	// 掃描記憶體
	for (i = (PUCHAR)pStartAddress; i < (PUCHAR)pEndAddress; i++)
	{
		// 判斷特徵碼
		for (m = 0; m < ulMemoryDataSize; m++)
		{
			if (*(PUCHAR)(i + m) != pMemoryData[m])
			{
				break;
			}
		}
		// 判斷是否找到符合特徵碼的地址
		if (m >= ulMemoryDataSize)
		{
			// 找到特徵碼位置, 獲取緊接著特徵碼的下一地址
			pAddress = (PVOID)(i + ulMemoryDataSize);
			break;
		}
	}

	return pAddress;
}

// 獲取到函數地址
PVOID GetMmGetVirtualForPhysical()
{
	PVOID VariableAddress = 0;
	UNICODE_STRING uioiTime = { 0 };

	RtlInitUnicodeString(&uioiTime, L"MmGetVirtualForPhysical");
	VariableAddress = (PVOID)MmGetSystemRoutineAddress(&uioiTime);
	if (VariableAddress != 0)
	{
		return VariableAddress;
	}
	return 0;
}

// 驅動解除安裝例程
VOID UnDriver(PDRIVER_OBJECT driver)
{
	DbgPrint("Uninstall Driver \n");
}

// 驅動入口地址
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	DbgPrint("Hello LyShark \n");

	// 獲取函數地址
	PVOID address = GetMmGetVirtualForPhysical();

	DbgPrint("GetMmGetVirtualForPhysical = %p \n", address);

	UCHAR pSecondSpecialData[50] = { 0 };
	ULONG ulFirstSpecialDataSize = 0;

	pSecondSpecialData[0] = 0x48;
	pSecondSpecialData[1] = 0xc1;
	pSecondSpecialData[2] = 0xe0;
	ulFirstSpecialDataSize = 3;

	// 定位特徵碼
	PVOID PTE = SearchMemory(address, (PVOID)((PUCHAR)address + 0xFF), pSecondSpecialData, ulFirstSpecialDataSize);
	__try
	{
		PVOID lOffset = (ULONG)PTE + 1;
		DbgPrint("PTE Address = %p \n", lOffset);
	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{
		DbgPrint("error");
	}

	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

執行如上程式碼可動態獲取到當前系統的PTE地址,然後將PTE填入到g_PTEBASE中,即可實現解析系統內的四個標誌位,完整解析程式碼如下所示;

// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]

#include <ntifs.h>
#include <ntstrsafe.h>

INT64 g_PTEBASE = 0;
INT64 g_PDEBASE = 0;
INT64 g_PPEBASE = 0;
INT64 g_PXEBASE = 0;

PULONG64 GetPteBase(PVOID va)
{
	return (PULONG64)((((ULONG64)va & 0xFFFFFFFFFFFF) >> 12) * 8) + g_PTEBASE;
}

PULONG64 GetPdeBase(PVOID va)
{
	return (PULONG64)((((ULONG64)va & 0xFFFFFFFFFFFF) >> 12) * 8) + g_PDEBASE;
}

PULONG64 GetPpeBase(PVOID va)
{
	return (PULONG64)((((ULONG64)va & 0xFFFFFFFFFFFF) >> 12) * 8) + g_PPEBASE;
}

PULONG64 GetPxeBase(PVOID va)
{
	return (PULONG64)((((ULONG64)va & 0xFFFFFFFFFFFF) >> 12) * 8) + g_PXEBASE;
}

// 驅動解除安裝例程
VOID UnDriver(PDRIVER_OBJECT driver)
{
	DbgPrint("Uninstall Driver \n");
}

// 驅動入口地址
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	DbgPrint("Hello LyShark \n");

	g_PTEBASE = 0XFFFFF20000000000;

	g_PDEBASE = (ULONG64)GetPteBase((PVOID)g_PTEBASE);
	g_PPEBASE = (ULONG64)GetPteBase((PVOID)g_PDEBASE);
	g_PXEBASE = (ULONG64)GetPteBase((PVOID)g_PPEBASE);

	DbgPrint("PXE = %p \n", g_PXEBASE);
	DbgPrint("PPE  = %p \n", g_PPEBASE);
	DbgPrint("PDE  = %p \n", g_PDEBASE);
	DbgPrint("PTE  = %p \n", g_PTEBASE);

	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

我的系統內PTE地址為0XFFFFF20000000000,填入變數內解析效果如下圖所示;