TUCTF23  Writeup

[TUCTF23](https://ctfd.tuctf.com/)に参加しました。
チーム[0nePadding](https://ctftime.org/team/217710)で参加して、67位でした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/247330/32cc06b9-e3cc-b6b9-931c-52f10e7e5158.png)

解けた問題のwriteupです。

# Hacker Typer (Programming)
タイピングチャンレンジというWebが与えられます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/247330/a701d6f6-b67c-0971-08ff-8653e567314e.png)
適当に手打ちしてもtoo slowと言われます。
ある程度の速度でStreakの回数タイピングできればよいのだろうと想像が付きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/247330/ed329113-f0f8-2fe0-302b-6811ab08b8e1.png)
送信した時どうなってるか知るためChromeの開発者ツールで確認すると/check_wordというAPIにPOSTしてjsonで次の単語やspeed等を受け取っていることが分かりました。

以下、こういう単純な問題を解く時に私が適当に解く時によくやる方法を紹介します。
（みんなは格好いいツールとか使ってるはず！参考にはしないでください！）

開発者ツールのNetworkタブから調べたいリクエスト名（今回だとcheck_word）を右クリックしてCopy > Copy as Fetch
これでjavascriptのfetch関数でこれと同等のリクエストする場合のコードがコピーできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/247330/85025d11-2c56-d1a7-c34f-a6f2f7611485.png)
今回だとこんな感じ
```javascript
fetch("https://hacker-typer.tuctf.com/check_word", {
  "headers": {
    "accept": "*/*",
    "accept-language": "ja,en-US;q=0.9,en;q=0.8",
    "content-type": "application/x-www-form-urlencoded",
    "sec-ch-ua": "\"Not_A Brand\";v=\"8\", \"Chromium\";v=\"120\", \"Google Chrome\";v=\"120\"",
    "sec-ch-ua-mobile": "?0",
    "sec-ch-ua-platform": "\"Windows\"",
    "sec-fetch-dest": "empty",
    "sec-fetch-mode": "cors",
    "sec-fetch-site": "same-origin"
  },
  "referrer": "https://hacker-typer.tuctf.com/",
  "referrerPolicy": "strict-origin-when-cross-origin",
  "body": "word=social%20engineering",
  "method": "POST",
  "mode": "cors",
  "credentials": "include"
});
```
これをどうこういじくってブラウザのコンソールに放り込めばよい訳です。
今回はStreakの回数だけfetchを繰り返すように改造します。

```javascript
const streak = 150
let word = 'hacktivist';
for (let i = 0; i < streak; i++) {
    const res =  await fetch("https://hacker-typer.tuctf.com/check_word", {
      "headers": {
        "accept": "*/*",
        "accept-language": "ja,en-US;q=0.9,en;q=0.8",
        "content-type": "application/x-www-form-urlencoded",
        "sec-ch-ua": "\"Not_A Brand\";v=\"8\", \"Chromium\";v=\"120\", \"Google Chrome\";v=\"120\"",
        "sec-ch-ua-mobile": "?0",
        "sec-ch-ua-platform": "\"Windows\"",
        "sec-fetch-dest": "empty",
        "sec-fetch-mode": "cors",
        "sec-fetch-site": "same-origin"
      },
      "referrer": "https://hacker-typer.tuctf.com/",
      "referrerPolicy": "strict-origin-when-cross-origin",
      "body": `word=${word}`,
      "method": "POST",
      "mode": "cors",
      "credentials": "include"
    });
    const js = await res.json();
    console.log(js);
    word = js.next_word;
}
```
最後は無事にフラグが返ってきました。

# Plenty O Fish in the Sea (Programming)
長いテキストファイルに隠された文字列の断片を探せ的な問題です。以下は冒頭の15行
```
Inside this coastal cave
Under this palm tree
Inside this shipwreck
Under this palm tree
Inside this shipwreck
Behind this foliage
In this stream
At the top of this cliff
Inside this shipwreck
Behind this foliage
In this stream
At the top of this cliff
Inside this shipwreck
Under this palm tree
Inside this shipwreck
...
```
ぱっとみると同じ文字列の行が多く、なんらかの規則性があるように見えます。
こういう時はとりあえず使われてる文字列を回数ごとに仕分けしてみます。

