EarlyBird APC Code Injection

EarlyBird APC Injection: A Deep Technical Analysis

Original text by Malforge Group

This article provides a detailed examination of the EarlyBird APC Injection technique, a sophisticated method for executing arbitrary code within the context of a trusted process. By understanding its mechanics, defenders and security researchers can better analyze and detect this stealthy form of process injection.
https://github.com/Malforge-Maldev-Public-Organization/EarlyBird-APC-Code-Injection
——————————————-

1. Introduction to Process Injection and Asynchronous Procedure Calls (APCs)

Process injection is a broad category of techniques used to run custom code within the address space of another, often legitimate, process. This allows the injected code to inherit the target process’s permissions and evade simple detection mechanisms. One such method leverages the Windows Asynchronous Procedure Call (APC) mechanism.

What is an APC?
An APC is a function that executes asynchronously in the context of a specific thread. When a thread enters an alertable state, the kernel checks its APC queue and executes any pending APCs.

  • How Threads Enter Alertable States: A thread becomes alertable by calling specific Windows API functions with the appropriate flags. Common examples include SleepEx()WaitForSingleObjectEx()WaitForMultipleObjectsEx(), and SignalObjectAndWait().
  • Kernel-Mode vs. User-Mode APCs: APCs can be queued from kernel-mode (e.g., for I/O completion) or user-mode. The QueueUserAPC() function is the primary user-mode API for this purpose.

Traditional APC injection targets an existing, running process. An attacker finds a thread in that process and queues an APC to it. The challenge is that the target thread might not enter an alertable state for an unknown or unpredictable amount of time.

2. The EarlyBird Technique: Gaining Control from the Start

The EarlyBird technique elegantly solves this timing problem. Instead of relying on the unpredictable state of an existing thread, it creates a new process with its primary thread in a suspended state. This gives the attacker full control to prepare the injection before execution begins.

The core innovation of EarlyBird is the precise orchestration of process creation and thread state to guarantee execution. By intervening in the window between a thread’s creation and its first opportunity to run, the attacker ensures the malicious APC is the first thing the thread processes when resumed.

3. Step-by-Step Technical Breakdown

Here is a detailed analysis of the five stages of the EarlyBird APC Injection technique, as demonstrated in the provided code example.

Stage 1: Process Creation in a Suspended State

The first step is to create a sacrificial process—a legitimate Windows executable that will host the malicious code.

  • API Call: CreateProcessA()
  • Key Parameter: dwCreationFlags is set to CREATE_SUSPENDED (0x00000004) .
  • Mechanism: This flag instructs the Windows loader to create the process, load its primary executable and necessary DLLs into memory, and form the primary thread. However, the thread’s execution is halted immediately. The system initializes the thread’s context but does not schedule it for execution on a CPU core. The handle to this suspended thread is returned in the PROCESS_INFORMATION structure (specifically, pi.hThread).

Stage 2: Memory Allocation in the Target Process

With a handle to the suspended process (pi.hProcess), memory must be allocated to store the payload (shellcode).

  • API Call: VirtualAllocEx()
  • Key Parameters:
    • hProcess: Handle to the target process (the suspended notepad.exe).
    • flAllocationTypeMEM_COMMIT (0x00001000) to reserve and commit physical storage in memory.
    • flProtectPAGE_EXECUTE_READ (0x20). This is critical. It allocates memory that is both readable and executable. While PAGE_EXECUTE_READWRITE is also possible, it is a stronger indicator of malicious activity.
  • Result: A region of memory is set aside within the address space of the target process, with permissions allowing the code to be read and executed.

Stage 3: Writing the Shellcode

The payload is then written into the newly allocated memory region.

  • API Call: WriteProcessMemory()
  • Mechanism: This function writes data from the current process’s memory (payload) directly into the address space of the target process at the location specified by pRemoteCode. The target process remains suspended throughout this operation.

Stage 4: Queuing the Asynchronous Procedure Call (APC)

