> Windows Syscalls
ntoskrnl.exeT1622T1106

NtCreateDebugObject

Erzeugt ein Kernel-DebugObject — den Debugger-spezifischen Port für Debug-Events der angehängten Prozesse.

Prototyp

NTSTATUS NtCreateDebugObject(
  PHANDLE            DebugObjectHandle,
  ACCESS_MASK        DesiredAccess,
  POBJECT_ATTRIBUTES ObjectAttributes,
  ULONG              Flags
);

Argumente

NameTypeDirDescription
DebugObjectHandlePHANDLEoutEmpfängt ein Handle auf das neu erzeugte DebugObject. An NtDebugActiveProcess weitergeben.
DesiredAccessACCESS_MASKinZugriffsmaske. DEBUG_ALL_ACCESS (0x1F000F) ist für Debugger üblich.
ObjectAttributesPOBJECT_ATTRIBUTESinObjekt-Attribute. Üblicherweise nullinitialisiert — DebugObjects sind selten benannt.
FlagsULONGinDEBUG_KILL_ON_CLOSE (1) beendet den Debuggee, wenn das letzte Handle des DebugObject geschlossen wird.

Syscall-IDs pro Windows-Version

Windows-VersionSyscall-IDBuild
Win10 15070x9Awin10-1507
Win10 16070x9Bwin10-1607
Win10 17030x9Ewin10-1703
Win10 17090x9Fwin10-1709
Win10 18030xA0win10-1803
Win10 18090xA0win10-1809
Win10 19030xA1win10-1903
Win10 19090xA1win10-1909
Win10 20040xA5win10-2004
Win10 20H20xA5win10-20h2
Win10 21H10xA5win10-21h1
Win10 21H20xA6win10-21h2
Win10 22H20xA6win10-22h2
Win11 21H20xA8win11-21h2
Win11 22H20xA9win11-22h2
Win11 23H20xA9win11-23h2
Win11 24H20xABwin11-24h2
Server 20160x9Bwinserver-2016
Server 20190xA0winserver-2019
Server 20220xA8winserver-2022
Server 20250xABwinserver-2025

Kernel-Modul

ntoskrnl.exeNtCreateDebugObject

Verwandte APIs

DebugActiveProcessDebugActiveProcessStopWaitForDebugEventExContinueDebugEventNtDebugActiveProcessNtRemoveProcessDebugNtDebugContinueNtWaitForDebugEvent

Syscall-Stub

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

Undokumentierte Hinweise

DebugObject ist die Kernel-Primitive hinter der Win32-Debug-API: kernel32!DebugActiveProcess, WaitForDebugEvent und ContinueDebugEvent arbeiten alle über ein solches Objekt. Ein Prozess kann immer nur an genau einen DebugObject angehängt sein — diese Exklusivität ist die tragende Eigenschaft der gesamten Self-Debug-Anti-Attach-Familie. Die SSN wandert mit fast jedem Feature-Update (0x9A → 0xAB von 1507 bis 24H2); statische Tabellen sind unsicher, dynamisch auflösen. Nach der Erzeugung folgt typischerweise NtCreateDebugObject → NtDebugActiveProcess(Ziel, dbgObj) → NtWaitForDebugEvent-Schleife, optional NtRemoveProcessDebug zum Lösen.

Häufige Malware-Nutzung

Self-Debug-Anti-Attach: Die Malware (oder ein mit DEBUG_PROCESS gestarteter Child-Prozess) hängt sich selbst oder ihr geschütztes Kind an einen eigenen DebugObject. Da Windows nur einen Debug-Port pro Prozess erlaubt, scheitert jeder spätere Attach-Versuch von WinDbg, x64dbg oder dem Attach-on-Crash eines EDRs via DebugActiveProcess mit STATUS_PORT_ALREADY_SET. Kommerzielle Protectoren (Themida, VMProtect, Enigma) bringen dieses Muster mit; ebenso die meisten Crypter, GuLoader-artige Loader und von Donut erzeugte Stager. Der Preis: Die Malware muss Debug-Events nun selbst bedienen, und unbehandelte Exceptions des Debuggees landen in dieser Schleife statt bei WER.

Erkennungs­möglichkeiten

Die Erzeugung eines DebugObject durch einen Nicht-Debugger-Prozess ist auf Workstations ungewöhnlich. Der ETW-Provider Microsoft-Windows-Kernel-Object emittiert Object-Create-Events; mit ImageFileName != bekannten Debuggern (windbg.exe, vsjitdebugger.exe, devenv.exe, ProcMon.exe) korrelieren. Hochwertiges Signal ist das Paar NtCreateDebugObject unmittelbar gefolgt von NtDebugActiveProcess auf Self oder ein frisch gespawntes Kind (besonders mit DEBUG_KILL_ON_CLOSE — das Killen des Debugger-Threads tötet den Loader, klar feindselig). Defender for Endpoint zeigt DebugActiveProcess über den ETW-Threat-Intelligence-Kanal.

Direkte Syscall-Beispiele

cSelf-debug anti-attach setup

// Phase 1: create a DebugObject owned by us, then attach to our own PID.
// Any later DebugActiveProcess() against us will return STATUS_PORT_ALREADY_SET.
typedef NTSTATUS(NTAPI* fnNtCreateDebugObject)(PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES, ULONG);
typedef NTSTATUS(NTAPI* fnNtDebugActiveProcess)(HANDLE, HANDLE);

HANDLE g_dbg = NULL;
void InstallSelfDebug(void) {
    HMODULE n = GetModuleHandleA("ntdll.dll");
    fnNtCreateDebugObject pCreate = (fnNtCreateDebugObject)GetProcAddress(n, "NtCreateDebugObject");
    fnNtDebugActiveProcess pAttach = (fnNtDebugActiveProcess)GetProcAddress(n, "NtDebugActiveProcess");
    OBJECT_ATTRIBUTES oa = { sizeof(oa) };
    pCreate(&g_dbg, 0x1F000F /* DEBUG_ALL_ACCESS */, &oa, 0 /* no kill-on-close */);
    pAttach((HANDLE)-1 /* self */, g_dbg);
    // Servicing the debug-event loop on a worker thread is still required so
    // the parent does not deadlock on its own exceptions.
}

asmx64 direct stub (Win11 24H2 SSN)

; SSN 0xAB on win11-24h2 / winserver-2025. Resolve dynamically across builds.
NtCreateDebugObject PROC
    mov  r10, rcx
    mov  eax, 0ABh
    syscall
    ret
NtCreateDebugObject ENDP

rustwindows-sys + ntdll lookup

// Cargo: windows-sys = "0.59"
use std::ffi::c_void;
use windows_sys::Win32::System::LibraryLoader::{GetModuleHandleA, GetProcAddress};

type NtCreateDebugObject = unsafe extern "system" fn(
    out_handle: *mut isize,
    desired_access: u32,
    object_attributes: *mut c_void,
    flags: u32,
) -> i32;

pub unsafe fn create_debug_object() -> Option<isize> {
    let ntdll = GetModuleHandleA(b"ntdll.dll\0".as_ptr());
    if ntdll.is_null() { return None; }
    let addr = GetProcAddress(ntdll, b"NtCreateDebugObject\0".as_ptr())?;
    let f: NtCreateDebugObject = std::mem::transmute(addr);
    let mut h: isize = 0;
    let mut oa = [0u8; 48];
    if f(&mut h, 0x1F000F, oa.as_mut_ptr().cast(), 0) == 0 { Some(h) } else { None }
}

MITRE ATT&CK-Mappings

Last verified: 2026-05-20