NtUnlockFile
Releases a previously-acquired byte-range lock on an open file.
Prototype
NTSTATUS NtUnlockFile( HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PLARGE_INTEGER ByteOffset, PLARGE_INTEGER Length, ULONG Key );
Arguments
| Name | Type | Dir | Description |
|---|---|---|---|
| FileHandle | HANDLE | in | Handle to the open file that owns the lock. Must be the same handle that acquired it. |
| IoStatusBlock | PIO_STATUS_BLOCK | out | Receives final NTSTATUS and Information (always 0 for NtUnlockFile). |
| ByteOffset | PLARGE_INTEGER | in | Starting byte offset of the range to release. Must exactly match the original NtLockFile call. |
| Length | PLARGE_INTEGER | in | Length in bytes of the range to release. Must exactly match the original NtLockFile. |
| Key | ULONG | in | Cookie that identifies which lock to release among overlapping locks held by the same handle. |
Syscall IDs by Windows version
| Windows version | Syscall ID | 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 |
Kernel module
Related APIs
Syscall stub
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
Undocumented notes
Mirror of NtLockFile. The unlock range must *exactly* match a previously-acquired range — there is no partial-overlap unlock, no "unlock everything" shortcut. Calling with a non-matching range returns STATUS_RANGE_NOT_LOCKED. Locks are also released implicitly when the FileHandle is closed (NtClose), which is how almost every legitimate unlock happens — explicit NtUnlockFile is mostly used by long-running database engines and Office applications that release per-record locks while keeping the file open.
Common malware usage
On its own, NtUnlockFile is uninteresting — it's just the cleanup half of NtLockFile abuse. In practice it appears as the closing step of two patterns: (a) the self-locking anti-analysis stub unlocks before exiting so that an uninstaller / updater can rewrite the binary cleanly, and (b) ransomware that locked its ransom-note during encryption unlocks once the encryption phase completes so the victim can read it. Honest signal: there is essentially no public reporting of NtUnlockFile being a discriminating IOC by itself, and most ransomware skips the explicit unlock and just closes the handle.
Detection opportunities
Volume profile and detection story are identical to NtLockFile — same ETW provider, same FltMgr minifilter hook point (IRP_MJ_LOCK_CONTROL covers both lock and unlock). The blue-team value of NtUnlockFile alerts is low; the value is in *correlation* with a preceding NtLockFile on a sensitive target. EDRs that already alert on the lock event don't gain much by also alerting on the unlock. Worth tracking when investigating ransomware artifacts post-incident: a sudden burst of NtUnlockFile against many user-document handles correlates with the end of the encryption sweep.
Direct syscall examples
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;
}MITRE ATT&CK mappings
Last verified: 2026-05-20