NNS CTF: Translator, not clanker, base64 unused bits
Challenge
I AM A TRANSLATOR. GIVE ME A WORD AND I WILL TRANSLATE. GIVE ME A LIFE STORY AND I WILL TRANSLATE. GIVE ME NOTHING AND I WILL NOT UNDERSTAND WHAT YOU MEAN, NOT BECAUSE I AM DUMB, BUT BECAUSE I DO NOT WANT TO UNDERSTAND. CALL ME A CLANKER AND I WILL UPSET YOU IN WAYS THAT YOU CAN NOT COMPREHEND, YET.
- Source: encode.py (
convertandb64eredacted in the handout) - Category: misc
The service base64-encodes whatever words you send it. What’s fun is what it does with the padding. I wrote this one for NNS CTF 2025.
The quirk
Encoding a should produce YQ==, but the service answers YU==:
ais one byte, 8 bits:01100001- Split into sextets:
011000 01xxxx - Standard base64 zeroes the unused bits:
011000 010000=YQ== - The service fills them with flag bits instead:
011000 010100=YU==
Both strings decode to a. A regular base64 decoder will ignore the last sextet’s unused bits. So each word you send quietly carries a few flag bits (4 bits for a one-byte input) that normal base64 decoding never shows. The solution is to collect all the nibbles and print the resulting bytes to see the flag.
Exploit
Send a bunch of short words whose sextets each reveal 4 unused bits. Convert them to binary, extract and collect the 4 unused bits off each base64 encoded word and lastly combine the bits resulting in the challenge flag.
from string import ascii_lowercase, ascii_uppercase, digits
from pwn import remote
BASE64 = ascii_uppercase + ascii_lowercase + digits + "+/"
def b64d(encoded: str) -> str:
result = ""
for e in encoded.replace("=", ""):
n = BASE64.index(e)
result += f"{n:06b}"
return result
with remote("<instance>", 3452) as io:
# Just a high enough number
payload = "a " * 100
io.sendlineafter(b"< ", payload.encode("utf-8"))
io.recvuntil(b"> ")
output = io.recvline().decode("utf-8").split()
flag_bits = "".join(b64d(o)[-4:] for o in output)
flag = int.to_bytes(int(flag_bits, 2), len(flag_bits) // 8)
print(flag.decode("utf-8", errors="ignore"))Why base64 has unused bits in the first place is covered in Base64.