NtAllocateUserPhysicalPages
Allocates physical memory pages for use with Address Windowing Extensions (AWE).
Prototype
NTSTATUS NtAllocateUserPhysicalPages( HANDLE ProcessHandle, PULONG_PTR NumberOfPages, PULONG_PTR UserPfnArray );
Arguments
| Name | Type | Dir | Description |
|---|---|---|---|
| ProcessHandle | HANDLE | in | Handle to the process that will own the physical pages. Usually NtCurrentProcess(). |
| NumberOfPages | PULONG_PTR | in/out | On input: number of physical pages requested. On output: number actually allocated. |
| UserPfnArray | PULONG_PTR | out | Caller-supplied array that receives an opaque page-frame identifier per allocated page. |
Syscall IDs by Windows version
| Windows version | Syscall ID | Build |
|---|---|---|
| Win10 1507 | 0x71 | win10-1507 |
| Win10 1607 | 0x71 | win10-1607 |
| Win10 1703 | 0x72 | win10-1703 |
| Win10 1709 | 0x72 | win10-1709 |
| Win10 1803 | 0x72 | win10-1803 |
| Win10 1809 | 0x72 | win10-1809 |
| Win10 1903 | 0x72 | win10-1903 |
| Win10 1909 | 0x72 | win10-1909 |
| Win10 2004 | 0x73 | win10-2004 |
| Win10 20H2 | 0x73 | win10-20h2 |
| Win10 21H1 | 0x73 | win10-21h1 |
| Win10 21H2 | 0x73 | win10-21h2 |
| Win10 22H2 | 0x73 | win10-22h2 |
| Win11 21H2 | 0x73 | win11-21h2 |
| Win11 22H2 | 0x73 | win11-22h2 |
| Win11 23H2 | 0x73 | win11-23h2 |
| Win11 24H2 | 0x75 | win11-24h2 |
| Server 2016 | 0x71 | winserver-2016 |
| Server 2019 | 0x72 | winserver-2019 |
| Server 2022 | 0x73 | winserver-2022 |
| Server 2025 | 0x75 | winserver-2025 |
Kernel module
Related APIs
Syscall stub
4C 8B D1 mov r10, rcx B8 75 00 00 00 mov eax, 0x75 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
Part of the Address Windowing Extensions (AWE) API family. AWE lets a process reserve a fixed virtual-address window and then map and remap arbitrary physical pages into it on demand — bypassing the Windows working-set and pagefile entirely. The opaque PFN values returned in UserPfnArray are *not* raw PFNs; they are kernel-side handles that can only be consumed by NtMapUserPhysicalPages and NtFreeUserPhysicalPages on the same process. The caller must hold **SeLockMemoryPrivilege** (usually only granted to LocalSystem and explicitly-configured service accounts), which is the practical gate that keeps AWE out of low-privilege malware.
Common malware usage
Three notable abuses. First, **non-pageable code stash**: pages allocated through AWE are never swapped to the pagefile, so a payload mapped through NtMapUserPhysicalPages stays out of pagefile.sys forever — defeating dead-disk forensics that recover RWX shellcode from a swap dump. Second, **kernel-R/W primitive amplification**: when paired with a vulnerable signed driver that exposes an arbitrary physical-memory read/write, the AWE PFNs let an exploit chain alias kernel-page contents into a user-mode window, turning a one-shot read into a persistent mapping. Third, **AV-evasion via unconventional VAD types**: AWE allocations show up as a `VadAwe` VAD subtype that some scanners ignore. SeLockMemoryPrivilege is the bottleneck; samples that need AWE usually pivot through a SYSTEM service first or chain a token-impersonation primitive that the privilege survives.
Detection opportunities
AWE usage outside SQL Server, Exchange, Oracle, and a handful of HPC frameworks is extremely rare. Event-log–based detection: monitor for `SeLockMemoryPrivilege` being added to a non-service account in Local Security Policy, and for token-adjustment events (Sysmon Event ID 4673 if subcategory auditing is enabled) on `SeLockMemoryPrivilege`. Kernel-side, the VAD-walk will surface `VadAwe` entries in unexpected processes (notepad.exe with an AWE region is wildly anomalous). EDRs with memory-scanner integration should treat MEM_PHYSICAL VirtualAlloc reservations in non-database processes as high-severity.
Direct syscall examples
cAWE allocation gated on SeLockMemoryPrivilege
// Enable SeLockMemoryPrivilege, then allocate physical pages and reserve a
// virtual address window for mapping.
#include <windows.h>
BOOL EnableLockMemoryPrivilege(void) {
HANDLE hTok;
TOKEN_PRIVILEGES tp;
if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hTok)) return FALSE;
LookupPrivilegeValueA(NULL, "SeLockMemoryPrivilege", &tp.Privileges[0].Luid);
tp.PrivilegeCount = 1;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
BOOL ok = AdjustTokenPrivileges(hTok, FALSE, &tp, 0, NULL, NULL) && GetLastError() == ERROR_SUCCESS;
CloseHandle(hTok);
return ok;
}
void awe_alloc(SIZE_T pages) {
EnableLockMemoryPrivilege();
ULONG_PTR request = pages;
ULONG_PTR *pfn = (ULONG_PTR*)HeapAlloc(GetProcessHeap(), 0, pages * sizeof(ULONG_PTR));
if (AllocateUserPhysicalPages(GetCurrentProcess(), &request, pfn)) {
SIZE_T window = pages * 4096;
PVOID view = VirtualAlloc(NULL, window,
MEM_RESERVE | MEM_PHYSICAL, PAGE_READWRITE);
MapUserPhysicalPages(view, request, pfn);
// 'view' now aliases the physical pages; remap on demand.
}
}asmx64 direct stub (Win11 24H2, SSN 0x75)
NtAllocateUserPhysicalPages PROC
mov r10, rcx
mov eax, 75h
syscall
ret
NtAllocateUserPhysicalPages ENDPMITRE ATT&CK mappings
Last verified: 2026-05-20