> Windows Syscalls
ntoskrnl.exeT1027.011T1027T1106

NtLockVirtualMemory

Fija una región de memoria virtual en el working set del proceso para que sus páginas no sean paginadas.

Prototipo

NTSTATUS NtLockVirtualMemory(
  HANDLE  ProcessHandle,
  PVOID  *BaseAddress,
  PSIZE_T RegionSize,
  ULONG   MapType
);

Argumentos

NameTypeDirDescription
ProcessHandleHANDLEinHandle al proceso objetivo. Casi siempre NtCurrentProcess() ((HANDLE)-1); bloquear memoria de otro proceso requiere PROCESS_VM_OPERATION.
BaseAddressPVOID*in/outPuntero a la base de la región a bloquear. Ajustado a la base alineada a página al retornar.
RegionSizePSIZE_Tin/outPuntero al tamaño en bytes. Redondeado a un múltiplo del tamaño de página al retornar.
MapTypeULONGinTipo de bloqueo: MAP_PROCESS (1) mantiene páginas en el working set (semántica VirtualLock); MAP_SYSTEM (2) requiere SeLockMemoryPrivilege y fija páginas en RAM física a nivel kernel.

IDs de syscalls por versión de Windows

Versión de WindowsID de syscallBuild
Win10 15070xFCwin10-1507
Win10 16070x101win10-1607
Win10 17030x105win10-1703
Win10 17090x106win10-1709
Win10 18030x107win10-1803
Win10 18090x107win10-1809
Win10 19030x108win10-1903
Win10 19090x108win10-1909
Win10 20040x10Dwin10-2004
Win10 20H20x10Dwin10-20h2
Win10 21H10x10Dwin10-21h1
Win10 21H20x10Ewin10-21h2
Win10 22H20x10Ewin10-22h2
Win11 21H20x114win11-21h2
Win11 22H20x115win11-22h2
Win11 23H20x115win11-23h2
Win11 24H20x117win11-24h2
Server 20160x101winserver-2016
Server 20190x107winserver-2019
Server 20220x113winserver-2022
Server 20250x117winserver-2025

Módulo del kernel

ntoskrnl.exeNtLockVirtualMemory

APIs relacionadas

VirtualLockVirtualUnlockNtUnlockVirtualMemoryNtAllocateVirtualMemoryNtProtectVirtualMemoryAllocateUserPhysicalPages

Stub del syscall

4C 8B D1            mov r10, rcx
B8 17 01 00 00      mov eax, 0x117
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

Notas no documentadas

La implementación kernel detrás de `VirtualLock` (MAP_PROCESS) y la cohorte raramente usada `AllocateUserPhysicalPages` (MAP_SYSTEM). Con MAP_PROCESS, el kernel aumenta el mínimo del working set del proceso para que el rango bloqueado permanezca residente — al contrario de la creencia común, las páginas todavía pueden paginarse si la cuota del working set es excedida después por otras asignaciones. MAP_SYSTEM es lo que realmente fija páginas a frames físicos, pero requiere SeLockMemoryPrivilege (que por defecto sólo LocalSystem posee). El SSN deriva modestamente (`0x108` Win10 1903, `0x10D` 2004, `0x117` Win11 24H2 / Server 2025), así que la resolución estilo Hell's Gate es recomendada.

Uso común por malware

El uso malware estrella es en diseños de **sleep-mask** como Ekko, Foliage, variantes tipo FOLIAGE y la familia UDRL `Sleep_Mask` de Cobalt Strike. El patrón: cifrar en sitio la región `.text`/heap del beacon, dormir N segundos, descifrar al despertar. Si la región cifrada se pagina durante el sueño, el OS puede escribir el cifrado al pagefile *y* refaultearlo ruidosamente al despertar — derrotando tanto el sigilo como el timing. `NtLockVirtualMemory(MAP_PROCESS)` mantiene la región en el working set para que el ciclo cifrar-dormir-descifrar quede predecible y silencioso. Uso secundario: packers custom que bloquean la región OEP desempacada para vencer escáneres de introspección de memoria basados en page-fault (p. ej. detección estilo Pafish que carrerea el scan EDR contra el timing de pagefault). Nota: MAP_SYSTEM es esencialmente inalcanzable desde código user no SYSTEM, así que el camino malware-relevante es siempre MAP_PROCESS.

