> Windows Syscalls
ntoskrnl.exeT1622T1106

NtCreateDebugObject

Crea un DebugObject del kernel — el puerto por depurador que recibe eventos de los procesos enganchados.

Prototipo

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

Argumentos

NameTypeDirDescription
DebugObjectHandlePHANDLEoutRecibe un handle al DebugObject recién creado. Pásalo a NtDebugActiveProcess.
DesiredAccessACCESS_MASKinMáscara de acceso. DEBUG_ALL_ACCESS (0x1F000F) es típica para depuradores.
ObjectAttributesPOBJECT_ATTRIBUTESinAtributos de objeto. Normalmente a cero — los DebugObjects rara vez se nombran.
FlagsULONGinDEBUG_KILL_ON_CLOSE (1) finaliza el debuggee al cerrarse el último handle del DebugObject.

IDs de syscalls por versión de Windows

Versión de WindowsID de syscallBuild
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

Módulo del kernel

ntoskrnl.exeNtCreateDebugObject

APIs relacionadas

DebugActiveProcessDebugActiveProcessStopWaitForDebugEventExContinueDebugEventNtDebugActiveProcessNtRemoveProcessDebugNtDebugContinueNtWaitForDebugEvent

Stub del syscall

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

Notas no documentadas

DebugObject es la primitiva del kernel que sustenta la API de depuración Win32: kernel32!DebugActiveProcess, WaitForDebugEvent y ContinueDebugEvent trabajan a través de uno de estos objetos. Un proceso solo puede estar enganchado a un único DebugObject a la vez — esa exclusividad es el pivote de toda la familia de trucos anti-attach por auto-depuración. El SSN se mueve con casi cada actualización mayor (0x9A → 0xAB de 1507 a 24H2), por lo que las tablas estáticas son peligrosas; resuelva dinámicamente. Tras crearlo, la secuencia típica es NtCreateDebugObject → NtDebugActiveProcess(objetivo, dbgObj) → bucle NtWaitForDebugEvent, opcionalmente NtRemoveProcessDebug para soltar.

Uso común por malware

Anti-attach por auto-depuración: el malware (o un hijo lanzado con DEBUG_PROCESS) se engancha a sí mismo o a su hijo protegido a un DebugObject que posee. Como Windows permite un único puerto de depuración por proceso, todo intento posterior de WinDbg, x64dbg o el attach-on-crash de un EDR vía DebugActiveProcess falla con STATUS_PORT_ALREADY_SET. Protectores comerciales (Themida, VMProtect, Enigma) llevan este patrón; igualmente la mayoría de crypters, loaders tipo GuLoader y stagers generados por Donut. El coste existe: el malware debe atender los eventos de depuración en su propio hilo y las excepciones no manejadas del debuggee suben a ese bucle en lugar de a WER.

Oportunidades de detección

La creación de un DebugObject por un proceso que no es depurador es inusual en estaciones de trabajo. El proveedor ETW Microsoft-Windows-Kernel-Object emite eventos de creación de objeto; correlacione con ImageFileName != depuradores conocidos (windbg.exe, vsjitdebugger.exe, devenv.exe, ProcMon.exe). La señal de alta fidelidad es el par NtCreateDebugObject seguido inmediatamente por NtDebugActiveProcess apuntando a sí mismo o a un hijo recién spawneado (sobre todo con DEBUG_KILL_ON_CLOSE — matar el hilo depurador mata el loader, intención hostil). Defender for Endpoint expone DebugActiveProcess vía el canal ETW Threat Intelligence.

Ejemplos de syscalls directos

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 }
}

Mapeos MITRE ATT&CK

Last verified: 2026-05-20