Difficulty: Beginner

Module 1: Process Injection Landscape

Classic injection techniques, why they fail against modern EDRs, and the motivation behind PoolParty’s thread pool approach.

Module Objective

Understand the traditional process injection techniques (DLL injection, shellcode injection, APC injection), the specific Windows API call patterns that EDRs hook and monitor, why these techniques are increasingly detected, and how PoolParty reframes the injection problem by targeting the Windows Thread Pool as an execution primitive.

1. What Is Process Injection?

Process injection is the act of executing arbitrary code within the address space of a separate, running process. Attackers use it to:

MITRE ATT&CK catalogs process injection under T1055, with over a dozen sub-techniques. Despite decades of research, injection remains a cornerstone of offensive tradecraft because executing code in another process is fundamentally useful.

2. Classic Injection Techniques

2.1 DLL Injection via CreateRemoteThread

The oldest and most well-known technique. The attacker writes the path of a malicious DLL into the target process and creates a remote thread that calls LoadLibraryA:

C++// Classic DLL Injection - highly detected
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPid);

// Allocate memory in target for DLL path
LPVOID remoteBuf = VirtualAllocEx(hProcess, NULL, dllPathLen,
                                   MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

// Write DLL path into target process
WriteProcessMemory(hProcess, remoteBuf, dllPath, dllPathLen, NULL);

// Create remote thread calling LoadLibraryA with our DLL path
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0,
    (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleA("kernel32.dll"),
                                           "LoadLibraryA"),
    remoteBuf, 0, NULL);

2.2 Shellcode Injection via VirtualAllocEx + WriteProcessMemory

Instead of a DLL path, raw shellcode is written to allocated memory with executable permissions:

C++// Shellcode injection - allocate RWX, write, execute
LPVOID remoteBuf = VirtualAllocEx(hProcess, NULL, shellcodeLen,
                                   MEM_COMMIT | MEM_RESERVE,
                                   PAGE_EXECUTE_READWRITE);

WriteProcessMemory(hProcess, remoteBuf, shellcode, shellcodeLen, NULL);

CreateRemoteThread(hProcess, NULL, 0,
    (LPTHREAD_START_ROUTINE)remoteBuf, NULL, 0, NULL);

2.3 APC Injection

Asynchronous Procedure Calls queue code to execute in the context of a specific thread. The target thread must enter an alertable wait state for the APC to fire:

C++// APC injection - queue shellcode as APC to alertable thread
LPVOID remoteBuf = VirtualAllocEx(hProcess, NULL, shellcodeLen,
                                   MEM_COMMIT | MEM_RESERVE,
                                   PAGE_EXECUTE_READWRITE);
WriteProcessMemory(hProcess, remoteBuf, shellcode, shellcodeLen, NULL);

// Queue APC to each thread in the target process
QueueUserAPC((PAPCFUNC)remoteBuf, hThread, 0);

2.4 Other Variants

TechniqueExecution TriggerKey APIs
Process HollowingReplace suspended process imageNtUnmapViewOfSection, SetThreadContext
Thread HijackingModify RIP/EIP of existing threadSuspendThread, SetThreadContext, ResumeThread
Atom BombingAbuse global atom table + APCGlobalAddAtom, NtQueueApcThread
Early BirdAPC to newly created suspended processCreateProcess(SUSPENDED), QueueUserAPC
Callback InjectionAbuse API callbacks (e.g., EnumWindows)EnumWindows, EnumDesktops, etc.

3. The API Call Pattern Problem

Every classic injection technique follows the same fundamental three-step pattern that EDRs exploit for detection:

Universal Injection Pattern

1. Allocate
VirtualAllocEx
2. Write
WriteProcessMemory
3. Execute
CreateRemoteThread
QueueUserAPC
SetThreadContext

This pattern — allocate, write, execute — is the Achilles’ heel of all traditional injection. EDRs monitor every step:

StepHooked APIsWhat EDR Detects
AllocateNtAllocateVirtualMemoryCross-process memory allocation, RWX permissions requested
WriteNtWriteVirtualMemoryCross-process memory writes, shellcode byte patterns
ExecuteNtCreateThreadEx, NtQueueApcThread, NtSetContextThreadRemote thread creation, suspicious thread start addresses, APC targeting

The Execution Trigger Is the Weakest Link

Allocating memory and writing bytes are relatively common operations. The execution trigger — the API call that causes the injected code to actually run — is what EDRs focus on most heavily. CreateRemoteThread, QueueUserAPC, and SetThreadContext targeting a remote process are high-fidelity indicators of injection. PoolParty’s insight is to find execution triggers that EDRs do not monitor.

