ALEX CTF 2016にチームm1z0r3として参加しました.私が解いたのは2問(+1問)ですが,そのWrite-upになります.
開催概要
- 開催期間 : 2月3日(金) 17:00 ~ 2月6日(月) 17:00
- IRC : #alexctf @freenode
- フラグ形式 : ALEXCTF{[A-Za-z0-9_]*} unless otherwise specified.
結果
1890点87位.悲しみ.なぜか右の円グラフでCRとTRとForeしか解いてないことになってるけどREとかも解いた.
手を付けた問題
- CR1 : Ultracoded
- CR2 : Many time secrets
- CR5 : Bring weakness
- Fore2 : Mail client
- Fore4 : Unknows format
- SC2 : Cutie cat
解いた問題
- CR1 : Ultracoded
- CR2 : Many time secrets
- CR5 : Bring weakness
CR1はチームメイトが解いていたが,情報が全く共有されていなかったのでフラグ形式確認がてら解いた(Ruleを見てもフラグ形式が書いてなかったが普通にトップページに書いてあった).
SC2とか他のCRとかはきゅうりくんが解いた.Foreはわからなかったが2に関しては惜しいところまでGuessingしてた.
Write-up
CR1 : Ultracoded
フラグが何らかの方式でエンコードされてるとのこと.渡されるデータは以下.
1 2 3 |
ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ZERO ZERO ONE ONE ZERO ZERO ONE ONE ONE ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ONE ONE ZERO ONE ZERO ZERO ONE ZERO ZERO ONE ONE ZERO ZERO ZERO ZERO ZERO ... こんなのがずっと続く |
“ONE”,”ZERO”を”1″,”0″に直し,ビット列を10進数値にしてlong_to_bytesする.すると base64が出てくるのでbase64デコードする .最後にモールス信号が出てくるのでデコードする
ということをするとフラグっぽいのが出てくる.拾ってきたモールス信号の辞書(CODE)をプログラムで整形(revCODE)してソルバを書く.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
from Crypto.Util.number import long_to_bytes as l2b CODE = {'A': '.-', 'B': '-...', 'C': '-.-.', 'D': '-..', 'E': '.', 'F': '..-.', 'G': '--.', 'H': '....', 'I': '..', 'J': '.---', 'K': '-.-', 'L': '.-..', 'M': '--', 'N': '-.', 'O': '---', 'P': '.--.', 'Q': '--.-', 'R': '.-.', 'S': '...', 'T': '-', 'U': '..-', 'V': '...-', 'W': '.--', 'X': '-..-', 'Y': '-.--', 'Z': '--..', '0': '-----', '1': '.----', '2': '..---', '3': '...--', '4': '....-', '5': '.....', '6': '-....', '7': '--...', '8': '---..', '9': '----.' } revCODE = {'---': 'O', '--.': 'G', '-...': 'B', '-..-': 'X', '.-.': 'R', '--.-': 'Q', '--..': 'Z', '.--': 'W', '..---': '2', '.-': 'A', '..': 'I', '-.-.': 'C', '..-.': 'F', '-.--': 'Y', '-': 'T', '.': 'E', '.-..': 'L', '...': 'S', '..-': 'U', '.----': '1', '-----': '0', '-.-': 'K', '-..': 'D', '----.': '9', '-....': '6', '.---': 'J', '.--.': 'P', '....-': '4', '--': 'M', '-.': 'N', '....': 'H', '---..': '8', '...-': 'V', '--...': '7', '.....': '5', '...--': '3'} data = open("zero_one","r").read().split() ans = "" for d in data: if d=="ONE": ans+="1" else: ans+="0" morse = l2b(int(ans,2)).decode("base64").split() ans = "" for d in morse: ans += revCODE[d] print ans |
“ALEXCTFTH15O1SO5UP3RO5ECR3TOTXT”というのが出てくるので,それっぽいところに{}を入れてOを_に変えるとフラグらしい.
The flag is ALEXCTF{TH15_1S_5UP3R_5ECR3T_TXT}
CR2 : Many time secrets
問題は以下.
This time Fady learned from his old mistake and decided to use onetime pad as his encryption technique, but he never knew why people call it one time pad!
One Time PadということはXORか.「なんでOne time padというか知らなかった」ということは複数のメッセージに鍵を使いまわしていそう.ということは与えられたファイルの一行(26文字分)について一つの鍵を使っているのか.XOR問題でヒント無しということは確実に推測できるものが鍵になっているだろう,などと考え,フラグのうち確実に推測できる”ALEXCTF“を含むものを鍵にしようと考え,とりあえず26文字で”ALEXCTF*******************”から”*******************ALEXCTF”を試すと,”ALEXCTFが先頭の時だけ可読な文字が現れたので,この時点で鍵がフラグであると予測.
あとはちょっと読めるようになった文章から,”Dear Fr”ということは”Dear Friend”だろうなとか,”encr”は”encryption”かな,とか推測するという方法,いわゆる古典暗号を解く方法で鍵(フラグ)を特定していった.
XORを取るときは下記のようにした.
1 2 3 4 5 6 7 8 |
# coding:utf-8 from m1z0r3.crypro import * # この中でCrypto.Util.numberのbytes_to_longとlong_to_bytesをそれぞれb2l,l2bとしてimportしてる ~~~ # dが与えられたテキストファイルの1行,keyが鍵候補(今回はALEXCTF{***}みたいなやつ) print l2b(int(d,16)^b2l(key)) ~~~ |
The flag is ALEXCTF{HERE_GOES_THE_KEY}
CR5 : Bring weakness
問題文は以下.
We got this PRNG as the most secure random number generator for cryptography.
Can you prove otherwisenc 195.154.53.62 7412
PRNGとは疑似乱数生成器のこと.平方採中法か?線形合同法か?メルセンヌツイスタか?とか言いながら,結局良く分からなかったので,とりあえず1コネクションの中で7万リクエストくらい送って得た乱数を貯めておいてSet型にしたら(重複を消したら)32768個しか残らなかった.何度やっても32768個しか残らないので,32768個を1周期としてループしてしまう超脆弱PRNGであることが分かった.あとはやるだけ.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# coding:utf-8 from m1z0r3.crypro import * #sock()とかshell()とかこの中に. from progressbar import ProgressBar #最初乱数たくさん得るのに進捗わかりやすいように, def select2(s): s.send("2\n") return def select1(s): s.send("1\n") return def select2_n(s,n): s.send("2\n"*n) def lcg(x,a,c,m): return (a*x+c) % m def main(): remoteip = "195.154.53.62" remoteport = 7412 s,f = sock(remoteip,remoteport) nums = [] p = ProgressBar() n = 32768 for i in xrange(0,32768+10): # p.update(i+1) if i==0: select2_n(s,n) if i<n: read_until(f,"Give me the next number\n") # select2(s) num = int(read_until(f)) # print "[+]",num nums.append(num) else: read_until(f,"Give me the next number\n") select1(s) s.send(str(nums[i-n])+"\n") # shell(s) shell(s) # print len(set(nums)) # open("prng.txt","w").write(",".join(map(str,nums))) if __name__ == '__main__': main() |
直してないので煩雑ですがshell(s)の時点でフラグが降ってきます.
The flag is ALEXCTF{f0cfad89693ec6787a75fa4e53d8bdb5}
感想
バイナリをやらねばというのはもちろんのこと,フォレンジックもできるようになりたいなという気持ちになりました(今回の問題は微妙だった気もしますが).
ばいなりばいなり