【セキュリティゲーム】SECCON Beginners CTF 2022 Writeup ~Crypto - CoughingFox~
はじめまして、ますみです!
株式会社Galirage(ガリレージ)という「生成AIに特化して、システム開発・アドバイザリー支援・研修支援をしているIT企業」で、代表をしております^^
先日、SECCON Beginners CTF 2022というイベント(2022/06/04~05)に4人チームで参加し、私はCryptoの問題を担当しました。
そこで、この記事では、その中のCoughingFoxというBeginner向けの問題の解説(Writeup)をします。
前提知識
CTFとは?
CTFとは、Capture The Flagの略で情報セキュリティ分野における「旗とりゲーム」です。
専門的な知識と技術を用いて、隠れされたFlag(文字列)を見つけ出します。
大会によって定義は異なりますが、一般的にCTFは以下のようなジャンルに分かれます。
名前 | ジャンル |
---|---|
Reversing (Binary) | バイナリ解析 |
Forensics | イメージファイル解析 |
Pwn | Programの脆弱性攻撃 |
Web | Webの脆弱性攻撃 |
Network | ネットワークパケットなどの解析 |
Crpto | 暗号の復号 |
Misc | その他(様々なデータからフラグを見つける) |
SECCONとは?
SECCONとは、Security Contestの略称で、情報セキュリティのコンテストイベントです。SECCONは、「CTF」と「ハッカソン」の二種類のコンテストを主催しています。
公式サイトでは、次のように定義されています。
情報セキュリティをテーマに多様な競技を開催する情報セキュリティコンテストイベントです。実践的情報セキュリティ人材の発掘・ 育成、技術の実践の場の提供を目的として設立されました。(SECCON 2021より引用)
問題内容
では、ここからSECCON Beginners CTF 2022のCrypto問題として出題されたCoughingFoxの問題を説明します。コンテストでは、下記のproblem.py
とoutput.txt
が渡されます。
この問題では、flag
がダミーの文字列になっていますが、「flag
が入力された状態で、problem.py
を実行すると、output.txt
が出力されますよ。」という条件になります。
問題の中身を少しだけ解説すると、以下のような形になります。
- flagの文字列の中身を一文字ずつ抜き出し、
f
に代入する。 - 抜き出した文字
f
が「何番目の文字か」というインデックスi
を足して、2乗して、もう一度i
を足す。(ちなみに、文字はASCII表に従って、暗黙的にint型に変換されています。) - 最後にシャッフル。
なんだか解けそうな気がしてきますよね😇
from random import shuffle
flag = b"ctf4b{XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}"
cipher = []
for i in range(len(flag)):
f = flag[i]
c = (f + i)**2 + i
cipher.append(c)
shuffle(cipher)
print("cipher =", cipher)
cipher = [12147, 20481, 7073, 10408, 26615, 19066, 19363, 10852, 11705, 17445, 3028, 10640, 10623, 13243, 5789, 17436, 12348, 10818, 15891, 2818, 13690, 11671, 6410, 16649, 15905, 22240, 7096, 9801, 6090, 9624, 16660, 18531, 22533, 24381, 14909, 17705, 16389, 21346, 19626, 29977, 23452, 14895, 17452, 17733, 22235, 24687, 15649, 21941, 11472]
Pythonに詳しくない方は、こちらの入門書をご覧ください
回答(筆者の回答)
筆者の作成した回答(solver)は以下の通りです。
-
まず
output.txt
の中身を取り出し、格納されている数字を一つずつi_output
でincrementして、取り出す。 -
次に、
try
の中で暗号化された処理と逆の処理を記述して、その復号結果の妥当性を検証します。フラグのフォーマットルールとして、ctf4b{[\x20-\x7e]+}
が提示されているため、この条件にマッチする検証を行いました。ここでのポイントとしては、暗号化した時の「何番目の文字か」というインデックスi
の情報をi_choice
という変数用いて、総当たりしていることです。 -
結果的に、何文字目かという情報とその文字が何かという情報がリストとして取得できたため、最後にソートを行いました。
flag = b"ctf4b{XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}"
output = [
12147, 20481, 7073, 10408, 26615, 19066, 19363, 10852, 11705, 17445, 3028,
10640, 10623, 13243, 5789, 17436, 12348, 10818, 15891, 2818, 13690, 11671,
6410, 16649, 15905, 22240, 7096, 9801, 6090, 9624, 16660, 18531, 22533,
24381, 14909, 17705, 16389, 21346, 19626, 29977, 23452, 14895, 17452,
17733, 22235, 24687, 15649, 21941, 11472
]
def is_right_char(value: int):
"""is_right_char
Args:
value (int): integer number in ascii
Returns:
bool: whether it matches the flag regulation rule
Example:
>> print(is_right_char(50))
True
"""
if 32 <= value and value <= 126 and value.is_integer():
return True
else:
return False
flag_len = len(flag)
index_list = []
word_list = []
for i_output in range(flag_len):
for i_choice in range(flag_len):
c = output[i_output] # <= 1. Extract Output Character
try: # <= 2. Decode and Validate
f = (c - i_choice)**(0.5) - i_choice
if is_right_char(f):
index_list.append(i_choice)
word_list.append(int(f))
f = -(c - i_choice)**(0.5) - i_choice
if is_right_char(f):
index_list.append(i_choice)
word_list.append(int(f))
except:
continue
# 3. Sort List
print(f"index_list: {index_list} \n")
print(f"word_list: {word_list} \n")
flag_list = ['~'] * flag_len
for i in range(flag_len):
flag_list[index_list[i]] = chr(word_list[i])
flag_answer = ''.join(flag_list)
print(f"flag_answer: {flag_answer}")
index_list: [47, 32, 17, 4, 46, 22, 42, 36, 41, 21, 3, 31, 14, 18, 13, 12, 27, 2, 15, 9, 1, 7, 10, 8, 29, 39, 40, 0, 6, 20, 19, 35, 33, 45, 25, 16, 5, 30, 26, 48, 43, 11, 28, 44, 34, 38, 24, 37, 23]
word_list: [63, 111, 67, 98, 117, 116, 97, 68, 67, 111, 52, 72, 89, 97, 63, 120, 84, 102, 111, 44, 116, 101, 70, 121, 97, 110, 44, 99, 72, 78, 110, 101, 117, 111, 97, 117, 123, 116, 114, 125, 110, 111, 104, 89, 115, 119, 101, 111, 84]
flag_answer: ctf4b{Hey,Fox?YouCanNotTearThatHouseDown,CanYou?}
以上のようにして、ctf4b{Hey,Fox?YouCanNotTearThatHouseDown,CanYou?}
というflagが取得できました🎉
作問者の回答の手法
作問者の回答はもっとスマートでした😲
gmpy2.iroot(c, e)
という「e乗根した時にその値(c)が整数かどうかを判定するライブラリ」を用いて解いていました。
最後に
最後まで読んでくださり、ありがとうございました!
この記事を通して、少しでもあなたの学びに役立てば幸いです!
宣伝:もしもよかったらご覧ください^^
『AIとコミュニケーションする技術(インプレス出版)』という書籍を出版しました🎉
これからの未来において「変わらない知識」を見極めて、生成AIの業界において、読まれ続ける「バイブル」となる本をまとめ上げました。
かなり自信のある一冊なため、もしもよろしければ、ご一読いただけますと幸いです^^
Discussion