4. How EDRs Hook These APIs

Modern EDRs use multiple hooking layers to intercept injection-related API calls:

4.1 User-Mode Hooks (ntdll.dll)

EDRs patch the prologue of ntdll.dll functions with a JMP to their monitoring DLL. When your code calls NtAllocateVirtualMemory, execution is redirected to the EDR first:

ASM; Original ntdll!NtAllocateVirtualMemory
mov r10, rcx
mov eax, 18h              ; syscall number
syscall
ret

; After EDR hooking
jmp edr_monitor.dll+0x1234  ; redirected to EDR
nop
nop
syscall                     ; never reached via normal path
ret

4.2 Kernel Callbacks

Windows provides kernel-mode callback mechanisms that EDR drivers register for:

4.3 ETW (Event Tracing for Windows)

The Microsoft-Windows-Threat-Intelligence ETW provider reports on cross-process memory operations directly from the kernel, bypassing any user-mode unhooking attempts.

5. Why Traditional Techniques Are Failing

The detection landscape has matured to the point where classic injection is reliably caught:

Evasion AttemptWhy It No Longer Works
Direct syscalls (bypass ntdll hooks)Kernel callbacks and ETW TI still report the operation
Indirect syscalls (jump to ntdll syscall instruction)Thread call stack analysis reveals non-ntdll callers
Unhooking ntdll (overwrite with clean copy)ETW TI operates from kernel, unaffected by user-mode changes
Using NtMapViewOfSection instead of WriteMapped sections still detected via VAD (Virtual Address Descriptor) analysis
Callback-based executionEDRs now monitor the specific callbacks (e.g., hooking KiUserApcDispatcher)

The Core Insight

All classic techniques are detected because they require a known execution trigger API. PoolParty sidesteps this entirely by using the Windows Thread Pool as the execution primitive — the target process’s own thread pool worker threads execute the injected code as part of their normal callback dispatch, with no suspicious API call required for the execution step.

6. PoolParty’s Approach

Alon Leviev’s research (SafeBreach Labs, Black Hat EU 2023) identified a fundamentally different injection surface: the Windows Thread Pool. Instead of creating a remote thread or queuing an APC, PoolParty injects work items into the target process’s thread pool. The thread pool’s own worker threads then execute the callback — no CreateRemoteThread, no QueueUserAPC, no SetThreadContext.

PoolParty vs Classic Injection

Classic
Alloc → Write → CreateRemoteThread
vs
PoolParty
Alloc → Write → Insert work item into thread pool

The 8 PoolParty variants each target a different thread pool entry point:

VariantTarget StructureMechanism
1Worker Factory StartRoutineOverwrite start routine via NtSetInformationWorkerFactory
2TP_WORKInsert crafted work item into target’s thread pool
3TP_WAITInsert wait item into target’s thread pool
4TP_IOInsert I/O completion item into target’s thread pool
5TP_ALPCInsert ALPC item into target’s thread pool
6TP_JOBInsert job item into target’s thread pool
7TP_DIRECTInsert direct item via NtSetIoCompletionEx
8TP_TIMERInsert timer item into target’s thread pool

7. Course Roadmap

What Comes Next

ModuleTopicFocus
1 (this)Process Injection LandscapeWhy classic techniques fail
2Thread Pool ArchitectureTP_POOL, work items, IOCP fundamentals
3Thread Pool InternalsTppWorkerThread, callback dispatch, kernel side
4Worker Factory & TP_WORKVariants 1–2: StartRoutine hijack and TP_WORK insertion
5Wait, I/O & ALPCVariants 3–5: TP_WAIT, TP_IO, and TP_ALPC
6Job & Direct VariantsVariants 6–7: TP_JOB and TP_DIRECT
7TP_TIMER InsertionVariant 8: Timer-based callback injection
8Full Chain & DetectionEDR results, detection strategies, IOCP monitoring

Knowledge Check

Q1: What is the three-step pattern that EDRs use to detect classic process injection?

A) Open, Read, Close
B) Allocate, Write, Execute
C) Hook, Patch, Resume
D) Suspend, Inject, Release

Q2: Why do direct syscalls no longer fully evade EDR detection?

A) Syscalls are disabled on modern Windows
B) EDRs block all syscall instructions
C) Kernel callbacks and ETW TI still report the operations
D) Direct syscalls crash the target process

Q3: What makes PoolParty fundamentally different from classic injection techniques?

A) It uses kernel-mode code for injection
B) It injects into the kernel thread pool
C) It does not require memory allocation
D) It uses the target's own thread pool workers as the execution trigger instead of monitored APIs