Shellcode is the payload an attacker places in memory — historically the overflowed buffer itself — and jumps to by hijacking the saved return address. Its name comes from the classic goal, execve("/bin/sh"): spawn a shell on the victim machine so the attacker has an interactive process with the program’s privileges. It is the injected-code variant of instruction pointer hijacking, the form the technique took before NX made the stack non-executable.
NOP Sled
Aiming at an unpredictable stack address
Stack addresses are not always predictable — they depend on the environment, on ASLR, and on how the program reached the vulnerable call. The attacker does not know exactly where their payload will land, only roughly. A NOP sled absorbs that uncertainty: a stretch of nop instructions (0x90) placed before the real payload so that landing anywhere inside the sled slides execution forward harmlessly until it reaches the payload.
guess an address somewhere in the sled;
overwrite the saved rip with the guess;
if the guess lands inside the sled, the CPU runs nops until it reaches execve("/bin/sh") at the end.
The [[Knowledge/x86-64 Instruction|nop]] is one byte on x86-64, so a wide sled tolerates a wide guess. The sled is a statistical trick, not a defence — it widens the attacker’s target.
Worked Example
A 23-byte execve("/bin/sh") payload
0: 48 31 f6 xor rsi,rsi ; rsi = 0 3: 56 push rsi ; null terminator for the string 4: 48 bf 2f 62 69 6e 2f 2f 73 68 movabs rdi,0x68732f2f6e69622f ; "/bin//sh" e: 57 push rdi ; push "/bin//sh\0" f: 54 push rsp ; rsp now points at the string10: 5f pop rdi ; rdi = address of "/bin//sh"11: b0 3b mov al, 0x3b ; rax = 59 (execve syscall number)13: 58 pop rax ; (alternative: pop the constant)14: 99 cltd ; rdx = 0 (sign-extend eax into edx)15: 0f 05 syscall ; execve("/bin//sh", NULL, NULL)
The shell "/bin//sh" is built on the stack as the constant 0x68732f2f6e69622f — the byte-reversed ASCII of //bin/sh — pushed through rdi, then the syscall fires with rdi pointing at the string, rsi/rdx null as the argv/envp.
No Null Bytes
Why the payload reads through gets / strcpy cleanly
The overflowed input often reaches the buffer through a string function — gets, strcpy, scanf("%s") — which stops at the null terminator. A single \0 inside the payload would truncate it, so a usable shellcode must contain no null bytes. The chosen assembly reflects this constraint:
xor rsi,rsi instead of mov rsi,0 (which would encode 0x00 literals);
mov al,0x3b writes one byte, leaving the rest of rax untouched;
the string constant /bin//sh is byte-reversed into a non-zero movabs.
A payload that reads through a string function is a payload that survives the string semantics; a payload with a single null is one that gets cut off before it reaches the stack.