Introduction to Shellcode
Shellcode is raw machine code you inject and run, and modern defenses exist to stop exactly that.
What you will learn
- -Explain what shellcode is and what a classic payload does
- -Assemble and run a small execve shellcode
- -Name the mitigations that break stack shellcode and why
is a short blob of with one job: give you a . You inject it into a process you have hijacked, point execution at it, and it runs execve("/bin/sh", NULL, NULL) so the exploited program hands you a command prompt. The name is literal. Historically you dropped these bytes into the same buffer you overflowed, then set the return address to point back at your bytes.
A Linux x86-64 shell is just one syscall done right. You load rax with 59 (the syscall number for execve), put a pointer to the string /bin/sh in rdi, zero out rsi and rdx for the empty argv and envp, and execute syscall. That is the entire , and assembled it is around 22 to 30 bytes.
xor rsi, rsi ; argv = NULL
push rsi ; null terminator for the string
mov rdi, 0x68732f6e69622f ; "/bin/sh" packed little-endian
push rdi
mov rdi, rsp ; rdi -> "/bin/sh" on the stack
xor rdx, rdx ; envp = NULL
push 0x3b ; 59 = execve
pop rax
syscall
Do not hand-assemble it
Writing shellcode byte by byte is a rite of passage, but for anything real you grab a known-good payload. pwntools ships an assembler and a shellcode library so you correct, null-free bytes without the tears.
$python3 -c 'from pwn import *; context.arch="amd64"; print(shellcraft.sh())'$python3 -c 'from pwn import *; context.arch="amd64"; print(asm(shellcraft.sh()))'One rule that bites everyone: watch for null bytes. If the vulnerable copy is a strcpy, a \x00 in your shellcode ends the copy early and truncates your payload. Good shellcode is written to avoid nulls, which is why xor rsi, rsi shows up instead of mov rsi, 0. The xor encodes with no zero bytes; the mov does not.
A working shell payload against a live target is unauthorized access and, depending on the machine, a serious crime. Only assemble and fire shellcode at binaries you compiled, VMs you own, or CTF and lab targets you are explicitly scoped to attack. The technique does not care whose box it lands on, so you have to.
Why your stack shellcode will not run today
Try the classic stack-injection attack on a default modern Linux box and it fails. Three mitigations are why, and knowing them is the point of this lesson.
NX, also called DEP, marks the stack non-executable. That rw-p on the stack from lesson one means the CPU refuses to run instructions there. Your bytes are in memory but they are data, not code, so the jump faults. NX is why return-oriented programming took over: instead of injecting new code, you reuse code already marked executable, exactly the gadget chaining from the last lesson.
ASLR randomizes the base addresses of the stack, , and libraries on every run, so you cannot hardcode where your shellcode sits. PIE extends that randomization to the program's own code. And the stack canary is a value the compiler drops between the locals and the saved return address; if your overflow smashes through it, the function aborts with a stack smashing detected message before ret ever runs.
$checksec --file=./vulnEach defense has a bypass, and those bypasses are the of a binary exploitation track: leak an address to beat ASLR, read or brute the canary, build a ROP chain to beat NX. Start every engagement with checksec so you know which fight you are actually in.
On a modern binary with NX enabled, you overflow the stack and jump to shellcode you placed in the buffer. It faults instead of running. Which technique reuses existing executable code to get around a non-executable stack?
NX makes the stack data-only, so injected bytes will not execute. The answer is return-oriented programming: chain gadgets that already live in executable memory instead of running new code on the stack.