Linux – Create and test x86-64 ELF executable shellcode on Linux machines

Create and test x86-64 ELF executable shellcode on Linux machines… here is a solution to the problem.

Create and test x86-64 ELF executable shellcode on Linux machines

I’m creating training on buffer overflows and stack/heap attacks. I’m working on an Ubuntu 12.04 x86_64 machine and want to show some example bugs and ways to exploit these vulnerabilities.

I’ve tried to start with the most basic shellcode I’ve found so far, a simple exit call, which should exit the overflowed program.

Hereby exitcall.asm:

; exitcall.asm

[SECTION .text]

global _start

_start:
    xor ebx,ebx     ; zero out ebx, same function as mov ebx,0
    mov al, 1       ; exit command to kernel
    int 0x80

I got this asm file from other tutorials, but it was written for the i386 schema. The next thing to do is to generate an object file and make it a binary executable:

# nasm -f elf64 exitcall.asm

# ld -o exitcall exitcall.o 

# file exitcall

exitcall: ELF 64-bit LSB  executable, x86-64, version 1 (SYSV), statically linked, not stripped

# strace ./exitcall

execve("./exitcall", ["./exitcall"], [/* 73 vars */]) = 0
        write(0, NULL, 0 <unfinished ... >
        +++ exited with 0 +++

# objdump -d exitcall

exitcall:     file format elf64-x86-64

Disassembly of section .text:

0000000000400080 <_start>:
          400080:   31 db                   xor    %ebx,%ebx
          400082:   b0 01                   mov    $0x1,%al
          400084:   cd 80                   int    $0x80

As you can see, the binary results perform well (exit 0 verified by strace) somehow convinced me that the asm file is correct as well. So what I should do now is create an array of shellcode characters from it so I can test in the following sample shellprogram.c executor. I just got the HEX value from objdump and started reading left to right, top to bottom, resulting in the following test:

char code[] = "\x31\xdb\xb0\x01\xcd\x80";

int main(int argc, char **argv) {

int (*exeshell)();
    exeshell = (int (*)()) code;
    (int) (*exeshell) ();

}

When I compile and execute this file, I get a segfault, but :

# gcc shellprogram.c -o shellprogram

# file shellprogram
        shellprogram: ELF 64-bit LSB  executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=765bdf6201099b9784b63a0111dc16c1115118bb, not stripped

# strace ./shellprogram 
        execve("./shellprogram", ["./shellprogram"], [/* 73 vars */]) = 0
        brk(0)                                  = 0x602000
        access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
        mmap(NULL, 8192, PROT_READ| PROT_WRITE, MAP_PRIVATE| MAP_ANONYMOUS, -1, 0) = 0x7ffff7ff8000
        access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
        open("/etc/ld.so.cache", O_RDONLY| O_CLOEXEC) = 3
        fstat(3, {st_mode=S_IFREG|0644, st_size=134914, ...}) = 0
        mmap(NULL, 134914, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7ffff7fd7000
        close(3)                                = 0
        access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
        open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY| O_CLOEXEC) = 3
        read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\320\37\2\0\0\0\0\0"..., 832) = 832
        fstat(3, {st_mode=S_IFREG|0755, st_size=1845024, ...}) = 0
        mmap(NULL, 3953344, PROT_READ| PROT_EXEC, MAP_PRIVATE| MAP_DENYWRITE, 3, 0) = 0x7ffff7a14000
        mprotect(0x7ffff7bcf000, 2097152, PROT_NONE) = 0
        mmap(0x7ffff7dcf000, 24576, PROT_READ| PROT_WRITE, MAP_PRIVATE| MAP_FIXED| MAP_DENYWRITE, 3, 0x1bb000) = 0x7ffff7dcf000
        mmap(0x7ffff7dd5000, 17088, PROT_READ| PROT_WRITE, MAP_PRIVATE| MAP_FIXED| MAP_ANONYMOUS, -1, 0) = 0x7ffff7dd5000
        close(3)                                = 0
        mmap(NULL, 4096, PROT_READ| PROT_WRITE, MAP_PRIVATE| MAP_ANONYMOUS, -1, 0) = 0x7ffff7fd6000
        mmap(NULL, 8192, PROT_READ| PROT_WRITE, MAP_PRIVATE| MAP_ANONYMOUS, -1, 0) = 0x7ffff7fd4000
        arch_prctl(ARCH_SET_FS, 0x7ffff7fd4740) = 0
        mprotect(0x7ffff7dcf000, 16384, PROT_READ) = 0
        mprotect(0x600000, 4096, PROT_READ)     = 0
        mprotect(0x7ffff7ffc000, 4096, PROT_READ) = 0
        munmap(0x7ffff7fd7000, 134914)          = 0
        --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_ACCERR, si_addr=0x601038} ---
        +++ killed by SIGSEGV (core dumped) +++
        Segmentation fault (core dumped)

