プログラム系統備忘録ブログ

記事中のコードは自己責任の下でご自由にどうぞ。

SECCON 2023 カプセルCTF write-up

SECCON 2023 電脳会議のイベントの1つ、SECCON カプセルCTFへ取り組んでいました。そのwrite-up記事です。(運営側の方からwrite-up公開の許可は得ていますが、まずい表記等あればお知らせください。)

コンテスト概要

SECCON 2023 電脳会議中の、2023/12/23(土) 10:00 +09:00 - 2023/12/24(日) 17:00 +09:00 の間の開催でした。特徴的だったのは問題の入手方法です:

  • 会場でスタッフさんにお声がけすると、1人1回ガチャガチャを回せます。回して出てくるカプセルの中に紙が入っており、その紙にはスコアサーバーのURLと、1問のパスワードが書かれています。
  • スコアサーバーへアクセスしてRegisterすると、問題一覧は通常通り表示されます。しかしほとんど問題にはZIPファイルのダウンロードリンクのみが記載されています。そのZIPファイルのパスワードが、カプセルの中の紙にかかれていたものです!(パスワードは問題ごとに異なります)

というわけで、他の参加者の皆様からパスワードを教えてもらって、様々な問題へ取り組む必要があります。なお後述するように、クラックも許されています。

なお、スコアサーバーへは現地のWifi回線からのみアクセスできました。そのため夜間ではフラグ提出等はできません。とはいえ問題ZIPファイル等を持ち帰ればオフラインで取り組むことは出来ました。その他、トップページやルールページから引用します:

競技形式: On-site / Jeopardy
カプセルが無くなり次第終了予定です
他の参加者とzipファイルのパスワードを交換して、複数の問題に挑戦することを想定しています
SECCON CTF 2023 Finalsへの出場権は得られません
競技形式
  Jeopardy形式

ルール
  得点は競技者毎に集計します。集計にはダイナミックスコアリング方式(多くの競技者が解いた問題ほど点数が低くなるような方式)を用います。
  原則競技中には問題の追加を行いません。問題の設定ミスなどが発覚した場合には、例外的に修正版の問題が公開される場合があります。
  フラグのフォーマットは SECCON{[\x20-\x7e]+} です。これと異なる形式を取る問題に関しては、別途問題文等でその旨を明示します。
  他競技者への問題・zipパスワードの共有を許可します。他競技者からの要望がある場合、ヒント・解答を共有することを許可します。
  問題・zipパスワードのSNSへのアップロードを許可します。ハッシュタグは #capsule_ctf です。ただし、スコアサーバは オンサイト環境からのみ アクセスできます。
  誤った解答を短時間の内に何度も送信した場合は、当該競技者からの回答を一定時間受け付けない状態(ロック状態)になる場合があります。またこの状態でさらに不正解を送信し続けた場合はロックされる時間がさらに延長される可能性があります。

禁止事項
  他競技者への妨害行為
  他競技者の回答などを許可なくのぞき見する行為
  他者への攻撃的な発言 (暴言 / 誹謗中傷 / 侮辱 / 脅迫 / ハラスメント行為など)
  設問によって攻撃が許可されているサーバ、ネットワーク以外への攻撃
  競技ネットワーク・サーバなどの負荷を過度に高める行為(リモートから総当たりをしないと解けない問題はありません!)
  その他、運営を阻害する行為
  不正行為が発見された場合、運営側の裁量によって減点・失格などのペナルティが競技者に対して課せられます。大会後に発覚した場合も同様とします。

特記事項
  出題内容や開催中のアナウンスは原則日本語としますが、問題中で英語が用いられる場合があります。
  本大会では上位競技者への賞金・賞状の授与等は行いません。
  SECCON CTF への出場権とは一切の関係がありませんので、ご注意ください。
  zipファイルのクラックなどによる問題文の取り出しは許可されています。

結果

正の得点を得ている64名中、4555 pointsで5位でした。1位の方も同点で、最終提出時刻による差でした。

順位と得点

灰色背景: 解けた問題

環境

大体の問題は、現地へ持っていったWindowsのノートPCで取り組みました。パスワードのクラックは夜にデスクトップPCでやりました。使用ツールのバージョン表記等は今回は省略します。

ZIPファイルのパスワード集め

前述したように、本コンテストで配布されるファイルはすべてZIP形式で、かつそれぞれ別のパスワードで暗号化されています。まずはパスワードを集めるところから始まりました。

まずは1つ: ガチャガチャ

前述したように、1人1回ガチャガチャを回せます。自分の分のガチャガチャ内容から、1問のパスワードが手に入りました。

真っ当に: 他の方と交換

本コンテストに取り組んでおられた他の方にお声がけして、パスワードを教えてもらいました。

自分が引いた1枚をお見せして、他の皆様が引かれた内容を見せていただく様子

最終手段: クラック

1日目終了時には、21問中19問のパスワードが集まっていました。さて、ルールの特記事項にzipファイルのクラックなどによる問題文の取り出しは許可されていますとあります。残る2問のパスワードをクラックすることにしました。

パスワードの最初と最後のパターン

各ZIPファイルのパスワードは、「英大文字、英小文字、数字のいずれか」7文字のようでした。加えて、パスワード交換時に他の方から、「一部の例外を除いて、パスワード最初の文字は問題名最初の文字の大文字、パスワード最後の文字は問題名最後の文字の大文字」との情報をいただきました。実際その時の手元にあったパスワードでは、snake問題を除いてそのパターンに適合していました(snake問題のパスワード最後は、問題名最後のEではなく、後ろから2文字目のKでした)。

なお、2日目朝に、運営の方からも同様のヒントが出されていました:

John the Ripperで頑張る

Kali Linuxのjohnコマンドで頑張ろうとしました。辞書を使って攻める方法は知っていましたが、「固定文字数、かつ最初と最後の文字は固定」のような指定方法を知らなかったので、ひたすら方法を調べました。以下では、例としてwhispering問題のパスワードをクラックします:

  • zipファイルからハッシュ値を計算してファイルへ保存
┌──(tan㉿user-pc)-[~/work]
└─$ zip2john whispering.zip > hash.txt
ver 1.0 whispering.zip/whispering/ is not encrypted, or stored with non-handled compression type
ver 2.0 efh 5455 efh 7875 whispering.zip/whispering/challenge.txt PKZIP Encr: TS_chk, cmplen=199, decmplen=220, crc=75BAAECA ts=8551 cs=8551 type=8
ver 1.0 whispering.zip/whispering/files/ is not encrypted, or stored with non-handled compression type
ver 2.0 efh 5455 efh 7875 whispering.zip/whispering/files/whispering.pcap PKZIP Encr: TS_chk, cmplen=48829, decmplen=167469, crc=FE252866 ts=850E cs=850e type=8
NOTE: It is assumed that all files in each archive have the same password.
If that is not the case, the hash may be uncrackable. To avoid this, use
option -o to pick a file at a time.

┌──(tan㉿user-pc)-[~/work]
└─$
  • 問題名最初の固定文字Wのみを持つ辞書mydict.txtを作成:
┌──(tan㉿user-pc)-[~/work]
└─$ echo 'W' > mydict.txt

┌──(tan㉿user-pc)-[~/work]
└─$ cat mydict.txt
W

┌──(tan㉿user-pc)-[~/work]
└─$
  • /etc/john/john.conf[List.Rules:Wordlist]行の上辺りに、以下のSECCONルールを追加して、問題名末尾の固定文字Gを含む6文字のパターンを辞書の単語に追加させる:
[List.Rules:SECCON]
# 辞書の各単語の末尾に、「英大文字/小文字/数字」5文字と固定文字「G」を追加、ここで「G」は問題名最後の文字の大文字
$[a-zA-Z0-9]$[a-zA-Z0-9]$[a-zA-Z0-9]$[a-zA-Z0-9]$[a-zA-Z0-9]$G
  • johnに辞書とルールを与えて頑張ってもらう:
┌──(tan㉿user-pc)-[~/work]
└─$ john --wordlist=./mydict.txt --rules=SECCON hash.txt
Using default input encoding: UTF-8
Loaded 1 password hash (PKZIP [32/64])
Will run 12 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
W<念のため削除>G          (whispering.zip)
1g 0:00:01:05 DONE (2023-12-26 00:37) 0.01532g/s 328809p/s 328809c/s 328809C/s WbB49IG..WbCbx5G
Use the "--show" option to display all of the cracked passwords reliably
Session completed.

┌──(tan㉿user-pc)-[~/work]
└─$

このようにクラックには成功しました。ただ、パターンを辞書とルールに分けて記述する方法がものすごく分かりづらかったです。もっと分かりやすい方法がどうかあってほしいです。また、~/.john/以下のログファイルに、数十万行数百万行の「ルールで試したそれぞれのパターン」が記録されていました。ディスク圧迫要因にもなりそうです。(パターンを前後に分けて、前半を辞書、後半をルールで表現すれば、ほどほどのディスク使用容量で収まるかもしれません。どちらにせよ大変だとは思います。)

ちなみにクラックに使ったデスクトップマシンのGPUは「NVIDIA GeForce GTX 1060 3GB」です。WSL2上のKali Linuxでjohnコマンド等を実行して、1つのパスワードを数分~数十分くらいでクラックできました。

hashcatならもっと素直にできるらしい

イベント終了後に、以下のツイートを見つけました:

hashcatならとても直感的にパターン指定ができるようです!hashcatはmodeの番号指定が必須らしい点で使いづらいと思っていましたが、使い分けが重要そうです。

解けた問題

各問題のZIPファイルを各種パスワードで展開すると、問題文の書かれたchallenge.txtと、各種配布ファイルが含まれていました。そういうわけで後は真っ当にCTFです。

各問題の正解者数や点数は、2日目17時時点のものを記述します。

[network] whispering (34 solved, 162 points)

TCPサーバでのやり取りを記録したpcapを入手しました!
41236番ポート宛に秘密のメッセージが送られたみたいですが見つけることはできるでしょうか

配布ファイルとして、whispering.pcapがありました。Wiresharkで確認すると、何か色々通信がありました。問題文にある41236番ポート宛の通信のみを表示するためtcp.port==41236でフィルタリングすると、PSHフラグ付きのTCP通信で、フラグ内容を送信していました:

