SECCON Beginners CTF 2024 参戦報告 (+ miniWriteup)

いつぞやぶりのBlogカテゴリーネタ。

えらくバランス良く解けたのと、初めてcryptoの問題を解けたのが嬉しい。

Safe Prime (crypto)

RSA暗号の脆弱性を突く問題。

リチェルカセキュリティのこちらの記事が非常に参考になった。

特に以下の式が便利。c, e, p, q, nが分かれば元のmが計算できるというのが1行で纏まってる。

print(long_to_bytes(pow(c, pow(e, -1, (p - 1) * (   (2*p + 1)    - 1)), n)).decode())

c, e, nは問題として提示されているので、p, qをなんとかしないといけない。

pは512ビット、qは2*p+1なので総当たりするのは無理があり、これがRSA暗号の強度の保証になっている模様。

 

で、解法としてはpとqに関連性がある所に着目。2次方程式の解の公式を引っ張り出してこれれば、実は割と簡単にp, qが導き出せることに気付ける。

以上、屁でもない計算量でmが導き出せる。

 

getRank (misc)

システム1位のスコアを叩き出すと報酬としてフラグが貰えるゲーム。

まともにゲームに付き合っている暇は無いので、なにはともあれスコアサーバーに直接スコアだけ送り付けられるようにする。

ゲームのランク確認ボタンの作りがこう↓

<button onclick="getRank()" class="bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded">
        Get Rank
      </button>

getRank関数を呼んでいる模様。getRank関数の作りはこう↓

 function getRank() {
        const rankElement = document.getElementById("rank");
        const messageElement = document.getElementById("message");

        fetch("/", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({ input: `${score}` }),
        })
          .then((response) => response.json())
          .then((data) => {
            rankElement.textContent = data.rank ?? "-1";
            messageElement.textContent =
              data.message ?? "Error occurred.";
          })
          .catch((error) => {
            rankElement.textContent = "-1";
            messageElement.textContent = "Error occurred.";
            console.error(error);
          });
      }

jsonでscoreを任意に指定してPOSTすればいけそうというのが分かる。私はChrome拡張の「Boomerang – SOAP & REST Client」というクライアントを使用。

続いて、送り付けるscoreについても検討が必要。

現在1位のスコアは10 ** 255点であり、これを超えないといけない。また、300文字以上の数字は受け付けてくれない。

更に、chall関数にある通り、

if (score > 10 ** 255) {
    // hmm...your score is too big?
    // you need a handicap!
    for (let i = 0; i < 100; i++) {
      score = Math.floor(score / 10);
    }
  }

提出したスコアが 10**255点を超えると超絶減点が自動でされる仕様。いやなんじゃそら()

つまり、スコア減点機能も回避するか、減点されても尚10**255を超える必要がある。

ここで肝になるのが39行目、parseInt関数。

  let score = parseInt(input);

300文字制限はstring型のinputの時点で、10**255点制限はparseInt後に判定されることを利用する。

具体的な解法としては、parseIntの仕様を利用する。仕様はこちらのサイトに分かりやすく纏められている。

“0x”から入力を始めることで、parseIntは16進数とかも表現できる。で、300文字制限は16進の状態で判定され、10**255点制限はparseInt後の10進で判定される為、この仕様を使えば上記の制限を回避可能。

16進で300文字以下であり、かつ10進に変換後10**355より大きい数字は存在する。以上、要件に合う適当な16進の数字を入れてPOST、フラグゲット。

simpleoverflow (pwnable)

ctf4bおなじみ。

配列外にまでread関数が書き込んでいるのを用いて、int型のis_admin変数に1を格納するだけ。

大学1年の時に苦しめられたネタで今は楽しんでるというのは感慨深い。

まとめ

去年手も足も出なかったcryptoに進展があったのは喜ばしい。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA