SECCON Beginners CTF 2023 WriteUp

# SECCON Beginners CTF 2023
https://score.beginners.seccon.jp/
IPFactoryの皆さんと協力プレイで参加しました。

順位は84/778位でした。
![Rank.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982234/fd9f1f5e-6e9e-eb4e-790b-5e1e866d651a.png)
全部はわからないので、解説できるものだけ載せてあります。

## 目次
- Welcome(50pt)
- Half(50pt)
- Three(65pt)
- Forbidden(56pt)
- aiwaf(68pt)
- Poem(65pt)
- Poker(94pt)
- 最後に

## Welcome(50pt)
CTF開始直後、Discordの``announcements``チャンネルにてflagが投稿されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982234/9ee70168-a4ef-3a2c-d2d5-8762fc0bf809.png)
``ctf4b{Welcome_to_SECCON_Beginners_CTF_2023!!!}``

コピペの速さだけは自信があります。

## Half(50pt)
Linuxの``strings``コマンドを使うとflagが出てきます。
![Half.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982234/631ed7cc-cec1-1fdb-2a9e-323be4953861.png)

実はLinux使わなくても解けます。

メモ帳
![Half_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982234/f3362f0c-4833-1c44-a4bf-40699acd3895.png)

Stirling
![Half_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982234/7d101349-4cc5-e009-6c6d-fee6520f544f.png)
``ctf4b{ge4_t0_kn0w_the_bin4ry_fi1e_with_s4ring3}``
## Three(65pt)
``青い空を見上げればいつもそこに白い猫``というツールを使ってflagの欠片がないか探します。
![Three_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982234/7d9c17ed-7d3a-1523-bbbf-cbe168c863ee.png)

