NtWriteVirtualMemory
Écrit un tampon de l'appelant dans l'espace d'adressage virtuel d'un processus cible.
Prototype
NTSTATUS NtWriteVirtualMemory( HANDLE ProcessHandle, PVOID BaseAddress, PVOID Buffer, SIZE_T NumberOfBytesToWrite, PSIZE_T NumberOfBytesWritten );
Arguments
| Name | Type | Dir | Description |
|---|---|---|---|
| ProcessHandle | HANDLE | in | Handle vers le processus cible avec les droits PROCESS_VM_WRITE | PROCESS_VM_OPERATION. |
| BaseAddress | PVOID | in | Adresse virtuelle de destination dans le processus cible. |
| Buffer | PVOID | in | Pointeur vers le tampon source dans l'espace d'adressage de l'appelant. |
| NumberOfBytesToWrite | SIZE_T | in | Nombre d'octets à copier depuis Buffer vers BaseAddress. |
| NumberOfBytesWritten | PSIZE_T | out | Optionnel. Reçoit le nombre d'octets effectivement écrits. Peut être NULL. |
IDs de syscalls par version de Windows
| Version 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 |
Module noyau
APIs liées
Stub du 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
Notes non documentées
Implémenté par `MmCopyVirtualMemory` dans ntoskrnl.exe avec `PreviousMode = UserMode`, ce qui force un probe-and-lock complet des deux tampons. Le SSN `0x3A` est resté inchangé sur chaque build Win10/11 livré — la disposition de la table n'a tout simplement pas bougé pour cette entrée. Le noyau effectue la copie via un mapping transitoire ; il ne requiert pas que la page cible soit accessible en écriture depuis l'userland, seulement que les permissions de section autorisent l'écriture côté noyau. C'est pourquoi l'injection dans des pages en lecture seule réussit souvent.
Usage courant par les malwares
L'étape `Write` de la chaîne classique OpenProcess → VirtualAllocEx → WriteProcessMemory → CreateRemoteThread. Utilisée pour déposer du shellcode, un PE avec IAT déjà résolue ou un stub de loader réflexif dans un processus distant. Au-delà de l'injection de shellcode, elle est aussi détournée pour réécrire des prologues de fonction afin d'installer des inline hooks, patcher `AMSI.dll!AmsiScanBuffer` ou `clr.jit!compileMethod` pour bypasser AMSI/ETW, et comme primitive d'écriture dans les variantes de PPID-spoofing et de process doppelgänging. Brute Ratel, Cobalt Strike, Sliver, Qakbot et IcedID en dépendent tous dans leur chaîne de loader.
Opportunités de détection
Les écritures inter-processus sont principalement remontées par Sysmon Event ID 10 (`ProcessAccess`) lorsque le masque d'accès contient `PROCESS_VM_WRITE` (0x0020) ou `PROCESS_VM_OPERATION` (0x0008). Le fournisseur ETW Threat Intelligence émet aussi des notifications d'écriture pour certains processus protégés. Les EDR hookent `NtWriteVirtualMemory` dans ntdll ; les syscalls directs contournent le hook, mais le noyau met toujours à jour les compteurs de référence VAD cibles et peut être observé via les callbacks `PsSetCreateProcessNotifyRoutineEx` si l'écriture déclenche une création de thread ultérieure. Les écritures visant des processus d'une autre session, ou ciblant `lsass.exe` ou des binaires de navigateur, méritent un score élevé.
Exemples de syscalls directs
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),
);
}Mappings MITRE ATT&CK
Last verified: 2026-05-20