← Back to Writeups
CUSTOMEasyReversing

Mirror Mirror

XESXOR9/10/20263 min read
#reversing#custom#easy#checker_reimplementation#self_referential_hash#source_code_analysis#xor_keystream_reconstruction#linux

Mirror Mirror

Platform: broncoctf2026 | Category: Reversing | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-07-11 | Status: Solved Techniques: checker_reimplementation, self_referential_hash, source_code_analysis, xor_keystream_reconstruction

Summary

Task: a self-referential Python flag checker reconstructs the flag at runtime by XORing a hardcoded byte blob with a SHA-256 keystream computed over a 300-char slice of its own source. Solution: ignore the anti-debug guards and re-implement the pure XOR math externally, feeding the same source file so the SHA-256 slice matches.

Mirror Mirror — broncoctf2026

Description

"I've been told that this magic mirror will only clear when I give it the flag. Can you help me figure out what I should say?"

A single Python file mirror.py is provided — a self-referential flag checker themed around Snow White's magic mirror. The flag is never stored directly; it is reconstructed at runtime from a hardcoded byte array and a keystream derived from the script's own source.

Recon

The full challenge source:

python
import sys
import hashlib

def verify(attempt):
    try:
        with open(__file__, 'r') as f:
            src = f.read()
            pivot = src.index("MIRROR_SURFACE_DO_NOT_SCRATCH")
            specular_map = hashlib.sha256(src[pivot:pivot+300].encode()).digest()
    except (FileNotFoundError, ValueError):
        return "The mirror has been shattered."

    if sys.gettrace() is not None:
        return "Nice try, but the glass turns opaque. No observers allowed!"
    if sys._getframe().f_code.co_name != 'verify' or __name__ != "__main__":
        return "You are looking at the mirror from a distorted angle."

    blob = [17, 241, 10, 247, 215, 233, 146, 221, 156, 40, 37, 198, 153, 173, 10, 103, 20, 56, 232, 116, 208, 121, 53, 12, 122, 86, 127, 164, 109, 62, 88, 200, 127, 234, 5]
    try:
        looking_glass = "MirrorMirror"
        flag = ""
        for i, b in enumerate(blob):
            reflection_byte = specular_map[i % len(specular_map)] ^ ord(looking_glass[i % len(looking_glass)])
            flag += chr(b ^ reflection_byte)

        if attempt == flag:
            return f"The reflection clears!"
    except Exception:
        pass

    return "All you see is a distorted blur. (Wrong Password)"

if __name__ == "__main__":
    inp = input("Enter the password to gaze into the mirror: ")
    print(verify(inp))

Analysis

The flag is derived, not stored. For each index i:

flag[i] = blob[i] ^ specular_map[i % 32] ^ ord("MirrorMirror"[i % 12])

where specular_map = SHA-256( src[pivot : pivot+300] ) and pivot is the offset of the literal string MIRROR_SURFACE_DO_NOT_SCRATCH inside the source.

Key observations:

  1. Self-referential keystream. The marker string MIRROR_SURFACE_DO_NOT_SCRATCH appears in the source only inside the src.index(...) call, so pivot and the 300-char slice are fully deterministic. To reproduce the SHA-256 you must use the exact same source text (byte-for-byte), so keep mirror.py unmodified when extracting.

  2. Anti-analysis guards are red herrings. Three checks try to block dynamic analysis:

    • sys.gettrace() is not None — refuses to run under a debugger/tracer.
    • sys._getframe().f_code.co_name != 'verify' — must be called as verify.
    • __name__ != "__main__" — refuses to run when imported.

    None of these matter for a static re-implementation: we do not run the checker at all. We re-derive the pure XOR math ourselves and only feed it the same source file for the hash.

  3. Theme/joke. The recovered flag bronco{REDACTED} ("who is the fairest reverser") is the classic Snow White magic-mirror line.

Solution

Re-implement the exact computation outside the checker, pointing it at the unmodified mirror.py so the SHA-256 slice matches:

bash
python3 -c 'import hashlib
s=open("mirror.py").read()
p=s.index("MIRROR_SURFACE_DO_NOT_SCRATCH")
m=hashlib.sha256(s[p:p+300].encode()).digest()
b=[17,241,10,247,215,233,146,221,156,40,37,198,153,173,10,103,20,56,232,116,208,121,53,12,122,86,127,164,109,62,88,200,127,234,5]
k="MirrorMirror"
print("".join(chr(x^m[i%len(m)]^ord(k[i%len(k)])) for i,x in enumerate(b)))'

Output:

bronco{REDACTED}

Verification against the unmodified checker (so all guards pass naturally):

bash
printf '%s\n' 'bronco{REDACTED}' | python3 mirror.py
# Enter the password to gaze into the mirror: The reflection clears!