منشور

InfoSec CTF – WALLS

They say there were five walls, not to keep enemies out, but to keep a secret in.

InfoSec CTF – WALLS

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:

  1. Base32-decode (because 5 walls → 5-bit alphabet).
  2. You’ll see zero-width characters sprinkled in the result.
  3. Strip those invisibles to reveal something that looks like Base64 but starts with `==`.
  4. Reverse the string to normalize padding to the end.
  5. Base64-decode to get the flag.

Steps

  1. Base32 (5-bit) reasoning
    Base32 encodes data in 5-bit groups. The phrase “five walls” maps neatly to this.

  2. Decode Base32
    After decoding, the text is polluted with zero-width characters (e.g., U+200B, U+200C, U+200D, U+FEFF).

  3. Strip zero-widths
    Removing those code points yields the intermediate string:
    ==0HanVHMuNzX3AjbfNXMfVTMxQzd7l1ZhxmR

  4. Reverse for proper padding
    Reversing gives a valid Base64 string:
    RmxhZ1l7dzQxMTVfMXNfbjA3XzNuMHVnaH0==

  5. Decode Base64
    Decoding the reversed string produces the flag text.

  6. 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}
هذا المنشور تحت ترخيص CC BY 4.0 بواسطة المؤلف.

الوسوم الشائعة