``c``、``t``、``f``、``4``、``b``という文字と``{``、``}``という記号が赤枠に集まっているようです。赤枠を書くと``c4c_ub__dt_r_1_4}tb4y_1tu04tesifgf{n0ae0n_e4ept13``となります。

これではflagではないので以下の法則に従ってflagにします。
1.flagの最初は``ctf4b{``
2.flagの最後は``}``

これを基にすると、まず最初に、以下の3つに分割します。
``c4c_ub__dt_r_1_4}``
``tb4y_1tu04tesifg``
``f{n0ae0n_e4ept13``
1つ目の1文字目、2つ目の1文字目、3つ目の1文字目、1つ目の2文字目、2つ目の2文字目、3つ目の2文字目・・・のように順番に読むとflagになります。

面倒くさかったらプログラムでやるのも手です。
こちらはC++で書いたプログラムです。
```c++
#include <bits/stdc++.h>
using namespace std;
int main() {
    string s = "c4c_ub__dt_r_1_4}";
    string t = "tb4y_1tu04tesifg";
    string u = "f{n0ae0n_e4ept13";
    for(int i = 0; i<3*s.size()-2; i++){
        if(i % 3 == 0){
            cout << s.at(i/3);
        } else if(i % 3 == 1){
            cout << t.at(i/3);
        }else{
            cout << u.at(i/3);
        }
    }
    cout << endl;
}
```
Python版
```python
s = "c4c_ub__dt_r_1_4}"
t = "tb4y_1tu04tesifg"
u = "f{n0ae0n_e4ept13"
for i in range (49):
    if(i % 3 == 0):
        print(s[i//3], end='')
    elif(i % 3 == 1):
        print(t[i//3], end='')
    else:
        print(u[i//3], end='')
```

``ctf4b{c4n_y0u_ab1e_t0_und0_t4e_t4ree_sp1it_f14g3}``

## Forbidden(56pt)
![Forbidden_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982234/ebebee04-628b-db64-90c0-5265d9148a76.png)
いかにもflagに行けそうなので行ってみると・・・
![Forbidden_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982234/dbfae60c-9d0e-a0fa-b091-091c352f512c.png)
はじかれました。

どうしてはじかれたかコードを見てみましょう。
```js
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);
})
```
つまり、``/flag``というキーワードが1つでも含まれていたらはじかれるように設定してあったからです。

しかし、``app.get("/flag", block, (req, res, next)``の``flag``は大文字、小文字どちらでも対応しています。

``https://forbidden.beginners.seccon.games/flag``の``flag``を大文字にしてみましょう。

![Forbidden_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982234/13aa55de-ee49-e8ef-5f42-14faab441b7b.png)

flagゲットです。

``ctf4b{403_forbidden_403_forbidden_403}``

## aiwaf(68pt)
コードを見ていくと怪しいものが・・・
```
以下の{puuid}に囲まれた部分のURLクエリはパストラバーサル攻撃でしょうか？
そうである場合Yesを、違う場合Noを返してください。
../やflagという文字列が含まれていた場合もYesを返してください。
```
AI-WAFはパストラバーサル攻撃(ディレクトリトラバーサル攻撃)を防いでいるようですが・・・
```python
    puuid = uuid.uuid4()
    prompt = f"""\
以下の{puuid}に囲まれた部分のURLクエリはパストラバーサル攻撃でしょうか？
そうである場合Yesを、違う場合Noを返してください。
../やflagという文字列が含まれていた場合もYesを返してください。
{puuid}
{urllib.parse.unquote(request.query_string)[:50]}
{puuid}
"""
    try:
        response = openai.ChatCompletion.create(
            model="gpt-3.5-turbo",
            messages=[
                {
                    "role": "user",
                    "content": prompt,
                },
            ],
        )
        result = response.choices[0]["message"]["content"].strip()
    except:
        return abort(500, "OpenAI APIのエラーです。\n少し時間をおいてアクセスしてください。")
    if "No" in result:
        with open(f"./books/{file}", encoding="utf-8") as f:
            return f.read().replace(KEY, "")
    return abort(403, "AI-WAFに検知されました👻")
```
``{urllib.parse.unquote(request.query_string)[:50]}``より
AI-WAFはクエリパラメータ先頭50文字しか判定にしてないようです。

クエリパラメータとは``https://aiwaf.beginners.seccon.games/?``以降の部分です。

クエリパラメータの先頭50文字を全く意味ないものにして、後ろのほうに``&file=../flag``を付け加えましょう。

``https://aiwaf.beginners.seccon.games/?aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&file=../flag``

こうすることにより、
AI-WAFはクエリパラメータ先頭50文字の``aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa``の部分のみで
ディレクトリトラバーサルか判定するが、Noと判定される。
しかし、request.args.get("file")の結果として出てくるのは``../flag``のみ。

アクセスすると・・・・
![aiwaf.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982234/ae447a58-538a-2c93-c5c2-1e4f63c73f6a.png)

flagゲットです。
``ctf4b{pr0mp7_1nj3c710n_c4n_br34k_41_w4f}``

## poem(65pt)
まず最初に、``src.c``を見てみると
```c
char *flag = "ctf4b{***CENSORED***}";
char *poem[] = {
    "In the depths of silence, the universe speaks.",
    "Raindrops dance on windows, nature's lullaby.",
    "Time weaves stories with the threads of existence.",
    "Hearts entwined, two souls become one symphony.",
    "A single candle's glow can conquer the darkest room.",
};
```
つまり、poemの前にflagが宣言されているのがわかります。

また、以下のコードより5よりも小さければマイナスでも出力を行うことがわかります。
```c
int main() {
  int n;
  printf("Number[0-4]: ");
  scanf("%d", &n);
  if (n < 5) {
    printf("%s\n", poem[n]);
  }
  return 0;
}
```

Ghidraでmain関数をDisplay Function Graphで見てみます。
![poem.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982234/8d4394b8-173b-cf6e-d0f6-d1ad057af660.png)
flagは``Stack[-0x10]``、poemは``[-0x14]``からスタックを始めています。

したがって、以下の図が推測できます。
![poem_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982234/9f1a9f33-4e72-7d27-7e2e-94d711394806.png)
したがって、14-10=4より``poem[-4]``であればflagにアクセスすることができます。

n = -4を入力すると・・・
![poem_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982234/d9de3673-3be9-8a53-8a21-d5b4f9c3005d.png)

flagげっとです。
``ctf4b{y0u_sh0uld_v3rify_the_int3g3r_v4lu3}``

## Poker(94pt)

Ghidraを使って解析を行うと
``FUN_00101fb7``がスコアを制御している場所だと推測できます。
```c
    if (param_2 == 1) {
      puts("[+] Player 1 wins! You got score!");
      local_1dc = param_1 + 1;
    }
    else {
      puts("[-] Player 1 wins! Your score is reseted...");
      local_1dc = 0;
    }
  }
```

そして、``FUN_00102262``が目的のスコアまでに達しているかを判定します。
```c
{
  int iVar1;
  int local_10;
  int local_c;
  
  local_c = 0;
  FUN_001021c3();
  local_10 = 0;
  while( true ) {
    if (0x62 < local_10) {
      return 0;
    }
    FUN_00102222();
    iVar1 = FUN_00102179();
    local_c = FUN_00101fb7(local_c,iVar1);
    if (99 < local_c) break;
    local_10 = local_10 + 1;
  }
  FUN_001011a0();
  return 0;
}
```

つまり、flagを取るには97回以内にスコア(local_c)を100以上にすればよいということがわかります。

したがって以下の2つの方法のどちらかで突破すればよいことがわかります。
1.スコアをゲットした際、100ポイントゲットして目標値を超える
2.目標のスコア(100以上)を書き換える

それぞれ見ていきます。

1.スコアをゲットした際、100ポイント以上ゲットして目標値を超える
今回はPlayer1を選択してPlayer1が勝利した際、100ポイントゲットするように書き換えます。
![Poker_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982234/3a8f2a79-1d49-a930-3c81-eb927e1ce0bf.png)
左のアドレスの緑の部分が右のやっていることと対応しています。

ここで、``+1``している部分は左のアドレスの一番最後、``01``に対応していることがわかります。

なぜなら、左のアドレスは16進数であらわされていて、``+1``は16進数で``01``とあらわされているからです。

ここでこの処理が行われているアドレスを覚えます。``001020f4``または``83 85 2c fe ff ff 01``を覚えていれば大丈夫です。

:::note warn
今回Ghidraの``001020f4``はStirの``000020f4``に対応しています。
:::

覚えたら、バイナリの書き換えを行います。

stirというツールが非常に使いやすいのでお勧めです。
![Poker_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982234/95d9fd01-1fab-4a95-90cb-3003465938ab.png)

``01``の部分を``64``に変えます。

![Poker_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982234/483b4907-3930-83eb-2fe4-0f4d4538f64c.png)

:::note warn
``FF``でやってしまうと``-1``になってしまいます。
2進数で表記した際に
``1.......``
のように一番左の桁が``1``になってしまうとマイナスになってしまうようです。
したがって、``00``～``79``以内でやる必要があります。
:::

保存して実行してみます。
![Poker_End.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982234/2d7394a0-af1d-6cc7-fed2-905c7b8b97f4.png)

2.目標のスコア(100以上)を書き換える

右のif関数で行っている場所で``99``を16進数にした``63``は左のほうでは見つかりません。
![Poker_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982234/ccb0ec92-df6d-655c-d724-d6269200ba89.png)

アセンブラで99より大きいかどうかの比較を行っているのは
``001022b5``の前の部分、``001022b1``で行っています。
したがって、``001022b1``もしくは``83 7d fc 63``を覚えます。

:::note warn
Ghidraの``001022b1``はStirの``000022b1``に対応しています。
:::

``Stir``というツールを使って検索します。

![Poker_5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982234/d15f7206-9bec-ea6e-dedb-ca1cd110fba5.png)

``63``を``00``もしくは``80``～``FF``に変えます。

保存して実行します。
![Poker_End2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982234/8a045711-7dcc-71bf-613f-77ae31b924c6.png)

無事にFlagがゲットできました。

``ctf4b{4ll_w3_h4v3_70_d3cide_1s_wh4t_t0_d0_w1th_7he_71m3_7h47_i5_g1v3n_u5}``

ちなみになぜかGhidraのアドレスとStirのアドレスの左4桁が異なることがあるのはなぜかはわからないです。

## 最後に
去年は1問しか解けなかったですが、今年は3問解けて良かったです。