~$ skillshelf
← OverTheWire Bandit

Bandit 24 → 25: ten thousand pincodes, and a grep that lied

banditlinuxbrute-forcenetcat

First pure brute-force level. A daemon on port 30002 wants the bandit24 password and a secret 4-digit pincode on one line, and there’s no clever way to the pincode — you try all ten thousand. The loop that does it is three lines. The interesting part is a grep that matches a word you never meant it to.

the goal

A daemon is listening on port 30002 and will give you the password for bandit25 if given the password for bandit24 and a secret numeric 4-digit pincode. There is no way to retrieve the pincode except by going through all of the 10000 combinations, called brute-forcing. You do not need to create new connections each time.

That last sentence is the design hint: one connection, all 10000 tries. So I don’t loop nc — I loop the input and feed it to a single nc.

the approach

The daemon reads one line per attempt: <password> <pincode>. So generate every line up front and pipe the whole stream into one connection:

PASS='<bandit24 password — redacted>'

{ for pin in $(seq -w 0 9999); do
    echo "$PASS $pin"
  done
  sleep 3
} | nc 127.0.0.1 30002 | grep -i correct

Two small choices in there earn their keep:

  • seq -w 0 9999 — the pincode is 4-digit, and -w pads to equal width, so I send 0000 0001 … 9999, not 0 1 … 9999.
  • the trailing sleep 3 — the whole { … } block is one stdin stream. Without the sleep, the loop finishes instantly, stdin hits EOF, and nc can tear the connection down before the daemon’s winning reply lands. The sleep holds stdin open a few seconds so the answer arrives. This is the one place the “you do not need new connections” hint actually bites.

Run it, and it prints a wall of rejections:

Wrong! Please enter the correct current password and pincode. Try again.
Wrong! Please enter the correct current password and pincode. Try again.
Wrong! Please enter the correct current password and pincode. Try again.
...

Which looks impossible when the filter is grep -i correct. Read one reject line closely:

Wrong! Please enter the correct current password and pincode. Try again.

The word correct is sitting inside every failure message. grep -i correct matched all ten thousand of them. And it was worse than noise — the success reply is two lines:

Correct!
The password of user bandit25 is <redacted>

Correct! is on its own line; the password is on the line below it, and that line doesn’t contain the word “correct” at all. So the filter does the wrong thing twice: it lets every Wrong! through, and drops the one line worth having.

The fix is to match what’s actually unique to success. The reject uses lowercase correct mid-sentence; the win uses capital Correct!. So drop -i, match case-sensitively, and grab the line after the marker:

} | nc 127.0.0.1 30002 | grep -A1 Correct
  • grep Correct (no -i) ignores the lowercase “correct” in every failure.
  • -A1 prints the matched line plus one line after — which is the password.

Run it again and the only thing on screen is Correct! and the bandit25 password under it.

the takeaway

The brute-force was never the hard part — it’s a loop and a pipe. The brick is the grep.

A filter matches against the whole stream, not the line you’re hoping for. correct is in the message you want — and also buried in the ten thousand you don’t. Before trusting a pattern, look at what else in the output contains it. Here the cheap fix was case: the failure says correct, the success says Correct!, and dropping -i is enough to tell them apart.

And one netcat-shaped lesson worth keeping: when you fire a batch of input down a single connection, hold stdin open past the last line (a trailing sleep, or the right nc flag) so the final reply has time to come back before EOF closes the socket out from under it.