```python
import collections
with open('lost_map.log') as f:
    print(collections.Counter(f.read().split("\n")))
```
結果
```
Counter({'Inside this shipwreck': 5421, 'In this stream': 3471, 'Behind this foliage': 3469, 'At the top of this cliff': 1805, 'Under this big rock': 1748, 'Under this palm tree': 681, 'Inside this burrow': 602, 'Under ye feet': 330, 'In this old rum stash': 288, 'Under this skeleton': 277, 'Inside this coastal cave': 175, 'Inside this coastal cave ': 167, 'In this here tree trunk': 92, 'TUCTF': 1, '%7B83h%2': 1, '1Nd_7h3_': 1, 'W%4073rF': 1, '%4011%7D': 1, '': 1})
```
もうフラグが見えました。1度だけ現れる行を抽出すると・・・
`TUCTF`, `%7B83h%2`, `1Nd_7h3_`, `W%4073rF`, `%4011%7D`
これをくっ付けてデコードすればフラグ `TUCTF{83h!Nd_7h3_W@73rF@11}` が得られます。
規則性とか考える気もないですが解ければいいですよね...

# You have found me treasure chest, but can you crack its code? (Programming)
netcat問。ダイヤル式の鍵を開けろとのこと。
まず1を送って実行開始、次にどの桁を回すを指定、最後に+/-のどちら方向に回すかを指定します。
```
$ nc chal.tuctf.com 30002
  ___________
 /           \
/___|0|0|0|___\
|      @      |
|_____________|
Enter 1 to rotate the lock, or 2 to exit
1
Which wheel would you like to rotate? (1-3)
1
Which direction would you like to rotate the wheel? (+/-)
+
  ___________
 /           \
/___|1|0|0|___\
|      @      |
|_____________|
The chest is still locked!
Enter 1 to rotate the lock, or 2 to exit
```
こんなのただの総当たりだろということで適当に書いてみます。

```python
import socket

class Netcat:
    def __init__(self, ip, port):
        self.buff = ""
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.connect((ip, port))

    def read(self):
        return self.socket.recv(1024).decode('utf-8')
    
    def write(self, data):
        self.socket.send(f"{str(data)}\n".encode('utf-8'))
        return self.read()

    def close(self):
        self.socket.close()

nc = Netcat('chal.tuctf.com', 30002)
nc.read()
for a in range(10):
    for b in range(10):
        for c in range(10):
            # 1の位を回す
            nc.write(1)
            nc.write(1)
            output = nc.write('+')
            if 'still locked!' not in output:
                print(output)
                break
        else:
            # 10の位を回す
            nc.write(1)
            nc.write(2)
            output = nc.write('+')
            if 'still locked!' not in output:
                print(output)
                break
            continue
        break
    else:
        # 100の位を回す
        nc.write(1)
        nc.write(3)
        output = nc.write('+')
        if 'still locked!' not in output:
            print(output)
            break
        continue
    break
nc.close()
```
結果
```
  ___________
 /           \
/___|8|4|5|___\
|      @      |
|_____________|
You have unlocked the treasure chest!
You have found the Flag!
--------------------------\  
|   .        .             \ 
|    .  . .     .           \
|                  X         |
>                            |
|  TUCTF{h3R3_1!3_M3_800Ty}  |
|                            |
>                            <
|                            |
-----^-----------------^------
```
余談ですがこの問題、最初は4桁でしたがサーバに負荷がかかりすぎたのか途中で3桁に変わってました。
おかげで寝ている間に回してて起きたら死んでてびっくりしたり、終了判定を `locked` が含まれているかとしていたら 解除完了のメッセージに `unlocked` があって終了しなかったり、簡単ながら躓いた問題でした。