Does anyone know what I did wrong. Was my strategy for converting binary elf64 files to shellcode strings wrong, or was the original ASM also 64-bit compatible?

I

read somewhere that in order to generate shellcode, I can use the following xxd linux command:

# xxd -i exitcall

unsigned char exitcall[] = {
      0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00,
      0x80, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 0x01, 0x00, 0x40, 0x00,
      0x05, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0xdb, 0xb0, 0x01,
      0xcd, 0x80, 0x00, 0x2e, 0x73, 0x79, 0x6d, 0x74, 0x61, 0x62, 0x00, 0x2e,
      0x73, 0x74, 0x72, 0x74, 0x61, 0x62, 0x00, 0x2e, 0x73, 0x68, 0x73, 0x74,
      0x72, 0x74, 0x61, 0x62, 0x00, 0x2e, 0x74, 0x65, 0x78, 0x74, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
      0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x40, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
      0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0xe8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
      0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0xa8, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x03, 0x00, 0x01, 0x00, 0x80, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
      0x04, 0x00, 0xf1, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x04, 0x00, 0xf1, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
      0x10, 0x00, 0x01, 0x00, 0x80, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
      0x10, 0x00, 0x01, 0x00, 0x86, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
      0x10, 0x00, 0x01, 0x00, 0x86, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
      0x10, 0x00, 0x01, 0x00, 0x88, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x78, 0x69,
      0x74, 0x63, 0x61, 0x6c, 0x6c, 0x2e, 0x61, 0x73, 0x6d, 0x00, 0x5f, 0x73,
      0x74, 0x61, 0x72, 0x74, 0x00, 0x5f, 0x5f, 0x62, 0x73, 0x73, 0x5f, 0x73,
      0x74, 0x61, 0x72, 0x74, 0x00, 0x5f, 0x65, 0x64, 0x61, 0x74, 0x61, 0x00,
      0x5f, 0x65, 0x6e, 0x64, 0x00
    };
    unsigned int exitcall_len = 725;

# objdump -d exitcall

exitcall:     file format elf64-x86-64

Disassembly of section .text:

0000000000400080 <_start>:
          400080:   31 db                   xor    %ebx,%ebx
          400082:   b0 01                   mov    $0x1,%al
          400084:   cd 80                   int    $0x80

This array is

much longer than the array in objdump (showing the assembler contents of the executable part) and also contains a large number of null bytes, which is undesirable in shell code because shellcode after null (00) is not executed.

I tried the same strategy for the example Hello World.asm, trying to make it x86-64 compatible and testing it with a shellprogram… Also failed. Does anyone have some good material on how to write shellcode compatible with asm (i.e. not using the data part of the string and making your address independent)?

Thank you very much for any help,
Wave.

Solution

The problem is that the test program shellprogram.c is incorrect for what I want to use it for, as mentioned @Iwillnotexist Idonotexist. Because the operating system enforces memory protection, you cannot execute data.

The end result is calling:

mprotect((void*)((intptr_t)code & ~0xFFF), 8192, PROT_READ| PROT_EXEC);  

The end result of the simple exitcall example:

<pre class=”lang-c prettyprint-override”>#include <unistd.h>
#include <sys/mman.h>

unsigned char code[] = {
0x31, 0xdb, 0xb0, 0x01, 0xcd, 0x80
};

int main(int argc, char **argv) {

mprotect((void*)((intptr_t)code & ~0xFFF), 8192, PROT_READ| PROT_EXEC);

int (*exeshell)();
exeshell = (int (*)()) code;
(int) (*exeshell) ();

printf("Failed to execute shellcode");

}

PrintYou Win!\r\n" to the console for the final result:

<pre class=”lang-c prettyprint-override”>#include <unistd.h>
#include <sys/mman.h>

unsigned char code[] = {
0xeb, 0x19, 0x31, 0xc0, 0x31, 0xdb, 0x31, 0xd2, 0x31, 0xc9, 0xb0, 0x04,
0xb3, 0x01, 0x59, 0xb2, 0x0a, 0xcd, 0x80, 0x31, 0xc0, 0xb0, 0x01, 0x31,
0xdb, 0xcd, 0x80, 0xe8, 0xe2, 0xff, 0xff, 0xff, 0x79, 0x6f, 0x75, 0x20,
0x77, 0x69, 0x6e, 0x21, 0x0d, 0x0a
};

int main(int argc, char **argv) {

mprotect((void*)((intptr_t)code & ~0xFFF), 8192, PROT_READ| PROT_EXEC);

int (*exeshell)();
exeshell = (int (*)()) code;
(int) (*exeshell) ();

printf("Failed to execute shellcode");

}

Thanks again for showing the solution!

Related Problems and Solutions