> Windows Syscalls
ntoskrnl.exeT1112T1070.001T1070.009

NtDeleteKey

Deletes a registry key when the handle is closed — used to wipe persistence and audit-key artefacts post-execution.

Prototype

NTSTATUS NtDeleteKey(
  HANDLE KeyHandle
);

Arguments

NameTypeDirDescription
KeyHandleHANDLEinHandle previously opened with the DELETE access right (or KEY_ALL_ACCESS).

Syscall IDs by Windows version

Windows versionSyscall IDBuild
Win10 15070xC5win10-1507
Win10 16070xC8win10-1607
Win10 17030xCBwin10-1703
Win10 17090xCCwin10-1709
Win10 18030xCDwin10-1803
Win10 18090xCEwin10-1809
Win10 19030xCFwin10-1903
Win10 19090xCFwin10-1909
Win10 20040xD3win10-2004
Win10 20H20xD3win10-20h2
Win10 21H10xD3win10-21h1
Win10 21H20xD4win10-21h2
Win10 22H20xD4win10-22h2
Win11 21H20xD9win11-21h2
Win11 22H20xDAwin11-22h2
Win11 23H20xDAwin11-23h2
Win11 24H20xDCwin11-24h2
Server 20160xC8winserver-2016
Server 20190xCEwinserver-2019
Server 20220xD8winserver-2022
Server 20250xDCwinserver-2025

Kernel module

ntoskrnl.exeNtDeleteKey

Related APIs

RegDeleteKeyWRegDeleteKeyExWRegDeleteTreeWNtDeleteValueKeyNtEnumerateKeyNtClose

Syscall stub

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

Single-argument syscall, but with a subtle semantic: the key is *marked* for deletion and is actually removed only when the handle is closed — concurrent readers retain access until they release their own handles. The key must be **leaf** (no subkeys) or the call returns `STATUS_CANNOT_DELETE`; defense-evasion tooling that wipes a tree must recurse depth-first, calling `NtEnumerateKey` and `NtDeleteKey` from the bottom up. SSN drifts substantially across builds (0xC5 on 1507 → 0xDC on 24H2), so dynamic resolution is mandatory.

Common malware usage

Pure **defense evasion (T1112 + T1070)**. Two recurring patterns. (1) **Persistence cleanup post-execution**: once an implant has staged its payload or no longer needs the autorun, it removes its own Run / RunOnce / Image File Execution Options / scheduled-task-cache registry footprint to break forensic reconstruction. Self-destruct stubs typically issue `NtDeleteKey` on `\Registry\Machine\Software\Microsoft\Windows\CurrentVersion\Run\<implant>` and on the COM CLSID hijack key they planted earlier. (2) **Indirect Event Log tampering (T1070.001)**: while the canonical attack is `wevtutil cl` or service-stop on EventLog, several stealers (LummaC2 variants, Atomic Stealer's Win port) instead null the audit subscription keys under `\Registry\Machine\System\CurrentControlSet\Services\EventLog\Security\Channels\*` via NtDeleteKey, breaking subsequent logging without raising the audible Event-ID 1102 (audit log cleared).

Detection opportunities

Microsoft-Windows-Kernel-Registry ETW emits a registry-delete event with full path and PID. Sysmon Event ID 12 with type=DeleteKey is the direct catch. Critically, an EDR registering `CmRegisterCallbackEx` receives `RegNtPreDeleteKey` and `RegNtPostDeleteKey` notifications even when user-mode hooks are bypassed by a direct syscall — kernel callbacks see the operation post-dispatch. High-value hunts: deletion of any path under `\Microsoft\Windows\CurrentVersion\Run*`, `\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\*`, `\Microsoft\Windows NT\CurrentVersion\Winlogon\Notify\*`, or `\Services\EventLog\*`. Defender's tamper-protection list flags writes/deletes against AV-relevant registry trees; correlation with the original CreateKey/SetValue from the same SHA-256 image hash provides an end-to-end staging-then-cleanup graph.

Direct syscall examples

cRemove our own Run-key persistence entry

// Open the value's parent key with DELETE access, then NtDeleteKey on the leaf.
UNICODE_STRING name;
RtlInitUnicodeString(&name,
    L"\\Registry\\Machine\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\Updater");

OBJECT_ATTRIBUTES oa;
InitializeObjectAttributes(&oa, &name, OBJ_CASE_INSENSITIVE, NULL, NULL);

HANDLE hKey = NULL;
if (NT_SUCCESS(NtOpenKey(&hKey, DELETE, &oa))) {
    NtDeleteKey(hKey);     // marks for deletion
    NtClose(hKey);          // actually deletes
}

asmDirect stub (SSN 0xDC, Win11 24H2)

; SSN drifts heavily; resolve dynamically before targeting multiple builds.
NtDeleteKey PROC
    mov  r10, rcx
    mov  eax, 0DCh         ; Win11 24H2
    syscall
    ret
NtDeleteKey ENDP

rustDepth-first key-tree wipe helper

use ntapi::ntregapi::{NtDeleteKey, NtEnumerateKey, NtOpenKey, KEY_BASIC_INFORMATION};
use winapi::shared::ntdef::HANDLE;

pub unsafe fn delete_tree(h_root: HANDLE) {
    let mut buf = vec![0u8; 0x400];
    let mut len = 0u32;
    loop {
        let s = NtEnumerateKey(
            h_root, 0,
            0 /* KeyBasicInformation */,
            buf.as_mut_ptr() as *mut _, buf.len() as u32, &mut len);
        if s != 0 { break; }
        let bi = &*(buf.as_ptr() as *const KEY_BASIC_INFORMATION);
        // open subkey here, recurse, then delete the leaf
    }
    NtDeleteKey(h_root);
}

MITRE ATT&CK mappings

Last verified: 2026-05-20