Back to writeups
FlagYardrevMediumsolved

locker

12 Jul 2026 · by 0xIDA

Solvers

locker

Author: 0xIDA
Platform: FlagYard · Category: rev · Difficulty: Medium

Solvers

FieldValue
PlatformFlagyard (SAFCSP)
Lab6 — Competitive Reversing
Challengelocker
Challenge IDfab9705b-7510-4406-98c3-572ae7654bcd
CategoryReversing
DifficultyMedium
Points250
Author0xIDA
FlagFlagY{ins3rt_go0d_flag_text_h3re}
SubmitLive success (isSuccess: true)
Date2026-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):

  1. srand(0x1337) — fixed seed (not time-based).
  2. Build index array perm[i] = i for i in 0..n-1.
  3. Biased Fisher–Yates: for i = 0 .. n-2, swap perm[i] with perm[j] where
    j = (rand() % (n - i - 1)) + (i + 1)
    (j always in [i+1, n-1], never identity at step i).
  4. 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

  1. Fixed srand + rand-driven permutation is fully deterministic offline — always reimplement with glibc rand (not Python random).
  2. “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.
  3. 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.
  4. For gcd(W,L) > 1 the 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 (glibc srand/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