3

I am creating a training on buffer overflows and stack/heap attacks. I am working on an Ubuntu 12.04 x86_64 machine and want to show some sample buggy programs and the ways you could exploit those vulnerabilities.

I am trying to start with the most basic shellcode I have found so far, the simple exit call, which should exit the program being overflowed.

Hereby the 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've got this asm file from other tutorials, written for i386 architectures however. Next thing to do is 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 result executes well (exit 0 verified with strace), somehow giving me the confidence that the asm file is correct as well. So what I am supposed to do now is to create a shellcode character array from it, so I can test is in the following sample shellprogram.c executor. I just took the HEX values 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 this file and execute it, I get a segmentation fault, however:

# 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 have an idea where I am doing things wrong. Is my strategy to convert the binary elf64 file to shellcode string wrong, or is the original asm not 64 bit compatible as well?

I read somewhere that in order to generate the shellcode I could 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 considerably longer then the one from objdump (display assembler contents of executable sections) and also contains a lot of null bytes, which is not desirable in shell code, since the shellcode after a null (00) won't get executed.

I tried the same strategy with a sample Hello World.asm, trying to make it x86-64 compatible and testing it with shellprogram... failed as well. Does anyone have some good material on how to write shellcode compatible asm (i.e. not using the data sections for strings and making your addresses independant)?

Any help really appreciated, Boeboe.

Boeboe
  • 2,070
  • 1
  • 17
  • 21
  • 2
    Your _exploit_ is so "classic" that memory protection has been put in place to make it harder to carry out what you want. Your shellcode string is considered data, and as such the memory page containing it is marked non-executable. Attempting to execute anyways will make the CPU raise an exception, which the OS will handle by smiting your process into oblivion. What you must additionally do is mark, with `mprotect()`, the page of memory containing the data as readable+executable (`PROT_READ | PROT_EXEC`). A reminder that `mprotect` can only change _whole pages_. – Iwillnotexist Idonotexist Jan 12 '15 at 11:24
  • I have added the following GCC flags to bypass default compiler protection mechanism for demo purposes: "CFLAGS = -g -w -fno-stack-protector -z execstack". I thought this would remove the "marked as none-executable" issue...? – Boeboe Jan 12 '15 at 11:29
  • But that doesn't make any difference; Your first shellcode is in a global `char` array. It's not on the stack at all, it's in the `rdata` section. You need to invoke `mprotect((void*)((intptr_t)code & ~0xFFF), 8192, PROT_READ|PROT_EXEC)` or similar to make the part of the data section containing that shellcode executable. – Iwillnotexist Idonotexist Jan 12 '15 at 11:31
  • Ah ok, I see what you mean now. The demo program used to test the shellcode's char array is *insufficient*. The shellcode is global and thus in de rdata section. I'll try to come up with another approach to test the shell code. Thanks for the hint! – Boeboe Jan 12 '15 at 11:42
  • 1
    It just hit me that the way you declared it, it isn't in the `rdata` section but the `data` section. Therefore, make sure to also ask for `PROT_WRITE`. Alternately, move the array within your main function to make sure it's on the stack. – Iwillnotexist Idonotexist Jan 12 '15 at 11:46
  • 1
    Keeping the code[] globally and using "mprotect((void*)((intptr_t)code & ~0xFFF), 8192, PROT_READ|PROT_EXEC)" did the trick and worked! Thanks for that. Moving code[] from global to inside the main function to get it on the stack did not work however... – Boeboe Jan 12 '15 at 13:12

2 Answers2

4

The problem was that the test program shellprogram.c was not correct for the purposes I wanted to use it for, as mentioned by @Iwillnotexist Idonotexist. You can't get data executed due to memory protection enforced by the OS.

The final result that worked (making the data section containing the char[] shellcode readable & executable) was calling:

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

Final result for simple exitcall example:

#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");
    
}

Final result for printing "you win!\r\n" to the console:

#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!

phuclv
  • 37,963
  • 15
  • 156
  • 475
Boeboe
  • 2,070
  • 1
  • 17
  • 21
  • 1
    Congratulations on figuring it out! For yourself and future readers, `& ~0xFFF` aligns the address to a page, and `8192` is 2x the common page size on x86 systems (4096 bytes, but this could change!), and in this case is enough to cover everything. If you have larger shellcode, increase this value. This is needed because `mprotect` can only change protections at the granularity of pages. – Iwillnotexist Idonotexist Jan 12 '15 at 13:35
  • Related / duplicate: [How to get c code to execute hex machine code?](https://stackoverflow.com/q/9960721) also mentions mprotect, but the accepted answer there allocates a new executable page and copies into it, instead of just adding exec permission to a page of `.data`. (This is kinda broken, though: it makes the shellcode read-only, unlike executable stack space would be, so you can't test self-modifying shellcode, e.g. that stores some zeros into its data.) – Peter Cordes Mar 09 '22 at 12:13
0

If it helps you are using the memory register pointers from 32-bit applications - you need to replace EAX and EBX with RAX and RBX for 64-bit.

A year late, I know, and doubtless you've moved on but I thought I'd mention it!

Tombas
  • 111
  • 1
  • 10
  • None of the registers he's using are being used as pointers. Also, `xor ebx,ebx` *does* clear the full 64bit register, because all writes to 32bit registers zero the upper 32 of the 64bit reg. The 32bit `int 0x80` ABI is available for 64bit processes. If there's any problem, it's potential garbage in the upper 24 bits of eax. I guess Linux only looks at `al`, not at `eax`, to get the system call number, or else he's just lucky that the upper bits are already zeroed (which they are at process startup in practice, even though the ABI doesn't require it.) – Peter Cordes Jan 16 '16 at 22:38
  • Linux does look at the full register (EAX) for call numbers; relying on the upper 24 of AL being zero only works in a static Linux executable, not dynamically linked, and will break if used as shellcode. See also [What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?](https://stackoverflow.com/q/46087730) - int 0x80 is a bad idea in 64-bit code *because* it ignores the high 32 bits of 64-bit registers, whether you want it or not. – Peter Cordes Oct 15 '20 at 07:51