Challenge: Flip Dat Bit · Platform: TryHackMe · Category: Crypto · Difficulty: Medium
1. Introduction / TL;DR
“Flip off!” — the server, before I understood what was happening.
“No way! You got it!” — the server, moments after.
This challenge drops you in front of a TCP service that asks you to log in as admin. Sounds simple enough — until you notice it both leaks the ciphertext of your own input and asks you to submit a valid ciphertext back. It’s handing you a loaded gun and betting you don’t know how to aim it.
The vulnerability is a textbook AES-CBC bit-flipping attack: by strategically corrupting one byte in the leaked ciphertext, you can surgically alter one byte in the decrypted plaintext — turning bdmin into admin and walking right through the door.
TL;DR: Send a username with a single character off from admin, receive the ciphertext, XOR one byte with 0x03, send it back, collect flag.
2. Initial Recon
Connecting to the service on port 1337 with netcat gives an immediate greeting:
| |
It asks for a username, then a password. When you enter credentials that don’t look suspicious, something interesting happens:
| |
The server encrypts our input and gives us the ciphertext, then asks us to provide a ciphertext of our own. If our ciphertext decrypts to something containing admin&password=sUp3rPaSs1, we get the flag.
The challenge is already showing its hand — this is a chosen-ciphertext scenario powered by the oracle of the service itself.
3. Analysis / Source Code Review
We’re given the server’s Python source. Let’s walk through it:
| |
Guard #1 rejects any raw input that already contains the magic string admin&password=sUp3rPaSs1. So you can’t just type the right credentials directly.
| |
The server encrypts the message using AES-128-CBC and leaks it. Then it decrypts whatever we send back using the same key and IV.
| |
Guard #2 (the win condition): after decryption, the plaintext just needs to contain admin&password=sUp3rPaSs1 as a substring. It doesn’t have to be a clean, perfectly-formed message — junk bytes elsewhere are fine.
Two guards. One cipher. One very exploitable mode of operation.
4. Vulnerability Identification
The critical detail is buried in the encryption call:
| |
AES-CBC — Cipher Block Chaining. And the IV is fixed for the session (same IV used to encrypt and to decrypt our returned ciphertext). This is the exact setup that enables a bit-flipping attack.
CBC has a well-known property: flipping a bit in ciphertext block N corrupts block N’s decryption but predictably flips the corresponding bit in block N+1’s decryption. If you control what’s in block N+1’s plaintext, you can use the ciphertext of block N as a lever to change it.
The plaintext structure is:
| |
The prefix access_username= is exactly 16 bytes — one AES block. That means our username starts at the beginning of block 1. This alignment is not a coincidence; it’s the key that makes the attack clean.
5. Exploitation Strategy — What Is CBC Bit-Flipping?
Let’s build the intuition from scratch.
How AES-CBC Decryption Works
In CBC mode, each plaintext block is produced by:
| |
For the first block, Ciphertext[-1] is the IV.
| |
Notice: Plaintext[1] depends directly on Ciphertext[0]. The AES decryption of Ciphertext[1] produces some intermediate bytes; those bytes are then XOR’d with Ciphertext[0] to produce the final plaintext.
The Lever
If we know what Plaintext[1] currently is, and we want to change byte i of Plaintext[1] from A to B, we simply:
| |
This is because:
| |
The price: modifying Ciphertext[0] makes Plaintext[0] decrypt to garbage. But since the win condition only checks for a substring anywhere in the decrypted output, a corrupted block 0 doesn’t matter.
Why This Challenge Is Vulnerable
- The prefix
access_username=aligns our username to the start of block 1. - We control the username, so we control what block 1 decrypts to.
- The server leaks the ciphertext, giving us Ciphertext[0] to manipulate.
- The win condition is a substring check — corrupted blocks elsewhere don’t matter.
Every ingredient is present.
6. Building the Exploit
Step 1 — Choose the Right Username
We need a username that:
- Fails Guard #1 (the raw string check)
- Places the target string
admin&password=sUp3rPaSs1in block 1 of plaintext after a single bit-flip
The target string admin&password=sUp3rPaSs1 is 25 bytes. If it starts at byte 16 (the beginning of block 1), it spans into block 2:
| |
We want block 1 to say admin&password=s. Currently, we can make the server encrypt a username that starts with bdmin&password=sUp3rPaSs1:
| |
Does bdmin&password=sUp3rPaSs1 trigger Guard #1?
| |
Searching for admin&password=sUp3rPaSs1 in this string: the only place admin could appear is at position 17, but position 16 is b, not a. Guard #1 does not trigger. ✓
The XOR value needed to flip b (0x62) → a (0x61):
| |
Step 2 — Verify the Block Layout
With username bdmin&password=sUp3rPaSs1 and password anything:
| |
- B0 (bytes 0–15):
access_username= - B1 (bytes 16–31):
bdmin&password=s - B2 (bytes 32–47):
Up3rPaSs1&passwo - B3 (bytes 48–63):
rd=anything+\x05\x05\x05\x05\x05(PKCS7 padding)
After flipping Ciphertext[0][0] ^= 0x03:
- B0 → garbage (doesn’t matter)
- B1 →
admin&password=s✓ - B2 →
Up3rPaSs1&passwo(untouched) - B3 →
rd=anything\x05…(untouched, valid padding)
unpad strips the trailing \x05 bytes from B3. The resulting plaintext contains:
| |
The substring admin&password=sUp3rPaSs1 is present. Guard #2 passes.
Step 3 — The Solve Script
| |
Step 4 — Running It
| |
One connection. One XOR. Done.
7. Conclusion / Flag
| |
The flag says it all: Flip Dat Bit Or Get Flipped. The challenge is a clean, minimal demonstration of why CBC mode should never be used as an authentication oracle.
The Three Mistakes That Made This Possible
| Mistake | Why It Matters |
|---|---|
| Leaking the ciphertext | Gives the attacker Ciphertext[0] to use as a lever |
| Decrypting attacker-controlled input with the same key/IV | Turns the server into a decryption oracle |
| Substring check instead of HMAC/signature | Means garbled blocks outside the target window are irrelevant |
Any one of these alone might be acceptable in a different context. Together they create a perfect storm.
Real-World Lesson
If you need AES and need to ensure the decrypted data hasn’t been tampered with, use an authenticated encryption mode — AES-GCM is the modern go-to. It provides both confidentiality and integrity; any modification to the ciphertext makes decryption fail outright rather than silently producing attacker-controlled plaintext.
CBC is not broken — but it is brittle. Use it wrong, and a single ^= 0x03 is all it takes to walk through the front door.
Written after solving the challenge live. All offsets and values are derived directly from the server source — no guessing required.