Linux shellcode explained: from C to bytes
How Linux shellcode works — syscalls, registers, and position-independent code — and how to assemble, encode, and test it safely.
Linux shellcode is small because the kernel does the heavy lifting through syscalls. You do not link against libc; you set up registers and ask the kernel directly. If the word "shellcode" is new, read what shellcode is first.
Syscalls: x86 vs x64
The two architectures differ in three things that matter for shellcode:
| x86 | x64 | |
|---|---|---|
| Syscall instruction | int 0x80 | syscall |
| Syscall number | eax | rax |
| Args | ebx, ecx, edx, … | rdi, rsi, rdx, … |
execve is syscall 11 on x86 and 59 on x64. Get the number wrong for the architecture and you call something unexpected — a common first-time bug.
Position-independent code
Your bytes land at an address you do not control, so you cannot hardcode pointers. The standard move for strings is to push them onto the stack and point a register at the stack:
xor eax, eax
push eax ; terminator
push 0x68732f2f ; "//sh"
push 0x6e69622f ; "/bin"
mov ebx, esp ; ebx now points at the string, wherever it is
No .data section, no absolute address — it works regardless of where the code runs.
Avoiding null bytes
xor eax, eax instead of mov eax, 0 is not style; the second form assembles with null bytes that break string-based delivery. The whole craft of compact Linux shellcode is getting the logic right while dodging forbidden byte values. See null bytes in shellcode.
Assembling to bytes
Write the assembly, assemble it, then extract the .text bytes. Prototyping the logic in C first is a good way to find the right syscalls and arguments before you hand-tune the assembly. Working examples with full byte strings are in shellcode examples.
Encoding pass order
If your target forbids certain bytes, you encode — but encoder order matters: a second pass can reintroduce bytes the first pass removed. This trips people up constantly; the reasoning is in encoder pipeline order.
Test it
Assemble, encode for your bad-char set, and verify — all client-side — in the Shellcodes builder, then run only inside an isolated lab VM you control. Authorized testing only: responsible use.