Causing a buffer Overflow with fgets

29.6k views Asked by At

I'm experimenting with buffer overflows and try to overwrite the return address of the stack with a certain input of fgets

This is the code:

void foo()
{
    fprintf(stderr, "You did it.\n");
}

void bar()
{
    char buf[20];
    puts("Input:");
    fgets(buf, 24, stdin);
    printf("Your input:.\n", strlen(buf));
}


int main(int argc, char **argv)
{
    bar();
    return 0;
}

On a normal execution the program just returns your input. I want it to output foo() without modifying the code.

My idea was to overflow the buffer of buf by entering 20 'A's. This works and causes a segmentation fault. My next idea was to find out the address of foo() which is \x4006cd and append this to the 20 'A's.

From my understanding this should overwrite the return address of the stack and make it jump to foo. But it only causes a segfault.

What am I doing wrong?

Update: Assembler dumps main

    Dump of assembler code for function main:
   0x000000000040073b <+0>: push   %rbp
   0x000000000040073c <+1>: mov    %rsp,%rbp
   0x000000000040073f <+4>: sub    $0x10,%rsp
   0x0000000000400743 <+8>: mov    %edi,-0x4(%rbp)
   0x0000000000400746 <+11>:    mov    %rsi,-0x10(%rbp)
   0x000000000040074a <+15>:    mov    $0x0,%eax
   0x000000000040074f <+20>:    callq  0x4006f1 <bar>
   0x0000000000400754 <+25>:    mov    $0x0,%eax
   0x0000000000400759 <+30>:    leaveq 
   0x000000000040075a <+31>:    retq   
   End of assembler dump.

foo

Dump of assembler code for function foo:
   0x00000000004006cd <+0>: push   %rbp
   0x00000000004006ce <+1>: mov    %rsp,%rbp
   0x00000000004006d1 <+4>: mov    0x200990(%rip),%rax        # 0x601068 <stderr@@GLIBC_2.2.5>
   0x00000000004006d8 <+11>:    mov    %rax,%rcx
   0x00000000004006db <+14>:    mov    $0x15,%edx
   0x00000000004006e0 <+19>:    mov    $0x1,%esi
   0x00000000004006e5 <+24>:    mov    $0x400804,%edi
   0x00000000004006ea <+29>:    callq  0x4005d0 <fwrite@plt>
   0x00000000004006ef <+34>:    pop    %rbp
   0x00000000004006f0 <+35>:    retq   
End of assembler dump.

bar:

Dump of assembler code for function bar:
   0x00000000004006f1 <+0>: push   %rbp
   0x00000000004006f2 <+1>: mov    %rsp,%rbp
   0x00000000004006f5 <+4>: sub    $0x20,%rsp
   0x00000000004006f9 <+8>: mov    $0x40081a,%edi
   0x00000000004006fe <+13>:    callq  0x400570 <puts@plt>
   0x0000000000400703 <+18>:    mov    0x200956(%rip),%rdx        # 0x601060 <stdin@@GLIBC_2.2.5>
   0x000000000040070a <+25>:    lea    -0x20(%rbp),%rax
   0x000000000040070e <+29>:    mov    $0x18,%esi
   0x0000000000400713 <+34>:    mov    %rax,%rdi
   0x0000000000400716 <+37>:    callq  0x4005b0 <fgets@plt>
   0x000000000040071b <+42>:    lea    -0x20(%rbp),%rax
   0x000000000040071f <+46>:    mov    %rax,%rdi
   0x0000000000400722 <+49>:    callq  0x400580 <strlen@plt>
   0x0000000000400727 <+54>:    mov    %rax,%rsi
   0x000000000040072a <+57>:    mov    $0x400821,%edi
   0x000000000040072f <+62>:    mov    $0x0,%eax
   0x0000000000400734 <+67>:    callq  0x400590 <printf@plt>
   0x0000000000400739 <+72>:    leaveq 
   0x000000000040073a <+73>:    retq   
End of assembler dump.
2

There are 2 answers

0
nmenezes On

You did not count with memory aligment. I changed the code a litte bit to make it easier to find the right spot.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int **x;
int z;

void foo()
{
    fprintf(stderr, "You did it.\n");
}

void bar()
{
    char buf[2];
    //puts("Input:");
    //fgets(buf, 70, stdin);
    x = (int**) buf;
    for(z=0;z<8;z++)
            printf("%d X=%x\n", z, *(x+z));
    *(x+3) = foo;
    printf("Your input: %d %s\n", strlen(buf), buf);
}


int main(int argc, char **argv)
{
        printf("Foo: %x\n", foo);
        printf("Main: %x\n", main);
        bar();
        return 0;
}

With a smaller buffer, 2 in my example, I found the return address 24 bytes away (x+3, for 8 byte pointers; 64 bits, no debug, no optimization...) from the beginning of the buffer. This position can change depending on the buffer size, architecture, etc. In this example, I manage to change the return address of bar to foo. Anyway you will get a segmentation fault at foo return, as it was not properly set to return to main.

I added x and z as global vars to not change the stack size of bar. The code will display an array of pointer like values, starting at buf[0]. In my case, I found the address in main in the position 3. That's why the final code has *(x+3) = foo. As I said, this position can change depending on compilation options, machine etc. To find the correct position, find the address of main (printed before calling bar) on the address list.

It is important to note that I said address in main, not the address of main, bacause the return address was set to the line after the call to bar and not to the beginning of main. So, in my case, it was 0x4006af instead of 0x400668.

In your example, with 20 bytes buffer, as far as I know, it was aligned to 32 bytes (0x20).

If you want to do the same with fgets, you have to figure out how to type the address of foo, but if you are running a x86/x64 machine, remember to add it in little enddian. You can change the code to display the values byte per byte, so you can get them in the right order and type them using ALT+number. Remember that the numbers you type while holding ALT are decimal numbers. Some terminals won't be friendly handling 0x00.

My output looks like:

$ gcc test.c -o test
test.c: In function ‘bar’:
test.c:21: warning: assignment from incompatible pointer type
$ ./test
Foo: 400594
Main: 400668
0 X=9560e9f0
1 X=95821188
2 X=889350f0
3 X=4006af
4 X=889351d8
5 X=0
6 X=0
7 X=95a1ed1d
Your input: 5 ▒▒`▒9
You did it.
Segmentation fault
0
jww On
void bar()
{
    char buf[20];
    puts("Input:");
    fgets(buf, 24, stdin);
    printf("Your input:.\n", strlen(buf));
}

... This works and causes a segmentation fault...

The compiler is probably replacing fgets with a safer variant that includes a check on the destination buffer size. If the check fails, the the prgram unconditionally calls abort().

In this particular case, you should compile the program with -U_FORTIFY_SOURCE or -D_FORTIFY_SOURCE=0.