NtCreateUserProcess
Crea un nuevo proceso en modo usuario y su hilo inicial a partir de una imagen ejecutable.
Prototipo
NTSTATUS NtCreateUserProcess( PHANDLE ProcessHandle, PHANDLE ThreadHandle, ACCESS_MASK ProcessDesiredAccess, ACCESS_MASK ThreadDesiredAccess, POBJECT_ATTRIBUTES ProcessObjectAttributes, POBJECT_ATTRIBUTES ThreadObjectAttributes, ULONG ProcessFlags, ULONG ThreadFlags, PRTL_USER_PROCESS_PARAMETERS ProcessParameters, PPS_CREATE_INFO CreateInfo, PPS_ATTRIBUTE_LIST AttributeList );
Argumentos
| Name | Type | Dir | Description |
|---|---|---|---|
| ProcessHandle | PHANDLE | out | Recibe un handle al nuevo objeto proceso. |
| ThreadHandle | PHANDLE | out | Recibe un handle al hilo principal del nuevo proceso. |
| ProcessDesiredAccess | ACCESS_MASK | in | Máscara de acceso deseada para el handle de proceso retornado (p. ej. PROCESS_ALL_ACCESS). |
| ThreadDesiredAccess | ACCESS_MASK | in | Máscara de acceso deseada para el handle de hilo retornado (p. ej. THREAD_ALL_ACCESS). |
| ProcessObjectAttributes | POBJECT_ATTRIBUTES | in | OBJECT_ATTRIBUTES opcionales para el objeto proceso (nombre, descriptor de seguridad, directorio raíz). |
| ThreadObjectAttributes | POBJECT_ATTRIBUTES | in | OBJECT_ATTRIBUTES opcionales para el objeto hilo principal. |
| ProcessFlags | ULONG | in | PROCESS_CREATE_FLAGS_* (BREAKAWAY, INHERIT_HANDLES, NO_DEBUG_INHERIT, SUSPENDED, ...). |
| ThreadFlags | ULONG | in | THREAD_CREATE_FLAGS_* (CREATE_SUSPENDED, SKIP_THREAD_ATTACH, HIDE_FROM_DEBUGGER, ...). |
| ProcessParameters | PRTL_USER_PROCESS_PARAMETERS | in | Bloque de parámetros construido por RtlCreateProcessParametersEx (ruta de imagen, línea de comandos, env, handles estándar). |
| CreateInfo | PPS_CREATE_INFO | in/out | Máquina de estados PS_CREATE_INFO: en entrada PsCreateInitialState, en salida el estado final más los handles de sección / archivo de imagen abiertos. |
| AttributeList | PPS_ATTRIBUTE_LIST | in | Array de entradas PS_ATTRIBUTE: ImageName, ParentProcess, MitigationOptions, JobList, Token, ChildProcessPolicy, ... |
IDs de syscalls por versión de Windows
| Versión de Windows | ID de syscall | Build |
|---|---|---|
| Win10 1507 | 0xBA | win10-1507 |
| Win10 1607 | 0xBD | win10-1607 |
| Win10 1703 | 0xC0 | win10-1703 |
| Win10 1709 | 0xC1 | win10-1709 |
| Win10 1803 | 0xC2 | win10-1803 |
| Win10 1809 | 0xC3 | win10-1809 |
| Win10 1903 | 0xC4 | win10-1903 |
| Win10 1909 | 0xC4 | win10-1909 |
| Win10 2004 | 0xC8 | win10-2004 |
| Win10 20H2 | 0xC8 | win10-20h2 |
| Win10 21H1 | 0xC8 | win10-21h1 |
| Win10 21H2 | 0xC9 | win10-21h2 |
| Win10 22H2 | 0xC9 | win10-22h2 |
| Win11 21H2 | 0xCE | win11-21h2 |
| Win11 22H2 | 0xCF | win11-22h2 |
| Win11 23H2 | 0xCF | win11-23h2 |
| Win11 24H2 | 0xD1 | win11-24h2 |
| Server 2016 | 0xBD | winserver-2016 |
| Server 2019 | 0xC3 | winserver-2019 |
| Server 2022 | 0xCD | winserver-2022 |
| Server 2025 | 0xD1 | winserver-2025 |
Módulo del kernel
APIs relacionadas
Stub del syscall
4C 8B D1 mov r10, rcx B8 D1 00 00 00 mov eax, 0xD1 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
NtCreateUserProcess es el punto de entrada de kernel moderno bajo toda la familia Win32 CreateProcess* desde Vista. Reemplaza la coreografía legacy NtCreateProcess / NtCreateProcessEx + NtCreateThread + LdrLoadDll por una transacción de kernel única que abre la imagen, construye la sección, prepara el PEB, aplica mitigaciones e inicia el hilo inicial. El SSN deriva en casi cada build (0xBA → 0xD1 en la ventana soportada) por inserciones/eliminaciones de syscalls vecinos; la resolución dinámica es obligatoria. El prototipo de 11 parámetros está dominado por dos estructuras grandes: PS_CREATE_INFO (máquina de estados que el kernel recorre al parsear la imagen) y PS_ATTRIBUTE_LIST (portadora de todo lo que los parámetros legacy lpStartupInfo / lpProcessAttributes / Token / Job de `CreateProcess` transportaban por separado).
Uso común por malware
Dos abusos destacados. **(1) Spoofing de PPID (T1134.004)**: colocar PS_ATTRIBUTE_PARENT_PROCESS en AttributeList sobre un handle duplicado de cualquier proceso al que se pueda OpenProcess(PROCESS_CREATE_PROCESS). El hijo aparece bajo ese padre en Process Explorer, Sysmon Event ID 1 y árboles de procesos de los EDR — clásico para ocultar un Beacon bajo explorer.exe o services.exe. **(2) Downgrade de mitigaciones / abuso de child-process policy**: PS_ATTRIBUTE_MITIGATION_OPTIONS permite *activar* mitigaciones en el hijo (bloquear DLL no Microsoft para impedir inyección EDR en un proceso auxiliar es un truco red-team documentado) o combinar con PS_ATTRIBUTE_CHILD_PROCESS_POLICY para impedir más hijos, complicando el análisis. Uso más sutil: llamar a NtCreateUserProcess directamente salta kernel32!CreateProcessInternalW, donde muchos EDR cuelgan su hook de telemetría en modo usuario.
Oportunidades de detección
La telemetría de kernel es excelente aquí. PsSetCreateProcessNotifyRoutineEx2 se dispara en cada retorno con un PS_CREATE_NOTIFY_INFO que incluye el PID padre *real* (`CreatingThreadId.UniqueProcess`) independientemente de cualquier spoof PS_ATTRIBUTE_PARENT_PROCESS. Sysmon Event ID 1 registra `ParentProcessGuid` (desde ProcessParameters del PEB) y el creador real derivado del kernel — valores divergentes son una señal de spoof de alta fidelidad. Los eventos ETW `Microsoft-Windows-Kernel-Process/ProcessStart` exponen los mismos campos. Los EDR que solo hookean CreateProcessInternalW o NtCreateProcessEx perderán a los llamadores por syscall directo — verificar ambas superficies.
Ejemplos de syscalls directos
asmx64 direct stub (Win11 24H2)
; Direct syscall stub for NtCreateUserProcess (SSN 0xD1 on 24H2/2025)
; SSN moves nearly every release — resolve dynamically.
NtCreateUserProcess PROC
mov r10, rcx ; syscall convention
mov eax, 0D1h ; SSN
syscall
ret
NtCreateUserProcess ENDPcPPID-spoof skeleton
// Minimal PPID-spoof skeleton: launch notepad.exe as a child of explorer.exe.
// Error handling and full PS_ATTRIBUTE / RTL_USER_PROCESS_PARAMETERS setup omitted for brevity.
#include <windows.h>
#include <winternl.h>
#define PROC_THREAD_ATTRIBUTE_PARENT_PROCESS 0x00020000
void SpoofParent(DWORD parent_pid) {
SIZE_T attr_size = 0;
InitializeProcThreadAttributeList(NULL, 1, 0, &attr_size);
LPPROC_THREAD_ATTRIBUTE_LIST attr =
(LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, attr_size);
InitializeProcThreadAttributeList(attr, 1, 0, &attr_size);
HANDLE parent = OpenProcess(PROCESS_CREATE_PROCESS, FALSE, parent_pid);
UpdateProcThreadAttribute(attr, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,
&parent, sizeof(HANDLE), NULL, NULL);
STARTUPINFOEXW si = { 0 };
si.StartupInfo.cb = sizeof si;
si.lpAttributeList = attr;
PROCESS_INFORMATION pi;
// CreateProcess wraps NtCreateUserProcess; the AttributeList carries
// PS_ATTRIBUTE_PARENT_PROCESS through to the kernel.
CreateProcessW(L"C:\\Windows\\System32\\notepad.exe", NULL, NULL, NULL,
FALSE, EXTENDED_STARTUPINFO_PRESENT,
NULL, NULL, (LPSTARTUPINFOW)&si, &pi);
DeleteProcThreadAttributeList(attr);
CloseHandle(parent);
}rustblock-non-Microsoft-DLLs mitigation
// Cargo: windows-sys = "0.59"
// Launch a helper process with the Microsoft-signed-DLLs-only mitigation —
// blocks EDR userland DLLs from being injected into the child.
use windows_sys::Win32::System::Threading::*;
use windows_sys::core::PCWSTR;
const PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY: usize = 0x00020007;
const PROCESS_CREATION_MITIGATION_POLICY2_LOADER_INTEGRITY_CONTINUITY_ALWAYS_ON: u64
= 0x0000_0001_0000_0000;
pub unsafe fn launch_locked_down(image: PCWSTR) {
let mut attr_size = 0usize;
InitializeProcThreadAttributeList(core::ptr::null_mut(), 1, 0, &mut attr_size);
let attr = std::alloc::alloc(
std::alloc::Layout::from_size_align(attr_size, 8).unwrap()) as *mut _;
InitializeProcThreadAttributeList(attr, 1, 0, &mut attr_size);
let mut policy: u64 =
PROCESS_CREATION_MITIGATION_POLICY2_LOADER_INTEGRITY_CONTINUITY_ALWAYS_ON;
UpdateProcThreadAttribute(attr, 0,
PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY,
&mut policy as *mut _ as _, 8,
core::ptr::null_mut(), core::ptr::null_mut());
let mut si: STARTUPINFOEXW = core::mem::zeroed();
si.StartupInfo.cb = core::mem::size_of_val(&si) as u32;
si.lpAttributeList = attr;
let mut pi: PROCESS_INFORMATION = core::mem::zeroed();
CreateProcessW(image, core::ptr::null_mut(), core::ptr::null(),
core::ptr::null(), 0, EXTENDED_STARTUPINFO_PRESENT,
core::ptr::null(), core::ptr::null(),
&si as *const _ as _, &mut pi);
}Mapeos MITRE ATT&CK
Last verified: 2026-05-20