NtQueryInformationAtom
Returns metadata about a single atom or the entire global atom table — name, refcount, pin count, usage counts.
Prototype
NTSTATUS NtQueryInformationAtom( RTL_ATOM Atom, ATOM_INFORMATION_CLASS InformationClass, PVOID AtomInformation, ULONG AtomInformationLength, PULONG ReturnLength );
Arguments
| Name | Type | Dir | Description |
|---|---|---|---|
| Atom | RTL_ATOM | in | Atom ID to query. Ignored when InformationClass = AtomTableInformation (whole-table dump). |
| InformationClass | ATOM_INFORMATION_CLASS | in | AtomBasicInformation (per-atom: name + refcount + pin) or AtomTableInformation (atom count + ID list). |
| AtomInformation | PVOID | out | Caller buffer that receives an ATOM_BASIC_INFORMATION or ATOM_TABLE_INFORMATION structure. |
| AtomInformationLength | ULONG | in | Size of AtomInformation in bytes. |
| ReturnLength | PULONG | out | Receives the number of bytes actually written. Used to size a follow-up call when STATUS_BUFFER_TOO_SMALL is returned. |
Syscall IDs by Windows version
| Windows version | Syscall ID | Build |
|---|---|---|
| Win10 1507 | 0x12F | win10-1507 |
| Win10 1607 | 0x135 | win10-1607 |
| Win10 1703 | 0x13A | win10-1703 |
| Win10 1709 | 0x13D | win10-1709 |
| Win10 1803 | 0x13F | win10-1803 |
| Win10 1809 | 0x140 | win10-1809 |
| Win10 1903 | 0x141 | win10-1903 |
| Win10 1909 | 0x141 | win10-1909 |
| Win10 2004 | 0x147 | win10-2004 |
| Win10 20H2 | 0x147 | win10-20h2 |
| Win10 21H1 | 0x147 | win10-21h1 |
| Win10 21H2 | 0x148 | win10-21h2 |
| Win10 22H2 | 0x148 | win10-22h2 |
| Win11 21H2 | 0x14E | win11-21h2 |
| Win11 22H2 | 0x150 | win11-22h2 |
| Win11 23H2 | 0x150 | win11-23h2 |
| Win11 24H2 | 0x152 | win11-24h2 |
| Server 2016 | 0x135 | winserver-2016 |
| Server 2019 | 0x140 | winserver-2019 |
| Server 2022 | 0x14D | winserver-2022 |
| Server 2025 | 0x152 | winserver-2025 |
Kernel module
Related APIs
Syscall stub
4C 8B D1 mov r10, rcx B8 52 01 00 00 mov eax, 0x152 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 read-side counterpart to NtAddAtom. The `AtomBasicInformation` class returns an `ATOM_BASIC_INFORMATION` containing the UsageCount (refcount), Flags (pinned vs ordinary), the NameLength (in bytes), and the actual Name bytes — and as with NtAddAtom, the kernel will hand back whatever raw bytes were stored, regardless of UTF-16 validity. The `AtomTableInformation` class returns the count of atoms followed by an array of all RTL_ATOM IDs — effectively a full enumeration primitive. The Win32 wrappers `GlobalGetAtomNameW` and `GetAtomNameW` are the most common path; covert tooling and the !atom WinDbg extension use the raw syscall.
Common malware usage
**Atom Bombing stage 2**: this syscall is the one that performs the cross-process memory write — the malware queues an APC into the target that calls `GlobalGetAtomNameA`, which internally calls NtQueryInformationAtom; the kernel itself writes the atom 'name' bytes (the smuggled shellcode) into the buffer in the target's address space. Because the write is performed by the kernel on behalf of the target, NtWriteVirtualMemory hooks in usermode see nothing. **Stealth IPC inspection**: red-team and persistence-monitoring tools call AtomTableInformation to dump the entire global atom table and look for binary residue, oversized entries, or known-bad atom names — the same primitive used to detect Atom Bomb residuals defensively can be used offensively to read the live IPC state of legitimate apps (Office DDE topics, IME state, Explorer registered class names).
Detection opportunities
AtomBasicInformation calls happen constantly via GlobalGetAtomName — every drag-and-drop, every COM marshalling round, every DDE conversation. Detection has to focus on context: AtomBasicInformation called from a thread that just resumed via NtTestAlert / KiUserApcDispatcher into a freshly-allocated buffer is the canonical Atom Bomb stage-2 indicator (EDRs deploying the 2017-era Atom Bomb rules look for exactly this). AtomTableInformation is rarer in normal code paths and is a slightly stronger signal — a non-system process calling it is worth pivoting on. WinDbg / forensic snapshot of the global atom table (`!atom`) is the orthogonal blue-team primitive — anything in the table with binary bytes or length > a few hundred is suspect.
Direct syscall examples
asmx64 direct stub (Win11 24H2)
; Direct syscall stub for NtQueryInformationAtom (SSN 0x152 on Win11 24H2)
NtQueryInformationAtom PROC
mov r10, rcx ; syscall convention
mov eax, 152h ; SSN — varies per build
syscall
ret
NtQueryInformationAtom ENDPcAtom Bombing — stage 2 (kernel writes shellcode into target)
// Inside the victim process, the queued APC runs this code.
// GlobalGetAtomNameA -> NtQueryInformationAtom -> kernel copies the atom
// 'name' bytes (which were our shellcode in stage 1) into 'buf'.
#include <windows.h>
VOID StageTwoApc(PVOID context) {
USHORT atomId = (USHORT)(ULONG_PTR)context;
char buf[4096]; // attacker-sized to fit the shellcode
UINT got = GlobalGetAtomNameA(atomId, buf, sizeof(buf));
// Stage 3 (not shown): NtSetContextThread / ROP -> NtProtectVirtualMemory
// flips buf to RWX and pivots execution into it.
(void)got;
}cAtom table enumeration (defensive or offensive)
// Dump every atom in the global table and print its name + refcount.
// Use defensively: hunt for binary residue (Atom Bomb IOC).
// Use offensively: read live IPC state (DDE topics, IME state).
#include <windows.h>
#include <winternl.h>
#include <stdio.h>
typedef struct _ATOM_TABLE_INFORMATION {
ULONG NumberOfAtoms;
USHORT Atoms[1];
} ATOM_TABLE_INFORMATION;
typedef struct _ATOM_BASIC_INFORMATION {
USHORT UsageCount;
USHORT Flags;
USHORT NameLength;
WCHAR Name[1];
} ATOM_BASIC_INFORMATION;
typedef NTSTATUS (NTAPI *pNtQueryInformationAtom)(
USHORT, ULONG, PVOID, ULONG, PULONG);
VOID DumpAtomTable(VOID) {
pNtQueryInformationAtom NtQueryInformationAtom = (pNtQueryInformationAtom)
GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQueryInformationAtom");
BYTE tbuf[8192]; ULONG ret = 0;
if (NtQueryInformationAtom(0, /*AtomTableInformation*/ 1,
tbuf, sizeof(tbuf), &ret) < 0) return;
ATOM_TABLE_INFORMATION* ti = (ATOM_TABLE_INFORMATION*)tbuf;
for (ULONG i = 0; i < ti->NumberOfAtoms; ++i) {
BYTE abuf[1024]; ULONG ar = 0;
if (NtQueryInformationAtom(ti->Atoms[i], /*AtomBasicInformation*/ 0,
abuf, sizeof(abuf), &ar) < 0) continue;
ATOM_BASIC_INFORMATION* ai = (ATOM_BASIC_INFORMATION*)abuf;
wprintf(L"0x%04X rc=%u name=%.*s\n",
ti->Atoms[i], ai->UsageCount, ai->NameLength / 2, ai->Name);
}
}MITRE ATT&CK mappings
Last verified: 2026-05-20