Trend Micro CTF 2015 Quals Writeup
今月末にあったトレンドマイクロCTFに参戦してました.
本当はISUCONの予選に参加したかったのですが友人にISUCONに出るマンがいないため,何もできないなと思って今回は参加をやめました. あと,CODEFESTIVALの予選もあったのですがその時間帯にすっかり忘れていて参加できなかったですw
私はご注文はうさぎですか?の大ファンなのでチーム名がg0tiu5aとごちうさをもじったチーム名なんですがとりあえず500ポイントを入れて181位でした. なんとも微妙な順位ですねw
実はインターンが終わってからモチベーションをあげようということでCTFtimeに載っているオンラインCTFに片っ端から参加していて,CSAW CTFとMMA CTFにも参加していたのですが特筆することもないので書きませんでした. その2回のCTFは1人で寂しくCTFをしていたのですが今回は友人3人と共にSlackでわいわいやっていたので結構楽しかったです.
とりあえず解けた問題のWriteupを載せておきます.
Analysis-defensive 100
実はこいつなぜ解けたのかわかっていない謎問題.VM上のUbuntuにダウンロードして実行したら一発目になぜかFLAGがでてきた.意味不明. 最初はダミーかなと思っていたのですがとりあえずスコアサーバーに提出したらFLAGが通った.
Cryptography 100
256ビットのRSA問題.この問題にすごい時間をかけてしまった. 問題の概要としては256ビットのRSA公開鍵を渡すんだけど中身1ビット壊れてるから直して素因数分解してみ?というような問題であった. とりあえずOpenSSLのコマンドで公開鍵の中身を見てみると以下のような表示になる.
$ openssl rsa -pubin -in PublicKey.pem -text -noout Modulus (256 bit): 00:b6:2d:ce:9f:25:81:63:57:23:db:6b:18:8f:12: f0:46:9c:be:e0:cb:c5:da:cb:36:c3:6e:0c:96:b6: ea:7b:fc Exponent: 65537 (0x10001)
とりあえず出てきた合成数をPari/GPに投げて素因数分解してみるとこんな感じになる.
? factor(82401872610398250859431855480217685317486932934710222647212042489320711027708) %1 = [ 2 2] [ 3 2] [ 11 1] [ 19 1] [ 307 1] [ 180728237 1] [ 2478211847 1] [79649944603613274174761346722176223218141026933909079 1]
ふむふむ.こりゃないなと.
とりあえず2進数でビットを1つずつ反転して素因数分解させてみればいいかなということに気づいた.どうやってそれをやろうかと思っていたのだが目的のビットの場所だけ1とXORすればいいじゃんということに気づいたのでそれを使って1つずつやってみる.ついでに偶数は結果に出力させないようにした.
#!/usr/bin/env python3 # -*- coding: utf-8 -*- # from sympy.ntheory import primefactors import random source = "".join("b6:2d:ce:9f:25:81:63:57:23:db:6b:18:8f:12:f0:46:9c:be:e0:cb:c5:da:cb:36:c3:6e:0c:96:b6:ea:7b:fc".split(":")) def solver(): vals = int(source, 16) # vals = 0b0001 k = vals print("F: " + hex(k)) start = 1 for i in range(0, 257): k = vals ^ start start = start << 1 if k % 2 != 0: print(str(i) + ": " + str(k)) if __name__ == "__main__": solver() print("END")
こうすると1つだけ奇数が含まれていることがわかった.
こいつ:82401872610398250859431855480217685317486932934710222647212042489320711027709
さて他の方のWriteupを読んでいるとPari/GPで素因数分解できたらしいのだが我がMacBook Airでは全く終わる気配がなかったので適当に素因数分解できそうなライブラリを探しているとmsieve
という強そうなソフトウェアを見つけた.ただこれを用いてもMBA上では全然素因数分解できないので強い計算機が必要だなということで大学で使える強そうな計算機サーバーくんを少し拝借して実行した.(今更だけどさくらのクラウドのクーポン券あるしクラウドにでかいインスタンス立てて実行すればよかったかも)
$ ./msieve -v -e -n -q 82401872610398250859431855480217685317486932934710222647212042489320711027709 82401872610398250859431855480217685317486932934710222647212042489320711027709 prp39: 279125332373073513017147096164124452877 prp39: 295214597363242917440342570226980714417
これで得られた2つの素数を元に秘密鍵を作成する.ここでrsatoolの存在を思い出したのでそれを用いて秘密鍵を作成する.
$ python rsatool.py -f PEM -o key.pem -p 279125332373073513017147096164124452877 -q 295214597363242917440342570226980714417
こんなかんじで得られた秘密鍵を使って復号する.最初復号しようとしても
$ openssl rsautl -decrypt -in encrypted.txt -out decrypt -inkey key.pem RSA operation error 7047:error:0406506C:rsa routines:RSA_EAY_PRIVATE_DECRYPT:data greater than mod len:/SourceCache/OpenSSL098/OpenSSL098-52.40.1/src/crypto/rsa/rsa_eay.c:521:
こんな感じのエラーに悩まされてキレていたのですがもしかしてbase64でデコードしたやつを復号するんじゃね?とふと思いやってみたらそのとおりでしたw
$ cat encrypted.txt | base64 -D | openssl rsautl -inkey key.pem -decrypt TMCTF{$@!zbo4+qt9=5}
わーい.
実はFactor DBなるサイトが合ってそこに投げるとすぐに得られたらしい. Factor DB
Programming 200
実は後述するクロスワード問題を解いていたのですが途中で飽きたのと英語読解するのが辛くなって深夜のテンションで解いた問題です.
サーバーに接続すると式がでてきて計算した結果を返せばいいようです.最初は普通に手でやっていたのですが辛くなったので自動化しました.最終的に以下のような形式が出てきました.
- 数字のみの計算 (1 + 1, 100 * 200 - (300 / 2) みたいなかんじ)
- 数字のカンマ表記 (100,000 + 2)
- ローマ数字 (IV + 3)
- 英語表記 (sixty handred one + 30)
ローマ数字の部分はPythonで書かれたライブラリのコードを適当に拝借し,英語表記の読解もStack OverflowにあったIs there a way to Convert Number words to Integers? Pythonを拝借して深夜の謎テンションで実装をキメました.(個人的には漢数字とかも出てくるのかなと思ってビクビクしてましたが(アジア圏なので)そんなことはなかったです)
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import socket import re URL = "ctfquest.trendmicro.co.jp" PORT = 51740 def text2int(textnum, numwords={}): if not numwords: units = [ "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", ] tens = ["", "", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"] scales = ["hundred", "thousand", "million", "billion", "trillion"] numwords["and"] = (1, 0) for idx, word in enumerate(units): numwords[word] = (1, idx) for idx, word in enumerate(tens): numwords[word] = (1, idx * 10) for idx, word in enumerate(scales): numwords[word] = (10 ** (idx * 3 or 2), 0) current = result = 0 for word in textnum.split(): if word not in numwords: raise Exception("Illegal word: " + word) scale, increment = numwords[word] current = current * scale + increment if scale > 100: result += current current = 0 return result + current # Define digit mapping romanNumeralMap = (('M', 1000), ('CM', 900), ('D', 500), ('CD', 400), ('C', 100), ('XC', 90), ('L', 50), ('XL', 40), ('X', 10), ('IX', 9), ('V', 5), ('IV', 4), ('I', 1)) # Define pattern to detect valid Roman numerals romanNumeralPattern = re.compile(""" ^ # beginning of string M{0,4} # thousands - 0 to 4 M's (CM|CD|D?C{0,3}) # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 C's), # or 500-800 (D, followed by 0 to 3 C's) (XC|XL|L?X{0,3}) # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 X's), # or 50-80 (L, followed by 0 to 3 X's) (IX|IV|V?I{0,3}) # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 I's), # or 5-8 (V, followed by 0 to 3 I's) $ # end of string """ ,re.VERBOSE) def roman2int(s): """convert Roman numeral to integer""" if not s: # raise InvalidRomanNumeralError('Input can not be blank') raise ValueError() if not romanNumeralPattern.search(s): return text2int(s) #raise InvalidRomanNumeralError('Invalid Roman numeral: %s' % s) result = 0 index = 0 for numeral, integer in romanNumeralMap: while s[index:index+len(numeral)] == numeral: result += integer index += len(numeral) return result def parser(d): data = d.replace(",", "").replace(" = ", "") op = [] for k in data: if k in ["+", "-", "*", "/", "(", ")"]: op.append(k) data = re.sub("[\+\-*/()]", " <> ", data).split(" <> ") print(data) for i in range(len(data)): data[i] = data[i].strip() if data[i] not in ["+", "-", "*", "/", "(", ")", "", "<>"] \ and data[i].isdecimal() is False: data[i] = str(roman2int(data[i])) data = " ".join(data) cnt = 0 res = "" for k in data: if k == " ": res += op[cnt] cnt += 1 else: res += k return res def main(): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((URL, PORT)) while True: data = s.recv(1024).decode() if data == "Congratulations!\n": print(s.recv(1024).decode()) print(s.recv(1024).decode()) break print(data) res = str(eval(parser(data))) print(res) s.send(res.encode()) if __name__ == "__main__": main() #print(parser("1 + 2 = "))
TMCTF{U D1D 17!}
Miscellaneous 100
友人と調べながら解いた問題.実は1問だけわからなかったのですがブルートフォースで沈めた.
頭文字を並べるとSVIEDTRVSCSECDHBBDITMASTCとなったのでこれをMD5でエンコードして提出.
TMCTF{88f5505a45c9e176e36898095f505187}
解けそうで解けなかった問題
Web 100
ログインするとIDがだらだら出てくる問題.途中でID1がログインしているのが特徴的だったのでもしかすると同時に接続すればいいのかなと思ったのですがうまくいかず断念.方向性はあっていた模様.
Fix me PDF
壊れたPDFが与えられる.いろいろ直してみたがよくわからない.どうやらPDFファイルをバイナリで覗くとBase64でエンコードされた画像がありそれがFLAGらしい. OSXでは普通に開けてしまっていたため何が壊れているのかさっぱりだった.
Programming 100
色を識別しろみたいな問題.PythonでやっていたのだがImage系のライブラリを触ったことがなかったため途中で挫折した.
Miscellaneous 300
200回連続でCAPTCHAを通過しろみたいな問題.時間制限は特に無いので最初は手でやっていたが間違えるとすぐ0になるのと数の多さに圧倒されてやっぱり自動化しなきゃなとおもった.
Pythonでやろうとしたのだが2値化してライブラリに投げてもうまく読み取ってくれず諦めた.@chibieggさんのブログに詳細コードが載っていたのであとで見てやり方を学びたい.
最後に
3回のCTFに参加してわかったこととしてはWeb力, Pwn力のなさがあるということ.Webはある意味Reconみたいなものと思っているがいろいろな脆弱性や攻撃手法を学んでいきたいなと思う.あとPwnというかReversingの力もほしいのでとりあえずx86のアセンブラを読めるようになりたいなと言った感じ.
とりあえずこれからも暇があればCTFの問題にたくさんアタックしていきたい.