Widthness Overflow – What? Where? How? Why?   Leave a comment

  • Post Info:
    1. # Author: Flavio do Carmo Junior aka waKKu
      # URL: Author’s Webpage
      # Date: January 28, 2012
      # Category: Assembly, Exploiting, Programming, Security

    As I said in my last post…

    I will be writing 3 quick and quite straightforward posts this week.
    All (partially) inspired by this video: http://www.youtube.com/watch?v=i2fhNVQPb5I (I am a C Programmer) and regarding to stack overflows.

    – 1) Signedness bug
    – 2) Widthness overflow
    – 3) Pointer Subterfuge

    Nothing new I suppose, however, as usual I look forward to demystifying them completely. Therefore, any doubts in any of these bugs, please feel free to comment, elaborate my explation or even curse me by my mistakes.
    All codes will be compiled using 32bits, just because I think it is easier to understand and be tested using VMs.

    2) Widthness Overflow

    Overview
    – Widthness overflow bugs are exactly what the name says. Very similar to signedness bugs seen on my last post.
    Imagine we have a field that must be small and controlable, skimp green as we are, we could declare a 1 byte variable (char) to save some space. The example is 1 byte long, although it could be any size of variable, as long as it is not the maximum size/width (otherwise would be impossible to overflow).

    Code Example:

    waKKu@0xcd80: Vulns$ cat width.c 
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int vuln(char *buf) {
        unsigned char len;
        char buffer[256];
    
        len = strlen(buf);
        if ((len < 0 ) || (len > 200)) {
            printf("String length too big!!!\n");
            return(1);
        } else {
            printf("Copying content into stack...");
            strcpy(buffer, buf);
            printf("done.\n");
        }
    
        return(0);
    }
    
    int main(int argc, char **argv) {
    
        if (argc < 2) {
            printf("Usage: %s <text>\n", argv[0]);
            return(1);
        }
    
        vuln(argv[1]);
    
        return(0);
    }
    waKKu@0xcd80: Vulns$ gcc -m32 -o width width.c -Wall
    waKKu@0xcd80: Vulns$ ./width
    Usage: ./width <text>
    waKKu@0xcd80: Vulns$ ./width hadouken
    Copying content into stack...done.
    waKKu@0xcd80: Vulns$ ./width $(perl -e 'print "A"x150')
    Copying content into stack...done.
    waKKu@0xcd80: Vulns$ ./width $(perl -e 'print "A"x200')
    Copying content into stack...done.
    waKKu@0xcd80: Vulns$ ./width $(perl -e 'print "A"x250')
    String length too big!!!
    waKKu@0xcd80: Vulns$ ./width $(perl -e 'print "A"x1250')
    String length too big!!!
    waKKu@0xcd80: Vulns$ ./width $(perl -e 'print "A"x33000')
    String length too big!!!
    

    Again, everything seems perfect.
    Allow you some time looking at the source and trying to see where is the bug.

    OK – let’s dive into the assembly…

    waKKu@0xcd80: Vulns$ gdb -q ./width
    Reading symbols from /data/research/0xcd80/posts/Vulns/width...(no debugging symbols found)...done.
    (gdb) disass vuln
    Dump of assembler code for function vuln:
       0x08048424 <+0>: push   %ebp
       0x08048425 <+1>: mov    %esp,%ebp
       0x08048427 <+3>: push   %edi
       0x08048428 <+4>: sub    $0x134,%esp
       0x0804842e <+10>:    mov    0x8(%ebp),%eax
       0x08048431 <+13>:    movl   $0xffffffff,-0x11c(%ebp)
       0x0804843b <+23>:    mov    %eax,%edx
       0x0804843d <+25>:    mov    $0x0,%eax
       0x08048442 <+30>:    mov    -0x11c(%ebp),%ecx
       0x08048448 <+36>:    mov    %edx,%edi
       0x0804844a <+38>:    repnz scas %es:(%edi),%al
       0x0804844c <+40>:    mov    %ecx,%eax
       0x0804844e <+42>:    not    %eax
       0x08048450 <+44>:    sub    $0x1,%eax
       0x08048453 <+47>:    mov    %al,-0x9(%ebp)
       0x08048456 <+50>:    cmpb   $0xc8,-0x9(%ebp)
       0x0804845a <+54>:    jbe    0x804846f <vuln+75>
       0x0804845c <+56>:    movl   $0x80485c4,(%esp)
       0x08048463 <+63>:    call   0x8048354 <puts@plt>
       0x08048468 <+68>:    mov    $0x1,%eax
       0x0804846d <+73>:    jmp    0x80484a2 <vuln+126>
       0x0804846f <+75>:    mov    $0x80485dd,%eax
       0x08048474 <+80>:    mov    %eax,(%esp)
       0x08048477 <+83>:    call   0x8048344 <printf@plt>
       0x0804847c <+88>:    mov    0x8(%ebp),%eax
       0x0804847f <+91>:    mov    %eax,0x4(%esp)
       0x08048483 <+95>:    lea    -0x109(%ebp),%eax
       0x08048489 <+101>:   mov    %eax,(%esp)
       0x0804848c <+104>:   call   0x8048334 <strcpy@plt>
       0x08048491 <+109>:   movl   $0x80485fb,(%esp)
       0x08048498 <+116>:   call   0x8048354 <puts@plt>
       0x0804849d <+121>:   mov    $0x0,%eax
       0x080484a2 <+126>:   add    $0x134,%esp
       0x080484a8 <+132>:   pop    %edi
       0x080484a9 <+133>:   pop    %ebp
       0x080484aa <+134>:   ret    
    End of assembler dump.
    (gdb) # Again we have a different "cmpb" instruction. As you probably guessed, "b" means BYTE.
    (gdb) # Set the breakpoint
    (gdb) break *vuln+50
    Breakpoint 1 at 0x8048456
    (gdb) r $(perl -e 'print "A"x224')
    Starting program: /data/research/0xcd80/posts/Vulns/width $(perl -e 'print "A"x224')
    
    Breakpoint 1, 0x08048456 in vuln ()
    Missing separate debuginfos, use: debuginfo-install glibc-2.14.1-5.i686
    (gdb) x/i $eip
    => 0x8048456 <vuln+50>: cmpb   $0xc8,-0x9(%ebp)
    (gdb) p /d 0xc8
    $1 = 200
    (gdb) x/x $ebp-0x9
    0xffffd14f: 0x000000e0
    (gdb) p /d 0xe0
    $2 = 224
    (gdb) # All similar to signedness bug post here. 224 greater than 200 and the algorithm works correct.
    (gdb) # I will finish this execution and start a new one, with a different value. Pay attention on that $ebp-0x9 value.
    (gdb) continue
    Continuing.
    String length too big!!!
    [Inferior 1 (process 5513) exited normally]
    (gdb) # As expected, string too length. Let's try with a different value
    (gdb) # The maximum unsigned value possible to be represented by 1 byte is 255d = 0xffh = 1111 1111b
    (gdb) r $(perl -e 'print "A"x256')
    Starting program: /data/research/0xcd80/posts/Vulns/width $(perl -e 'print "A"x256')
    
    Breakpoint 1, 0x08048456 in vuln ()
    (gdb) x/i $eip
    => 0x8048456 <vuln+50>: cmpb   $0xc8,-0x9(%ebp)
    (gdb) p /d 0xc8
    $3 = 200
    (gdb) x/x $ebp-0x9
    0xffffd12f: 0x00000000
    (gdb) # o.0 - We sent 256 (255 + 1) and we got zero!
    (gdb) # This is due to the fact that:
    (gdb) p /x 0xff + 0x1
    $3 = 0x100
    (gdb) # As you see, if we get only the first right-byte of 0x100, we will get only 0x00.
    (gdb) # In the same vein:
    (gdb) p /x 0xff + 0x2
    $4 = 0x101
    (gdb) # So, if we send 257 chars, we must get our char variable (len) equal 1. Let's try.
    (gdb) r $(perl -e 'print "A"x257')
    The program being debugged has been started already.
    Start it from the beginning? (y or n) y
    Starting program: /data/research/0xcd80/posts/Vulns/width $(perl -e 'print "A"x257')
    
    Breakpoint 1, 0x08048456 in vuln ()
    (gdb) x/i $eip
    => 0x8048456 <vuln+50>: cmpb   $0xc8,-0x9(%ebp)
    (gdb) p /d 0xc8
    $5 = 200
    (gdb) x/x $ebp-0x9
    0xffffd12f: 0x00000001
    (gdb) # Perfect! ... We have successfully overflowed our 1 byte variable. 
    (gdb) # If you didn't understand how it will be useful to our exploit, let's see our string in memory.
    (gdb) # If you read about calling convention and you saw that vuln() has only one argument, you may know that:
    (gdb) # - We use positive offsets from EBP to access function parameters
    (gdb) # - We use negative offsets from EBP to access function local variables
    (gdb) # As we know better, the first value after EBP is our *precious* Return Address, the following must be our string pointer *buf.
    (gdb) x/wx $ebp+4
    0xffffd13c: 0x080484e7
    (gdb) # Our precious...
    (gdb) x/wx $ebp+8
    0xffffd140: 0xffffd3ce
    (gdb) # *buf
    (gdb) x/s 0xffffd3ce
    0xffffd3ce:  'A' <repeats 200 times>...
    (gdb) 
    0xffffd496:  'A' <repeats 57 times>
    (gdb) 
    0xffffd4d0:  "XDG_SESSION_ID=1"
    (gdb) # Environment variables. As we could check, argv[1] has really 257 bytes, and not only 1.
    (gdb) # Know we must rely on a non-boundary check function to overflow the reserved buffer and give us control over EIP
    (gdb) # That is exactly what strcpy() will do. It will read bytes from *buf (argv[1]) and copy it to our limited buffer
    (gdb) # until it reaches a NULL byte.
    (gdb) # In order to successfully overwrite the EIP, we need to send some extra bytes to overwrite other local variables
    (gdb) # some alignments and EBP, until we reach EIP. Let's try 280.
    (gdb) del breakpoints
    Delete all breakpoints? (y or n) y
    (gdb) r $(perl -e 'print "A"x280')
    The program being debugged has been started already.
    Start it from the beginning? (y or n) y
    Starting program: /data/research/0xcd80/posts/Vulns/width $(perl -e 'print "A"x280')
    Copying content into stack...done.
    
    Program received signal SIGSEGV, Segmentation fault.
    0x41414141 in ?? ()
    (gdb) # Success again. EIP properly overwritten.
    (gdb) quit
    A debugging session is active.
    
        Inferior 1 [process 5816] will be killed.
    
    Quit anyway? (y or n) y
    

    As promised, EIP overwritten.
    Local exploitation of non-protected stack overflows are so trivial that I have no intention to cover it anyhow.
    Our window of exploitation here was quite big, 200 bytes from 255. However, it is possible that you face some very small window that a fuzzer could not detect.
    Imagine if the code was something like:

        if ((len < 0 ) || (len > 5))
    

    Here we would need a perfect widthness overflow of 4 bytes (256 to 259) to bypass, as anything bigger or smaller would be blocked.

    Conclusion
    – Perhaps is better to use mainstream code ideas if you don’t really understand how the architecture/C/asm works. Additionally, always try to compile/keep your programs protected. A crash/DoS is a way better than a Command Execution.

    Update:
    Also, try to get used to compile your programs using -Wall -Wextra:

    waKKu@0xcd80: Vulns$ gcc -m32 -o width width.c -Wall -Wextra
    width.c: In function ‘vuln’:
    width.c:10:5: warning: comparison is always false due to limited range of data type [-Wtype-limits]
    

    This message would help you identify the problem.

    As usual, any thoughts are welcome.

    –waKKu

    Advertisements

    Posted January 28, 2012 by waKKu in Assembly, Exploiting, Programming, Security

    Leave a Reply

    Fill in your details below or click an icon to log in:

    WordPress.com Logo

    You are commenting using your WordPress.com account. Log Out / Change )

    Twitter picture

    You are commenting using your Twitter account. Log Out / Change )

    Facebook photo

    You are commenting using your Facebook account. Log Out / Change )

    Google+ photo

    You are commenting using your Google+ account. Log Out / Change )

    Connecting to %s

    %d bloggers like this: