NtUnlockVirtualMemory
Hebt eine zuvor von NtLockVirtualMemory gesetzte Working-Set-Sperre auf.
Prototyp
NTSTATUS NtUnlockVirtualMemory( HANDLE ProcessHandle, PVOID *BaseAddress, PSIZE_T RegionSize, ULONG MapType );
Argumente
| Name | Type | Dir | Description |
|---|---|---|---|
| ProcessHandle | HANDLE | in | Handle des Zielprozesses. Fast immer NtCurrentProcess() ((HANDLE)-1). |
| BaseAddress | PVOID* | in/out | Zeiger auf die Basis der zu entsperrenden Region. Beim Rückkehren seitenausgerichtet. |
| RegionSize | PSIZE_T | in/out | Zeiger auf die Größe in Bytes. Beim Rückkehren auf ein Vielfaches der Seitengröße aufgerundet. |
| MapType | ULONG | in | Muss dem MapType beim Sperren entsprechen — MAP_PROCESS (1) für die VirtualUnlock-Semantik, MAP_SYSTEM (2) für kernel-verankerte Seiten. |
Syscall-IDs pro Windows-Version
| Windows-Version | Syscall-ID | Build |
|---|---|---|
| Win10 1507 | 0x1AE | win10-1507 |
| Win10 1607 | 0x1B7 | win10-1607 |
| Win10 1703 | 0x1BD | win10-1703 |
| Win10 1709 | 0x1C1 | win10-1709 |
| Win10 1803 | 0x1C3 | win10-1803 |
| Win10 1809 | 0x1C4 | win10-1809 |
| Win10 1903 | 0x1C5 | win10-1903 |
| Win10 1909 | 0x1C5 | win10-1909 |
| Win10 2004 | 0x1CB | win10-2004 |
| Win10 20H2 | 0x1CB | win10-20h2 |
| Win10 21H1 | 0x1CB | win10-21h1 |
| Win10 21H2 | 0x1CD | win10-21h2 |
| Win10 22H2 | 0x1CD | win10-22h2 |
| Win11 21H2 | 0x1D7 | win11-21h2 |
| Win11 22H2 | 0x1DB | win11-22h2 |
| Win11 23H2 | 0x1DB | win11-23h2 |
| Win11 24H2 | 0x1DE | win11-24h2 |
| Server 2016 | 0x1B7 | winserver-2016 |
| Server 2019 | 0x1C4 | winserver-2019 |
| Server 2022 | 0x1D3 | winserver-2022 |
| Server 2025 | 0x1DE | winserver-2025 |
Kernel-Modul
Verwandte APIs
Syscall-Stub
4C 8B D1 mov r10, rcx B8 DE 01 00 00 mov eax, 0x1DE 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
Undokumentierte Hinweise
Das strikte Pendant zu NtLockVirtualMemory. Die SSN driftet bei jedem Major-Release (`0x1C5` Win10 1903, `0x1CB` 2004, `0x1DE` Win11 24H2 / Server 2025). Der Aufruf auf einer nicht gesperrten Region gibt `STATUS_NOT_LOCKED` (0xC000002A) zurück — harmlos; mit nicht passendem `MapType` gibt es `STATUS_INVALID_PARAMETER`. Die Kernel-Implementierung senkt das Working-Set-Minimum wieder ab, lagert die Region aber nicht aktiv aus — sie erlaubt nur den Trim. Praktisch jeder legitime Einsatz ist eins-zu-eins mit einem vorherigen NtLockVirtualMemory gepaart.
Häufige Malware-Nutzung
Die Aufwach-Seite des unter NtLockVirtualMemory dokumentierten Sleep-Mask-Musters. Ekko, Foliage und die Cobalt-Strike-`Sleep_Mask`-Familie nutzen einen `Lock → Verschlüsseln → Schlafen → Entschlüsseln → Unlock`-Zyklus bei jedem Beacon-Callback-Intervall; das passende `Unlock` gibt das Working-Set-Quota frei, damit nachfolgende Allokationen nicht verdrängt werden. Einige Packer entsperren die OEP-Region auch nach einem Watchdog-Timeout, um ihren sichtbaren Working-Set-Fußabdruck zu reduzieren. Für sich allein ist NtUnlockVirtualMemory praktisch nie der Brennpunkt böswilligen Verhaltens — sein Auftreten ist vor allem als *Pairing*-Signal neben einem NtLockVirtualMemory desselben Threads auf derselben Range früher im Trace nützlich.
Erkennungsmöglichkeiten
Das *Paar* erkennen, nicht den einzelnen Aufruf. Die klassische hochsignalbringende Sequenz innerhalb eines Threads: `NtAllocateVirtualMemory(RWX)` → `NtLockVirtualMemory(MAP_PROCESS)` → `NtDelayExecution(lang)` → `NtUnlockVirtualMemory` → ausführen. User-Mode-VirtualUnlock-Thunk hooken und mit der vorherigen VirtualLock-Stelle sowie den Schutzbits der Allokation korrelieren. Memory-Scanner, die Working-Set-Mitgliedschaft über die Zeit snapshotten, können den Lock-dann-Unlock-Puls manchmal erfassen — viele EDRs halten diese Historie aber nicht vor. Wie bei NtLockVirtualMemory existiert kein erstklassiges ETW-Event; Verhaltensverkettung ist der Weg.
Direkte Syscall-Beispiele
asmx64 stub (Win11 24H2 SSN 0x1DE)
; Direct syscall stub for NtUnlockVirtualMemory
NtUnlockVirtualMemory PROC
mov r10, rcx ; syscall convention
mov eax, 1DEh ; SSN (Win11 24H2 / Server 2025)
syscall
ret
NtUnlockVirtualMemory ENDPcWake side of an Ekko-style sleep
// Symmetric counterpart to the SleepWithLockedRegion example: after the
// caller wakes and decrypts in place, unlock so the working-set quota is
// returned and subsequent allocations aren't crowded.
#include <windows.h>
typedef NTSTATUS (NTAPI *pNtUnlockVirtualMemory)(HANDLE, PVOID*, PSIZE_T, ULONG);
#define MAP_PROCESS 1
NTSTATUS UnlockRegion(PVOID base, SIZE_T size) {
pNtUnlockVirtualMemory NtUnlockVirtualMemory = (pNtUnlockVirtualMemory)
GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtUnlockVirtualMemory");
PVOID b = base;
SIZE_T s = size;
return NtUnlockVirtualMemory((HANDLE)-1, &b, &s, MAP_PROCESS);
}rustRAII wrapper that pairs Lock / Unlock
// Cargo: windows-sys = "0.59" (Win32_System_Memory)
use windows_sys::Win32::System::Memory::{VirtualLock, VirtualUnlock};
pub struct Pinned { ptr: *mut u8, len: usize }
impl Pinned {
pub fn new(p: *mut u8, len: usize) -> std::io::Result<Self> {
if unsafe { VirtualLock(p as _, len) } == 0 {
return Err(std::io::Error::last_os_error());
}
Ok(Self { ptr: p, len })
}
}
impl Drop for Pinned {
fn drop(&mut self) {
// VirtualUnlock — underneath, NtUnlockVirtualMemory(MAP_PROCESS).
unsafe { VirtualUnlock(self.ptr as _, self.len); }
}
}MITRE ATT&CK-Mappings
Last verified: 2026-05-20