フラグを入手できました: SECCON{Wait,IsSomeoneEavesdropping???}

[network] icmp (16 solved, 227 points)

怪しいicmp通信があったのでパケットをキャプチャしました!
Wiresharkで見てみよう!

配布ファイルとして、icmp.pcapがありました:Wiresharkで確認すると、ICMPでのecho request/repryが記録されていました:

echo requestでフラグを1文字ずつ送信しているように見えたので、ひたすら人力で1文字1文字拾ってフラグを入手しました: SECCON{h1dden_d4ta_1s_1n_1cmp_p4cke4}

おまけです。本記事執筆中に「tsharkで賢く自動化したい!」と思ったので色々調べて達成しました:

$ tshark -r icmp.pcap -Y 'ip.src==10.1.10.1' -T fields -e data | xxd -r -p
SECCON{h1dden_d4ta_1s_1n_1cmp_p4cke4}
$

[network] bad (8 solved, 305 points)

Bad USBでフラグのbase64文字列を送ったよ
読めるわけないね

配布ファイルとして、usb.pcapngがありました。USB通信のキャプチャ内容のようでした:

No.24のパケットまではデバイスの準備をしているらしく、No.25以降がURB_INTERRUPT inのようです。URB_INTERRUPT inをhostへ送信する際にHID Dataが含まれているようでした。HID DataなどでググっているとWireshark · Display Filter Reference: USB HIDを見つけました。tsharkコマンドを使って、HID Data内容を抽出しました:

$ tshark -r usb.pcapng -Y 'usbhid.data' -T fields -e 'usbhid.data' > hiddata.txt
$ cat hiddata.txt
010200180000000000
010000000000000000
010000270000000000
010000000000000000
010200190000000000
010000000000000000
010200070000000000
010000000000000000
010200140000000000
010000000000000000
010000270000000000
010000000000000000
010000260000000000
010000000000000000
010200120000000000
010000000000000000
010000080000000000
010000000000000000
010000200000000000
010000000000000000
0100000e0000000000
010000000000000000
0100001a0000000000
010000000000000000
010000070000000000
010000000000000000
010200190000000000
010000000000000000
010000250000000000
010000000000000000
010000270000000000
010000000000000000
010000060000000000
010000000000000000
0100000d0000000000
010000000000000000
010200110000000000
010000000000000000
010000090000000000
010000000000000000
010200110000000000
010000000000000000
010200090000000000
010000000000000000
010000260000000000
010000000000000000
0100000c0000000000
010000000000000000
010200110000000000
010000000000000000
0102000a0000000000
010000000000000000
010200150000000000
010000000000000000
0100001e0000000000
010000000000000000
010200110000000000
010000000000000000
0102001a0000000000
010000000000000000
0102000c0000000000
010000000000000000
010000160000000000
010000000000000000
0102001b0000000000
010000000000000000
0100001f0000000000
010000000000000000
010000210000000000
010000000000000000
0100001a0000000000
010000000000000000
010200110000000000
010000000000000000
0100001e0000000000
010000000000000000
010000250000000000
010000000000000000
010000270000000000
010000000000000000
0102001b0000000000
010000000000000000
0100001f0000000000
010000000000000000
0100000b0000000000
010000000000000000
0100001e0000000000
010000000000000000
010000050000000000
010000000000000000
010200170000000000
010000000000000000
010200150000000000
010000000000000000
010000180000000000
010000000000000000
0102000c0000000000
010000000000000000
010200160000000000
010000000000000000
010200090000000000
010000000000000000
010000260000000000
010000000000000000
010000000000000000
$

さて残るは、どうやって抽出したデータから、USBキーボードの内容を復号するかどうかです。CTF "usbhid.data"でGoogle検索するとCTFtime.org / Cyber Apocalypse 2021 / Key mission / Writeupを見つけました。そこで紹介されていたスクリプトを変更して解析できるか試してみましたが[-] Unknow Key : 01エラーで終わりました。

USBキーボード関連のCTF問題のwrite-upをいくつか見ているうちに、「データの先頭は0x000x02であるべきで、今回の全データが0x01で始まっている点がなにかおかしい?」と思ったので、先頭の0x01を削ったものを試してみました:

#!/usr/bin/env python3
# 改変元: https://github.com/WangYihang/UsbKeyboardDataHacker/blob/master/UsbKeyboardDataHacker.py

DataFileName = "hiddata_remove_01_prefix.txt"

presses = []

normalKeys = {"04":"a", "05":"b", "06":"c", "07":"d", "08":"e", "09":"f", "0a":"g", "0b":"h", "0c":"i", "0d":"j", "0e":"k", "0f":"l", "10":"m", "11":"n", "12":"o", "13":"p", "14":"q", "15":"r", "16":"s", "17":"t", "18":"u", "19":"v", "1a":"w", "1b":"x", "1c":"y", "1d":"z","1e":"1", "1f":"2", "20":"3", "21":"4", "22":"5", "23":"6","24":"7","25":"8","26":"9","27":"0","28":"<RET>","29":"<ESC>","2a":"<DEL>", "2b":"\t","2c":"<SPACE>","2d":"-","2e":"=","2f":"[","30":"]","31":"\\","32":"<NON>","33":";","34":"'","35":"<GA>","36":",","37":".","38":"/","39":"<CAP>","3a":"<F1>","3b":"<F2>", "3c":"<F3>","3d":"<F4>","3e":"<F5>","3f":"<F6>","40":"<F7>","41":"<F8>","42":"<F9>","43":"<F10>","44":"<F11>","45":"<F12>"}

shiftKeys = {"04":"A", "05":"B", "06":"C", "07":"D", "08":"E", "09":"F", "0a":"G", "0b":"H", "0c":"I", "0d":"J", "0e":"K", "0f":"L", "10":"M", "11":"N", "12":"O", "13":"P", "14":"Q", "15":"R", "16":"S", "17":"T", "18":"U", "19":"V", "1a":"W", "1b":"X", "1c":"Y", "1d":"Z","1e":"!", "1f":"@", "20":"#", "21":"$", "22":"%", "23":"^","24":"&","25":"*","26":"(","27":")","28":"<RET>","29":"<ESC>","2a":"<DEL>", "2b":"\t","2c":"<SPACE>","2d":"_","2e":"+","2f":"{","30":"}","31":"|","32":"<NON>","33":":","34":"\"","35":"<GA>","36":"<","37":">","38":"?","39":"<CAP>","3a":"<F1>","3b":"<F2>", "3c":"<F3>","3d":"<F4>","3e":"<F5>","3f":"<F6>","40":"<F7>","41":"<F8>","42":"<F9>","43":"<F10>","44":"<F11>","45":"<F12>"}


with open(DataFileName, "r") as f:
    for line in f:
        presses.append(line[0:-1])
# handle
result = ""
for press in presses:
    if press == '':
        continue
    if ':' in press:
        Bytes = press.split(":")
    else:
        Bytes = [press[i:i+2] for i in range(0, len(press), 2)]
    if Bytes[0] == "00":
        if Bytes[2] != "00" and normalKeys.get(Bytes[2]):
            result += normalKeys[Bytes[2]]
    elif int(Bytes[0],16) & 0b10 or int(Bytes[0],16) & 0b100000: # shift key is pressed.
        if Bytes[2] != "00" and normalKeys.get(Bytes[2]):
            result += shiftKeys[Bytes[2]]
    else:
        print("[-] Unknow Key : %s" % (Bytes[0]))
print("[+] Found : %s" % (result))
$ cat hiddata.txt | cut -c 3- > hiddata_remove_01_prefix.txt
$ solve.py
[+] Found : U0VDQ09Oe3kwdV80cjNfNF9iNGR1NWIsX24wN180X2h1bTRuISF9
$ base64 -d
U0VDQ09Oe3kwdV80cjNfNF9iNGR1NWIsX24wN180X2h1bTRuISF9
SECCON{y0u_4r3_4_b4du5b,_n07_4_hum4n!!}
$

フラグを入手できました: SECCON{y0u_4r3_4_b4du5b,_n07_4_hum4n!!}

[misc, osint] ctf4b (9 solved, 278 points)

カプセルCTFの前にいる運営が持っているNFCタグを読み取ってください。
※読み取れる端末がない場合は運営からFlagを聞き出そう!

配布ファイルはありません。NFCタグの読み込み方法を調べているとNFC Tools - Apps on Google Playを見つけました。Android端末へ入れて、運営の方にお声がけしてタグを読み取らせていただくと、何か色々表示されました。その中のData箇所だったかにhttps://hi120ki.github.io/seccon2023/capsule.htmlとありました。アクセスするとフラグが書かれていました: SECCON{welcome_to_capsule_ctf}

[misc] execfck (18 solved, 215 points)

このわけのわからないコードを実行できますか?

配布ファイルとして、execfck.txtがありました:

