Shellcodes

Shellcode is the hex representation of a binary's machine code.

If we look at a example program like below

global _start

section .data
    message db "Hello HTB Academy!"  ; 18 bytes of text

section .text
_start:
    mov rsi, message   ; rsi = address of the message (text pointer)
    mov rdi, 1         ; rdi = file descriptor 1 (stdout)
    mov rdx, 18        ; rdx = number of bytes to write
    mov rax, 1         ; rax = syscall number 1 (write)
    syscall            ; do the write

    mov rax, 60        ; syscall number 60 (exit)
    mov rdi, 0         ; exit code 0 (success)
    syscall            ; exit

Converted to to hex we get the shellcode:

48be0020400000000000   ; mov rsi, 0x402000 (address of message)
bf01000000             ; mov edi, 1
ba12000000             ; mov edx, 18
b801000000             ; mov eax, 1
0f05                   ; syscall

b83c000000             ; mov eax, 60
bf00000000             ; mov edi, 0
0f05                   ; syscall

Assembly to Machine Code

Each x86 instruction and each register has its own binary machine represented in hex. When assembling code with nasm it converts assembl instructions to machine code which the processor can understand.

We can assemble and disassemble:

# Assemble
$ pwn asm 'push rax'  -c 'amd64'
   0:    50                       push   eax
   
# Disassemble
$ pwn disasm '50' -c 'amd64'
   0:    50                       push   eax

Extract Shellcode

A binary's shellcode is only the .text section where the instructions and code is found. We can use the ELF library and read the elf binary.

$ python3

>>> from pwn import *
>>> file = ELF('helloworld')

>>> file.section(".text").hex()
'48be0020400000000000bf01000000ba12000000b8010000000f05b83c000000bf000000000f05'

Or use this script shellcoder.py

#!/usr/bin/python3

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())

Loading Shellcode

We can run shellcode using pwntools.

$ python3

>>> from pwn import *
>>> context(os="linux", arch="amd64", log_level="error")
>>> run_shellcode(unhex('4831db66bb79215348bb422041636164656d5348bb48656c6c6f204854534889e64831c0b0014831ff40b7014831d2b2120f054831c0043c4030ff0f05')).interactive()

Hello HTB Academy!

Or use this script loader.py

#!/usr/bin/python3

import sys
from pwn import *

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

run_shellcode(unhex(sys.argv[1])).interactive()

Then run it

python3 loader.py '4831db536a0a48b86d336d307279217d5048b833645f316e37305f5048b84854427b6c303464504889e64831c0b0014831ff40b7014831d2b2190f054831c0043c4030ff0f05'

Debuggin Shellcode

We can use pwntools to build an elf binary in a script assembler.py

#!/usr/bin/python3

import sys, os, stat
from pwn import *

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

ELF.from_bytes(unhex(sys.argv[1])).save(sys.argv[2])
os.chmod(sys.argv[2], stat.S_IEXEC)

Use it like

$ python assembler.py '4831db66bb79215348bb422041636164656d5348bb48656c6c6f204854534889e64831c0b0014831ff40b7014831d2b2120f054831c0043c4030ff0f05' 'helloworld'

GCC

We can build shellcode into an elf executable using gcc. Using this C template where we must escape hex byte with \x:

#include <stdio.h>

int main()
{
    int (*ret)() = (int (*)()) "\x48\x31\xdb\x66\xbb\...SNIP...\x3c\x40\x30\xff\x0f\x05";
    ret();
}

The compile it

$ gcc helloworld.c -o helloworld -fno-stack-protector -z execstack -Wl,--omagic -g --static

Working Shellcode

Not all shellcode can loaded and ran. There can be requirements:

  1. Does not contain variables

  2. Does not refer to direct memory addresses. we can call labels.

  3. Does not contain any NULL bytes 00

Shellcode is expected to be executable once loaded into memory, onlye from the text segment. So I can't load data from .data or .bss.

section .data
    message db "Hello HTB Academy!"

We can convert to but a 64-bit register can only hold 8 bytes.

mov rsi, 'Academy!'

We have mov the string to rbx and the push rbx.

global _start

section .text
_start:
    xor rbx, rbx
    mov bx, 'y!'
    push rbx
    mov rbx, 'B Academ'
    push rbx
    mov rbx, 'Hello HT'
    push rbx
    mov rsi, rsp
    xor rax, rax
    mov al, 1
    xor rdi, rdi
    mov dil, 1
    xor rdx, rdx
    mov dl, 18
    syscall

    xor rax, rax
    add al, 60
    xor dil, dil
    syscall

We must ensure that our shellcode does not contain any NULL bytes 00 . They are used as string terminators and can cause issues. We can check with this python script.

#!/usr/bin/python3

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("%d bytes - Found NULL byte" % len(shellcode)) if [i for i in shellcode if i == 0] else print("%d bytes - No NULL bytes" % len(shellcode))

Then load it

$ python3 loader.py '4831db66bb79215348bb422041636164656d5348bb48656c6c6f204854534889e64831c0b0014831ff40b7014831d2b2120f054831c0043c4030ff0f05'

Hello HTB Academy!

Tools of the trade

Shellcraft.

From pwntools we can use shellcraft.

$ pwn shellcraft amd64.linux.sh
6a6848b82f62696e2f2f2f73504889e768726901018134240101010131f6566a085e4801e6564889e631d26a3b580f05

$ pwn shellcraft amd64.linux.sh -r
$ whoami
root

Msfvenome

We use msfvenom for shellcode.

$ msfvenom -l payloads | grep 'linux/x64'
linux/x64/exec                                      Execute an arbitrary command
...SNIP...

# Create a /bin/sh
msfvenom -p 'linux/x64/exec' CMD='sh' -a 'x64' --platform 'linux' -f 'hex'

Shellcode Encoding

Encoding shellcode is used to bypass defender and antivirus. We can use msfvenom for encoding.

msfvenom -l encoders

Framework Encoders [--encoder <value>]
======================================
    Name                          Rank       Description
    ----                          ----       -----------
    cmd/brace                     low        Bash Brace Expansion Command Encoder
    cmd/echo                      good       Echo Command Encoder

We can use one like x64/xor. Using the -i COUNT flag we can specify amount of iterations.

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

Last updated

Was this helpful?