NtWriteVirtualMemory
Escribe un búfer del invocador en el espacio de direcciones virtuales de un proceso objetivo.
Prototipo
NTSTATUS NtWriteVirtualMemory( HANDLE ProcessHandle, PVOID BaseAddress, PVOID Buffer, SIZE_T NumberOfBytesToWrite, PSIZE_T NumberOfBytesWritten );
Argumentos
| Name | Type | Dir | Description |
|---|---|---|---|
| ProcessHandle | HANDLE | in | Handle al proceso objetivo con derechos PROCESS_VM_WRITE | PROCESS_VM_OPERATION. |
| BaseAddress | PVOID | in | Dirección virtual de destino en el proceso objetivo. |
| Buffer | PVOID | in | Puntero al búfer fuente en el espacio de direcciones del invocador. |
| NumberOfBytesToWrite | SIZE_T | in | Número de bytes a copiar desde Buffer hacia BaseAddress. |
| NumberOfBytesWritten | PSIZE_T | out | Opcional. Recibe el número de bytes realmente escritos. Puede ser NULL. |
IDs de syscalls por versión de Windows
| Versión de Windows | ID de syscall | Build |
|---|---|---|
| Win10 1507 | 0x3A | win10-1507 |
| Win10 1607 | 0x3A | win10-1607 |
| Win10 1703 | 0x3A | win10-1703 |
| Win10 1709 | 0x3A | win10-1709 |
| Win10 1803 | 0x3A | win10-1803 |
| Win10 1809 | 0x3A | win10-1809 |
| Win10 1903 | 0x3A | win10-1903 |
| Win10 1909 | 0x3A | win10-1909 |
| Win10 2004 | 0x3A | win10-2004 |
| Win10 20H2 | 0x3A | win10-20h2 |
| Win10 21H1 | 0x3A | win10-21h1 |
| Win10 21H2 | 0x3A | win10-21h2 |
| Win10 22H2 | 0x3A | win10-22h2 |
| Win11 21H2 | 0x3A | win11-21h2 |
| Win11 22H2 | 0x3A | win11-22h2 |
| Win11 23H2 | 0x3A | win11-23h2 |
| Win11 24H2 | 0x3A | win11-24h2 |
| Server 2016 | 0x3A | winserver-2016 |
| Server 2019 | 0x3A | winserver-2019 |
| Server 2022 | 0x3A | winserver-2022 |
| Server 2025 | 0x3A | winserver-2025 |
Módulo del kernel
APIs relacionadas
Stub del syscall
4C 8B D1 mov r10, rcx B8 3A 00 00 00 mov eax, 0x3A 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
Implementado por `MmCopyVirtualMemory` en ntoskrnl.exe con `PreviousMode = UserMode`, lo que fuerza un probe-and-lock completo de ambos búferes. La SSN `0x3A` ha permanecido sin cambios en cada build Win10/11 lanzada — la disposición de la tabla simplemente no ha movido esta entrada. El kernel realiza la copia mediante un mapeo transitorio; no requiere que la página destino sea escribible desde userland, solo que los permisos de sección permitan escritura desde el kernel. Por eso la inyección en páginas de solo lectura tiene éxito en muchos casos.
Uso común por malware
El paso `Write` de la cadena clásica OpenProcess → VirtualAllocEx → WriteProcessMemory → CreateRemoteThread. Se usa para depositar shellcode, un PE con IAT ya resuelta o un stub de loader reflexivo en un proceso remoto. Más allá de la inyección de shellcode también se abusa para sobrescribir prólogos de función para inline hooks, parchear `AMSI.dll!AmsiScanBuffer` o `clr.jit!compileMethod` para bypasses AMSI/ETW, y como primitiva de escritura en variantes de PPID spoofing y process doppelgänging. Brute Ratel, Cobalt Strike, Sliver, Qakbot e IcedID dependen de ella en su cadena de loader.
Oportunidades de detección
Las escrituras entre procesos se observan principalmente vía Sysmon Event ID 10 (`ProcessAccess`) cuando la máscara de acceso solicitada contiene `PROCESS_VM_WRITE` (0x0020) o `PROCESS_VM_OPERATION` (0x0008). El proveedor ETW Threat Intelligence también emite notificaciones de escritura para ciertos procesos protegidos. Los EDR hookean `NtWriteVirtualMemory` en ntdll; los syscalls directos eluden el hook pero el kernel sigue actualizando los contadores VAD del objetivo y puede observarse mediante callbacks `PsSetCreateProcessNotifyRoutineEx` si la escritura desencadena una creación de hilo posterior. Las escrituras dirigidas a procesos de otra sesión o que apuntan a `lsass.exe` o binarios de navegador merecen una puntuación elevada.
Ejemplos de syscalls directos
asmx64 direct stub
; Direct syscall stub for NtWriteVirtualMemory (SSN 0x3A, stable across all Win10/11)
NtWriteVirtualMemory PROC
mov r10, rcx ; syscall convention
mov eax, 3Ah ; SSN
syscall
ret
NtWriteVirtualMemory ENDPcRemote shellcode injection
// Write shellcode into a previously-allocated remote region.
SIZE_T written = 0;
NTSTATUS st = NtWriteVirtualMemory(hRemote, remote_base,
shellcode, shellcode_len, &written);
if (!NT_SUCCESS(st) || written != shellcode_len) {
// partial write: typically PAGE_NOACCESS encountered halfway
return st;
}rustHell's Gate indirect syscall
// Cargo: ntapi = "0.4"
use std::arch::asm;
#[unsafe(naked)]
unsafe extern "system" fn nt_write_virtual_memory_stub() {
// SSN 0x3A across every supported build.
asm!(
"mov r10, rcx",
"mov eax, 0x3A",
"syscall",
"ret",
options(noreturn),
);
}Mapeos MITRE ATT&CK
Last verified: 2026-05-20