locker
Author: 0xIDA
Platform: FlagYard · Category: rev · Difficulty: Medium
Solvers
| Field | Value |
|---|---|
| Platform | Flagyard (SAFCSP) |
| Lab | 6 — Competitive Reversing |
| Challenge | locker |
| Challenge ID | fab9705b-7510-4406-98c3-572ae7654bcd |
| Category | Reversing |
| Difficulty | Medium |
| Points | 250 |
| Author | 0xIDA |
| Flag | FlagY{ins3rt_go0d_flag_text_h3re} |
| Submit | Live success (isSuccess: true) |
| Date | 2026-07-12 |
Challenge Description
Please unlock my flag
Files:
locker— ELF64 encryptor (stripped, no PIE)flag.png.enc— encrypted PNG (119996 bytes, ~8.0 bits/byte entropy)
No remote instance; pure offline reverse + decrypt.
Reconnaissance
file locker flag.png.enc
# locker: ELF 64-bit LSB executable, x86-64, stripped
# flag.png.enc: data
strings locker
# Usage: %s <file>
# %s.enc
# imports: srand, rand, fopen, fread, fwrite, malloc, ...
checksec --file=locker
# Partial RELRO, No canary, NX, No PIE
Usage: ./locker <file> reads the file and writes <file>.enc.
Vulnerability Analysis
Static reverse of the encrypt routine at 0x4011e0 (called from main at 0x401360):
srand(0x1337)— fixed seed (not time-based).- Build index array
perm[i] = ifori in 0..n-1. - Biased Fisher–Yates: for
i = 0 .. n-2, swapperm[i]withperm[j]where
j = (rand() % (n - i - 1)) + (i + 1)
(j always in[i+1, n-1], never identity at step i). - For each output index
i:acc = 0 idx = i for k in 0..122: # 0x7b = 123 iterations acc ^= input[perm[idx]] idx = perm[idx] output[i] = acc
So output[i] = XOR_{t=1}^{123} input[perm^t(i)] — a 123-step sliding XOR along the functional graph of perm.
The biased shuffle produced a single n-cycle on the challenge file (n = 119996). Ordering the cycle as c[0..L-1] with perm[c[j]] = c[j+1]:
y[j] = x[j+1] XOR x[j+2] XOR ... XOR x[j+123] (indices mod L)
This is a circulant linear system over GF(2) (byte-wise XOR).
gcd(123, 119996) = 1 ⇒ full rank ⇒ unique plaintext.
Exploitation
Reconstruct encryption (glibc rand via ctypes)
import ctypes, ctypes.util
libc = ctypes.CDLL(ctypes.util.find_library('c'))
libc.srand.argtypes = [ctypes.c_uint]
libc.rand.restype = ctypes.c_int
def build_perm(n, seed=0x1337):
libc.srand(seed)
perm = list(range(n))
for i in range(n - 1):
j = (libc.rand() % (n - i - 1)) + (i + 1)
perm[i], perm[j] = perm[j], perm[i]
return perm
Verified: reimplemented encrypt matches ./locker on a test file.
Invert window XOR on the cycle
Difference identity:
y[j] XOR y[j-1] = x[j] XOR x[j+123]
⇒ x[j+123] = x[j] XOR b[j] where b[j] = y[j] XOR y[j-1]
Because gcd(123, L) = 1, the map j → j+123 is a single orbit of length L. Express every x[j] = c XOR offset[j] along that orbit, then fix free byte c with one original equation (W = 123 is odd):
y[0] = XOR_{t=1}^{123} x[t] = c XOR (XOR offsets)
⇒ c = y[0] XOR (XOR offsets)
Decrypt + recover flag
enc = open('flag.png.enc','rb').read()
plain = decrypt(enc) # algorithm above
open('flag.png','wb').write(plain)
# header: 89 50 4e 47 0d 0a 1a 0a ... IHDR → valid PNG 800x200
Image text:
FlagY{ins3rt_go0d_flag_text_h3re}
Submit
mcp_flagyard_submit_flag(lab_id=6, challenge_id=fab9705b-..., flag=FlagY{ins3rt_go0d_flag_text_h3re})
→ isSuccess: true
Flag Retrieval
Decrypted flag.png (PNG signature verified) contains the flag string in cleartext on a gradient background.
Flag: FlagY{ins3rt_go0d_flag_text_h3re} (platform-accepted)
Key Takeaways
- Fixed
srand+rand-driven permutation is fully deterministic offline — always reimplement with glibcrand(not Pythonrandom). - “Encrypt” was a long XOR walk on a permutation, i.e. a circulant linear map over GF(2); invert via window differences + one absolute equation when
gcd(window, n) = 1. - Non-standard Fisher–Yates (
j ∈ [i+1,n-1]) still gave a single full-length cycle here; always measure cycle type before choosing a solver. - For
gcd(W,L) > 1the system is singular (e.g. n=66, gcd=3, rank L−2) — need known plaintext (PNG header) or a fuller GE per coset.
Tools Used
file,strings,checksec,objdump,readelf- Python 3 +
ctypes(glibcsrand/rand) - ImageMagick
identify/ vision read of decrypted PNG - Flagyard MCP (
download_challenge_files,submit_flag)
Files
- Workdir:
/root/ctf/flagyard/locker/ - Decrypted image:
/root/ctf/flagyard/locker/flag.png