[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+(+[![]]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]+(+(!+[]+!+[]+!+[]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([]+[])[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][[]]+[])[+!+[]]+(![]+[])[+!+[]]+((+[])[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]]](!+[]+!+[]+!+[]+[!+[]+!+[]])+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]])()([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+([]+[])[(![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]()[+!+[]+[!+[]+!+[]]]+((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(+[![]]+[])[+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]]+([][[]]+[])[!+[]+!+[]]+[+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[+!+[]]+[+[]]+([][[]]+[])[+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]]+([][[]]+[])[+!+[]]+[+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+(![]+[])[+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+!+[]])[(![]+[])[!+[]+!+[]+!+[]]+(+(!+[]+!+[]+[+!+[]]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([]+[])[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][[]]+[])[+!+[]]+(![]+[])[+!+[]]+((+[])[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]]](!+[]+!+[]+!+[]+[+!+[]])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]]((!![]+[])[+[]])[([][(!![]+[])[!+[]+!+[]+!+[]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([![]]+[][[]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]](([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+(![]+[+[]])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+!+[]+[+[]]]+![]+(![]+[+[]])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+!+[]+[+[]]])()[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[+[]])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+!+[]+[+[]]])+[])[+!+[]])+([]+[])[(![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]()[+!+[]+[!+[]+!+[]]])())

この種のコードは知っています、jsfuckと呼ばれる種類の難読化が施されたJavascriptです!というわけでデコーダがあるか探すとJSfuck Decoder | De-Obfuscatorを見つけました。貼り付けてDecodeするとalert("SECCON{d0_y0u_kn0w_j5f*ck?}")とのことでした。フラグを入手できました: SECCON{d0_y0u_kn0w_j5f*ck?}

[misc] yomenaijs (18 solved, 215 points)

読めないのですが、代わりに読んでくれますか?

配布ファイルとして、yomenaijs.jsがありました:

function _0x8189(){const _0x11c7fe=['3388086MBYssd','9MJqCXY','Enter\x20The\x20Flag:','6997224qEdUCP','join','2524865AhAHAd','36iybSzZ','4276570geyaSR','Wrong','474945XplSPD','9464287kGrdGL','2YksKuz','1211167XYwFiZ'];_0x8189=function(){return _0x11c7fe;};return _0x8189();}function _0x1b4e(_0x178ebb,_0x5ee2fa){const _0x818938=_0x8189();return _0x1b4e=function(_0x1b4ea9,_0x4ae4b9){_0x1b4ea9=_0x1b4ea9-0xc1;let _0x554a5f=_0x818938[_0x1b4ea9];return _0x554a5f;},_0x1b4e(_0x178ebb,_0x5ee2fa);}(function(_0x168aee,_0x57d0ff){const _0xdd2669=_0x1b4e,_0xc23902=_0x168aee();while(!![]){try{const _0x51180f=parseInt(_0xdd2669(0xcd))/0x1*(-parseInt(_0xdd2669(0xcc))/0x2)+parseInt(_0xdd2669(0xca))/0x3*(parseInt(_0xdd2669(0xc7))/0x4)+-parseInt(_0xdd2669(0xc6))/0x5+-parseInt(_0xdd2669(0xc1))/0x6+parseInt(_0xdd2669(0xcb))/0x7+parseInt(_0xdd2669(0xc4))/0x8+-parseInt(_0xdd2669(0xc2))/0x9*(parseInt(_0xdd2669(0xc8))/0xa);if(_0x51180f===_0x57d0ff)break;else _0xc23902['push'](_0xc23902['shift']());}catch(_0x1ebbfe){_0xc23902['push'](_0xc23902['shift']());}}}(_0x8189,0xe63cb));function reassemble(_0x4db1de){const _0x35e8b8=_0x1b4e;let _0x1eab9b=[0x2,0x3,0xe,0x10,0xb,0xc,0x6,0x7,0x4,0xd,0xf,0x1,0x9,0x0,0x5,0xa,0x8];return _0x1eab9b['map'](_0x3cb398=>_0x4db1de[_0x3cb398])[_0x35e8b8(0xc5)]('');}function checkInput(){const _0x4b32c7=_0x1b4e;let _0x1477b2=prompt(_0x4b32c7(0xc3)),_0x2348b3=reassemble(['4d','un','SE','CC','0n','4b','c4','71','}','r3','l3','bf','u5','_1','ON','5_','{0']);_0x1477b2===_0x2348b3?alert('Success'):alert(_0x4b32c7(0xc9));}checkInput();

なるほど難読化されていて読みづらいです。とはいえ最後の方にある_0x1477b2===_0x2348b3?alert('Success'):alert(_0x4b32c7(0xc9));で正誤判定をしていそうです。そこをalert(_0x1477b2); alert(_0x2348b3); alert(_0x4b32c7(0xc9));へ変更した、以下のコードをブラウザの開発者コンソールへ貼り付けました:

function _0x8189(){const _0x11c7fe=['3388086MBYssd','9MJqCXY','Enter\x20The\x20Flag:','6997224qEdUCP','join','2524865AhAHAd','36iybSzZ','4276570geyaSR','Wrong','474945XplSPD','9464287kGrdGL','2YksKuz','1211167XYwFiZ'];_0x8189=function(){return _0x11c7fe;};return _0x8189();}function _0x1b4e(_0x178ebb,_0x5ee2fa){const _0x818938=_0x8189();return _0x1b4e=function(_0x1b4ea9,_0x4ae4b9){_0x1b4ea9=_0x1b4ea9-0xc1;let _0x554a5f=_0x818938[_0x1b4ea9];return _0x554a5f;},_0x1b4e(_0x178ebb,_0x5ee2fa);}(function(_0x168aee,_0x57d0ff){const _0xdd2669=_0x1b4e,_0xc23902=_0x168aee();while(!![]){try{const _0x51180f=parseInt(_0xdd2669(0xcd))/0x1*(-parseInt(_0xdd2669(0xcc))/0x2)+parseInt(_0xdd2669(0xca))/0x3*(parseInt(_0xdd2669(0xc7))/0x4)+-parseInt(_0xdd2669(0xc6))/0x5+-parseInt(_0xdd2669(0xc1))/0x6+parseInt(_0xdd2669(0xcb))/0x7+parseInt(_0xdd2669(0xc4))/0x8+-parseInt(_0xdd2669(0xc2))/0x9*(parseInt(_0xdd2669(0xc8))/0xa);if(_0x51180f===_0x57d0ff)break;else _0xc23902['push'](_0xc23902['shift']());}catch(_0x1ebbfe){_0xc23902['push'](_0xc23902['shift']());}}}(_0x8189,0xe63cb));function reassemble(_0x4db1de){const _0x35e8b8=_0x1b4e;let _0x1eab9b=[0x2,0x3,0xe,0x10,0xb,0xc,0x6,0x7,0x4,0xd,0xf,0x1,0x9,0x0,0x5,0xa,0x8];return _0x1eab9b['map'](_0x3cb398=>_0x4db1de[_0x3cb398])[_0x35e8b8(0xc5)]('');}function checkInput(){const _0x4b32c7=_0x1b4e;let _0x1477b2=prompt(_0x4b32c7(0xc3)),_0x2348b3=reassemble(['4d','un','SE','CC','0n','4b','c4','71','}','r3','l3','bf','u5','_1','ON','5_','{0']);alert(_0x1477b2); alert(_0x2348b3); alert(_0x4b32c7(0xc9));}checkInput();

2つ目のalert(_0x2348b3);箇所でフラグが出てくれました: SECCON{0bfu5c4710n_15_unr34d4bl3}

[misc] poopizer (10 solved, 278 points)

power💩💩💩💩💩

配布ファイルとして、poopizer.txtがありました:

$("$($([char][int]"$("$(([int[]][char[]]"💩")[1]+([int[]][char[]]"💩")[1])"[-1])$("$(([int[]][char[]]"💩")[1]*([int[]][char[]]"💩")[1])"[0])"))$($([char][int]"$("$([int[]][char[]]"💩"[1])"[1])$("$([int[]][char[]]"💩"[1])"[-1])"))$($([char][int]"$("$([int[]][char[]]"💩"[1])"[1])$("$([int[]][char[]]"💩"[0])"[-1])"))$($([char][int]"$("$([int[]][char[]]"💩"[1])"[1])$("$([int[]][char[]]"💩"[0])"[-1])"))$($([char][int]"$("$([int[]][char[]]"💩"[0])"[-1])$("$([int[]][char[]]"💩"[1])"[-1])"))$($([char][int]"$("$([int[]][char[]]"💩"[0])"[-1])$("$(([int[]][char[]]"💩")[1]+([int[]][char[]]"💩")[1])"[-1])"))$($([char][int]"$("$(([int[]][char[]]"💩")[1]%([int[]][char[]]"💩")[0])"[0])$("$(([int[]][char[]]"💩")[1]-([int[]][char[]]"💩")[0])"[-1])$("$(([int[]][char[]]"💩")[1]*([int[]][char[]]"💩")[1])"[0])"))$($([char][int]"$("$(([int[]][char[]]"💩")[1]%([int[]][char[]]"💩")[0])"[0])$("$(([int[]][char[]]"💩")[1]%([int[]][char[]]"💩")[0])"[0])$("$([int[]][char[]]"💩"[1])"[-1])"))$("$(([int[]][char[]]"💩")[1]%([int[]][char[]]"💩")[0])"[0])$("$([int[]][char[]]"💩"[0])"[-1])$($([char][int]"$("$(([int[]][char[]]"💩")[1]%([int[]][char[]]"💩")[0])"[0])$("$(([int[]][char[]]"💩")[0]*([int[]][char[]]"💩")[0])"[1])$("$(([int[]][char[]]"💩")[0]+([int[]][char[]]"💩")[0])"[-1])"))$($([char][int]"$("$([int[]][char[]]"💩"[1])"[-1])$("$([int[]][char[]]"💩"[1])"[0])"))$("$([int[]][char[]]"💩"[1])"[1])$($([char][int]"$("$(([int[]][char[]]"💩")[1]%([int[]][char[]]"💩")[0])"[0])$("$(([int[]][char[]]"💩")[1]%([int[]][char[]]"💩")[0])"[0])$("$(([int[]][char[]]"💩")[0]+([int[]][char[]]"💩")[0])"[-1])"))$("$(([int[]][char[]]"💩")[1]*([int[]][char[]]"💩")[1])"[0])$("$(([int[]][char[]]"💩")[0]+([int[]][char[]]"💩")[0])"[-1])$("$([int[]][char[]]"💩"[0])"[-1])$($([char][int]"$("$([int[]][char[]]"💩"[1])"[-1])$("$([int[]][char[]]"💩"[1])"[0])"))$($([char][int]"$("$(([int[]][char[]]"💩")[1]%([int[]][char[]]"💩")[0])"[0])$("$(([int[]][char[]]"💩")[1]%([int[]][char[]]"💩")[0])"[0])$("$(([int[]][char[]]"💩")[1]-([int[]][char[]]"💩")[0])"[-1])"))$("$(([int[]][char[]]"💩")[0]*([int[]][char[]]"💩")[0])"[1])$($([char][int]"$("$(([int[]][char[]]"💩")[1]%([int[]][char[]]"💩")[0])"[0])$("$(([int[]][char[]]"💩")[1]%([int[]][char[]]"💩")[0])"[0])$("$([int[]][char[]]"💩"[1])"[-1])"))$("$(([int[]][char[]]"💩")[1]*([int[]][char[]]"💩")[1])"[0])$($([char][int]"$("$(([int[]][char[]]"💩")[1]%([int[]][char[]]"💩")[0])"[0])$("$(([int[]][char[]]"💩")[1]%([int[]][char[]]"💩")[0])"[0])$("$(([int[]][char[]]"💩")[0]+([int[]][char[]]"💩")[0])"[-1])"))$($([char][int]"$("$([int[]][char[]]"💩"[1])"[-1])$("$([int[]][char[]]"💩"[1])"[0])"))$($([char][int]"$("$([int[]][char[]]"💩"[1])"[-1])$("$([int[]][char[]]"💩"[1])"[-1])"))$("$(([int[]][char[]]"💩")[0]*([int[]][char[]]"💩")[0])"[1])$($([char][int]"$("$(([int[]][char[]]"💩")[1]%([int[]][char[]]"💩")[0])"[0])$("$(([int[]][char[]]"💩")[0]*([int[]][char[]]"💩")[0])"[1])$("$([int[]][char[]]"💩"[1])"[-1])"))$("$(([int[]][char[]]"💩")[1]*([int[]][char[]]"💩")[1])"[0])$("$([int[]][char[]]"💩"[1])"[0])$($([char][int]"$("$([int[]][char[]]"💩"[1])"[-1])$("$([int[]][char[]]"💩"[1])"[0])"))$("$([int[]][char[]]"💩"[1])"[1])$($([char][int]"$("$(([int[]][char[]]"💩")[1]%([int[]][char[]]"💩")[0])"[0])$("$(([int[]][char[]]"💩")[1]%([int[]][char[]]"💩")[0])"[0])$("$(([int[]][char[]]"💩")[0]+([int[]][char[]]"💩")[0])"[-1])"))$("$(([int[]][char[]]"💩")[1]*([int[]][char[]]"💩")[1])"[0])$("$(([int[]][char[]]"💩")[0]+([int[]][char[]]"💩")[0])"[-1])$("$([int[]][char[]]"💩"[0])"[-1])$($([char][int]"$("$([int[]][char[]]"💩"[1])"[-1])$("$([int[]][char[]]"💩"[1])"[0])"))$($([char][int]"$("$(([int[]][char[]]"💩")[1]%([int[]][char[]]"💩")[0])"[0])$("$(([int[]][char[]]"💩")[1]%([int[]][char[]]"💩")[0])"[0])$("$(([int[]][char[]]"💩")[1]-([int[]][char[]]"💩")[0])"[-1])"))$("$(([int[]][char[]]"💩")[0]*([int[]][char[]]"💩")[0])"[1])$("$(([int[]][char[]]"💩")[0]*([int[]][char[]]"💩")[0])"[1])$($([char][int]"$("$(([int[]][char[]]"💩")[1]%([int[]][char[]]"💩")[0])"[0])$("$(([int[]][char[]]"💩")[1]%([int[]][char[]]"💩")[0])"[0])$("$(([int[]][char[]]"💩")[1]-([int[]][char[]]"💩")[0])"[-1])"))$($([char][int]"$("$(([int[]][char[]]"💩")[1]%([int[]][char[]]"💩")[0])"[0])$("$(([int[]][char[]]"💩")[1]-([int[]][char[]]"💩")[0])"[-1])$("$([int[]][char[]]"💩"[1])"[0])"))")

問題文の通りの絵文字が目立ちます!それはともかく$で始まっているので最初は一瞬jQueryかのように見えました。しかしブラウザの開発者コンソールへ貼り付けてもUncaught SyntaxError: missing ) after argument listエラーとなったので言語が違いそうです。

もしかしてPowerShellかと思ったので実行してみました:

PS C:\Users\WDAGUtilityAccount> $("$($([char][int]"$("$(([int[]][char[]]"??")[1]+([int[]][char[]]"??")[1])"[-1])$("$(([int[]][char[]]"??")[1]*([int[]][char[]]"??")[1])"[0])"))$($([char][int]"$("$([int[]][char[]]"??"[1])"[1])$("$([int[]][char[]]"??"[1])"[-1])"))$($([char][int]"$("$([int[]][char[]]"??"[1])"[1])$("$([int[]][char[]]"??"[0])"[-1])"))$($([char][int]"$("$([int[]][char[]]"??"[1])"[1])$("$([int[]][char[]]"??"[0])"[-1])"))$($([char][int]"$("$([int[]][char[]]"??"[0])"[-1])$("$([int[]][char[]]"??"[1])"[-1])"))$($([char][int]"$("$([int[]][char[]]"??"[0])"[-1])$("$(([int[]][char[]]"??")[1]+([int[]][char[]]"??")[1])"[-1])"))$($([char][int]"$("$(([int[]][char[]]"??")[1]%([int[]][char[]]"??")[0])"[0])$("$(([int[]][char[]]"??")[1]-([int[]][char[]]"??")[0])"[-1])$("$(([int[]][char[]]"??")[1]*([int[]][char[]]"??")[1])"[0])"))$($([char][int]"$("$(([int[]][char[]]"??")[1]%([int[]][char[]]"??")[0])"[0])$("$(([int[]][char[]]"??")[1]%([int[]][char[]]"??")[0])"[0])$("$([int[]][char[]]"??"[1])"[-1])"))$("$(([int[]][char[]]"??")[1]%([int[]][char[]]"??")[0])"[0])$("$([int[]][char[]]"??"[0])"[-1])$($([char][int]"$("$(([int[]][char[]]"??")[1]%([int[]][char[]]"??")[0])"[0])$("$(([int[]][char[]]"??")[0]*([int[]][char[]]"??")[0])"[1])$("$(([int[]][char[]]"??")[0]+([int[]][char[]]"??")[0])"[-1])"))$($([char][int]"$("$([int[]][char[]]"??"[1])"[-1])$("$([int[]][char[]]"??"[1])"[0])"))$("$([int[]][char[]]"??"[1])"[1])$($([char][int]"$("$(([int[]][char[]]"??")[1]%([int[]][char[]]"??")[0])"[0])$("$(([int[]][char[]]"??")[1]%([int[]][char[]]"??")[0])"[0])$("$(([int[]][char[]]"??")[0]+([int[]][char[]]"??")[0])"[-1])"))$("$(([int[]][char[]]"??")[1]*([int[]][char[]]"??")[1])"[0])$("$(([int[]][char[]]"??")[0]+([int[]][char[]]"??")[0])"[-1])$("$([int[]][char[]]"??"[0])"[-1])$($([char][int]"$("$([int[]][char[]]"??"[1])"[-1])$("$([int[]][char[]]"??"[1])"[0])"))$($([char][int]"$("$(([int[]][char[]]"??")[1]%([int[]][char[]]"??")[0])"[0])$("$(([int[]][char[]]"??")[1]%([int[]][char[]]"??")[0])"[0])$("$(([int[]][char[]]"??")[1]-([int[]][char[]]"??")[0])"[-1])"))$("$(([int[]][char[]]"??")[0]*([int[]][char[]]"??")[0])"[1])$($([char][int]"$("$(([int[]][char[]]"??")[1]%([int[]][char[]]"??")[0])"[0])$("$(([int[]][char[]]"??")[1]%([int[]][char[]]"??")[0])"[0])$("$([int[]][char[]]"??"[1])"[-1])"))$("$(([int[]][char[]]"??")[1]*([int[]][char[]]"??")[1])"[0])$($([char][int]"$("$(([int[]][char[]]"??")[1]%([int[]][char[]]"??")[0])"[0])$("$(([int[]][char[]]"??")[1]%([int[]][char[]]"??")[0])"[0])$("$(([int[]][char[]]"??")[0]+([int[]][char[]]"??")[0])"[-1])"))$($([char][int]"$("$([int[]][char[]]"??"[1])"[-1])$("$([int[]][char[]]"??"[1])"[0])"))$($([char][int]"$("$([int[]][char[]]"??"[1])"[-1])$("$([int[]][char[]]"??"[1])"[-1])"))$("$(([int[]][char[]]"??")[0]*([int[]][char[]]"??")[0])"[1])$($([char][int]"$("$(([int[]][char[]]"??")[1]%([int[]][char[]]"??")[0])"[0])$("$(([int[]][char[]]"??")[0]*([int[]][char[]]"??")[0])"[1])$("$([int[]][char[]]"??"[1])"[-1])"))$("$(([int[]][char[]]"??")[1]*([int[]][char[]]"??")[1])"[0])$("$([int[]][char[]]"??"[1])"[0])$($([char][int]"$("$([int[]][char[]]"??"[1])"[-1])$("$([int[]][char[]]"??"[1])"[0])"))$("$([int[]][char[]]"??"[1])"[1])$($([char][int]"$("$(([int[]][char[]]"??")[1]%([int[]][char[]]"??")[0])"[0])$("$(([int[]][char[]]"??")[1]%([int[]][char[]]"??")[0])"[0])$("$(([int[]][char[]]"??")[0]+([int[]][char[]]"??")[0])"[-1])"))$("$(([int[]][char[]]"??")[1]*([int[]][char[]]"??")[1])"[0])$("$(([int[]][char[]]"??")[0]+([int[]][char[]]"??")[0])"[-1])$("$([int[]][char[]]"??"[0])"[-1])$($([char][int]"$("$([int[]][char[]]"??"[1])"[-1])$("$([int[]][char[]]"??"[1])"[0])"))$($([char][int]"$("$(([int[]][char[]]"??")[1]%([int[]][char[]]"??")[0])"[0])$("$(([int[]][char[]]"??")[1]%([int[]][char[]]"??")[0])"[0])$("$(([int[]][char[]]"??")[1]-([int[]][char[]]"??")[0])"[-1])"))$("$(([int[]][char[]]"??")[0]*([int[]][char[]]"??")[0])"[1])$("$(([int[]][char[]]"??")[0]*([int[]][char[]]"??")[0])"[1])$($([char][int]"$("$(([int[]][char[]]"??")[1]%([int[]][char[]]"??")[0])"[0])$("$(([int[]][char[]]"??")[1]%([int[]][char[]]"??")[0])"[0])$("$(([int[]][char[]]"??")[1]-([int[]][char[]]"??")[0])"[-1])"))$($([char][int]"$("$(([int[]][char[]]"??")[1]%([int[]][char[]]"??")[0])"[0])$("$(([int[]][char[]]"??")[1]-([int[]][char[]]"??")[0])"[-1])$("$([int[]][char[]]"??"[1])"[0])"))")
SECCON{w17h_6r347_p0w3r_c0m35_6r347_p00p}
PS C:\Users\WDAGUtilityAccount>

フラグを入手できました: SECCON{w17h_6r347_p0w3r_c0m35_6r347_p00p}

[misc] easycheat (10 solved, 278 points)

本問題のみ、スコアサーバーの問題一覧時点(=パスワードZIPのダウンロード時点)で※Windows端末が手元にない場合は運営に言って、もう一回ガチャを引こう!との注意書きがありました。

チートしていいよ!
※Windows端末が手元にない場合は運営に言って、もう一回ガチャを引こう!

配布ファイルとして、各種ファイルがありました:

$ file *
Config.exe:       PE32 executable (GUI) Intel 80386, for MS Windows
Data.wolf:        data
Game.exe:         PE32 executable (GUI) Intel 80386, for MS Windows
Game.ini:         Unicode text, UTF-8 text, with CRLF, CR line terminators
GuruguruSMF4.dll: PE32 executable (DLL) (GUI) Intel 80386, for MS Windows
$

ウディタ製ゲームです!起動しました:

赤いドラゴンを主人公として操作できます。右の緑のドラゴンへ話しかけられたら何かが起こりそうですが、岩のマスへは移動できないため、右の方へ行けません。

主人公のX座標の改ざんを試みました。まずはX座標が入っていそうなアドレスを特定します:

  1. うさみみハリケーンを起動してGame.exeへアタッチします。
  2. うさみみハリケーンのメニューから検索→メモリ範囲を指定して検索...をクリックします。
  3. 左下の方の比較用メモリグループにある確保・記録ボタンをクリックします。
  4. 「主人公の座標は、左上が(0, 0)である」と仮定して以下の操作を繰り返します:
    1. Game.exe側で主人公を少し右へ移動させて、うさみみハリケーンの変動検索実行グループにある増加ボタンを1回クリックします。
    2. Game.exe側で主人公を少し左へ移動させて、うさみみハリケーンの変動検索実行グループにある減少ボタンを1回クリックします。

上記手順をしばらく繰り返していると、最終的にX座標のアドレスの候補は6箇所に絞れました(おそらくゲーム起動ごとに候補のアドレスは変化すると思います):

試しにアドレス0103BD8Cの値を改ざんすると、主人公が岩をすり抜けて自動的に指定X座標へ移動しました。X座標を0x20へ改ざんすると、いい感じに緑のドラゴンがいる場所へ到達できました:

早速緑のドラゴンに話しかけてみると、別のマップへ飛ばされました:

ハニワでフラグが書かれていそうです。しかし主人公がハニワに埋まっているので動けません。そういうわけで改めてX座標を改ざんします。0x00へ改ざんすると一番左へ移動してハニワから脱出できるので、あとはマップを歩いてフラグを拾いました: SECCON{H1_CH3473R!!}

[misc] stegimg (8 solved, 305 points)

画像にフラグを隠しました。
すき焼きで我慢してください!

配布ファイルとして、ideal_sukiyaki.pngがありました。美味しそうなすき焼き画像でした。問題名からステガノグラフィーの予感がしたので、うさみみハリケーン同梱の青い空を見上げればいつもそこに白い猫で画像を読み込ませてステガノグラフィー解析すると、画像左上部分のrgbそれぞれのビット0に、何か情報が仕込まれていそうなことが分かりました:

ここから非常に悩みました。最終的にCTF steganography LSBでGoogle検索してCTFにおけるステガノグラフィ入門とまとめ - はまやんはまやんはまやんを見つけ、そこで紹介されていたAperi'Solveへ画像を解析させるとフラグが出てきました:

フラグを入手できました: SECCON{l00k_47_7h3_d3741l5_1n_7h3_p1c7ur3_b351d35_7h3_5uk1y4k1!}

悩んでいる間は、opencvを使ってLSBを抽出して可視化を頑張っていました。しかし全く何も見当がついていませんでした。残骸です:

#!/usr/bin/env python3

import cv2
import numpy as np

image_sukiyaki = cv2.imread("files/ideal_sukiyaki.png")

def extract_bits(rgb):
    y = 0
    bits = ""
    result = []
    for x in range(image_sukiyaki.shape[0]):
        hidden_bit = image_sukiyaki[y, x, rgb] & 1
        bits += str(hidden_bit)
        if len(bits) >= 1:
            value = int(bits, 2)
            result.append(value)
            bits = ""
    return result

width = 80*3
img = np.zeros((3, width, 3), np.uint8)
for rgb in range(3):
    bits = extract_bits(rgb)[:80*8]
    for (x, v) in enumerate(bits):
        if x >= width: break
        img[rgb, x, rgb] = v * 255
cv2.imwrite("result.png", img)

生成結果です:

頑張っているときは「r成分で何かやる、g成分でなにかやる、b成分でなにかやる、最後に結果を結合する」と思っていましたが、もしかしたら「左の画素からrgbまとめてなにかする」だったのでしょうか。ともかく、便利なツールを知れました!

[crypto] exclusive (6 solved, 341 points)

Flagを復元してください!

配布ファイルとしていろいろありました:

$ file *
Makefile:  makefile script, ASCII text
exclusive: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=945aab81bc1c89b622b959eab2547fe7b95a11d5, for GNU/Linux 3.2.0, not stripped
flag.txt:  ASCII text
main.c:    C source, ASCII text
out:       data
$ cat flag.txt
SECCON{dummy_flag}
$

どうやら、main.cMakefileでビルドしたものがexclusiveで、それを使ってflag.txtを暗号化したものがoutのようです。main.cは以下の内容です:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

void xorFlag(char* flag, char* key) {
    for (size_t i=0; flag[i]!=0; ++i) {
        printf("%c",flag[i] ^ key[i%7]);
    }
    printf("\n");
}

int main() {
    // read flag
    int fd = open("flag.txt",0);
    if (fd == -1) {
        printf("failed to open flag.txt.\n");
        return 1;
    }
    char flag[32];
    int n = read(fd, flag, 31);
    if (n == -1) {
        printf("failed to read the data.\n");
        close(fd);
        return 1;
    }
    close(fd);
    flag[31]=0;

    // read random
    fd = open("/dev/urandom",0);
    if (fd == -1) {
        printf("failed to open /dev/urandom.\n");
        return 1;
    }
    char randStr[8];
    n = read(fd, randStr, 7);
    if (n == -1) {
        printf("failed to read the data.\n");
        close(fd);
        return 1;
    }
    close(fd);
    randStr[7]=0;

    // xor
    xorFlag(flag, randStr);
}

/dev/urandomから8バイト読み込んでいますが、最後のバイトを後に0x00で上書きしています。そのあとフラグをその8バイトで繰り返しXORしています。

フラグはSECCON{の7バイトから始まることと、XORする8バイトの8バイト目は0x00固定であることから、XORする8バイトを逆算して、残りのフラグを復号するソルバーを書きました:

#!/usr/bin/env python3

import pwn

with open("files/out", "rb") as f:
    encrypted = f.read()

prefix = "SECCON{"
key = bytes([e ^ ord(p) for (e, p) in zip(encrypted, prefix)])
key += b"\x00"
print(pwn.xor(key, encrypted).decode())

実行しました:

$ ./solve.py
SECCON{w3_c4n_r3st0r3_3as1ly}
\x11
$

flag[31]=0;箇所もろとも出力したせいか最後に変なものも混ざっていますが、フラグを入手できました: SECCON{w3_c4n_r3st0r3_3as1ly}

[crypto] encode1364 (19 solved, 210 points)

13と64って何?

配布ファイルとして、flag1364がありました:

RlJQUEJBe2UwNzEzXzRhcV9vNDUzNjRfNGUzX2EwN18zYXBlbGM3MTBhfQ==

Base64エンコードされていそうです。また、問題名からROT13も使っていそうだと思いました。試行錯誤しながらCyberChefへ投げると、フラグを入手できました: SECCON{r0713_4nd_b45364_4r3_n07_3ncryp710n}

[pwn] int (19 solved, 210 points)

ポエム

nc int.capsulectf.seccon.games 9999

配布ファイルとして、問題本体のchallと、元ソースのsrc.cがありました:

$ file *
chall: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=f8b090697e03bc2440baacae81da2a89aea6db1e, for GNU/Linux 3.2.0, not stripped
src.c: C source, ASCII text
$

src.cは以下の内容です:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

char buf[31] = {0};

int main() {
    char *flag = buf;
    char *text[4] = {
        "Life is but a fleeting dream, Slippery as a mountain stream.",
        "Time ticks away, silent and stern, Teaching us we must learn.",
        "Wisdom's voice is often low, But in its echo, truths will grow.",
        "A single act of kindness throws Out roots in all directions, and the roots spring up and make new trees."
    };
    int selection = 0;

    puts("Welcome to the fortune teller!");
    printf("Index(0~3): ");
    scanf("%d", &selection);
    if (selection > 3) {
        puts("Invalid index!");
        return 1;
    }
    puts("Here is your fortune:");
    puts(text[selection]);
}

__attribute__((constructor))
void init() {
    FILE *f = fopen("flag.txt", "r");
    if (!f) {
        puts("Missing flag.txt. Contact an admin if you see this on remote.");
        exit(1);
    }
    fgets(buf, sizeof(buf), f);
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    alarm(60);
}

index3以下であるかどうかのチェックのみを行っており、0以上であるかどうかを検証していません。そのため負の数を入力すれば範囲外参照させられます。text変数の直前にflag変数が割り当てられていることを期待して、とりあえず接続して-1を入力しました:

$ nc int.capsulectf.seccon.games 9999
Welcome to the fortune teller!
Index(0~3): -1
Here is your fortune:
SECCON{L0ng_D5t4nc3_Run4r0und}
^C
$

フラグを入手できました: SECCON{L0ng_D5t4nc3_Run4r0und}

[reversing] just-read (17 solved, 221 points)

読めばわかる

配布ファイルとして、just_readバイナリがありました:

$ file *
just_read:     ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=0ebaa919fc5c5ce649aedd6238335b44fb77404d, for GNU/Linux 3.2.0, not stripped
$

IDAで開いて逆コンパイルすると、以下の内容でした(一部変数名を変更しています):

int __fastcall main(int argc, const char **argv, const char **envp)
{
  int result; // eax
  char s[107]; // [rsp+0h] [rbp-70h] BYREF
  char charCurrent; // [rsp+6Bh] [rbp-5h]
  int i; // [rsp+6Ch] [rbp-4h]

  printf("Enter the FLAG: ");
  __isoc99_scanf("%99s%*[^\n]", s);
  if ( strlen(s) == 67 )
  {
    for ( i = 0; i <= 66; ++i )
    {
      if ( s[i] <= '`' || s[i] > 'z' )
      {
        if ( s[i] <= '@' || s[i] > 'Z' )
          charCurrent = s[i];
        else
          charCurrent = (s[i] - 52) % 26 + 65;
      }
      else
      {
        charCurrent = (s[i] - 84) % 26 + 97;
      }
      switch ( i )
      {
        case 0:
          if ( charCurrent != 70 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 1:
          if ( charCurrent != 82 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 2:
          if ( charCurrent != 80 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 3:
          if ( charCurrent != 80 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 4:
          if ( charCurrent != 66 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 5:
          if ( charCurrent != 65 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 6:
          if ( charCurrent != 123 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 7:
          if ( charCurrent != 48 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 8:
          if ( charCurrent != 97 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 9:
          if ( charCurrent != 51 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 10:
          if ( charCurrent != 95 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 11:
          if ( charCurrent != 97 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 12:
          if ( charCurrent != 51 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 13:
          if ( charCurrent != 51 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 14:
          if ( charCurrent != 113 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 15:
          if ( charCurrent != 95 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 16:
          if ( charCurrent != 97 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 17:
          if ( charCurrent != 48 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 18:
          if ( charCurrent != 103 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 19:
          if ( charCurrent != 95 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 20:
          if ( charCurrent != 117 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 21:
          if ( charCurrent != 52 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 22:
          if ( charCurrent != 105 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 23:
          if ( charCurrent != 51 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 24:
          if ( charCurrent != 95 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 25:
          if ( charCurrent != 111 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 26:
          if ( charCurrent != 51 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 27:
          if ( charCurrent != 51 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 28:
          if ( charCurrent != 97 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 29:
          if ( charCurrent != 95 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 30:
          if ( charCurrent != 80 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 31:
          if ( charCurrent != 110 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 32:
          if ( charCurrent != 114 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 33:
          if ( charCurrent != 102 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 34:
          if ( charCurrent != 110 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 35:
          if ( charCurrent != 101 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 36:
          if ( charCurrent != 95 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 37:
          if ( charCurrent != 49 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 38:
          if ( charCurrent != 97 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 39:
          if ( charCurrent != 95 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 40:
          if ( charCurrent != 48 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 41:
          if ( charCurrent != 101 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 42:
          if ( charCurrent != 113 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 43:
          if ( charCurrent != 51 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 44:
          if ( charCurrent != 101 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 45:
          if ( charCurrent != 95 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 46:
          if ( charCurrent != 103 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 47:
          if ( charCurrent != 48 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 48:
          if ( charCurrent != 95 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 49:
          if ( charCurrent != 104 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 50:
          if ( charCurrent != 97 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 51:
          if ( charCurrent != 113 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 52:
          if ( charCurrent != 51 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 53:
          if ( charCurrent != 101 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 54:
          if ( charCurrent != 53 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 55:
          if ( charCurrent != 103 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 56:
          if ( charCurrent != 52 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 57:
          if ( charCurrent != 97 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 58:
          if ( charCurrent != 113 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 59:
          if ( charCurrent != 95 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 60:
          if ( charCurrent != 80 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 61:
          if ( charCurrent != 110 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 62:
          if ( charCurrent != 114 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 63:
          if ( charCurrent != 102 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 64:
          if ( charCurrent != 110 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 65:
          if ( charCurrent != 101 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        case 66:
          if ( charCurrent != 125 )
          {
            puts("Incorrect");
            exit(1);
          }
          return result;
        default:
          continue;
      }
    }
    puts("Correct");
    return 0;
  }
  else
  {
    puts("Incorrect");
    return 1;
  }
}

以下のことを行っていそうです:

  • 入力されたフラグ文字数が67文字であることを検証
  • 各文字ごとに、何か変換して、indexごとに期待する値であるかを検証

「何か変換」する処理はシーザー暗号のように見えました。「indexごとに期待する値」をテキストエディタのキーボードマクロで取り出して、CyberChefへ投げると、フラグを入手できました: SECCON{0n3_n33d_n0t_h4v3_b33n_Caesar_1n_0rd3r_t0_und3r5t4nd_Caesar}

念のための実行結果です:

$ ./just_read
Enter the FLAG: SECCON{0n3_n33d_n0t_h4v3_b33n_Caesar_1n_0rd3r_t0_und3r5t4nd_Caesar}
Correct
$

[reversing] snake (13 solved, 248 points)

Python読めますか

配布ファイルとして、問題本体のsnake.pyと、その出力のoutputがありました:

$ file *
output:   data
snake.py: a /use/bin/env python3 script, ASCII text executable
$

snake.pyの内容です:

#!/use/bin/env python3
# -*- coding: utf-8 -*-

flag = open('FLAG', 'r').read()
open('output', 'wb').write(bytes([(ord(c) ^ 0x42) + 10 for c in flag]))

真っ当に逆算するソルバーを書きました:

#!/usr/bin/env python3

flag_encrypted = open('output', 'rb').read()
print("".join(map(lambda b: chr((b - 10) ^ 0x42), flag_encrypted)))

実行しました:

$ ./solve.py
SECCON{D4z3d_4nd_c0nfu53d}

$

フラグを入手できました: SECCON{D4z3d_4nd_c0nfu53d}

[reversing] strings (25 solved, 186 points)

stringsコマンドが使えない?

配布ファイルとして、mainがありました:

$ file *
main: Mach-O 64-bit arm64 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>
$ strings main
__PAGEZERO
__TEXT
__text
__TEXT
__stubs
__TEXT
__unwind_info
__TEXT
__DATA_CONST
__got
__DATA_CONST
__DATA
__data
__DATA
__LINKEDIT
/usr/lib/dyld
/usr/lib/libSystem.B.dylib
SECCON{
_write
_mh_execute_header
main
__mh_execute_header
_main
_write
main
$E?<
$

珍しいMach-O形式です!その上arm64です!また、問題文通りにstringsコマンドでは、フラグらしいものはSECCON{だけが見えるようです。

Free版IDAではarm64に非対応なので、Ghidraで開いて逆コンパイルしました:

undefined8 entry(void)
{
  _write(1,_a,0x36);
  return 0;
}

エントリーポイントで、グローバル変数_aの内容を出力しているようです。_aの内容を調べました:

                             //
                             // __data
                             // __DATA
                             // ram:100008000-ram:100008033
                             //
                             _a                                              XREF[2]:     Entry Point(*),
                                                                                          entry:100003f94(*)
       100008000 53 45 43        ds         "SECCON{"
                             _b                                              XREF[1]:     Entry Point(*)
       100008008 73              ??         73h    s
       100008009 74              ??         74h    t
       10000800a 72              ??         72h    r
       10000800b 00              ??         00h
                             _c                                              XREF[1]:     Entry Point(*)
       10000800c 31              ??         31h    1
       10000800d 6e              ??         6Eh    n
       10000800e 67              ??         67h    g
       10000800f 00              ??         00h
                             _d                                              XREF[1]:     Entry Point(*)
       100008010 73              ??         73h    s
       100008011 5f              ??         5Fh    _
       100008012 31              ??         31h    1
       100008013 00              ??         00h
                             _e                                              XREF[1]:     Entry Point(*)
       100008014 35              ??         35h    5
       100008015 5f              ??         5Fh    _
       100008016 6e              ??         6Eh    n
       100008017 00              ??         00h
                             _f                                              XREF[1]:     Entry Point(*)
       100008018 30              ??         30h    0
       100008019 74              ??         74h    t
       10000801a 5f              ??         5Fh    _
       10000801b 00              ??         00h
                             _g                                              XREF[1]:     Entry Point(*)
       10000801c 73              ??         73h    s
       10000801d 68              ??         68h    h
       10000801e 30              ??         30h    0
       10000801f 00              ??         00h
                             _h                                              XREF[1]:     Entry Point(*)
       100008020 77              ??         77h    w
       100008021 5f              ??         5Fh    _
       100008022 73              ??         73h    s
       100008023 00              ??         00h
                             _i                                              XREF[1]:     Entry Point(*)
       100008024 68              ??         68h    h
       100008025 30              ??         30h    0
       100008026 72              ??         72h    r
       100008027 00              ??         00h
                             _j                                              XREF[1]:     Entry Point(*)
       100008028 74              ??         74h    t
       100008029 5f              ??         5Fh    _
       10000802a 77              ??         77h    w
       10000802b 00              ??         00h
                             _k                                              XREF[1]:     Entry Point(*)
       10000802c 30              ??         30h    0
       10000802d 72              ??         72h    r
       10000802e 64              ??         64h    d
       10000802f 00              ??         00h
                             _l                                              XREF[1]:     Entry Point(*)
       100008030 7d              ??         7Dh    }
       100008031 00              ??         00h
       100008032 00              ??         00h
       100008033 00              ??         00h

どうやらblのシンボルがそれぞれ存在しているようですが、write関数で0x36バイトだけ出力しようとしているので、結局全部出力されそうです。GhidraがHex値の横にASCII文字を表示してくれているので、テキストエディタの矩形編集などで取り出して、フラグを入手できました: SECCON{str1ngs_15_n0t_sh0w_sh0rt_w0rd}

フラグ内容を見てstringsコマンドのmanドキュメントを調べると、-n min-lenの説明にIf not specified a default minimum length of 4 is used.とありました。今回の問題では3文字ごとにNUL終端されているため、デフォルトオプションのstringsコマンドでは表示されなかったようです。

[reversing] pl (11 solved, 267 points)

ダイヤモンド/パール

問題説明文がポケットなモンスターです。それはともかく、配布ファイルとして、check.plなPerl言語のスクリプトがありました:

#!/usr/bin/env perl
use MIME::Base64;eval(decode_base64('c3ViIHIgewogIG15KCRkLCAkaykgPSBAXzsKICBteSBAcyA9IDAuLjI1NTsKICBteSAoJGksICRqLCAkYykgPSAoMCwgMCwgJycpOwogIG15ICRsID0gQCRrOwogIGZvciAoJGkgPSAwOyAkaSA8IDI1NjsgJGkrKykgewogICAgJGogPSAoJGogKyAkc1skaV0gKyAkay0+WyRpJSRsXSkgJSAyNTY7CiAgICBAc1skaSwgJGpdID0gQHNbJGosICRpXTsKICB9CiAgJGkgPSAkaiA9IDA7CiAgZm9yZWFjaCBteSAkY2hhciAoc3BsaXQgLy8sICRkKSB7CiAgICAkaSA9ICgkaSArIDEpICUgMjU2OwogICAgJGogPSAoJGogKyAkc1skaV0pICUgMjU2OwogICAgQHNbJGksICRqXSA9IEBzWyRqLCAkaV07CiAgICAkYyAuPSBjaHIob3JkKCRjaGFyKSBeICRzWygkc1skaV0gKyAkc1skal0pICUgMjU2XSk7CiAgfQogIHJldHVybiAkYzsKfQpteSAkayA9IFs1NSwgNDksIDUwLCAxMDAsIDk5LCA5NywgMTAyLCA1NiwgMTAxLCA1NiwgNTAsIDUxLCA1NywgNTAsIDUyLCAxMDIsIDEwMCwgNTcsIDk3LCAxMDAsIDEwMiwgNTcsIDk3LCA1NSwgMTAxLCA5NywgOTksIDUzLCA5OSwgNTYsIDU2LCAxMDJdOwpteSAkZT0gJzg3ZDg1Y2Y1MDM3NWY3M2NhMDY2ZGEzNjQ1NjQwYWVmYjBkYzc4NTFjMzIzNGE2NDVkY2FjYjVmMjInOwpteSAkeCA9IHIoJGUsICRrKTsKcHJpbnQgImZsYWc6ICI7Cm15ICRwID0gPFNURElOPjsKY2hvbXAgJHA7Cm15ICRpID0gcigkcCwgJGspOwpwcmludCB1bnBhY2soJ0gqJywgJGkpIGVxICRlPyJDb3JyZWN0IVxuIjoiV3JvbmchXG4iOwo='));

Base64デコードしてevalする内容です。Base64デコードすると、以下のPerlスクリプト内容を得られました:

sub r {
  my($d, $k) = @_;
  my @s = 0..255;
  my ($i, $j, $c) = (0, 0, '');
  my $l = @$k;
  for ($i = 0; $i < 256; $i++) {
    $j = ($j + $s[$i] + $k->[$i%$l]) % 256;
    @s[$i, $j] = @s[$j, $i];
  }
  $i = $j = 0;
  foreach my $char (split //, $d) {
    $i = ($i + 1) % 256;
    $j = ($j + $s[$i]) % 256;
    @s[$i, $j] = @s[$j, $i];
    $c .= chr(ord($char) ^ $s[($s[$i] + $s[$j]) % 256]);
  }
  return $c;
}
my $k = [55, 49, 50, 100, 99, 97, 102, 56, 101, 56, 50, 51, 57, 50, 52, 102, 100, 57, 97, 100, 102, 57, 97, 55, 101, 97, 99, 53, 99, 56, 56, 102];
my $e= '87d85cf50375f73ca066da3645640aefb0dc7851c3234a645dcacb5f22';
my $x = r($e, $k);
print "flag: ";
my $p = <STDIN>;
chomp $p;
my $i = r($p, $k);
print unpack('H*', $i) eq $e?"Correct!\n":"Wrong!\n";

ここでサブルーチンr処理を読むと、RC4で暗号化しているようです。鍵として使っていそうな$kの内容をHex文字列にしたくなったので、CyberChefへ投げると、712dcaf8e823924fd9adf9a7eac5c88fを得ました。

残るRC4復号もやっぱりCyberChefへ投げると、フラグを入手できました: SECCON{R1d3_0n_5h00t1ng_5t4r}

[web] redirects (18 solved, 215 points)

Flagが散らばってしまったらしいので、回収してください!

https://redirects.capsulectf.seccon.games:31555/

配布ファイルとして、サーバー側プログラムの各種ファイルがありました:

$ find . -type f -print0 | xargs -0 file
./app/app.py:                Python script, ASCII text executable
./app/Dockerfile:            ASCII text
./app/requirements.txt:      ASCII text
./app/templates/hidden.html: ASCII text, with no line terminators
./app/templates/index.html:  HTML document, ASCII text, with no line terminators
./app/uwsgi.ini:             ASCII text
./docker-compose.yml:        ASCII text
./nginx/Dockerfile:          ASCII text
./nginx/nginx.conf:          ASCII text
$

./app/app.pyは以下の内容です:

import os
import re
from flask import Flask, abort, make_response, render_template


app = Flask(__name__)

flag = os.getenv("FLAG")
assert flag is not None
re_obj = re.search(r"SECCON{(.+)}", flag)
assert re_obj is not None

flag_content = re_obj.group(1)
partial_length = len(flag_content) // 3
flag1 = flag_content[:partial_length]
flag2 = flag_content[partial_length : 2 * partial_length]
flag3 = flag_content[partial_length * 2 :]


@app.route("/<path:path>")
def not_found(path):
    abort(404, "page not found")


# The flag is scattered to the following three parts:
# - inner contents of hidden page
# - session cookie
# - response header
@app.route("/", methods=["GET"])
def index_get():
    return render_template("index.html")


@app.route("/hidden", methods=["GET"])
def hidden_get():
    resp = make_response(render_template("hidden.html", flag=flag1), 302)
    resp.headers["FLAG"] = flag2
    resp.set_cookie(key="flag", value=flag3, expires=None, secure=True, httponly=True)
    resp.location = "/"
    return resp


if __name__ == "__main__":
    app.run(debug=False, host="0.0.0.0", port=31555)

フラグを3分割して、/hiddenアクセス時にそれぞれ別の箇所に格納して応答する内容です。実際に問題文のサーバーと通信しました:

$ curl -i 'http://redirects.capsulectf.seccon.games:31555/hidden'
HTTP/1.1 302 FOUND
Server: nginx/1.25.3
Date: Sat, 23 Dec 2023 08:32:32 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 10
Connection: keep-alive
FLAG: 15_1s_s000
Set-Cookie: flag=00000_e45y; Secure; HttpOnly; Path=/
Location: /

I_gue33_th
$

レスポンスボディ、FLAGヘッダー、クッキーのflag値を結合して、フラグフォーマットになるよう前後に追加して、フラグを入手できました: SECCON{I_gue33_th15_1s_s00000000_e45y}

(コンテスト本番時では、配布ファイルを見ることを忘れたまま、curl結果を見ながらああでもないこうでもないと試行錯誤していました。配布内容はちゃんと確認しましょう……)

[web] poem (21 solved, 201 points)

ChatGPTがポエムを書いてくれたのでサイトで公開しました!

https://poem.capsulectf.seccon.games:4567/

配布ファイルとして、サーバー側プログラムの各種ファイルがありました:

$ find . -type f -print0 | xargs -0 file
./.env:               ASCII text
./build/app.rb:       Ruby script, ASCII text
./build/Gemfile:      ASCII text
./build/Gemfile.lock: ASCII text
./build/poem1.html:   HTML document, ASCII text
./build/poem2.html:   HTML document, ASCII text
./build/poem3.html:   HTML document, ASCII text
./docker-compose.yml: ASCII text
./Dockerfile:         ASCII text
$

./build/app.rbは以下の内容です:

require 'sinatra'

get '/' do
  """
  <html>
    <body>
      <h1>My Poems</h1>
      <ul>
        <li><a href='/read?poem=poem1.html'>Poem 1</a></li>
        <li><a href='/read?poem=poem2.html'>Poem 2</a></li>
        <li><a href='/read?poem=poem3.html'>Poem 3</a></li>
      </ul>
    </body>
  </html>
  """
end

get '/read' do
  file = params[:poem]
  begin
    File.open(file, 'r') do |f|
      f.read
    end
  rescue
    "Can't read"
  end
end

get '/flag.txt' do
  "Do not look at me!"
end

/flag.txtへのアクセスが防がれているように見えますが、/readアクセス時の処理でflag.txtを読み込ませられそうです。ブラウザでhttp://poem.capsulectf.seccon.games:4567/read?poem=flag.txtへアクセスすると、フラグを入手できました: SECCON{Und3r_Th3_Br1dg3}

[web] path (21 solved, 193 points)

flagファイルは見せないよ!!

https://path.capsulectf.seccon.games:31337/

配布ファイルとして、サーバー側プログラムの各種ファイルがありました:

$ find . -type f -print0 | xargs -0 file
./app/app.py:           Python script, Unicode text, UTF-8 text executable
./app/books/book0.txt:  HTML document, Unicode text, UTF-8 text, with CRLF line terminators
./app/books/book1.txt:  HTML document, Unicode text, UTF-8 text, with CRLF line terminators
./app/books/book2.txt:  HTML document, Unicode text, UTF-8 text, with CRLF line terminators
./app/books/book3.txt:  HTML document, Unicode text, UTF-8 text, with CRLF line terminators
./app/books/book4.txt:  HTML document, Unicode text, UTF-8 text, with CRLF line terminators
./app/books/book5.txt:  HTML document, Unicode text, UTF-8 text, with CRLF line terminators
./app/books/book6.txt:  HTML document, Unicode text, UTF-8 text, with CRLF line terminators
./app/Dockerfile:       ASCII text, with CRLF line terminators
./app/flag:             ASCII text, with no line terminators
./app/requirements.txt: ASCII text, with CRLF line terminators
./app/uwsgi.ini:        ASCII text, with CRLF line terminators
./docker-compose.yml:   ASCII text
./nginx/Dockerfile:     ASCII text
./nginx/nginx.conf:     ASCII text
$

./app/app.pyは以下の内容です:

from flask import Flask, request, abort


app = Flask(__name__)

top_page = """
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8" />
    <title>曇空文庫</title>
</head>

<body>
    <h1>曇空文庫</h1>
    迷作を無料で。<br>
<ul>
    <li><a href="/?file=book0.txt">Book0</a></li>
    <li><a href="/?file=book1.txt">Book1</a></li>
    <li><a href="/?file=book2.txt">Book2</a></li>
    <li><a href="/?file=book3.txt">Book3</a></li>
    <li><a href="/?file=book4.txt">Book4</a></li>
    <li><a href="/?file=book5.txt">Book5</a></li>
    <li><a href="/?file=book6.txt">Book6</a></li>
</ul>

</body>

</html>
"""


@app.route("/")
def top():
    file = request.args.get("file")
    if not file:
        return top_page
    try:
        with open(f"./books/{file}", encoding="utf-8") as f:
            return f.read()
    except:
        return abort(403, "エラーが発生しました。👻")


if __name__ == "__main__":
    app.run(debug=True, host="0.0.0.0", port=31337)

一見すると/books/ディレクトリ以下のファイルのみにアクセス可能に見えますが、../を含めてディレクトリトラバーサルをするとflag.txtへアクセスできそうです。ブラウザでhttp://path.capsulectf.seccon.games:31337/?file=../flagへアクセスすると、フラグを入手できました: SECCON{y0u_kn0w_d1r3c70ry_7r4v3r54l?}

解けなかった問題

どなたも解けていない問題です。他の問題は「分かればすぐ解ける」物が多かったのでおそらくこれらの問題も分かればすぐ行けるのだと思いますが、pwn用のヒープの知識が全く無いため、一切合切何一つ分かりませんでした……。

[pwn, super rare] heapw (0 solved, 500 points)

Heapとかw

nc heapw.capsulectf.seccon.games 31415

配布ファイルとして、問題本体のfilewwと、元ソースのmain.cなどがありました:

$ file *
Dockerfile:         ASCII text
docker-compose.yml: ASCII text
heapw:              ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=29b0076fe4822aa199ef8d6d632c73f5307da3ab, for GNU/Linux 3.2.0, not stripped
libc.so.6:          ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=229b7dc509053fe4df5e29e8629911f0c3bc66dd, for GNU/Linux 3.2.0, stripped
main.c:             C source, ASCII text
$ pwn checksec fileww
[*] '/mnt/d/Documents/work/ctf/20231223_SECCON_2023_CapsuleCTF/fileww/files/fileww'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
$ pwn checksec libc.so.6
[*] '/mnt/d/Documents/work/ctf/20231223_SECCON_2023_CapsuleCTF/fileww/files/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
$ ./libc.so.6
GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.3) stable release version 2.35.
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 11.3.0.
libc ABIs: UNIQUE IFUNC ABSOLUTE
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
$

main.cは以下の内容です:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define NOTE_NUM 16
#define NOTE_SIZE 0x40

char *g_note[NOTE_NUM];

/* Input a line of string */
void get_line(const char *msg, char *buf, unsigned size) {
  unsigned i;
  printf("%s", msg);
  for (i = 0; i < size; i++) {
    if (read(STDIN_FILENO, buf + i, 1) != 1) exit(1);
    if (buf[i] == '\n') break;
  }
  buf[i] = '\0';
}

/* Input value */
unsigned get_val(const char *msg) {
  char buf[0x10];
  get_line(msg, buf, sizeof(buf));
  return atol(buf);
}

/* Input and validate index */
unsigned get_index(const char *msg) {
  unsigned index = get_val(msg);
  if (index >= NOTE_NUM) {
    puts("[-] Invalid index");
    exit(1);
  }
  return index;
}

/* Entry point */
int main(void) {
  unsigned index, size;
  char *p;

  puts("1. new\n2. edit\n3. show\n4. delete");
  while (1) {
    switch (get_val("> ")) {
      case 1: { // new
        index = get_index("Index: ");
        if (g_note[index])
          puts("[-] Note in use");
        else {
          g_note[index] = (char*)malloc(NOTE_SIZE);
        }
        break;
      }

      case 2: { // edit
        index = get_index("Index: ");
        if (g_note[index]) {
          get_line("Data: ", g_note[index], 0x100);
        } else
          puts("[-] Empty note");
        break;
      }

      case 3: // show
        index = get_index("Index: ");
        if (g_note[index])
          printf("Note: %s\n", g_note[index]);
        else
          puts("[-] Empty note");
        break;

      case 4: // delete
        index = get_index("Index: ");
        if (g_note[index]) {
          free(g_note[index]);
          g_note[index] = NULL;
        } else
          puts("[-] Empty note");
        break;

      default: return 0;
    }
  }
}

__attribute__((constructor))
void setup(void) {
  setvbuf(stdin, NULL, _IONBF, 0);
  setvbuf(stdout, NULL, _IONBF, 0);
  setvbuf(stderr, NULL, _IONBF, 0);
}

new操作ではmalloc(0x40)していますが、edit操作ではサイズ0x100まで変更できるため、heap overflowの脆弱性があります。というところまでは分かりましたが、では実際に何をどうすればRCEを達成できるのかが全く分かりませんでした……。

[pwn, super rare] fileww (0 solved, 500 points)

Fileとかww

nc fileww.capsulectf.seccon.games 31416

配布ファイルとして、問題本体のfilewwと、元ソースのmaub.cがありました:

$ file *
Dockerfile:         ASCII text
docker-compose.yml: ASCII text
fileww:             ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=77e68cdeeb24ea85e9107b5fe55f8dcda4f35a02, for GNU/Linux 3.2.0, not stripped
libc.so.6:          ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=229b7dc509053fe4df5e29e8629911f0c3bc66dd, for GNU/Linux 3.2.0, with debug_info, not stripped
main.c:             C source, ASCII text
$ pwn checksec fileww
[!] Could not populate PLT: module 'unicorn' has no attribute 'UC_ARCH_RISCV'
[*] '/mnt/d/Documents/work/ctf/SECCON_2023_CapsuleCTF/fileww/files/fileww'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
$ pwn checksec libc.so.6
[!] Could not populate PLT: module 'unicorn' has no attribute 'UC_ARCH_RISCV'
[*] '/mnt/d/Documents/work/ctf/SECCON_2023_CapsuleCTF/fileww/files/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
$ ./libc.so.6
GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.3) stable release version 2.35.
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 11.3.0.
libc ABIs: UNIQUE IFUNC ABSOLUTE
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
$

main.cは以下の内容です:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
  size_t size;
  unsigned offset;
  char *ptr;

  setbuf(stdin, NULL);
  setbuf(stdout, NULL);
  setbuf(stderr, NULL);

  printf("size: ");
  if (scanf("%lu", &size) != 1) return 1;
  ptr = (char*)malloc(size);
  if (!ptr) return 1;

  for (int i = 0; i < 2; i++) {
    printf("offset: ");
    if (scanf("%u", &offset) != 1) break;
    printf("size: ");
    if (scanf("%lu", &size) != 1) break;
    printf("data: ");
    if (read(0, ptr + offset, size) <= 0) break;
  }

  return 0;
}

指定サイズでmallocしたあと、2回まで「malloc結果の任意相対位置から、任意サイズだけ、任意内容に改ざん」が出来ます。しかし、その改ざんで何を達成できるのか全く分からないまま終わりました……。

感想

  • 他に参加していた人々と交流できて良かったです!
  • pwnのヒープ問題から今まで逃げ続けていましたが、流石にそろそろ入門したくなりました。今回の問題を全部解きたいです!
  • カプセルCTFの会場は、SECCON本戦の会場と同じ部屋で、赤いテープ(のようなもの。あの器具の名前が分かりません)で区切られていました。そのためカプセルCTF参加中は、本戦の皆様の様子や、どこかのチームが新しく問題を解いたときのアナウンス等が聞こえる会場でした。そのような様子を見られたのが良かったです!
  • 願わくば、本戦側へいつか参加してみたいです。

その他、カプセルCTFそのものとは無関係な、電脳会議の全体的な感想です。どこへフィードバックを送ればいいのか分からなかったので、この記事へねじ込みます。

  • SECCON 2023 電脳会議Webサイトの各種イベントの詳細箇所は、横1行に3個のイベントが表示されています。初期状態では各種イベントのタイトルと登壇者様だけ表示されている状態です。各イベントの下にあるopen detailをクリックするとイベント表示欄が下へ伸びてそのイベントの詳細が表示されるのですが、その際に同一行の他2つのイベントも下へ伸びて、それらのイベントのopen detailも下へ移動します。全イベントの詳細を見ようとする際に、「移動後のopen detailを探してクリック」ということが必要であるため、open detailの位置は動かなかったら嬉しかったです。
  • 本カプセルCTFへの参加時も含めて、seccon idでログインする際、「日本所在か」「所在都道府県」「業種」等を何度か選択する必要がありました。1回のイベントでそれらの内容が変わることは早々ないと思うので、事前登録時等の1回だけの選択で済むと嬉しかったです。
  • 電脳会議のいくつかのワークショップでは、抽選に申し込む必要がありました。その抽選ページでのワークショップの説明が途中で途切れており、完全な説明を抽選ページでは見られませんでした。そのため電脳会議のタイムテーブルのページと行き来する必要がありました。抽選ページだけで必要な情報をすべて得られると嬉しかったです。
  • 会場内でコーヒーが提供されているのがありがたかったです。集中力を保てました!
  • 抽選に申し込んでいたワークショップの中には残念ながら落選してしまったものもありましたが、それでも参加できたワークショップはとても楽しめました!