diff --git a/EVENSTAR.sln b/EVENSTAR.sln index c4c26e4..396401a 100644 --- a/EVENSTAR.sln +++ b/EVENSTAR.sln @@ -15,6 +15,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GetCurrentProcessorNumber", EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KernelToUserInjector", "KernelToUserInjector\KernelToUserInjector.vcxproj", "{455ED3CD-DF55-4D4F-8C22-895E06AE469B}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KernelWriteProtect", "KernelWriteProtect\KernelWriteProtect.vcxproj", "{E81DEF46-8802-4B42-AC86-09E3052D488F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Release|x64 = Release|x64 @@ -33,6 +35,9 @@ Global {455ED3CD-DF55-4D4F-8C22-895E06AE469B}.Release|x64.ActiveCfg = Release|x64 {455ED3CD-DF55-4D4F-8C22-895E06AE469B}.Release|x64.Build.0 = Release|x64 {455ED3CD-DF55-4D4F-8C22-895E06AE469B}.Release|x64.Deploy.0 = Release|x64 + {E81DEF46-8802-4B42-AC86-09E3052D488F}.Release|x64.ActiveCfg = Release|x64 + {E81DEF46-8802-4B42-AC86-09E3052D488F}.Release|x64.Build.0 = Release|x64 + {E81DEF46-8802-4B42-AC86-09E3052D488F}.Release|x64.Deploy.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/KernelWriteProtect/Inc/BaseDataTypes.h b/KernelWriteProtect/Inc/BaseDataTypes.h new file mode 100644 index 0000000..ef564ae --- /dev/null +++ b/KernelWriteProtect/Inc/BaseDataTypes.h @@ -0,0 +1,74 @@ +// ======================================================================== +// File: BaseDataTypes.h +// +// Author: winterknife +// +// Description: Type definitions for common Windows base data types made +// for maintaining uniformity across all User Mode and Kernel Mode code +// +// Modifications: +// 2021-07-31 Created +// 2022-08-13 Updated +// ======================================================================== + +// ======================================================================== +// Pragmas +// ======================================================================== + +#pragma once + +// ======================================================================== +// Type definitions +// ======================================================================== + +#pragma region TYPEDEFS + +// 8-bit unsigned integer, range = 0 - 255, unsigned char +typedef unsigned __int8 BYTE, *PBYTE; + +// 16-bit unsigned integer, range = 0 - 65535, unsigned short +typedef unsigned __int16 WORD, *PWORD; + +// 32-bit unsigned integer, range = 0 - 4294967295, unsigned int or unsigned long +typedef unsigned long DWORD, *PDWORD; + +// 64-bit unsigned integer, range = 0 - 18446744073709551615, unsigned long long +typedef unsigned __int64 QWORD, *PQWORD; + +// Should be TRUE(1) or FALSE(0), unsigned char or bool +typedef unsigned __int8 BOOLEAN, *PBOOLEAN; + +// 8-bit UTF-8/Multibyte/ANSI character, char +typedef __int8 CHAR, *PCHAR; + +// 16-bit UTF-16/Wide/UNICODE character, unsigned short +typedef wchar_t WCHAR, *PWCHAR; + +// Pointer to any type, size = 4 bytes or 8 bytes depending on code bitness +typedef void* PVOID; + +// Pointer to a constant of any type +typedef const void* PCVOID; + +// Pointer to constant null-terminated string of ANSI characters +typedef _Null_terminated_ const __int8* PCSTR; + +// Pointer to constant null-terminated string of UNICODE characters +typedef _Null_terminated_ const wchar_t* PCWSTR; + +// Pointer to constant null-terminated string of ANSI or UNICODE characters depending on character encoding scheme +#if defined(UNICODE) +typedef const __wchar_t* PCTSTR; +#else +typedef const __int8* PCTSTR; +#endif + +// 32-bit or 64-bit unsigned integer, range = 0 - 4294967295 or 18446744073709551615 depending on compiler bitness +// Used for casting pointers +#if defined(_WIN64) +typedef unsigned __int64 DWORD_PTR, *PDWORD_PTR; +#else +typedef unsigned long DWORD_PTR, *PDWORD_PTR; +#endif + +#pragma endregion \ No newline at end of file diff --git a/KernelWriteProtect/Inc/Common.h b/KernelWriteProtect/Inc/Common.h new file mode 100644 index 0000000..0974f96 --- /dev/null +++ b/KernelWriteProtect/Inc/Common.h @@ -0,0 +1,153 @@ +// ======================================================================== +// File: Common.h +// +// Author: winterknife +// +// Description: Contains the common stuff for this project +// +// Modifications: +// 2021-08-21 Created +// 2026-05-12 Updated +// ======================================================================== + +// ======================================================================== +// Pragmas +// ======================================================================== + +#pragma once + +// Specify program entry point +#pragma comment(linker, "/ENTRY:DriverEntry") + +// Merge sections +#if defined (_MSC_VER) +#if (_MSC_VER >= 1920) +#pragma comment(linker, "/MERGE:_RDATA=.rdata") +#endif +#endif + +// Emit error message at compile time if trying to build for 32-bit x86 target +#if !defined(_WIN64) +#error x86 build target is unsupported! +#endif + +// ======================================================================== +// Includes +// ======================================================================== + +#include +#include +#include +#include "BaseDataTypes.h" + +#pragma intrinsic(__stosb) +#pragma intrinsic(__movsb) +#pragma intrinsic(strlen) +#pragma intrinsic(strcmp) + +// ======================================================================== +// Macros +// ======================================================================== + +#pragma region MACROS + +// Module name +#define __MODULE__ "KernelWriteProtect" + +// Comment out for final build +#define DEBUG_BUILD + +// Print debug message macro +#ifdef DEBUG_BUILD +#define STRIP(...) __VA_ARGS__ +#define DEBUG_PREFIX "[DBG]: " +#define DEBUG_PRINT(_x_, ...) DbgPrint(DEBUG_PREFIX _x_, ##__VA_ARGS__) +#else +#define DEBUG_PRINT(_x_, ...) void(0) +#endif + +// Pool tag macro in reverse order due to Little Endianness +#define __POOLTAG__ 'ONC' + +// Stringizing operator macro +#ifndef _CRT_STRINGIZE +#define _CRT_STRINGIZE_(x) #x +#define _CRT_STRINGIZE(x) _CRT_STRINGIZE_(x) +#endif + +// Get mangled name of a function +#define GET_MANGLED_FUNCTION_NAME __pragma(message(__FILE__ _CRT_STRINGIZE((__LINE__): \nfunction:\t) __FUNCSIG__ " is mangled to: " __FUNCDNAME__)) + +// To set alternate name of decorated x86 IAT global function pointer +#ifdef _X86_ +// Uses undocumented /ALTERNATENAME linker flag +#define ALT_NAME(name, n) __pragma(comment(linker, _CRT_STRINGIZE(/ALTERNATENAME:__imp__##name##@##n####=___imp_##name))) +#else +#define ALT_NAME(name, n) +#endif + +// To declare/initialize global IAT function pointers for run-time dynamic linking, use on global scope +#define IMP_FUNC(name, n) EXTERN_C_START PVOID __imp_##name = NULL; EXTERN_C_END ALT_NAME(name, n) + +// To declare/initialize local function pointers for run-time dynamic linking, use on local scope +// Warning: will ignore SAL annotations +#define IMP_FUNC_PIC(name) typedef decltype(name) __type_##name; __type_##name* ##name = NULL; + +// Macro to indicate a specific function to the linker as the first in the link order +#define CODE_BEGIN code_seg(push, ".text") + +// Macro to indicate the end of the specific function to the linker +#define CODE_END code_seg(pop) + +// Macro to disable all compiler optimizations for a specific function +#define OPTIMIZATION_OFF optimize("", off) + +// Macro to re-enable/reset compiler optimizations for a specific function +#define OPTIMIZATION_ON optimize("", on) + +// Macro to enable compiler optimizations for generating short sequences of machine code for a specific function +#if defined(_M_IX86) +#define OPTIMIZATION_SIZE optimize("gsy", on) +#else +#define OPTIMIZATION_SIZE optimize("gs", on) +#endif + +// Macro to enable compiler optimizations for generating fast sequences of machine code for a specific function +#if defined(_M_IX86) +#define OPTIMIZATION_SPEED optimize("gty", on) +#else +#define OPTIMIZATION_SPEED optimize("gt", on) +#endif + +// Macro to fill a block of memory with zeroes given it's address and length in bytes by generating store string instruction(rep stosb) +// Enhanced REP STOSB/MOVSB(ERMSB) are only available since Ivy Bridge Intel microarchitecture(2012/2013) +// Processors that provide support for enhanced MOVSB/STOSB operations are enumerated by the CPUID feature flag: CPUID:(EAX=7H, ECX=0H):EBX.ERMSB[bit 9] = 1 +#define ZERO_MEMORY(Destination, Length) __stosb((PBYTE)Destination, 0, Length) + +// Macro to copy a block of memory given it's source address, destination address and the number of bytes to copy by generating move string instruction(rep movsb) +#define COPY_MEMORY(Destination, Source, Count) __movsb((PBYTE)Destination, (const PBYTE)Source, Count) + +#pragma endregion + +// ======================================================================== +// C inline routines +// ======================================================================== + +#pragma region INLINES + +/// @brief To compare two blocks of memory and return the number of bytes that match +/// @param pcSource1 Pointer to the first block of memory +/// @param pcSource2 Pointer to the second block of memory +/// @param dwptrLength The number of bytes to compare +/// @return Number of bytes in the two blocks that match, if all bytes match up to the specified Length value, the Length value is returned +extern "C" __forceinline DWORD_PTR __stdcall compare_memory(_In_ const PVOID pcSource1, _In_ const PVOID pcSource2, _In_ DWORD_PTR dwptrLength) { + // Init local variables + DWORD_PTR dwptrIndex = 0; + + // Start comparing the two blocks of memory + for (dwptrIndex = 0; (dwptrIndex < dwptrLength) && (((PBYTE)pcSource1)[dwptrIndex] == ((PBYTE)pcSource2)[dwptrIndex]); dwptrIndex++); + + return dwptrIndex; +} + +#pragma endregion \ No newline at end of file diff --git a/KernelWriteProtect/Inc/WriteProtectBypass.h b/KernelWriteProtect/Inc/WriteProtectBypass.h new file mode 100644 index 0000000..c7a9f17 --- /dev/null +++ b/KernelWriteProtect/Inc/WriteProtectBypass.h @@ -0,0 +1,53 @@ +// ======================================================================== +// File: WriteProtectBypass.h +// +// Author: winterknife +// +// Description: Header file for WriteProtectBypass.cpp source file +// +// Modifications: +// 2026-05-12 Created +// 2026-05-14 Updated +// ======================================================================== + +// ======================================================================== +// Pragmas +// ======================================================================== + +#pragma once + +// ======================================================================== +// Includes +// ======================================================================== + +#include "Common.h" + +// ======================================================================== +// C routine declarations +// ======================================================================== + +#pragma region DECLARATIONS + +/// @brief Copies the contents of a source memory block to a destination memory block with write protect bypass using CR0.WP manipulation +/// @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_(DISPATCH_LEVEL) +EXTERN_C DECLSPEC_NOINLINE VOID __stdcall copy_memory_cr0_wp( + _Out_writes_bytes_all_(dwptrLength) VOID* pDestination, + _In_reads_bytes_(dwptrLength) CONST VOID* pcSource, + _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 +/// @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_(DISPATCH_LEVEL) +EXTERN_C DECLSPEC_NOINLINE VOID __stdcall copy_memory_double_mapping( + _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 new file mode 100644 index 0000000..53aafcf --- /dev/null +++ b/KernelWriteProtect/KernelWriteProtect.vcxproj @@ -0,0 +1,86 @@ + + + + + Release + x64 + + + + {E81DEF46-8802-4B42-AC86-09E3052D488F} + {dd38f7fc-d7bd-488b-9242-7d8754cde80d} + v4.5 + 12.0 + Debug + Win32 + KernelWriteProtect + 10.0.26100.0 + + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + WDM + Spectre + 1 + true + + + + + + + + + + + DbgengKernelDebugger + .\Bin\x64\ + .\Temp\x64\ + true + false + true + + + + true + true + MinSpace + AnySuitable + false + false + /KERNEL /homeparams %(AdditionalOptions) + false + false + true + + + /EMITPOGOPHASEINFO /NOVCFEATURE /NOCOFFGRPINFO /FILEALIGN:0x200 /PDBALTPATH:$(ProjectName).pdb /Brepro /RELEASE /EMITTOOLVERSIONINFO:NO %(AdditionalOptions) + + Ntoskrnl.lib + DebugFull + LinkVerbose + UseLinkTimeCodeGeneration + + + SHA1 + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/KernelWriteProtect/KernelWriteProtect.vcxproj.filters b/KernelWriteProtect/KernelWriteProtect.vcxproj.filters new file mode 100644 index 0000000..fe24858 --- /dev/null +++ b/KernelWriteProtect/KernelWriteProtect.vcxproj.filters @@ -0,0 +1,30 @@ + + + + + {a88b16d9-5c2c-48e4-9eb1-23dddf6ea064} + + + {1c52dd49-2f39-4d9a-ab53-2ba8836d6616} + + + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/KernelWriteProtect/Lib/.placeholder b/KernelWriteProtect/Lib/.placeholder new file mode 100644 index 0000000..e69de29 diff --git a/KernelWriteProtect/Misc/.placeholder b/KernelWriteProtect/Misc/.placeholder new file mode 100644 index 0000000..e69de29 diff --git a/KernelWriteProtect/README.md b/KernelWriteProtect/README.md new file mode 100644 index 0000000..a8abf30 --- /dev/null +++ b/KernelWriteProtect/README.md @@ -0,0 +1,62 @@ +# EVENSTAR - KernelWriteProtect + +## Version + +- `v1.0.0` + +## Brief + +- `ISA: x86` +- `Mode: Long` +- `Bitness: 64-bit` +- `CPL: 0` +- `OS: Windows` +- `Language: C` +- Sample code that demonstrates two techniques for writing into read-only pages in kernel space using `CR0.WP` manipulation and `MDL` double mapping + +## Usage + +``` +[DBG]: +++ KernelWriteProtect.sys Loaded +++ +[DBG]: KernelWriteProtect.sys Built May 14 2026 18:20:42 +[DBG]: KernelWriteProtect: DriverObject = FFFF808F23410E20 +[DBG]: KernelWriteProtect: RegistryPath = \REGISTRY\MACHINE\SYSTEM\ControlSet001\Services\KernelWriteProtect +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`d1efb1b0 cc int 3 +0: kd> !pte 0xFFFFF78000000000 + VA fffff78000000000 +PXE at FFFFEFF7FBFDFF78 PPE at FFFFEFF7FBFEF000 PDE at FFFFEFF7FDE00000 PTE at FFFFEFFBC0000000 +contains 0000000000285063 contains 0000000000284063 contains 0000000000283063 contains 8A00000000282161 +pfn 285 ---DA--KWEV pfn 284 ---DA--KWEV pfn 283 ---DA--KWEV pfn 282 -G-DA--KR-V + +0: kd> db 0xFFFFF78000000000 L8 +fffff780`00000000 41 41 41 41 41 41 41 41 AAAAAAAA +0: kd> g +[DBG]: --- KernelWriteProtect.sys Unloaded --- +``` + +## Tested OS Versions + +- `Windows 11 25H2 Build 26200 Revision 8246 64-bit` + +## References + +1. [BattlEye hypervisor detection](https://secret.club/2020/01/12/battleye-hypervisor-detection.html) +2. [ac](https://github.com/donnaskiez/ac) +3. [EfiGuard](https://github.com/Mattiwatti/EfiGuard) +4. [kernelhook](https://github.com/adrianyy/kernelhook) \ No newline at end of file diff --git a/KernelWriteProtect/Src/DrvMain.cpp b/KernelWriteProtect/Src/DrvMain.cpp new file mode 100644 index 0000000..5faa1e8 --- /dev/null +++ b/KernelWriteProtect/Src/DrvMain.cpp @@ -0,0 +1,64 @@ +// ======================================================================== +// File: DrvMain.cpp +// +// Author: winterknife +// +// Description: Source file that contains the SYS entry point +// +// Modifications: +// 2026-04-13 Created +// 2026-05-14 Updated +// ======================================================================== + +// ======================================================================== +// Includes +// ======================================================================== + +#include "../Inc/Common.h" +#include "../Inc/WriteProtectBypass.h" + +// ======================================================================== +// 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 +/// @return STATUS_SUCCESS or an appropriate error status +_Success_(return >= 0) _Must_inspect_result_ _IRQL_requires_(PASSIVE_LEVEL) +EXTERN_C __declspec(code_seg("INIT")) NTSTATUS __stdcall DriverEntry( + _In_ PDRIVER_OBJECT pDriverObject, + _In_ PUNICODE_STRING puncRegistryPath +) { + DEBUG_PRINT("+++ %s.sys Loaded +++\n", __MODULE__); + DEBUG_PRINT("%s.sys Built %s %s\n", __MODULE__, __DATE__, __TIME__); + 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); + + // Write data into RO _KUSER_SHARED_DATA structure + BYTE byarrPayload[] = { 0x41, 0x41, 0x41, 0x41 }; + QWORD qwKuserSharedData = 0xFFFFF78000000000ULL; + copy_memory_cr0_wp(reinterpret_cast(qwKuserSharedData), byarrPayload, sizeof(byarrPayload)); + copy_memory_double_mapping(reinterpret_cast(qwKuserSharedData + 0x4ULL), byarrPayload, sizeof(byarrPayload)); + + return STATUS_SUCCESS; +} + +#pragma endregion \ No newline at end of file diff --git a/KernelWriteProtect/Src/WriteProtectBypass.cpp b/KernelWriteProtect/Src/WriteProtectBypass.cpp new file mode 100644 index 0000000..ac5db81 --- /dev/null +++ b/KernelWriteProtect/Src/WriteProtectBypass.cpp @@ -0,0 +1,161 @@ +// ======================================================================== +// File: WriteProtectBypass.cpp +// +// Author: winterknife +// +// 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 +// +// Modifications: +// 2026-05-12 Created +// 2026-05-14 Updated +// ======================================================================== + +// ======================================================================== +// Includes +// ======================================================================== + +#include "../Inc/WriteProtectBypass.h" + +// ======================================================================== +// Routines +// ======================================================================== + +#pragma region ROUTINES + +_Use_decl_annotations_ +VOID __stdcall copy_memory_cr0_wp( + VOID* pDestination, + CONST VOID* pcSource, + DWORD_PTR dwptrLength +) { + // Init local variables + DWORD dwCurrentProcessorNumber = 0; + KAFFINITY kaffinityNew = 0; + KAFFINITY kaffinityOld = 0; + KIRQL kirqlOld = 0; + QWORD qwCr4Value = 0; + BOOLEAN bCetDisabled = false; + QWORD qwCr0Value = 0; + BOOLEAN bWpDisabled = false; + + // Get the current processor number + dwCurrentProcessorNumber = KeGetCurrentProcessorNumber(); + + // Set a processor affinity mask for the current thread + // This will lock the current thread to the logical processor it is running on (CPU pinning) + kaffinityNew = __ll_lshift(0x1, static_cast(dwCurrentProcessorNumber)); + kaffinityOld = KeSetSystemAffinityThreadEx(kaffinityNew); + + // Raise the Interrupt Request Level (IRQL) of the current processor to the highest level + // This will prevent the thread scheduler from preempting the current thread + KeRaiseIrql(HIGH_LEVEL, &kirqlOld); + + // Disable interrupts by clearing the RFLAGS.IF bit + // This will prevent the current processor from servicing any maskable interrupts + _disable(); // CLI - Clear Interrupt Flag + + // Read the CR4 register value + qwCr4Value = __readcr4(); + + // Clear the CR4.CET bit + // CR0.WP cannot be cleared as long as CR4.CET is set + // WARNING: HyperGuard / Secure Kernel Patch Guard (SKPG) intercepts writes to CR0 and CR4 registers + bCetDisabled = _bittestandreset64(reinterpret_cast(&qwCr4Value), 23LL); + __writecr4(qwCr4Value); + + // Read the CR0 register value + qwCr0Value = __readcr0(); + + // Clear the CR0.WP bit + // When clear, it allows kernel-mode code to write into read-only pages + bWpDisabled = _bittestandreset64(reinterpret_cast(&qwCr0Value), 16LL); + __writecr0(qwCr0Value); + + // Write into RO pages + __movsb(static_cast(pDestination), static_cast(pcSource), dwptrLength); + + // Set the CR0.WP bit if it was cleared before + // When set, it inhibits kernel-mode code from writing into read-only pages + if (bWpDisabled) { + _bittestandset64(reinterpret_cast(&qwCr0Value), 16LL); + __writecr0(qwCr0Value); + } + + // Set the CR4.CET bit if it was cleared before + // This flag can be set only if CR0.WP is set + if (bCetDisabled) { + _bittestandset64(reinterpret_cast(&qwCr4Value), 23LL); + __writecr4(qwCr4Value); + } + + // Enable interrupts by setting the RFLAGS.IF bit + _enable(); // STI - Set Interrupt Flag + + // Restore the original IRQL of the current processor + KeLowerIrql(kirqlOld); + + // Restore the original processor affinity mask for the current thread + KeRevertToUserAffinityThreadEx(kaffinityOld); +} + +_Use_decl_annotations_ +VOID __stdcall copy_memory_double_mapping( + VOID* pDestination, + CONST VOID* pcSource, + DWORD_PTR dwptrLength +) { + // Init local variables + PMDL pMdl = nullptr; + BOOLEAN bLocked = false; + PVOID pBufferKva = nullptr; + NTSTATUS status = STATUS_SUCCESS; + + // Allocate a MDL for the read-only destination memory block in KVAS + pMdl = IoAllocateMdl(pDestination, static_cast(dwptrLength), false, false, nullptr); + if (pMdl == nullptr) { + goto cleanup; + } + + // Lock the pages in the MDL + __try { + MmProbeAndLockPages(pMdl, KernelMode, IoReadAccess); + } __except (EXCEPTION_EXECUTE_HANDLER) { + goto cleanup; + } + + bLocked = true; + + // Map the locked pages into KVAS effectively creating a second read-write mapping of the same physical memory + pBufferKva = MmMapLockedPagesSpecifyCache(pMdl, KernelMode, MmCached, nullptr, false, HighPagePriority | MdlMappingNoExecute); + if (pBufferKva == nullptr) { + goto cleanup; + } + + // Set the protection type for the double mapping to RW + status = MmProtectMdlSystemAddress(pMdl, PAGE_READWRITE); + if (!NT_SUCCESS(status)) { + goto cleanup; + } + + // Write into RW pages + // WARNING: If the page is protected by KDP then it will be marked as read-only in the SLAT entry + __movsb(static_cast(pBufferKva), static_cast(pcSource), dwptrLength); + + // Cleanup +cleanup: + // Release the double mapping + if (pBufferKva) + MmUnmapLockedPages(pBufferKva, pMdl); + + // Unlock the pages in the MDL + if (bLocked) + MmUnlockPages(pMdl); + + // Release the MDL + if (pMdl) + IoFreeMdl(pMdl); +} + +#pragma endregion \ No newline at end of file