# Cube (2023) (Programming)
netcat問その2
個人的には見たことないですが、有名な映画Cubeのパロディ問題とのこと。
解けた問題の中では最も正解者を少なかったです。（38 Solves）
PDFで割と長文のルールが与えられたのですが、それが少し難解だったのが理由だと思います。
ざっくり要約すると、
- 6x6x6の立方体の形に部屋が並んでいて、プレイヤーはある部屋に閉じ込められている
- 各部屋の壁6面の外側に数字のラベルが書かれている
- 部屋の中からは上下左右前の5方向の壁に書かれている数字が見える（後ろは見えない）
- プレイヤーが実行できる移動方法は以下の5通り
  - 右を向く
  - 左を向く
  - 前の部屋に移動する
  - 上の部屋に移動する
  - 下の部屋に移動する  
- 一番端の部屋からさらに移動するとその方向の初めの部屋に戻る（6回進むとループする）
- 合計216の部屋のうち26部屋は正解である。
  - 正解の条件は、壁の外側に書かれている6つの数のうち2つの組み合わせがすべての互いに素（coprime）であること
  - プレイヤーは今いる部屋が正解であることを宣言することができる
  - 一度も間違わずにすべての正解の部屋を宣言するとゲームクリア

これでもわかりずらいですが、画面はこんな感じ
```
$ nc chal.tuctf.com 30009

Commands include:
Move Forward / F
Move Up / U
Move Down / D
Turn Right / R
Turn Left / L
Check Room / C

 .@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*
 .@ %@@@                                                                 %@@@,@*
 .@      /@@@(                        567361                        @@@&      @*
 .@           .@@@/                                           *@@@#           @*
 .@               &@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@               @*
 .@               &@                                         %@               @*
 .@               &@                                         %@               @*
 .@               &@                                         %@               @*
 .@               &@                                         %@               @*
 .@               &@                                         %@               @*
 .@               &@                                         %@               @*
 .@               &@                                         %@               @*
 .@               &@                                         %@               @*
 .@    23390443   &@                 1268651                 %@    860875     @*
 .@               &@                                         %@               @*
 .@               &@                                         %@               @*
 .@               &@                                         %@               @*
 .@               &@                                         %@               @*
 .@               &@                                         %@               @*
 .@               &@                                         %@               @*
 .@               &@                                         %@               @*
 .@               &@                                         %@               @*
 .@                @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@               @*
 .@         %@@@@@@                                            /@@@@@@        @*
 .@  ,@@@@                           1613789                           %@@@@  @*
 .@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*

Action:
```

5つの数字が見えます。これはそれぞれ面している部屋に対応する数です。
つまり、自分の部屋に対応する数は隣の部屋からしか見えません。
なので方針として、以下を考えました。

- (1周目) すべての部屋を順番に訪れてその部屋から見えるラベルを記憶する。
- (2周目) すべての部屋を順番に訪れて正解の部屋かをチェックする

後ろは見えませんが左右どちらかを向けば確認してか
もちろん1周目はすべての部屋を訪れずともラベルをすべて回収できるかもしれませんし、周目は正解の部屋を決めてから最短経路で巡ったほうが効率がよいですが、しかし216部屋程度であれば無視できる差でしょう。

