【picoCTF】Binary Search - Bashスクリプトを二分探索に書き換えてフラグ取得

問題概要

picoCTFの「Binary Search」という問題の解説記事です。

  • カテゴリ: Forensics
  • 難易度: Easy

問題文

picoCTF Binary Search picoCTF Binary Search

解説

この問題では、与えられたZIPファイル challenge.zip を解析して、隠されたフラグを見つけることが求められます。

ステップ1: ZIPファイルの内容確認

まずは、ZIPファイルの内容を確認します。unzip コマンドを使用します。
$ unzip challenge.zip
Archive:  challenge.zip
   creating: home/ctf-player/drop-in/
  inflating: home/ctf-player/drop-in/guessing_game.sh
challenge.zip には home/ctf-player/drop-in/guessing_game.sh というシェルスクリプトファイルが含まれていることがわかります。

ステップ2: シェルスクリプトの確認

次に、シェルスクリプトの内容を確認します。cat コマンドを使用します。
$ cat home/ctf-player/drop-in/guessing_game.sh

#!/bin/bash

# Generate a random number between 1 and 1000
target=$(( (RANDOM % 1000) + 1 ))

echo "Welcome to the Binary Search Game!"
echo "I'm thinking of a number between 1 and 1000."

# Trap signals to prevent exiting
trap 'echo "Exiting is not allowed."' INT
trap '' SIGQUIT
trap '' SIGTSTP

# Limit the player to 10 guesses
MAX_GUESSES=10
guess_count=0

while (( guess_count < MAX_GUESSES )); do
    read -p "Enter your guess: " guess

    if ! [[ "$guess" =~ ^[0-9]+$ ]]; then
        echo "Please enter a valid number."
        continue
    fi

    (( guess_count++ ))

    if (( guess < target )); then
        echo "Higher! Try again."
    elif (( guess > target )); then
        echo "Lower! Try again."
    else
        echo "Congratulations! You guessed the correct number: $target"

        # Retrieve the flag from the metadata file
        flag=$(cat /challenge/metadata.json | jq -r '.flag')
        echo "Here's your flag: $flag"
        exit 0  # Exit with success code
    fi
done

# Player has exceeded maximum guesses
echo "Sorry, you've exceeded the maximum number of guesses."
exit 1  # Exit with error code to close the connection

ステップ3: スクリプトの動作の理解

まず、この guessing_game.sh がどのように動作するかを理解します。

ゲームの正解となる数値の生成

target=$(( (RANDOM % 1000) + 1 ))

この行で、1〜1000 の範囲の乱数が正解(target)として生成されます。 つまり、毎回実行するたびに正解の数値は変わります。


試行回数の制限

MAX_GUESSES=10
guess_count=0

プレイヤーが入力できる回数は 最大 10 回 に制限されています。 これは問題文にある

You'll have 1000 possibilities and only 10 guesses.

という条件そのものです。


入力値に対する判定処理

if (( guess < target )); then
    echo "Higher! Try again."
elif (( guess > target )); then
    echo "Lower! Try again."
else
    echo "Congratulations! You guessed the correct number: $target"
プレイヤーの入力値(guess)が正解(target)より小さい場合は「Higher!」、大きい場合は「Lower!」と表示されます。
正解の場合は「Congratulations!」と表示されます。
典型的な二分探索ゲームのロジックです。

ステップ4: なぜ二分探索が必要か

ここで重要なのは、

  • 候補の数は 1000
  • 試行回数は 10 回まで という条件です。

この条件下で確実に正解を見つける方法は、二分探索しかありません。 二分探索では、毎回探索範囲を半分に減らします。

試行回数探索範囲のサイズ
11000 → 500
2500 → 250
3250 → 125
4125 → 63
563 → 32
632 → 16
716 → 8
88 → 4
94 → 2
102 → 1

10回の試行で 1000 → 1 まで絞り込めるため、必ず正解を見つけられます。


ステップ5: ゲームをクリアしてフラグを取得

このロジックを踏まえて、実際に二分探索を行います。

picoCTFのサーバーにSSHで接続する

問題文に記載されている情報を使って、picoCTFのサーバーにSSHで接続します。

ssh -p xxxxx ctf-player@atlas.picoctf.net
xxxxxは問題文に記載されているポート番号に置き換えてください。
接続後、パスワードを求められるので、問題文に記載されているパスワードを入力します。
初回接続時にフィンガープリント確認が表示された場合は yes と入力して承認します。
接続後ゲームが自動的に開始されます。

実際の探索例

初期状態では、範囲は 1〜1000 です。 1回目の入力として、中央の 500 を入力します。

Enter your guess: 500
  • Higher! Try again. → 正解は 501〜1000 の範囲に絞られます。
  • Lower! Try again. → 正解は 1〜499 の範囲に絞られます。
    このように、毎回常に現在の範囲の中央の値を入力し、探索範囲を半分に絞り込みながら、10回以内に正解を見つけます。
    例えば501〜1000の範囲に絞られた場合、次は750を入力します。

フラグの取得

最終的に正解を見つけた場合、以下のようにフラグが表示されます。

Congratulations! You guessed the correct number: 795
Here's your flag: picoCTF{xxxxx}

※フラグはマスクしています。


使用したコマンドの軽い解説

unzip

unzip challenge.zip

ZIPファイルを展開します。展開結果(どのファイルが出てきたか)も出力に表示されます。


cat

cat home/ctf-player/drop-in/guessing_game.sh

ファイルの中身を表示します。CTFでは「まずスクリプト/設定/ログを読む」が定石です。


まとめ

この問題は、ゲームに直接「裏技」を使うのではなく、与えられた条件(1000通りを10回)から 二分探索が唯一の確実解だと気付けるかがポイントでした。

▼ポイントは以下の通りです。

  • guessing_game.sh を読んで「試行回数10回」「範囲1〜1000」を確認する
  • 二分探索で毎回範囲を半分にし、10回以内に必ず当てる

閲覧ありがとうございました!

NEXT
次におすすめ

【picoCTF】Secret of the Polyglot - PNGとPDFのポリグロットファイルから2つのフラグ断片を復号

カテゴリ: Forensics難易度: Easy#picoCTF
次の記事へ →
同じカテゴリ/難易度/picoCTFでの表示順が近い記事を優先しておすすめしています。