SECCON Beginners 2023 解けたものだけ Write Up

# #ctf4b 2023
[**SECCON Beginners 2023**](https://www.seccon.jp/2023/)
**全778チーム中191位(505pt)でした。**
解けたやつだけの Write UP です。~~(CTF 歴 8ヶ月の限界)~~

✅：自分で解いたもの
💠：チームの人が解いたもの
🏳️：割と考えたけど解けなかったもの
| Category | Challenges  |  Solved  |
| :-------: | :---------------------------: | :---:|
| crypto    | CoughingFox2                  | 💠 |
| crypto    | [Conquer](#conquer)           | ✅ |
| pwnable   | [poem](#poem)                 | ✅ |
| misc      | [YARO](#yaro)                 | ✅ |
| misc      | [polyglot4b](#polyglot4b)     | 🏳️ |
| web       | [Forbidden](#forbidden)       | ✅ |
| web       | [aiwaf](#aiwaf)               | ✅ |
| web       | [phisher2](#phisher2)         | 🏳️ |
| reversing | [Half](#half)                 | ✅ |
| welcome   | [Welcome](#welcome)           | 💠 |

# 解けた問題

## Conquer
- `length` は `flag` のビット長
- `key` は `flag` と同じビット長のランダムな値
- `cihper` = `flag XOR key`
- `ROL(bits, N)` と `cipher = cipher XOR key` を32 回繰り返す
- `ROL(bits, N)` は `bits = abcdef`, `N=6` の場合、(`a-f = {0, 1}`)
     - `(bits << 1) = abcdef0` (1 ビット左に)
     - `2**length - 1 = 111111` (1 が N 個)
     - `bits >> (length - 1) = a` (最上位ビットだけ) なので
     - `bits = bcdefa` と左に 1 ビット巡回シフトしてるはず

これより、`flag` の長さだけわかれば全て逆順の操作をすると `flag` を出せるはず...
だけどわからないのでとりあえずローカルで試してみる。
```decode.py
def decrypt(length):
    # length = flag.bit_length()
    key = 364765105385226228888267246885507128079813677318333502635464281930855331056070734926401965510936356014326979260977790597194503012948
    cipher = 92499232109251162138344223189844914420326826743556872876639400853892198641955596900058352490329330224967987380962193017044830636379

    for i in range(32):
        cipher ^= key
        # Reverse ROL
        for _ in range(pow(cipher, 3, length)):
            low = key & 0b1
            high = low << (length - 1)
            key = (key >> 1) | high


    flag = cipher ^ key
    print(hex(flag))
    return hex(flag)

# main()
for i in range(1, 500): # i is the length of flag.
    print(decrypt(i))
```
`ctf4b{` は HexString で `0x63746634627b` だからこれから始めるやつを探す。

```decode.py → output
...
0x1ee93f2ed5a6a18e8937b056f1c4e67abcd4adb3f6b80cf1a382cce01c602514dc54a018da71083629f03d51fde6114dc0a432752a5978
0x1ee93f2ed5a6a18e8937b056f1c4e67abcd4adb3f6b80cf1a382cce01c602514dc54a018da71083629f03d51fde6114dc0a432752a5978
0x63746634627b53656d69434952434c457243616e616c73486176654265656e436f6e7175657265644279546865434952434c452121217d
0x63746634627b53656d69434952434c457243616e616c73486176654265656e436f6e7175657265644279546865434952434c452121217d
0x976e990c74598678799961485336b0d6384ff98ea091b46b6e61e97b8e91fbf9ed246eeb410d49bc778adcec167ee5c34d3b3760794e36
0x976e990c74598678799961485336b0d6384ff98ea091b46b6e61e97b8e91fbf9ed246eeb410d49bc778adcec167ee5c34d3b3760794e36
...
```
[decode](https://string-functions.com/hex-string.aspx) して`ctf4b{Semi...}` になる。

## poem
プログラムを見る限り、0-4 を入力すると配列の対応したところに格納されている文章を出すだけっぽい。`flag` は別の値として格納されてるけど C 言語の変数の格納アドレスってどうだっけ…？
考えるより手探りで 6 とか -1 とか入れてみる方が早そうなので順に入れていくと...
```console
% nc poem.beginners.seccon.games 9000
Number[0-4]: -4
ctf4b{y0u_...}
```

## YARO
まず実行してみる
```console
 % nc yaro.beginners.seccon.games 5003
rule:
【Enter を入力】
OK. Now I find the malware from this rule:

Not found: ./redir.sh
Not found: ./server.py
Not found: ./flag.txt
Not found: ./requestments.txt
```
問題文は「サーバーにマルウェアが混入している可能性があるので、あなたの完璧なシグネチャで探してください」
YARA というツールがあるらしいので調べながら書いてみる。おそらく入力した YARA のルールに基づいて 4 つのファイルを調べてるっぽい？
```yara0.txt
rule find_flag {
        strings:
            $s = /ctf4b\{[\w_]*/
        condition:
            $s
    }
```
を入力してみると
```yara0.txt → output
% nc yaro.beginners.seccon.games 5003 < yara0.txt
rule:
OK. Now I find the malware from this rule:
rule find_flag {
        strings:
            $s = /ctf4b\{[\w_]*/
        condition:
            $s
    }
Not found: ./redir.sh
Not found: ./server.py
Found: ./flag.txt, matched: [find_flag]
Not found: ./requestments.txt
```

`$s = /ctf4b\{[\w_]*/` の部分をなんとかすれば良さそう。
`$s = /ctf4b\{[A-Z]/` にして実行すると Found になるから最初の文字は大文字のアルファベット。あとは[A-M]に分けたり(2分探索)を繰り返しで ~~(実質ブルートフォースで)~~ 突き詰めていくと

```yara.txt
rule find_flag {
        strings:
            $s = /ctf4b\{Y3t_...(実際のファイルには記載)\}/
        condition:
            $s
    }
```

```yara.txt → output
% nc yaro.beginners.seccon.games 5003 < yara.txt
rule:
OK. Now I find the malware from this rule:
rule find_flag {
        strings:
            $s = /ctf4b\{Y3t_...\}/
        condition:
            $s
    }
Not found: ./redir.sh
Not found: ./server.py
Found: ./flag.txt, matched: [find_flag]
Not found: ./requestments.txt
```

## Forbidden
`flag` は `https://forbidden.beginners.seccon.games/flag` にあるらしいけど、見てみると

Forbidden :(

と…
プログラムを見ると
```index.js
var express = require("express");
var app = express();

const HOST = process.env.CTF4B_HOST;
const PORT = process.env.CTF4B_PORT;
const FLAG = process.env.CTF4B_FLAG;

app.get("/", (req, res, next) => {
    return res.send('FLAG はこちら: <a href="/flag">/flag</a>');
});

const block = (req, res, next) => {
    if (req.path.includes('/flag')) {
        return res.send(403, 'Forbidden :(');
    }

    next();
}

app.get("/flag", block, (req, res, next) => {
    return res.send(FLAG);
})

var server = app.listen(PORT, HOST, () => {
    console.log("Listening:" + server.address().port);
});
```
"`/flag`" を含む URL をブロックするらしい。
そういえば URL は大文字小文字を区別しなかった気がする。
→"`/flaG`" にしてみる？
``` :https://forbidden.beginners.seccon.games/flaG
ctf4b{403_...}
```

## aiwaf
個人的に一番面白かった問題。
URL のクエリ `?file=<FILE>` を入力すると `./books/<FILE>` にアクセスするらしい。
ディレクトリ構造は
```console
.
├── books
│   ├── book0.txt
│   ├── book1.txt
│   └── ...
└── flag
```
になってるので `?file=../flag` にすると良さそう。
ただ問題はサーバプログラムで「 `../` や `flag` が含まれている場合には Yes を返す」ように指示された AI (ChatGPT ?) の検査をしてるのでアクセスできない...けどプログラムから察するにその検査対象になってる URL は最大 50 文字までっぽいので適当なクエリを増やす。
`https\://aiwaf.beginners.seccon.games/?random=jwndsnbhjnbghjvgnbhguihjbvhjfyiuhbvhjcfyuihbjvhjffyuhjbvcgfygvmcgfjygvmjfhgbvhjbvmhc&file=../flag` とかでアクセスすると `flag` が出てくる。

## Half
バイナリデータの中身を見てみる。
```
% strings half
...(略)...
%99s%*[^
Invalid FLAG
ctf4b{ge4_t0_kn0w_the
_bin4ry...}
Correct!
:*3$"
...(略)...
```
実際には `% hexdump -C half` で解きました。

## Welcome
歓迎(洗礼)を受けました。

# 解きたかった問題

## phisher2

### 自力でできたこと
わかったことは
HTTPリクエスト(POST)
```
POST / HTTP/1.1
Host: phisher2.beginners.seccon.games
Connection: close
Content-Type: application/json

{"text":"<URL>"}
```
を送ると、`<URL>` をチェックして色々返してくれる。
チェックは
- 受け取った URL (`input_url`) をサーバにてchromeで開き、
- スクリーンショットを撮って文字列として抽出し (`ocr_url`)
- それぞれ `r"https?://[\w/:&\?\.=]+"` が含まれてかつ
- `ocr_url` が指定の URL (おそらく`https://phisher2.beginners.seccon.games/` ?) から始まるなら
- `input_rul` に ** `flag`を含んだ** HTTP リクエストを送る。

ので考えとしては
- `input_url` に HTTP リクエストを送ってくれないと `flag` がわからない。
- `ocr_url` は基本的には `input_url` と同じになるはず。(後述)
- `ocr_url` は `https://phisher2.beginners.seccon.games/` から始まらないと HTTP リクエストは送られない。

色々考え、**なぜかスクリーンショットから文字列検索している**ことに注目し、最初に送る POST リクエストに HTML ぶちこんで画像を操作できないかと考えた。

``` request
POST / HTTP/1.1
Host: phisher2.beginners.seccon.games
Connection: close
Content-Type: application/json
Content-Length: 101

{"text":"<span display='none'>https://example.com</span> https://phisher2.beginners.seccon.games/"}
```
こうすれば `ocr_url` は `https://phisher2.beginners.seccon.games/` で `input_url` は `https://exsample.com` になるのかと期待してたら

``` response
HTTP/1.1 200 OK
Server: nginx/1.25.0
Date: Mon, 05 Jun 2023 09:21:36 GMT
Content-Type: application/json
Content-Length: 182
Connection: close

{"input_url":"<span display='none'>https://example.com</span> https://phisher2.beginners.seccon.games/","message":"admin: It's not url or safe url.","ocr_url":"https://example.com"}

```
だめでした。

### ここから解けなかったとこ
チェックの正規表現で `%` が使われていないので
```request
POST / HTTP/1.1
Host: phisher2.beginners.seccon.games
Connection: close
Content-Type: application/json
Content-Length: 75

{"text":"https://%65xample.com https://phisher2.beginners.seccon.games/"}
```
こうするといいらしい。
URL に %ascii 使っていいのか…

そもそも `flag` を含んだ HTTP リクエストを受け取るためにサーバを用意しなければなかったらしい…
~~ローカル（とCTFサーバ）以外の範疇なんて考えてもなかった…~~

## polyglot4b

### 自力でできたこと
プログラムを見る限り `file` コマンドに JPG, PNG, GIF, ASCII の全てだと思わせれば良さそう？
`GIF89a` を入力すれば GIF, ASCII だとは認識してくれる。
ただ JPG, PNG のマジックナンバーはそもそも非 ASCII だし無理では?

### ここから解けなかったとこ
サンプルが含まれてる意味をもっと考えるべきでした。
```console
% file -bkr sushi.jpg 
JPEG image data, Exif standard: [TIFF image data, big-endian, direntries=4, description=CTF4B], baseline, precision 8, 1404x790, components 3
- data
```
`[description=CTF4B]` って任意の文字列を入れているのであれば同様のことができそう？
どうやって入れるのかはいまだにわかってない…

# The game is over.
まあ初参加ならこんなもんでしょう。まだ強くなる余地があると思えば…