InfoSec CTF – WALLS
They say there were five walls, not to keep enemies out, but to keep a secret in.
WALLS Crypto Write-up
Category: Crypto
Prompt: They say there were five walls, not to keep enemies out, but to keep a secret in.
Artifact: ⬇️ message.txt
Flag format: FlagY{…}
Intro
A compact, elegant crypto puzzle. The hint five walls suggests a 5-bit encoding → Base32.
“Keep a secret in” nudges toward invisible zero-width characters hidden inside the intermediate text.
Finally, the odd placement of padding (==) at the start hints you should reverse the string before the final decode.
Challenge Description
You’re given a single encoded blob in message.txt. The goal is to recover the flag.
High-level path:
- Base32-decode (because 5 walls → 5-bit alphabet).
- You’ll see zero-width characters sprinkled in the result.
- Strip those invisibles to reveal something that looks like Base64 but starts with `==`.
- Reverse the string to normalize padding to the end.
- Base64-decode to get the flag.
Steps
Base32 (5-bit) reasoning
Base32 encodes data in 5-bit groups. The phrase “five walls” maps neatly to this.Decode Base32
After decoding, the text is polluted with zero-width characters (e.g., U+200B, U+200C, U+200D, U+FEFF).Strip zero-widths
Removing those code points yields the intermediate string:
==0HanVHMuNzX3AjbfNXMfVTMxQzd7l1ZhxmRReverse for proper padding
Reversing gives a valid Base64 string:
RmxhZ1l7dzQxMTVfMXNfbjA3XzNuMHVnaH0==Decode Base64
Decoding the reversed string produces the flag text.Flag
FlagY{w4115_1s_n07_3n0ugh}
Solver
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# WALLS solver — tolerant of missing Base32 padding and zero-width characters.
import sys
import re
import base64
from pathlib import Path
FALLBACK_DATA = (
"HU6TASDB4KAIW3SWJBGXLYUARNHHUWBTIHRIBC3KMJTE4WHCQCFU2ZSWKRG6FAELPBIXUZBX4KAIW3BRLJUHRYUARNWVE"
)
ZERO_WIDTH_RE = re.compile(r'[\u200B-\u200D\uFEFF]') # U+200B, U+200C, U+200D, U+FEFF
def read_data():
if len(sys.argv) > 1:
return Path(sys.argv[1]).read_text(encoding="utf-8").strip()
p = Path("message.txt")
if p.exists():
return p.read_text(encoding="utf-8").strip()
return FALLBACK_DATA.strip()
def pad_base32(s: str) -> str:
s = re.sub(r'\s+', '', s)
need = (8 - (len(s) % 8)) % 8
return s + ('=' * need)
def b32_decode(s: str) -> bytes:
s = pad_base32(s)
return base64.b32decode(s, casefold=True)
def strip_zero_widths(s: str) -> str:
return ZERO_WIDTH_RE.sub('', s)
def try_b64(s: str) -> str:
def _b64(x):
return base64.b64decode(x, validate=True).decode('utf-8')
try:
return _b64(s)
except Exception:
return _b64(s[::-1])
def main():
data = read_data()
print("[*] Input length:", len(data))
raw = b32_decode(data)
print("[*] Base32 decoded bytes:", len(raw))
inter = raw.decode('utf-8', errors='ignore')
clean = strip_zero_widths(inter)
print("[*] After stripping zero-widths:", clean)
flag = try_b64(clean)
print("[+] Flag:", flag)
if __name__ == "__main__":
main()
Output:
1
2
3
4
[*] Input length: 100
[*] Base32 decoded bytes: 40
[*] After stripping zero-widths: ==0HanVHMuNzX3AjbfNXMfVTMxQzd7l1ZhxmR
[+] Flag: FlagY{w4115_1s_n07_3n0ugh}