This is the crucial step that links the shellcode to the suspended thread.

  • API Call: QueueUserAPC()
  • Key Parameters:
    • pfnAPC: A pointer to the function to be called. Here, the pRemoteCode address (where the shellcode resides) is typecast to a PAPCFUNC function pointer. When the APC is executed, the thread will begin executing code from this address.
    • hThread: A handle to the suspended thread (pi.hThread).
  • Mechanism: The APC routine (pointing to the shellcode) is placed at the end of the specified thread’s APC queue. Because the thread is suspended, it is not in an alertable state, so the APC remains pending in the kernel.

Stage 5: Resuming the Thread to Trigger Execution

With the shellcode written and the APC queued, the final step is to let the thread run.

  • API Call: ResumeThread()
  • Mechanism: This function decrements the thread’s suspend count. When the count reaches zero, the thread becomes schedulable. The Windows scheduler will eventually give it CPU time.
  • Execution Flow: As part of its initialization and return from the suspended state, the thread checks its APC queue. It finds the queued APC and, because it’s entering a state that can process user-mode APCs, executes it. The thread’s instruction pointer is set to the shellcode’s starting address, and the payload runs within the context of the legitimate notepad.exe process.

4. Code Walkthrough and API Analysis

The provided C code is a minimalist but fully functional implementation of the technique. Let’s analyze it line by line within its context.

int main(void) {
    int pid = 0; // Unused variable, can be removed.
    HANDLE hProc = NULL; // Unused variable, can be removed.

    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    void *pRemoteCode; // Will hold the address of allocated memory in the target.

    // Initialize the STARTUPINFO structure to prevent undefined behavior.
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si); // Must set the structure size.
    ZeroMemory(&pi, sizeof(pi));

    // 1. Create the target process in a suspended state.
    CreateProcessA(0, "notepad.exe", 0, 0, 0, CREATE_SUSPENDED, 0, 0, &si, &pi);

    // 2. Allocate memory in the remote process for the payload.
    //    Payload and payload_len are external variables (e.g., generated by msfvenom).
    pRemoteCode = VirtualAllocEx(pi.hProcess, NULL, payload_len, MEM_COMMIT, PAGE_EXECUTE_READ);
    
    // 3. Write the shellcode into the allocated memory.
    WriteProcessMemory(pi.hProcess, pRemoteCode, (PVOID) payload, (SIZE_T) payload_len, (SIZE_T *) NULL);

    // 4. Queue the shellcode as an APC to the suspended thread.
    //    The shellcode address is cast to a function pointer type.
    QueueUserAPC((PAPCFUNC)pRemoteCode, pi.hThread, NULL);

    // 5. Resume the thread. The thread will execute the queued APC immediately.
    ResumeThread(pi.hThread);

    // In a real-world scenario, handles should be closed with CloseHandle().
    return 0;
}

Critical Observations for Malware Analysts and Red Teamers:

  • Error Handling: Production code must include robust error checking after each API call (e.g., if (hProcess == NULL) { ... }).
  • Cleanup: The code lacks calls to CloseHandle() for pi.hProcess and pi.hThread, which would cause a resource leak. A well-written implant would include this.
  • Memory Permissions: Allocating memory with PAGE_EXECUTE_READ is less suspicious than PAGE_EXECUTE_READWRITE, but the subsequent WriteProcessMemory into an executable region is a classic and heavily monitored pattern.

5. Why EarlyBird is Effective and Stealthy

The EarlyBird technique offers several advantages that make it a favored tool for adversaries and a critical subject for defenders.

  • Predictable Execution: Guarantees execution as soon as the thread resumes, unlike targeting an existing thread that may never become alertable.
  • Bypasses Hooks: By creating a fresh process, the injected code often runs before security products have a chance to install user-mode hooks within that process, allowing it to execute undetected.
  • Lives off the Land: The technique primarily uses legitimate, signed Windows APIs (kernel32.dll functions), making it harder to distinguish from normal system operations.
  • Code Integrity: The host process (e.g., notepad.exe) remains on disk unchanged. Only its in-memory instance is modified, which complicates file-based scanning.

Comments are closed.