Welcome back, today, we're peeling back the layers on a classic yet still highly relevant code injection technique used
by both malware and legitimate software known as Asynchronous Procedure Call (APC) Injection. If you've ever wondered how a process can be forced to execute code that
isn't part of its original binary, you're in the right place. We'll break down everything from the fundamental concepts to the step-by-step mechanics of how APC
injection works.
What is an Asynchronous Procedure Call (APC)?
At its core, an Asynchronous Procedure Call (APC) is a function that executes asynchronously in the context of a particular thread. Think of it like a callback or an interrupt,
that the OS will invoke on a specific thread when that thread is in an alertable state When a thread is in a state where it can be interrupted, the OS will deliver any pending
APCs, causing the thread to execute the APC function before resuming its normal work. User-mode APCs
Quick analogy: an APC is like slipping a note into a worker’s pocket — when the worker pauses for a moment, they read and act on the note.
What is APC Injection?
APC Injection is the technique of abusing the Windows APC mechanism to force a thread within a remote (target) process to execute malicious code. Here's the high-level attack flow:
An attacker gains the necessary permissions to manipulate a target process, often via OpenProcess
They write their malicious shellcode into the memory of that target process.
Instead of creating a remote thread, they identify a thread within the target process that is, or can be put into, an alertable state.
They queue a User-Mode APC that points to the injected shellcode.
The next time that thread enters an alertable wait, it will automatically execute the attacker's shellcode.
The beauty and danger of this technique is that it doesn't require creating a new thread CreateRemoteThread, which can be a more detectable indicator of compromise (IoC).
When can an APC run? (Alertable State)
This is the most critical concept to grasp for understanding APC injection. A user-mode APC can only be delivered to a thread that is in an "Alertable State". A thread enters an
alertable state when it voluntarily makes a call to specific Windows API functions that wait for an event, signal, or I/O operation. These functions include:
Suspends the current thread for specified time while allowing APC interruption
DWORD SleepEx(
DWORD dwMilliseconds, // Time to sleep in milliseconds
BOOL bAlertable // TRUE = enable APC processing
);
Waits for a single synchronization object to become signaled
DWORD WaitForSingleObjectEx(
HANDLE hHandle, // Handle to object (mutex, event, process, etc.)
DWORD dwMilliseconds, // Timeout interval
BOOL bAlertable // TRUE = enable APC processing
/*
Common Wait Objects:
- hProcess - Wait for process to terminate
- hThread - Wait for thread to exit
- hEvent - Wait for event to be signaled
- hMutex - Wait for mutex ownership
*/
);
Waits for multiple synchronization objects simultaneously
DWORD WaitForMultipleObjectsEx(
DWORD nCount, // Number of handles in array
const HANDLE *lpHandles, // Array of object handles
BOOL bWaitAll, // Wait for all or any object
DWORD dwMilliseconds, // Timeout interval
BOOL bAlertable // TRUE = enable APC processing
/*
Wait Modes:
- bWaitAll = TRUE: Wait until ALL objects are signaled
- bWaitAll = FALSE: Wait until ANY object is signaled
*/
);
Atomically signals one object and waits on another
DWORD SignalObjectAndWait(
HANDLE hObjectToSignal, // Object to signal (mutex, event, semaphore)
HANDLE hObjectToWaitOn, // Object to wait for
DWORD dwMilliseconds, // Timeout interval
BOOL bAlertable // TRUE = enable APC processing
);
Waits for objects OR specific window messages - crucial for UI threads
DWORD MsgWaitForMultipleObjectsEx(
DWORD nCount, // Number of handles
const HANDLE *pHandles, // Array of handles
DWORD dwMilliseconds, // Timeout interval
DWORD dwWakeMask, // Types of messages to wait for
DWORD dwFlags // Wait flags (MWMO_ALERTABLE, etc.)
/*
Key Parameters:
- dwWakeMask: What messages to wake for (e.g., QS_ALLINPUT for any message)
- dwFlags: MWMO_ALERTABLE enables APC processing
*/
);
When a thread calls one of these functions, it effectively says to the OS: "I'm going to sleep for a while. If anyone leaves a note (queues an APC) for me, wake me up so I can handle it immediately."
APC Injection Attack Lifecycle
APC injection follows a systematic sequence of operations to execute unauthorized code within a target process. The complete attack flow comprises five distinct phases:
1) Process and Thread Handle Acquisition
The attacker first gains access to the target environment by Opening a handle to the victim process using OpenProcess() with privileges including PROCESS_VM_OPERATION for memory allocation
and PROCESS_VM_WRITE for writing shellcode.
2) Allocate memory in the target address space
Memory is reserved inside the victim’s address space using VirtualAllocEx with PAGE_EXECUTE_READWRITE permissions to host the payload.
This creates a writable and executable region for hosting malicious payload and the allocation size matches the shellcode footprint to minimize detection.
3) Write payload into the target
The payload bytes are written into the allocated region using WriteProcessMemory, and the shellcode becomes accessible within the target process's
context. Now the payload is ready to run but is still inactive.
4) APC Queueing and Targeting
An APC is queued to a target thread. It will execute only when that thread enters an alertable wait (EX: SleepEx/WaitForSingleObjectEx with alertable enabled).
5) Trigger or await an alertable state and execute
Now, when the target thread enters to an alertable wait for example, via SleepEx, WaitForSingleObjectEx, SignalObjectAndWait, or other alertable mechanisms), the OS dispatches queued APCs and the callback at the staged address executes in the context of that thread.
Why Attackers Abuse APC Injection for Payload Delivery
Attackers use APC-queue techniques because they allow them to run code in another process and they provide a low-noise, high-stealth way. These methods are quiet and hard to notice
because they don’t immediately create clear signs like new threads or files on the disk. APCs let adversaries leverage legitimate OS callback mechanisms, like I/O completion
routines or timers, to hide their execution inside regular threads. This helps them avoid simple security tools that check for unusual behavior or track the origin of code.
APC injection also gives attackers better access and memory permissions, since it runs with the same rights and memory as the target process. There are different ways to
use APCs, some of which are more reliable and others that focus more on staying stealth. These advantages explain why attackers often use APC-based methods in advanced
malware and targeted attacks.
Stealth/low API noise:
APCs avoid creating a visible remote thread, there is no CreateRemoteThread signature, so the common heuristic “remote thread creation = injection” is bypassed.
Instead execution happens inside an existing thread’s context, which is much less conspicuous in many telemetry pipelines.
Living-off-the-land (LotL) and legitimacy blending:
APCs are legitimate OS primitive used by applications for I/O completions and timers such as ReadFileEx, SetWaitableTimer etc. Attackers piggyback on
these legitimate mechanisms so behavior can appear normal.
Fileless / memory-resident capabilities (reduced disk artifacts):
Staging payloads into process memory and executing them via APCs minimizes on-disk footprints, there is no new dropped EXE or DLL required, hindering signature-based AV and
forensic triage that focuses on files.
Tradeoff: staging leaves memory-write and allocation telemetry that defenders can detect and correlate.
Reduced immediate forensic linkage:
Because the code runs under an existing process and thread stack, initial triage may attribute malicious behavior to the host process rather than an external
actor, complicating attribution and response.