> Windows Syscalls
ntoskrnl.exeT1055T1106T1070

NtDeleteAtom

Decrements the reference count of a global atom and removes it when the count reaches zero.

Prototype

NTSTATUS NtDeleteAtom(
  RTL_ATOM Atom
);

Arguments

NameTypeDirDescription
AtomRTL_ATOMin16-bit atom ID returned by NtAddAtom / NtFindAtom. Must be in the user range 0xC000-0xFFFF for global user atoms.

Syscall IDs by Windows version

Windows versionSyscall IDBuild
Win10 15070xC1win10-1507
Win10 16070xC4win10-1607
Win10 17030xC7win10-1703
Win10 17090xC8win10-1709
Win10 18030xC9win10-1803
Win10 18090xCAwin10-1809
Win10 19030xCBwin10-1903
Win10 19090xCBwin10-1909
Win10 20040xCFwin10-2004
Win10 20H20xCFwin10-20h2
Win10 21H10xCFwin10-21h1
Win10 21H20xD0win10-21h2
Win10 22H20xD0win10-22h2
Win11 21H20xD5win11-21h2
Win11 22H20xD6win11-22h2
Win11 23H20xD6win11-23h2
Win11 24H20xD8win11-24h2
Server 20160xC4winserver-2016
Server 20190xCAwinserver-2019
Server 20220xD4winserver-2022
Server 20250xD8winserver-2025

Kernel module

ntoskrnl.exeNtDeleteAtom

Related APIs

DeleteAtomGlobalDeleteAtomNtAddAtomNtFindAtomNtQueryInformationAtom

Syscall stub

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

The mirror of `NtAddAtom`. Each successful AddAtom on an existing name increments a refcount; DeleteAtom decrements it. The atom is only physically removed from the global table when the count hits zero. The Win32 wrappers `GlobalDeleteAtom` and `DeleteAtom` route through here. There is no per-process bookkeeping — any process that knows the atom ID can decrement the count, which is both the convenience of the API and the cleanup hazard. Returns STATUS_INVALID_HANDLE for an ID outside the user-atom range or for a never-added atom.

Common malware usage

**Atom Bombing cleanup**: enSilo's original PoC (Tal Liberman 2016) and the Dridex 2017 adaptation both call NtDeleteAtom on the smuggled-shellcode atom IDs after the target APC has copied the bytes into the victim's address space. Skipping cleanup leaves the shellcode bytes in the global atom table forever (well, until reboot) — a forensic gift for any analyst who runs `!atom` in WinDbg against a live machine, or who diffs the atom table before and after a suspected injection. Outside Atom Bomb cleanup the signal is essentially zero — `NtDeleteAtom` is fired millions of times a day by legitimate apps that register and tear down window classes, DDE topics and IME strings.

Detection opportunities

On its own, NtDeleteAtom is one of the noisiest syscalls on a Windows desktop — Explorer, every WPF/WinForms app, the input stack, DWM, all churn the atom table constantly. The detection pivot is `NtAddAtom` with anomalous (long, binary) names; if you alert on suspicious AddAtom traffic, the matching DeleteAtom comes for free as part of the same flow. WinDbg `!atom` extension can be scripted via LiveKD / lkd to periodically snapshot the global table and diff for entries whose names are not valid UTF-16 or whose byte length exceeds the typical 32-byte window-class size — those are Atom Bomb residuals. ETW Microsoft-Windows-Kernel-Audit-API-Calls covers the call but at very high volume.

Direct syscall examples

asmx64 direct stub (Win11 24H2)

; Direct syscall stub for NtDeleteAtom (SSN 0xD8 on Win11 24H2)
NtDeleteAtom PROC
    mov  r10, rcx          ; syscall convention
    mov  eax, 0D8h         ; SSN — varies per build
    syscall
    ret
NtDeleteAtom ENDP

cAtom Bombing cleanup pass

// After NtQueueApcThread has delivered the GlobalGetAtomName APC into the
// target and the shellcode bytes have landed in the victim's buffer,
// scrub the smuggled atom IDs from the global table.
#include <windows.h>
#include <winternl.h>

typedef NTSTATUS (NTAPI *pNtDeleteAtom)(USHORT);

VOID AtomBombCleanup(const USHORT* ids, ULONG count) {
    pNtDeleteAtom NtDeleteAtom = (pNtDeleteAtom)
        GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtDeleteAtom");
    for (ULONG i = 0; i < count; ++i) {
        // Refcount may be >1 if the OS interned the same string elsewhere
        // — call until the kernel returns STATUS_INVALID_HANDLE.
        while (NtDeleteAtom(ids[i]) == 0) { /* loop */ }
    }
}

rustBenign teardown via Win32 wrapper

// Cargo: windows-sys = "0.59" (Win32_System_DataExchange)
// Symmetric DeleteAtom for a string we registered with GlobalAddAtomW.
use windows_sys::Win32::System::DataExchange::GlobalDeleteAtom;

pub unsafe fn drop_atom(id: u16) {
    // Returns 0 on success — refcount hit zero.
    let _residual = GlobalDeleteAtom(id);
}

MITRE ATT&CK mappings

Last verified: 2026-05-20