NtUnlockFile
Libera un bloqueo de rango de bytes previamente adquirido sobre un archivo abierto.
Prototipo
NTSTATUS NtUnlockFile( HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PLARGE_INTEGER ByteOffset, PLARGE_INTEGER Length, ULONG Key );
Argumentos
| Name | Type | Dir | Description |
|---|---|---|---|
| FileHandle | HANDLE | in | Handle al archivo abierto que posee el bloqueo. Debe ser el mismo handle que lo adquirió. |
| IoStatusBlock | PIO_STATUS_BLOCK | out | Recibe el NTSTATUS final e Information (siempre 0 para NtUnlockFile). |
| ByteOffset | PLARGE_INTEGER | in | Desplazamiento inicial en bytes del rango a liberar. Debe coincidir exactamente con la llamada NtLockFile original. |
| Length | PLARGE_INTEGER | in | Longitud en bytes del rango a liberar. Debe coincidir exactamente con el NtLockFile original. |
| Key | ULONG | in | Cookie que identifica qué bloqueo liberar entre los solapados retenidos por el mismo handle. |
IDs de syscalls por versión de Windows
| Versión de Windows | ID de syscall | Build |
|---|---|---|
| Win10 1507 | 0x1AD | win10-1507 |
| Win10 1607 | 0x1B6 | win10-1607 |
| Win10 1703 | 0x1BC | win10-1703 |
| Win10 1709 | 0x1C0 | win10-1709 |
| Win10 1803 | 0x1C2 | win10-1803 |
| Win10 1809 | 0x1C3 | win10-1809 |
| Win10 1903 | 0x1C4 | win10-1903 |
| Win10 1909 | 0x1C4 | win10-1909 |
| Win10 2004 | 0x1CA | win10-2004 |
| Win10 20H2 | 0x1CA | win10-20h2 |
| Win10 21H1 | 0x1CA | win10-21h1 |
| Win10 21H2 | 0x1CC | win10-21h2 |
| Win10 22H2 | 0x1CC | win10-22h2 |
| Win11 21H2 | 0x1D6 | win11-21h2 |
| Win11 22H2 | 0x1DA | win11-22h2 |
| Win11 23H2 | 0x1DA | win11-23h2 |
| Win11 24H2 | 0x1DD | win11-24h2 |
| Server 2016 | 0x1B6 | winserver-2016 |
| Server 2019 | 0x1C3 | winserver-2019 |
| Server 2022 | 0x1D2 | winserver-2022 |
| Server 2025 | 0x1DD | winserver-2025 |
Módulo del kernel
APIs relacionadas
Stub del syscall
4C 8B D1 mov r10, rcx B8 DD 01 00 00 mov eax, 0x1DD 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
Espejo de NtLockFile. El rango a desbloquear debe coincidir *exactamente* con uno previamente adquirido — no hay desbloqueo de solape parcial, ni atajo de "desbloquearlo todo". Una llamada con un rango no coincidente devuelve STATUS_RANGE_NOT_LOCKED. Los bloqueos también se liberan implícitamente al cerrar el FileHandle (NtClose), que es como ocurre casi toda liberación legítima — el NtUnlockFile explícito lo usan sobre todo motores de base de datos de larga duración y aplicaciones Office que liberan bloqueos por registro manteniendo el fichero abierto.
Uso común por malware
Por sí solo, NtUnlockFile es poco interesante — es simplemente la mitad de limpieza de un abuso de NtLockFile. En la práctica aparece como paso final de dos patrones: (a) el stub anti-análisis auto-bloqueado desbloquea antes de salir para que un desinstalador / updater pueda reescribir el binario limpio, y (b) un ransomware que bloqueó la nota durante el cifrado desbloquea una vez completada la fase para que la víctima pueda leerla. Señal honesta: prácticamente no hay informes públicos que conviertan a NtUnlockFile en un IOC discriminante por sí solo, y la mayoría de los ransomware omiten el desbloqueo explícito y simplemente cierran el handle.
Oportunidades de detección
Perfil de volumen y narrativa de detección idénticos a NtLockFile — mismo proveedor ETW, mismo punto de hook minifilter FltMgr (IRP_MJ_LOCK_CONTROL cubre lock y unlock). El valor blue-team de las alertas NtUnlockFile es bajo; el valor está en la *correlación* con un NtLockFile precedente sobre un objetivo sensible. Los EDR que ya alertan en el evento lock no ganan mucho alertando también en el unlock. Vale la pena rastrearlo en investigación post-incidente de ransomware: una ráfaga súbita de NtUnlockFile contra muchos handles de documentos del usuario correlaciona con el final del barrido de cifrado.
Ejemplos de syscalls directos
cRelease a whole-file lock before exit
// Pair-of-pair: matches the NtLockFile call that took the whole-file lock.
IO_STATUS_BLOCK iosb;
LARGE_INTEGER off = { .QuadPart = 0 };
LARGE_INTEGER len = { .QuadPart = 0x7FFFFFFFFFFFFFFFLL };
NTSTATUS st = NtUnlockFile(hSelf, &iosb, &off, &len, 0);
if (st == STATUS_RANGE_NOT_LOCKED) {
// Some other code already unlocked it (or the offsets don't match).
}asmx64 direct stub (Win11 24H2 SSN 0x1DD)
NtUnlockFile PROC
mov r10, rcx
mov eax, 1DDh
syscall
ret
NtUnlockFile ENDPrustRelease ransom-note lock after encryption sweep
// Cargo: ntapi = "0.4"
unsafe {
let off: i64 = 0;
let len: i64 = i64::MAX;
let mut iosb = zeroed();
let st = NtUnlockFile(h_note,
&mut iosb,
&off as *const _ as _,
&len as *const _ as _,
0);
// STATUS_RANGE_NOT_LOCKED is benign here — handle close would have done it anyway.
let _ = st;
}Mapeos MITRE ATT&CK
Last verified: 2026-05-20