```python
import re
import socket
from itertools import combinations
from math import gcd
 
class Netcat:
    def __init__(self, ip, port):
        self.buff = ""
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.connect((ip, port))
 
    def read(self, length = 8192):
        return self.socket.recv(length).decode('utf-8')

    def write(self, data):
        self.socket.sendall(((str(data)+'\n')).encode('utf-8'))
 
    def close(self):
        self.socket.close()
        
class Room:
    def __init__(self):
        self.u = None
        self.d = None
        self.l = None
        self.r = None
        self.f = None
        self.b = None
    
    def is_coprime(self):
        pairs = combinations([self.u, self.d, self.l, self.r, self.f, self.b], 2)
        for pair in pairs:
            if not gcd(pair[0], pair[1]) == 1:
                return False
        return True

n = 6
cube = [[[Room() for _ in range(n)] for _ in range(n)] for _ in range(n)]

nc = Netcat('chal.tuctf.com', 30009)
# CUBE探索
for i in range(n):
    for j in range(n):
        for k in range(n):
            output = nc.read()
            # 後ろ以外は分かる
            u, l, f, r, d = map(int, re.findall(' (\d+) ', output))
            # 後ろを確認（左を向いた時の左）
            nc.write('L')
            output = nc.read()
            _, b, _, _, _ = map(int, re.findall(' (\d+) ', output))
            nc.write('R')
            output = nc.read()
            # 自分の部屋の上： 上の部屋の下
            cube[(i-1)%n][j][k].d = u
            # 自分の部屋の左： 左の部屋の右
            cube[i][(j-1)%n][k].r = l
            # 自分の部屋の前： 前の部屋の後
            cube[i][j][(k+1)%n].b = f
            # 自分の部屋の右： 右の部屋の左
            cube[i][(j+1)%n][k].l = r
            # 自分の部屋の下： 下の部屋の上
            cube[(i+1)%n][j][k].u = d
            # 自分の部屋の後： 後の部屋の前
            cube[i][j][(k-1)%n].f = b
            # 前に進む
            nc.write('F')
        # 右に移動して前を向く
        nc.read()
        nc.write('R')
        nc.read()
        nc.write('F')
        nc.read()
        nc.write('L')
    # 下に移動する
    nc.read()
    nc.write('D')

# 2週目で互いに素か確認する
for i in range(n):
    for j in range(n):
        for k in range(n):
            output = nc.read()
            if(cube[i][j][k].is_coprime()):
                nc.write('C')
                nc.read()
            # 前に進む
            nc.write('F')
        # 右に移動して前を向く
        nc.read()
        nc.write('R')
        nc.read()
        nc.write('F')
        nc.read()
        nc.write('L')
    # 下に移動する
    nc.read()
    nc.write('D')

nc.close()
```
結果
```
{正解の部屋}
.
.
.

Action: 
c
Correct!  This room is coprime!

                                                                               
 .@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*
 .@ %@@@                                                                 %@@@,@*
 .@      /@@@(                        621857                        @@@&      @*
 .@           .@@@/                                           *@@@#           @*
 .@               &@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@               @*
 .@               &@                                         %@               @*
 .@               &@                                         %@               @*
 .@               &@                                         %@               @*
 .@               &@                                         %@               @*
 .@               &@                                         %@               @*
 .@               &@                                         %@               @*
 .@               &@                                         %@               @*
 .@               &@                                         %@               @*
 .@     542521    &@                  402641                 %@   3227578     @*
 .@               &@                                         %@               @*
 .@               &@                                         %@               @*
 .@               &@                                         %@               @*
 .@               &@                                         %@               @*
 .@               &@                                         %@               @*
 .@               &@                                         %@               @*
 .@               &@                                         %@               @*
 .@               &@                                         %@               @*
 .@                @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@               @*
 .@         %@@@@@@                                            /@@@@@@        @*
 .@  ,@@@@                            263581                           %@@@@  @*
 .@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*
                                                                                
Action: 
c
Correct!  This room is coprime!

Congratulations, you found all the coprime rooms and successfully escaped the Cube!
Here's your flag: TUCTF{F34R_P4R4N014_5U5P1C10N_D35P3R4T10N_Esjc0rPj3K4}
```
ctfでsocket書く時いつもread wrtite の繰り返しで読みにくくなってしまう...
良い方法を募集中です。

# まとめ
解けた問題は以上です。
CTFの問題として `Programing` というジャンルは初めて見ました。（それぞれ別のジャンルに入ってそうな問題でしたが...）
簡単なものが多かったので楽しく進めることができました。
また、時間が取れたときに参加したいです。


# （忘備録）3要素以上の「互いに素」について
Cubeの問題で `coprime` の意味で引っかかったのでメモ
互いに素(coprime) とは2つの数に共通するを正の約数が1のみ（最大公約数が1）のこと。
3つ以上の数に対する「互いに素」には以下の考え方が存在する。

- 互いに素(setwise coprime)
    - すべての整数の最大公約数が1であること
- 対ごとに素(pairwise coprime)
    - 要素のうち2つの組み合わせですべての最大公約数が1であること

問題文には単純にcoprimeと書いてあったが、今回の問題では後者であった。
ちゃんと読めばわかるが勘違いしてしまった・・・。