NtMapUserPhysicalPages
Maps AWE-allocated physical pages into a previously reserved virtual address window.
Prototype
NTSTATUS NtMapUserPhysicalPages( PVOID VirtualAddress, ULONG_PTR NumberOfPages, PULONG_PTR UserPfnArray );
Arguments
| Name | Type | Dir | Description |
|---|---|---|---|
| VirtualAddress | PVOID | in | Base of a virtual address window reserved with MEM_RESERVE | MEM_PHYSICAL. |
| NumberOfPages | ULONG_PTR | in | Number of pages to map. Must not exceed the size of the reserved window. |
| UserPfnArray | PULONG_PTR | in | Array of opaque page identifiers from NtAllocateUserPhysicalPages, or NULL to unmap. |
Syscall IDs by Windows version
| Windows version | Syscall ID | Build |
|---|---|---|
| Win10 1507 | 0x101 | win10-1507 |
| Win10 1607 | 0x106 | win10-1607 |
| Win10 1703 | 0x10A | win10-1703 |
| Win10 1709 | 0x10B | win10-1709 |
| Win10 1803 | 0x10C | win10-1803 |
| Win10 1809 | 0x10D | win10-1809 |
| Win10 1903 | 0x10E | win10-1903 |
| Win10 1909 | 0x10E | win10-1909 |
| Win10 2004 | 0x113 | win10-2004 |
| Win10 20H2 | 0x113 | win10-20h2 |
| Win10 21H1 | 0x113 | win10-21h1 |
| Win10 21H2 | 0x114 | win10-21h2 |
| Win10 22H2 | 0x114 | win10-22h2 |
| Win11 21H2 | 0x11A | win11-21h2 |
| Win11 22H2 | 0x11B | win11-22h2 |
| Win11 23H2 | 0x11B | win11-23h2 |
| Win11 24H2 | 0x11D | win11-24h2 |
| Server 2016 | 0x106 | winserver-2016 |
| Server 2019 | 0x10D | winserver-2019 |
| Server 2022 | 0x119 | winserver-2022 |
| Server 2025 | 0x11D | winserver-2025 |
Kernel module
Related APIs
Syscall stub
4C 8B D1 mov r10, rcx B8 1D 01 00 00 mov eax, 0x11D F6 04 25 08 03 FE 7F 01 test byte ptr [0x7FFE0308], 1 75 03 jne short +3 0F 05 syscall C3 ret CD 2E int 2Eh C3 ret
Undocumented notes
NtMapUserPhysicalPages binds physical pages obtained from NtAllocateUserPhysicalPages into the virtual window reserved with `VirtualAlloc(MEM_RESERVE | MEM_PHYSICAL)`. Pass NULL for UserPfnArray to unmap the entire window in one call — a property database engines exploit for fast page eviction. The remapping is essentially a TLB-only operation in the steady state: the physical PFNs are pinned, so there's no IO, no working-set adjustment, no soft fault. That latency profile (sub-microsecond on warm TLBs) is exactly what made AWE attractive for 32-bit SQL Server back when PAE was a workaround for the 2 GB limit.
Common malware usage
Two patterns. First, **page-aliasing trick**: an attacker maps the same physical page into two virtual addresses with different protections (e.g. one window RW, another RX), getting a stable write-XOR-execute split without ever calling NtProtectVirtualMemory — bypassing EDRs that hook protection changes. Second, **swap-resistant payload staging**: pages are AWE-allocated once and remapped through this syscall on each beacon callback, keeping the payload bytes off the pagefile permanently. When combined with a vulnerable driver that exposes physical-memory I/O, the same syscall can splice kernel-page contents (after building the right PFN handle) into a user-mode window for read primitives that survive across calls. Tradecraft requires SeLockMemoryPrivilege as the upstream gate.
Detection opportunities
MEM_PHYSICAL virtual windows are visible in `!vad` as a distinct subtype and via `VirtualQueryEx` (Type contains `MEM_PHYSICAL`). EDRs that don't introspect VAD details will miss the mapping, but the *upstream* NtAllocateUserPhysicalPages call (and the corresponding `SeLockMemoryPrivilege` adjustment) is a much more reliable trigger. Hunt for processes that hold both a MEM_PHYSICAL VirtualAlloc reservation and call NtMapUserPhysicalPages in a loop — that's the page-flipping pattern of a database or a payload-aliasing implant; if the binary isn't sqlservr.exe / exsetup.exe / oracle.exe, it's worth a deeper look. ETW Microsoft-Windows-Kernel-Memory doesn't surface AWE remaps directly; rely on VAD walks at memory-scan time.
Direct syscall examples
cPage-flip remap loop
// Rotate two batches of physical pages through the same virtual window —
// classic AWE pattern used by SQL Server for buffer-pool extension.
#include <windows.h>
void awe_flip(PVOID window, ULONG_PTR pages, PULONG_PTR pfnA, PULONG_PTR pfnB) {
for (;;) {
MapUserPhysicalPages(window, pages, pfnA); // bring batch A into view
// ... work on 'window' ...
MapUserPhysicalPages(window, pages, NULL); // unmap (NULL pfn array)
MapUserPhysicalPages(window, pages, pfnB); // bring batch B into view
// ... work on 'window' ...
MapUserPhysicalPages(window, pages, NULL);
}
}cW^X aliasing without NtProtectVirtualMemory
// Map the same physical pages twice: one writable window, one executable.
// EDR hooks on NtProtectVirtualMemory never fire because protection never changes.
#include <windows.h>
void* alias_wx(PULONG_PTR pfn, ULONG_PTR pages) {
SIZE_T size = pages * 4096;
PVOID wr = VirtualAlloc(NULL, size, MEM_RESERVE | MEM_PHYSICAL, PAGE_READWRITE);
PVOID ex = VirtualAlloc(NULL, size, MEM_RESERVE | MEM_PHYSICAL, PAGE_EXECUTE_READ);
MapUserPhysicalPages(wr, pages, pfn);
MapUserPhysicalPages(ex, pages, pfn);
// Write shellcode via 'wr', execute via 'ex' — same physical memory.
return ex;
}asmx64 direct stub (Win11 24H2, SSN 0x11D)
NtMapUserPhysicalPages PROC
mov r10, rcx
mov eax, 11Dh
syscall
ret
NtMapUserPhysicalPages ENDPMITRE ATT&CK mappings
Last verified: 2026-05-20