Oportunidades de detección

VirtualLock por sí solo es extremadamente raro en software user-mode legítimo (Adobe Reader para material de clave de licencia, KeePass para buffers de secretos, OpenSSL FIPS para claves, y eso es aproximadamente toda la población). Un binario no firmado que llama a NtLockVirtualMemory sobre una región RWX recién `NtAllocateVirtualMemory`-da, luego `NtDelayExecution`-ea varios segundos, luego desbloquea — ese patrón es mucho más raro en código benigno que en sleep-masks Ekko / Foliage. ETW Threat Intelligence *no* expone eventos de bloqueo nativamente; la señal más confiable es hookear el thunk user-mode `VirtualLock` y correlacionar con flags de asignación y módulo del call-site. Los EDR que hacen scans periódicos del working set (CrowdStrike, MDE) efectivamente derrotan el truco de ocultación del sleep-mask porque leen los bytes de la región sin importar si están bloqueados.

Ejemplos de syscalls directos

asmx64 stub (Win11 24H2 SSN 0x117)

; Direct syscall stub for NtLockVirtualMemory
NtLockVirtualMemory PROC
    mov  r10, rcx          ; syscall convention
    mov  eax, 117h         ; SSN (Win11 24H2 / Server 2025)
    syscall
    ret
NtLockVirtualMemory ENDP

cEkko-style sleep-mask page pin

// Sleep-mask skeleton: lock the encrypted region so the kernel can't
// swap ciphertext to disk while we wait on a timer-queue or APC.
#include <windows.h>

typedef NTSTATUS (NTAPI *pNtLockVirtualMemory)(HANDLE, PVOID*, PSIZE_T, ULONG);
typedef NTSTATUS (NTAPI *pNtUnlockVirtualMemory)(HANDLE, PVOID*, PSIZE_T, ULONG);

#define MAP_PROCESS 1

void SleepWithLockedRegion(PVOID base, SIZE_T size, DWORD ms) {
    HMODULE n = GetModuleHandleA("ntdll.dll");
    pNtLockVirtualMemory   NtLock   = (pNtLockVirtualMemory)  GetProcAddress(n, "NtLockVirtualMemory");
    pNtUnlockVirtualMemory NtUnlock = (pNtUnlockVirtualMemory)GetProcAddress(n, "NtUnlockVirtualMemory");

    PVOID  b = base;
    SIZE_T s = size;
    NtLock((HANDLE)-1, &b, &s, MAP_PROCESS);
    // ... XOR-encrypt b[0..s] in place ...
    Sleep(ms);
    // ... XOR-decrypt b[0..s] in place ...
    NtUnlock((HANDLE)-1, &b, &s, MAP_PROCESS);
}

rustPin a secret buffer (KeePass-style)

// Defensive use: keep a derived key out of the pagefile.
use windows_sys::Win32::System::Memory::{VirtualLock, VirtualUnlock};

pub struct PinnedSecret { ptr: *mut u8, len: usize }

impl PinnedSecret {
    pub fn new(buf: &mut [u8]) -> std::io::Result<Self> {
        let ptr = buf.as_mut_ptr();
        let len = buf.len();
        if unsafe { VirtualLock(ptr as _, len) } == 0 {
            return Err(std::io::Error::last_os_error());
        }
        Ok(Self { ptr, len })
    }
}

impl Drop for PinnedSecret {
    fn drop(&mut self) {
        unsafe {
            std::ptr::write_bytes(self.ptr, 0, self.len);
            VirtualUnlock(self.ptr as _, self.len);
        }
    }
}

Mapeos MITRE ATT&CK

Last verified: 2026-05-20