Process Injection¶
Process injection is the core delivery mechanism for post-exploitation operations in Stentor. Every fork-and-run command (execute-assembly, powerpick, shspawn, etc.) and every explicit injection command (inject, shinject, dllinject) uses one of these techniques to execute code in a target process.
Stentor supports 23 injection techniques, each producing distinct forensic artifacts and offering different OPSEC tradeoffs. The default technique is CRT (CreateRemoteThread), but operators can configure alternatives through the malleable profile or per-beacon overrides for EDR-heavy environments.
MITRE ATT&CK
Process injection maps to T1055 - Process Injection and its sub-techniques. Each technique section below includes the specific sub-technique ID.
OPSEC Comparison Table¶
This table provides a quick reference for selecting the right injection technique based on your OPSEC requirements.
| Technique | ID | Method | Memory Allocation | Thread Creation | OPSEC Level | Best For |
|---|---|---|---|---|---|---|
crt | T1055.003 | VirtualAllocEx + WriteProcessMemory + CreateRemoteThread | VirtualAllocEx (RW->RX) | CreateRemoteThread | ⅕ | Lab/testing, compatibility |
ntmap | T1055.012 | NtCreateSection + NtMapViewOfSection + RtlCreateUserThread | Shared section mapping | RtlCreateUserThread | ⅗ | Standard engagements |
setthreadctx | T1055.003 | Get/SetThreadContext thread hijack on suspended thread | VirtualAllocEx (RW->RX) | Thread RIP hijack | ⅗ | Fork-and-run only |
queueapc | T1055.004 | QueueUserAPC early-bird on suspended thread | VirtualAllocEx (RW->RX) | APC before entry point | ⅗ | Fork-and-run only |
rtlthread | T1055 | VirtualAllocEx + WriteProcessMemory + RtlCreateUserThread | VirtualAllocEx (RW->RX) | RtlCreateUserThread | ⅖ | Bypassing kernel32 hooks |
selfinject | T1055 | VirtualAlloc + CreateThread in current process | VirtualAlloc local (RW->RX) | CreateThread local | ⅖ | BOF/beacon self-injection |
udrl | T1620 | Custom reflective loader prepended to payload | Operator-controlled | Operator-controlled | ⅘ | Custom engagements |
obfsetthreadctx | T1055.003 | Create thread at legitimate function, then hijack RIP | VirtualAllocEx (RW->RX) | CreateRemoteThread (spoofed start) | ⅘ | EDR evasion |
cloneprocess | T1055.012 | RtlCloneUserProcess + APC on clone | VirtualAllocEx (RW->RX) | APC on cloned thread | ⅘ | Avoiding remote threads |
tpdirect | T1055 | Fake TP_DIRECT + thread pool callback hijack | VirtualAllocEx + TP_DIRECT struct | Thread pool callback | ⅘ | EDR-heavy environments |
earlycascade | T1055.012 | Suspended process + pre-DLL-load RIP hijack | VirtualAllocEx (RW->RX) | Thread RIP hijack before init | 5/5 | Maximum evasion |
tpstartroutinestub | T1055 | Fake TP_WORK + thread pool worker dispatch | VirtualAllocEx + TP_WORK struct | Thread pool worker | 5/5 | EDR-heavy environments |
modulestomp | T1055.001 | Load DLL + overwrite .text section with shellcode | MEM_IMAGE backed (signed DLL) | CreateRemoteThread | ⅘ | Memory scanner evasion |
dllinject | T1055.001 | sRDI conversion + shellcode injection | VirtualAllocEx (RW->RX) | Default injector | ⅗ | Reflective DLL loading |
threadless | T1055 | Trampoline hook on exported function | VirtualAllocEx (RW->RX) | No thread creation | 5/5 | Maximum stealth |
hollowing | T1055.012 | CreateProcess(SUSPENDED) + replace image | VirtualAllocEx (RW->RX) | Resume suspended main thread | ⅘ | Process replacement |
poolparty_workitem | T1055 | TP_WORK insertion into target thread pool | VirtualAllocEx + TP_WORK struct | Thread pool worker | 5/5 | EDR-heavy environments |
poolparty_timer | T1055 | TP_TIMER insertion into target thread pool | VirtualAllocEx + TP_TIMER struct | Thread pool timer callback | 5/5 | EDR-heavy environments |
poolparty_io | T1055 | TP_IO completion into target thread pool | VirtualAllocEx + TP_IO struct | Thread pool I/O callback | 5/5 | EDR-heavy environments |
poolparty_alpc | T1055 | TP_ALPC port into target thread pool | VirtualAllocEx + TP_ALPC struct | Thread pool ALPC callback | 5/5 | EDR-heavy environments |
cb_fontenum | T1055 | EnumFontFamiliesEx callback shellcode trigger | VirtualAllocEx (RW->RX) | Callback from GDI API | ⅘ | Callback-based evasion |
cb_timer | T1055 | CreateTimerQueueTimer callback shellcode trigger | VirtualAllocEx (RW->RX) | Timer queue callback | ⅘ | Callback-based evasion |
cb_winenum | T1055 | EnumWindows callback shellcode trigger | VirtualAllocEx (RW->RX) | Callback from user32 API | ⅘ | Callback-based evasion |
Reading the OPSEC Level
OPSEC Level ⅕ = most detectable (highest artifact footprint). OPSEC Level 5/5 = least detectable (minimal API footprint, legitimate-looking execution paths). Higher is stealthier.
Technique Selection Guide¶
Choosing the right injection technique depends on the target environment's detection capabilities. Use this guide as a starting point.
Which technique should I use?
Lab or testing environment: Use crt (default). Most reliable, broadest compatibility, easiest to debug.
Standard engagement (no EDR): Use ntmap or queueapc. Good balance of stealth and reliability. ntmap avoids VirtualAllocEx/WriteProcessMemory entirely; queueapc avoids CreateRemoteThread.
EDR-heavy environment: Use tpdirect, tpstartroutinestub, or earlycascade. These techniques have the smallest API footprint and execute through legitimate Windows thread pool or process initialization paths.
Memory scanner evasion: Use modulestomp. Shellcode resides in MEM_IMAGE memory backed by a signed DLL, defeating scanners that flag unbacked executable memory.
Self-injection (BOF/beacon in-process): Use selfinject. No cross-process operations -- shellcode runs inside the beacon process itself.
Custom loader for unique environments: Use udrl. Operator supplies their own reflective loader shellcode, enabling per-engagement customization with no fixed signatures.
Threadless injection (no new threads): Use threadless. Hooks an exported function in the target process with a trampoline -- shellcode executes when the function is called naturally. No thread creation artifacts at all.
Thread pool abuse (PoolParty): Use poolparty_workitem, poolparty_timer, poolparty_io, or poolparty_alpc. These hijack Windows thread pool internals to execute shellcode through legitimate worker dispatch paths. Based on SafeBreach Labs' PoolParty research.
Callback-based injection: Use cb_fontenum, cb_timer, or cb_winenum. Trigger shellcode execution via legitimate Win32 callback APIs (EnumFontFamiliesEx, CreateTimerQueueTimer, EnumWindows). The callback execution appears as normal API activity.
Process replacement: Use hollowing. Creates a suspended process and replaces its image with shellcode before it initializes. Shellcode runs as the main thread of a clean-looking process.
Standalone Injection Commands¶
These commands let the operator inject code into a specific process by PID. They are distinct from fork-and-run operations (which spawn and inject into a sacrificial process automatically).
inject¶
Inject a beacon payload into an existing process by PID. The backend generates shellcode for the specified listener and architecture, then the beacon injects it using the configured injection technique.
Task type: inject (method: inject)
OPSEC
inject creates a remote thread in the target process. Sysmon Event 8 (CreateRemoteThread) and Event 10 (ProcessAccess) will fire. EDR products commonly monitor for cross-process injection. Choose the target process carefully -- injecting into a browser or Office process is less suspicious than injecting into notepad.exe.
shinject¶
Inject raw shellcode (from a file) into an existing process by PID. Unlike inject, which generates beacon shellcode from a listener, shinject takes arbitrary shellcode provided by the operator.
Task type: inject (method: shinject)
Example:
OPSEC
Same OPSEC profile as inject. The backend reads the file from the operator's machine, base64-encodes it, and sends it to the beacon for injection. The shellcode file never touches the target disk.
dllinject¶
Reflective DLL injection via sRDI (Shellcode Reflective DLL Injection). The DLL is converted to position-independent shellcode that maps itself in memory without using LoadLibrary, then injected into the target PID.
Task type: inject (method: dllinject)
OPSEC
Reflective DLL injection (T1055.001) loads a DLL in memory without touching disk and without calling LoadLibrary. However, the injected DLL resides in unbacked executable memory, which memory scanners can flag. For MEM_IMAGE-backed execution, use modulestomp as the injection technique instead.
Configuration¶
The default injection technique and memory allocator are configured through the malleable profile at payload generation time. These defaults apply to all fork-and-run operations and can be overridden per-beacon.
Malleable Profile Settings¶
The process-inject block in the malleable profile controls injection behavior:
| Setting | Values | Default | Description |
|---|---|---|---|
set-executemethod | CreateRemoteThread, SetThreadContext, RtlCreateUserThread, NtQueueApcThread-s | CreateRemoteThread | Primary injection execution method |
set-allocator | VirtualAllocEx, NtMapViewOfSection | VirtualAllocEx | Memory allocation strategy |
set-min_alloc | Integer (bytes) | 4096 | Minimum allocation size (padding to avoid small allocations that stand out to memory scanners) |
set-startrwx | true, false | false | Deprecated. Start with RWX permissions. Stentor enforces W^X (write XOR execute) regardless of this setting. |
set-bof_allocator | VirtualAlloc, MapViewOfFile, HeapAlloc | VirtualAlloc | Memory allocator for BOF execution |
set-bof_reuse_memory | true, false | false | Reuse BOF memory allocations |
Execute Method Mapping¶
The malleable profile set-executemethod values map to Stentor injection techniques:
| Profile Value | Stentor Technique | Notes |
|---|---|---|
CreateRemoteThread | crt | Default. Most compatible. |
SetThreadContext | setthreadctx | Thread hijacking (fork-and-run only). |
RtlCreateUserThread | rtlthread | NT API alternative to CRT. |
NtQueueApcThread-s | queueapc | Early-bird APC (fork-and-run only). |
Advanced techniques
Techniques like tpdirect, tpstartroutinestub, earlycascade, obfsetthreadctx, cloneprocess, and modulestomp are selected per-beacon via the injection technique override in the task data or the spawn-to configuration, not through the malleable profile execute method.
MinAlloc Setting¶
The min_alloc setting pads injection allocations to a minimum size. Small memory allocations (e.g., 4 KB for a tiny shellcode payload) are unusual and can be flagged by memory scanners. Setting min_alloc to a larger value (e.g., 16384 or 65536) makes the allocation size blend in with legitimate allocations.
Per-Beacon Override¶
The injection technique can be overridden per-beacon in the task JSON:
The technique field accepts any registered technique name from the OPSEC Comparison Table above.
Detailed Technique Reference¶
Each technique below includes its API call chain, forensic artifacts, limitations, and MITRE ATT&CK mapping.
CRT (CreateRemoteThread)¶
Technical Details: CRT
ID: crt MITRE ATT&CK: T1055.003 - Thread Execution Hijacking
How it works: Allocates memory in the remote process with VirtualAllocEx(PAGE_READWRITE), copies shellcode via WriteProcessMemory, changes protection to PAGE_EXECUTE_READ via VirtualProtectEx, then creates a remote thread at the shellcode address with CreateRemoteThread. All API calls are routed through BeaconGate when enabled.
API Call Chain:
VirtualAllocEx(processHandle, PAGE_READWRITE)WriteProcessMemory(processHandle, shellcode)VirtualProtectEx(processHandle, PAGE_EXECUTE_READ)CreateRemoteThread(processHandle, shellcodeAddr)
Forensic Artifacts:
- Sysmon Event 8: CreateRemoteThread detected
- Sysmon Event 10: ProcessAccess with PROCESS_VM_WRITE
- Memory: RW -> RX protection transition in target process
- ETW: Thread creation from unexpected parent process
Limitations: Most monitored technique. Every major EDR product detects CreateRemoteThread from non-standard callers. Use only in lab environments or when detection is not a concern.
Supports InjectByPID: Yes -- opens process by PID, injects, closes handle.
Supports InjectByHandle: Yes -- direct injection with existing process handle.
NtMapViewOfSection¶
Technical Details: NtMapViewOfSection
ID: ntmap MITRE ATT&CK: T1055.012 - Process Hollowing (variant)
How it works: Creates a shared memory section backed by the page file using NtCreateSection, maps a writable view in the beacon process (NtMapViewOfSection with PAGE_READWRITE), copies shellcode to the local view, maps an executable view in the target process (PAGE_EXECUTE_READ), unmaps the local view, then executes via RtlCreateUserThread.
API Call Chain:
NtCreateSection(PAGE_EXECUTE_READWRITE)-- section max protection, not page protectionNtMapViewOfSection(local, PAGE_READWRITE)-- writable view in beaconmemcpyto local viewNtMapViewOfSection(remote, PAGE_EXECUTE_READ)-- executable view in targetNtUnmapViewOfSection(local)-- cleanupRtlCreateUserThread(remote, shellcodeAddr)
Forensic Artifacts:
- Section object creation (visible in kernel object manager)
- Mapped view in target process (no VirtualAllocEx artifact)
- Thread creation via RtlCreateUserThread (different from CRT in ETW)
Limitations: Section objects may be visible to advanced memory scanners. The section maximum protection is PAGE_EXECUTE_READWRITE (required for the technique), though actual page protections are never RWX.
Supports InjectByPID: Yes. Supports InjectByHandle: Yes.
SetThreadContext¶
Technical Details: SetThreadContext
ID: setthreadctx MITRE ATT&CK: T1055.003 - Thread Execution Hijacking
How it works: Allocates and writes shellcode to the remote process, then finds the main thread of the suspended child process (fork-and-run). Opens the thread, calls GetThreadContext to capture the current register state, modifies RIP to point to the shellcode address, and applies with SetThreadContext. When the caller resumes the thread, it executes shellcode instead of the original entry point.
API Call Chain:
VirtualAllocEx+WriteProcessMemory+VirtualProtectExGetThreadContext(threadHandle, CONTEXT_CONTROL)- Set
ctx.Rip = shellcodeAddr SetThreadContext(threadHandle, ctx)- Caller calls
ResumeThread
Forensic Artifacts:
- Memory: RW -> RX transition in target process
- Thread context modification (detectable via ETW)
- No CreateRemoteThread artifact
Limitations: Only works with fork-and-run (requires a suspended main thread). InjectByPID falls back to CRT because hijacking threads in running processes is unreliable -- the thread may hold locks or be in kernel mode.
Supports InjectByPID: Falls back to CRT. Supports InjectByHandle: Yes (fork-and-run only).
QueueUserAPC¶
Technical Details: QueueUserAPC
ID: queueapc MITRE ATT&CK: T1055.004 - Asynchronous Procedure Call
How it works: Allocates and writes shellcode, finds the main thread of the suspended child process, opens the thread, and queues an APC pointing to the shellcode address via QueueUserAPC. When the caller resumes the thread, the APC fires before the thread's normal entry point -- this is the "early-bird" pattern.
API Call Chain:
VirtualAllocEx+WriteProcessMemory+VirtualProtectExOpenThread(THREAD_SET_CONTEXT)QueueUserAPC(shellcodeAddr, threadHandle)- Caller calls
ResumeThread-- APC fires first
Forensic Artifacts:
- Memory: RW -> RX transition in target process
- APC queue modification (visible in kernel structures)
- No CreateRemoteThread artifact
- Early-bird: shellcode executes before process entry point
Limitations: Only works reliably with fork-and-run (early-bird pattern). For InjectByPID, the technique falls back to CRT because APC on existing threads requires an alertable wait state (SleepEx, WaitForMultipleObjectsEx), which is unreliable for arbitrary processes.
Supports InjectByPID: Falls back to CRT. Supports InjectByHandle: Yes (fork-and-run only).
RtlCreateUserThread¶
Technical Details: RtlCreateUserThread
ID: rtlthread MITRE ATT&CK: T1055 - Process Injection
How it works: Standard memory allocation and write, then creates a remote thread using RtlCreateUserThread from ntdll instead of CreateRemoteThread from kernel32. This bypasses usermode hooks that EDR products place on kernel32.dll exports.
API Call Chain:
VirtualAllocEx+WriteProcessMemory+VirtualProtectExRtlCreateUserThread(processHandle, shellcodeAddr)
Forensic Artifacts:
- Memory: RW -> RX transition in target process
- Thread creation via NT API (different ETW event from kernel32 CRT)
- Thread start address in remote process memory
Limitations: While it bypasses kernel32 hooks, kernel-level ETW instrumentation still logs the thread creation. Less evasive than techniques that avoid thread creation entirely.
Supports InjectByPID: Yes. Supports InjectByHandle: Yes.
SelfInject¶
Technical Details: SelfInject
ID: selfinject MITRE ATT&CK: T1055 - Process Injection
How it works: Allocates memory in the current (beacon) process with VirtualAlloc(PAGE_READWRITE), copies shellcode, changes protection to PAGE_EXECUTE_READ, then creates a local thread with CreateThread. When drip-loading staging is enabled, uses DripAlloc for chunked reserve-then-commit allocation to reduce allocation artifacts.
API Call Chain:
VirtualAlloc(MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)-- orDripAllocif enabledmemcpy(shellcode)VirtualProtect(PAGE_EXECUTE_READ)-- orDripProtectCreateThread(shellcodeAddr)
Forensic Artifacts:
- Memory: RW -> RX transition in current process
- New thread creation (visible in ETW)
- No remote process involvement (no Sysmon Event 8/10)
Limitations: Shellcode runs inside the beacon process. If the shellcode crashes, the beacon crashes. Cannot inject into remote processes -- InjectByHandle returns an error, InjectByPID only accepts the current process PID.
Supports InjectByPID: Only if PID matches current process. Supports InjectByHandle: No (returns error).
UDRL (User-Defined Reflective Loader)¶
Technical Details: UDRL
ID: udrl MITRE ATT&CK: T1620 - Reflective Code Loading
How it works: The operator provides custom reflective loader shellcode via aggressor script (SetUDRL()). The loader is prepended to the beacon payload. During injection, the combined payload (loader + beacon) is written to the target process. The remote thread starts at the loader entry point, which handles its own PE mapping, import resolution, and relocation -- completely bypassing standard LoadLibrary detection.
API Call Chain:
- Prepend loader shellcode to beacon payload
VirtualAllocEx(PAGE_READWRITE)via BeaconGateWriteProcessMemory(loader + beacon)via BeaconGate- Track regions in BUD (Beacon User Data) for sleep mask
VirtualProtectEx(PAGE_EXECUTE_READ)via BeaconGateCreateRemoteThread(loaderAddr)via BeaconGate
Forensic Artifacts:
- Unbacked executable memory (no corresponding file on disk)
- PE header anomalies (custom loader may omit/modify headers)
- Memory layout differs from standard PE loader
- Operator-controlled: artifacts vary per loader implementation
Limitations: Not registered by default -- only available when the operator provides loader shellcode at runtime via SetUDRL(). The quality and stealth of the loader depends entirely on the operator's implementation. If the loader requires RWX for self-modification, the UseRWX config can be set, but this degrades OPSEC.
Supports InjectByPID: Yes. Supports InjectByHandle: Yes.
Obfuscated SetThreadContext¶
Technical Details: Obfuscated SetThreadContext
ID: obfsetthreadctx MITRE ATT&CK: T1055.003 - Thread Execution Hijacking
How it works: Unlike standard SetThreadContext which hijacks an existing thread, this technique creates a NEW thread at a legitimate function address (e.g., ntdll!RtlUserThreadStart+0x21) using CreateRemoteThread with CREATE_SUSPENDED. The thread start address appears legitimate to EDR tools that analyze thread origins. After creation, GetThreadContext captures the full register state, RIP is modified to point to shellcode, SetThreadContext applies the change, and ResumeThread executes the shellcode.
API Call Chain:
VirtualAllocEx+WriteProcessMemory+VirtualProtectEx- Resolve
ntdll!RtlUserThreadStart+0x21in remote process CreateRemoteThread(processHandle, legitimateAddr, CREATE_SUSPENDED)GetThreadContext(threadHandle, CONTEXT_FULL)- Set
ctx.Rip = shellcodeAddr SetThreadContext(threadHandle, ctx)ResumeThread(threadHandle)
Forensic Artifacts:
- Thread created at legitimate image-backed address (harder to detect)
- Memory: RW -> RX transition in target process
- Thread context modification (detectable via ETW)
- CreateRemoteThread call present, but start address appears legitimate
Limitations: Requires ntdll or kernel32 for reliable cross-process address resolution (ASLR is per-boot for system DLLs, so addresses match across processes). Other modules are not supported for the start address.
Supports InjectByPID: Yes. Supports InjectByHandle: Yes.
CloneProcess¶
Technical Details: CloneProcess
ID: cloneprocess MITRE ATT&CK: T1055.012 - Process Hollowing (variant)
How it works: Uses RtlCloneUserProcess to create a copy of a target process with a suspended main thread. Shellcode is written into the clone using standard allocation, then a QueueUserAPC call targets the clone's main thread. When ResumeThread is called, the APC fires and shellcode executes in the cloned process. This avoids creating remote threads in existing processes.
API Call Chain:
RtlCloneUserProcess(CREATE_SUSPENDED | NO_SYNCHRONIZE)- Extract clone process/thread handles
VirtualAllocEx+WriteProcessMemory+VirtualProtectExin cloneQueueUserAPC(shellcodeAddr, cloneThread)ResumeThread(cloneThread)
Forensic Artifacts:
- Process clone creation (unusual
RtlCloneUserProcesscall) - Memory: RW -> RX transition in cloned process
- APC queue modification on clone's main thread
- No CreateRemoteThread artifact in existing processes
Limitations: Requires Windows 10+ (RtlCloneUserProcess availability). Falls back to CRT on older versions. The cloned process is a copy of the current process, which may be suspicious if the beacon is running as an unusual executable.
Supports InjectByPID: Yes (opens process, then clones). Supports InjectByHandle: Yes.
TP_DIRECT (Thread Pool Direct)¶
Technical Details: TP_DIRECT
ID: tpdirect MITRE ATT&CK: T1055 - Process Injection
How it works: Based on the PoolParty research by SafeBreach Labs. Allocates shellcode in the remote process, then creates a fake TP_DIRECT structure with its callback pointer at offset 0x08 pointing to the shellcode address. Both the shellcode (RX) and the TP_DIRECT structure (RW) are written to the target process. A remote thread is created to execute through the thread pool callback path, making execution appear as a legitimate thread pool work item.
API Call Chain:
VirtualAllocEx+WriteProcessMemory+VirtualProtectEx(shellcode, RX)- Build fake
TP_DIRECTstructure (callback = shellcode address) VirtualAllocEx(PAGE_READWRITE)for TP_DIRECT structureWriteProcessMemory(TP_DIRECT to target)CreateRemoteThread(shellcodeAddr, tpDirectAddr)-- thread pool dispatch
Forensic Artifacts:
- Memory: RW -> RX transition for shellcode
- Additional RW allocation for fake TP_DIRECT structure
- Remote thread creation targeting thread pool infrastructure
- Thread pool callback execution appears as legitimate worker activity
Limitations: The TP_DIRECT structure layout is version-specific (callback at offset 0x08 on Windows 10/11 x64). May break on future Windows versions if the internal structure layout changes.
Supports InjectByPID: Yes. Supports InjectByHandle: Yes.
EarlyCascade¶
Technical Details: EarlyCascade
ID: earlycascade MITRE ATT&CK: T1055.012 - Process Hollowing (variant)
How it works: Creates a process in a suspended state (CREATE_SUSPENDED | CREATE_NO_WINDOW) and hijacks its initialization path before any DLLs load. At this point, RIP points to RtlUserThreadStart or ntdll!LdrInitializeThunk -- the very first usermode instruction. The technique writes shellcode, modifies RIP via SetThreadContext, and the caller resumes the thread. Shellcode executes in the "early cascade" window before ntdll initialization, DLL loading, TLS callbacks, or any usermode hooks are active.
API Call Chain:
VirtualAllocEx+WriteProcessMemory+VirtualProtectExGetThreadContext(CONTEXT_CONTROL)-- captures RIP before any code executes- Set
ctx.Rip = shellcodeAddr SetThreadContext(threadHandle, ctx)ResumeThread-- shellcode runs before ntdll init
For InjectByPID, the technique additionally:
CreateProcessW(rundll32.exe, CREATE_SUSPENDED | CREATE_NO_WINDOW)- Inject via
InjectByHandle ResumeThread
Forensic Artifacts:
- Suspended process creation (
CREATE_SUSPENDEDflag) - Memory: RW -> RX transition in target process
- Thread context modification before first instruction
- Entry point never executes (distinguishes from normal execution)
Limitations: For InjectByPID, uses rundll32.exe as the default sacrificial process. This can be customized via spawn-to configuration. If injection fails, the suspended process is terminated to avoid orphaned processes.
Supports InjectByPID: Yes (creates new suspended process). Supports InjectByHandle: Yes (fork-and-run).
TpStartRoutineStub¶
Technical Details: TpStartRoutineStub
ID: tpstartroutinestub MITRE ATT&CK: T1055 - Process Injection
How it works: Based on PoolParty variant 7 (SafeBreach Labs). Creates a fake TP_WORK structure whose CleanupGroupMember.Callback pointer (at offset 0x40) points to the shellcode. The Task LIST_ENTRY field (at offset 0x20) is initialized as a self-referencing doubly-linked list to prevent crashes. The structure is written to the target process, and a remote thread is created with the shellcode as the entry point and the TP_WORK pointer as the parameter, simulating how TppWorkerThread dispatches work items through TpStartRoutineStub.
API Call Chain:
VirtualAllocEx+WriteProcessMemory+VirtualProtectEx(shellcode, RX)- Build fake
TP_WORKstructure (0x68 bytes):- Offset 0x20: Task LIST_ENTRY (self-referencing Flink/Blink)
- Offset 0x40: CleanupGroupMember.Callback = shellcode address
VirtualAllocEx(PAGE_READWRITE)for TP_WORK structureWriteProcessMemory(TP_WORK to target)CreateRemoteThread(shellcodeAddr, tpWorkAddr)-- worker dispatch
Forensic Artifacts:
- Memory: RW -> RX transition for shellcode
- Additional RW allocation for fake TP_WORK structure
- Remote thread creation targeting thread pool infrastructure
- Thread pool work item execution appears as legitimate worker activity
Limitations: The TP_WORK structure layout is version-specific (callback at offset 0x40, task at offset 0x20 on Windows 10/11 x64). Unlike tpdirect which manipulates existing pool structures, this technique creates new work items, which may be detectable by advanced kernel-mode monitoring.
Supports InjectByPID: Yes. Supports InjectByHandle: Yes.
Module Stomping¶
Technical Details: Module Stomping
ID: modulestomp MITRE ATT&CK: T1055.001 - Dynamic-link Library Injection (variant)
How it works: Loads a legitimate signed DLL into the remote process via CreateRemoteThread(LoadLibraryW), then overwrites its .text section with shellcode. The result is shellcode backed by MEM_IMAGE memory that appears to reside in a legitimate loaded module. This defeats memory scanners that flag unbacked executable memory.
API Call Chain:
- Load DLL locally to parse PE header (find .text offset and size)
- Write DLL path (UTF-16) to remote process
CreateRemoteThread(LoadLibraryW, pathAddr)-- load DLL in targetWaitForSingleObject(wait for DLL load)- Enumerate remote modules to find DLL base address
VirtualProtectEx(.text, PAGE_READWRITE)WriteProcessMemory(.text, shellcode)VirtualProtectEx(.text, PAGE_EXECUTE_READ)- Clean up remote path memory
Forensic Artifacts:
- DLL load in remote process (legitimate, signed DLL)
- Memory protection change on .text section
- Shellcode appears as MEM_IMAGE backed by signed module
- Remote thread for LoadLibraryW (common API pattern)
Limitations: The DLL's .text section must be large enough to hold the shellcode. The DLL is configured via the module_stomp_dll evasion option (default: xpsservices.dll). The technique uses CreateRemoteThread for the initial LoadLibraryW call, which is detectable.
Configuration: Set the stomped DLL via the evasion config:
Supports InjectByPID: Handled via allocAndWriteModuleStomp + default injector. Supports InjectByHandle: Handled via allocAndWriteModuleStomp + thread execution.
DLL Inject (sRDI)¶
Technical Details: DLL Inject via sRDI
ID: dllinject MITRE ATT&CK: T1055.001 - Dynamic-link Library Injection
How it works: Converts a DLL to position-independent shellcode using the sRDI (Shellcode Reflective DLL Injection) technique. The shellcode stub handles PE mapping, import resolution, and relocation entirely in memory -- no LoadLibrary call is made. The converted shellcode is then injected using the default injection technique (CRT).
API Call Chain:
- Validate PE header (MZ check)
- Convert DLL to shellcode via sRDI
- Inject shellcode using default injector (
InjectByPID)
Forensic Artifacts:
- Unbacked executable memory (DLL mapped without LoadLibrary)
- PE header may be present in memory (detectable by memory scanners)
- Injection artifacts depend on the underlying technique (default: CRT)
Limitations: The DLL is specified by the operator via the dllinject command. The sRDI conversion adds a shellcode stub that handles the reflective loading. The underlying injection technique is always the default (CRT) and cannot be overridden for DLL injection.
Supports InjectByPID: Yes (via default injector after sRDI conversion). Supports InjectByHandle: No (standalone command only).
Threadless Injection¶
Technical Details: Threadless Injection
ID: threadless MITRE ATT&CK: T1055 - Process Injection
How it works: Hooks an exported function in the target process by writing a trampoline (save registers + call shellcode + jmp to original function) over the function's entry point. When the target process naturally calls the hooked function, the trampoline redirects execution to the shellcode. No thread is ever created in the target process -- execution piggybacks on existing threads. Default hook target: ntdll!RtlExitUserProcess.
API Call Chain:
- Resolve exported function address in remote process
- Read original function bytes (for trampoline return)
VirtualAllocEx(RW) for shellcode + trampoline- Build trampoline: push registers, call shellcode, restore registers, jmp original
WriteProcessMemory(shellcode + trampoline)VirtualProtectEx(RW -> RX)WriteProcessMemoryto overwrite function entry with jmp to trampoline
Forensic Artifacts:
- Modified function prologue in target process (detectable by code integrity checks)
- Memory: RW -> RX transition for shellcode allocation
- No CreateRemoteThread or APC artifacts
- No new thread creation events (Sysmon Event 8 absent)
Limitations: The hooked function must be called by the target process for shellcode to execute. Timing is non-deterministic. Default target RtlExitUserProcess fires on process exit, which may not be immediate. Choose a frequently-called export for faster triggering.
Supports InjectByPID: Yes. Supports InjectByHandle: Yes.
Process Hollowing¶
Technical Details: Process Hollowing
ID: hollowing MITRE ATT&CK: T1055.012 - Process Hollowing
How it works: Creates a new process in suspended state (CREATE_SUSPENDED | CREATE_NO_WINDOW), then replaces its image memory with shellcode. The main thread's RIP is redirected to the shellcode via SetThreadContext, and the thread is resumed. The shellcode executes as the main thread of a seemingly legitimate process. Default sacrificial process: svchost.exe.
API Call Chain:
CreateProcessW(sacrificialProcess, CREATE_SUSPENDED | CREATE_NO_WINDOW)VirtualAllocEx+WriteProcessMemory+VirtualProtectEx(shellcode, RX)GetThreadContext(CONTEXT_FULL)on main thread- Set
ctx.Rip = shellcodeAddr SetThreadContext(mainThread, ctx)ResumeThread(mainThread)
Forensic Artifacts:
- Suspended process creation (CREATE_SUSPENDED flag)
- Thread context modification on main thread
- Memory: RW -> RX transition in hollowed process
- Process image mismatch (memory content doesn't match on-disk binary)
Limitations: EDR products that compare process memory against the on-disk image will detect the mismatch. The sacrificial process can be configured via spawn-to settings. If injection fails, the suspended process is terminated to avoid orphans.
Supports InjectByPID: Yes (creates new suspended process). Supports InjectByHandle: Yes.
PoolParty: Work Item Insertion¶
Technical Details: PoolParty Work Item (poolparty_workitem)
ID: poolparty_workitem MITRE ATT&CK: T1055 - Process Injection
How it works: Based on SafeBreach Labs' PoolParty research. Locates the target process's thread pool worker factory handle via NtQuerySystemInformation, then inserts a fake TP_WORK item pointing to the shellcode. When the thread pool dispatches the next work item, the shellcode executes as a legitimate thread pool worker callback.
API Call Chain:
NtQuerySystemInformation(SystemHandleInformation)to find worker factory handleVirtualAllocEx+WriteProcessMemory+VirtualProtectEx(shellcode, RX)- Build fake
TP_WORKstructure with callback → shellcode address VirtualAllocEx(PAGE_READWRITE)for TP_WORK structureWriteProcessMemory(TP_WORK to target)- Insert work item into thread pool queue via
NtSetInformationWorkerFactory
Forensic Artifacts:
- No CreateRemoteThread -- execution via thread pool dispatch
- Memory: RW -> RX transition for shellcode
- Additional RW allocation for TP_WORK structure
- Thread pool activity appears as legitimate worker execution
Supports InjectByPID: Yes. Supports InjectByHandle: Yes.
PoolParty: Timer¶
Technical Details: PoolParty Timer (poolparty_timer)
ID: poolparty_timer MITRE ATT&CK: T1055 - Process Injection
How it works: Similar to work item insertion but uses the thread pool timer mechanism. Creates a fake TP_TIMER structure with the callback pointing to shellcode and inserts it into the target's timer queue. When the timer fires, the thread pool dispatches the callback, executing the shellcode.
API Call Chain:
NtQuerySystemInformation(SystemHandleInformation)to find worker factory handleVirtualAllocEx+WriteProcessMemory+VirtualProtectEx(shellcode, RX)- Build fake
TP_TIMERstructure with callback → shellcode address - Write structure to target process
- Insert timer into thread pool timer queue
Forensic Artifacts:
- No CreateRemoteThread artifact
- Timer callback execution appears as legitimate thread pool timer activity
- Memory: RW -> RX transition for shellcode
Supports InjectByPID: Yes. Supports InjectByHandle: Yes.
PoolParty: I/O Completion¶
Technical Details: PoolParty I/O (poolparty_io)
ID: poolparty_io MITRE ATT&CK: T1055 - Process Injection
How it works: Uses the thread pool I/O completion mechanism. Creates a fake TP_IO structure with the callback pointing to shellcode and associates it with an I/O completion port in the target process. When an I/O completion packet arrives, the thread pool dispatches the callback.
API Call Chain:
NtQuerySystemInformation(SystemHandleInformation)to find I/O completion port handleVirtualAllocEx+WriteProcessMemory+VirtualProtectEx(shellcode, RX)- Build fake
TP_IOstructure with callback → shellcode address - Write structure to target process
- Queue I/O completion packet to trigger callback
Forensic Artifacts:
- No CreateRemoteThread artifact
- I/O completion callback appears as legitimate async I/O handling
- Memory: RW -> RX transition for shellcode
Supports InjectByPID: Yes. Supports InjectByHandle: Yes.
PoolParty: ALPC Port¶
Technical Details: PoolParty ALPC (poolparty_alpc)
ID: poolparty_alpc MITRE ATT&CK: T1055 - Process Injection
How it works: Uses the thread pool ALPC (Advanced Local Procedure Call) mechanism. Creates a fake TP_ALPC structure with the callback pointing to shellcode and associates it with an ALPC port in the target process. When an ALPC message arrives on the port, the thread pool dispatches the callback.
API Call Chain:
NtQuerySystemInformation(SystemHandleInformation)to find ALPC port handleVirtualAllocEx+WriteProcessMemory+VirtualProtectEx(shellcode, RX)- Build fake
TP_ALPCstructure with callback → shellcode address - Write structure to target process
- Send ALPC message to trigger callback dispatch
Forensic Artifacts:
- No CreateRemoteThread artifact
- ALPC callback appears as legitimate inter-process communication handling
- Memory: RW -> RX transition for shellcode
Supports InjectByPID: Yes. Supports InjectByHandle: Yes.
Callback: EnumFontFamiliesEx¶
Technical Details: Font Enumeration Callback (cb_fontenum)
ID: cb_fontenum MITRE ATT&CK: T1055 - Process Injection
How it works: Uses the Windows GDI EnumFontFamiliesExW API as a shellcode trigger. Creates a suspended process, writes shellcode to it, then creates a remote thread that calls EnumFontFamiliesExW with the shellcode address as the callback parameter. When GDI enumerates fonts, it calls the callback (shellcode) for each font family.
API Call Chain:
CreateProcessW(sacrificialProcess, CREATE_SUSPENDED | CREATE_NO_WINDOW)VirtualAllocEx+WriteProcessMemory+VirtualProtectEx(shellcode, RX)GetThreadContext→ modifyRIPto point toEnumFontFamiliesExWstub- Stub calls
EnumFontFamiliesExW(NULL, NULL, shellcodeAddr, 0) ResumeThread-- GDI font enumeration triggers shellcode callback
Forensic Artifacts:
- Suspended process creation
- GDI API call appears legitimate (font enumeration is common)
- Memory: RW -> RX transition for shellcode
- Thread context modification
Supports InjectByPID: Yes (creates new suspended process). Supports InjectByHandle: Yes.
Callback: Timer Queue¶
Technical Details: Timer Queue Callback (cb_timer)
ID: cb_timer MITRE ATT&CK: T1055 - Process Injection
How it works: Uses CreateTimerQueueTimer as a shellcode trigger. Creates a suspended process, writes shellcode, then redirects execution to a stub that calls CreateTimerQueueTimer with the shellcode address as the timer callback. The timer fires immediately (due period = 0), executing the shellcode.
API Call Chain:
CreateProcessW(sacrificialProcess, CREATE_SUSPENDED | CREATE_NO_WINDOW)VirtualAllocEx+WriteProcessMemory+VirtualProtectEx(shellcode, RX)GetThreadContext→ modifyRIPto point toCreateTimerQueueTimerstub- Stub calls
CreateTimerQueueTimer(NULL, NULL, shellcodeAddr, NULL, 0, 0, WT_EXECUTEDEFAULT) ResumeThread-- timer fires immediately, triggering shellcode callback
Forensic Artifacts:
- Suspended process creation
- Timer queue creation (legitimate Windows API pattern)
- Memory: RW -> RX transition for shellcode
- Thread context modification
Supports InjectByPID: Yes (creates new suspended process). Supports InjectByHandle: Yes.
Callback: EnumWindows¶
Technical Details: Window Enumeration Callback (cb_winenum)
ID: cb_winenum MITRE ATT&CK: T1055 - Process Injection
How it works: Uses the EnumWindows API as a shellcode trigger. Creates a suspended process, writes shellcode, then redirects execution to a stub that calls EnumWindows with the shellcode address as the enumeration callback. When user32 enumerates top-level windows, it calls the callback (shellcode) for each window.
API Call Chain:
CreateProcessW(sacrificialProcess, CREATE_SUSPENDED | CREATE_NO_WINDOW)VirtualAllocEx+WriteProcessMemory+VirtualProtectEx(shellcode, RX)GetThreadContext→ modifyRIPto point toEnumWindowsstub- Stub calls
EnumWindows(shellcodeAddr, 0) ResumeThread-- window enumeration triggers shellcode callback
Forensic Artifacts:
- Suspended process creation
- Window enumeration API call (common user32 activity)
- Memory: RW -> RX transition for shellcode
- Thread context modification
Supports InjectByPID: Yes (creates new suspended process). Supports InjectByHandle: Yes.