Beneath the Armor
Beneath the Armor (Forensics) - Detailed Writeup#
Given file: iron-challenge.png
Challenge prompt:
“History repeats itself, even for ironman, life goes on cycles”
Expected flag format: apoorvctf{...}
Challenge intuition#
The prompt hints at something cyclic/repetitive:
- “History repeats itself”
- “life goes on cycles”
In steganography tasks, this often suggests periodic extraction patterns such as:
- bit-plane extraction,
- channel-wise cycles (R -> G -> B -> R …),
- repeating masks or modular indexing.
So instead of only metadata carving, we should test pixel-bit extraction paths.
1) Fast triage#
First, verify if this is a plain image or has obvious appended data.
file "iron-challenge.png"
exiftool "iron-challenge.png"
binwalk "iron-challenge.png"bashObserved:
- Valid PNG,
1920 x 1076, RGB, non-interlaced. - No suspicious textual metadata fields carrying a flag.
- No additional embedded file signatures after image end.
Conclusion: likely in-pixel stego, not metadata/appended payload.
2) PNG structure sanity check#
I parsed chunk layout to see whether uncommon chunks (tEXt, zTXt, iTXt, custom chunks) exist.
Result:
IHDRx1IDATx50IENDx1
No extra ancillary chunks with hidden text.
That reinforces the bit-level pixel hypothesis.
3) Guided hypothesis from the hint#
The phrase “goes on cycles” naturally maps to cycling through channels and/or bit positions.
A strong candidate is:
- walk pixels in row-major order,
- for each pixel, take bits in a repeating channel pattern,
- pack extracted bits into bytes,
- search for
apoorvctf{in the resulting byte stream.
Because RGB itself is a 3-step cycle, one useful pattern is using different bit-planes per channel in a periodic way.
4) Successful extraction pattern#
The pattern that yields the flag:
- traversal: row-major (top-left to bottom-right),
- per pixel bit picks:
R-> bit0(LSB),G-> bit1,B-> bit2,
- pack bits with big-endian bit order into bytes.
This is effectively a modular cycle on channels/bit positions:
- channel cycle:
R, G, B - bit cycle:
0, 1, 2
which matches the challenge hint about cycles.
5) Reproducible solver script#
I saved the solver as solve_iron.py.
#!/usr/bin/env python3
from PIL import Image
import numpy as np
def extract_rgb_lsb_012_big(path: str) -> bytes:
"""
Extract a byte stream by reading RGB pixels in row-major order,
taking R bit0, G bit1, B bit2 for each pixel, then packing bits big-endian.
"""
img = Image.open(path).convert("RGB")
arr = np.array(img)
flat = arr.reshape(-1, 3)
bits = np.empty(flat.shape[0] * 3, dtype=np.uint8)
bits[0::3] = (flat[:, 0] >> 0) & 1
bits[1::3] = (flat[:, 1] >> 1) & 1
bits[2::3] = (flat[:, 2] >> 2) & 1
return np.packbits(bits, bitorder="big").tobytes()
def find_flag(data: bytes) -> str | None:
needle = b"apoorvctf{"
start = data.find(needle)
if start == -1:
return None
end = data.find(b"}", start)
if end == -1:
return None
frag = data[start : end + 1]
try:
return frag.decode("ascii", errors="strict")
except UnicodeDecodeError:
return frag.decode("ascii", errors="ignore")
def main() -> None:
path = "iron-challenge.png"
stream = extract_rgb_lsb_012_big(path)
flag = find_flag(stream)
if not flag:
raise SystemExit("Flag not found")
print(flag)
if __name__ == "__main__":
main()pythonRun:
python3 solve_iron.pybashOutput:
apoorvctf{m0dul4r_4r17hm371c_15_fun_y34h}text6) Why this works#
- The hidden payload is not in metadata/chunks; it is encoded in pixel bits.
- The challenge hint explicitly points to cyclic logic.
- The successful decoder uses a cyclic modular mapping over channels/bit positions.
- Reconstructing bytes with the correct bit order reveals printable ASCII flag text.
7) Final flag#
apoorvctf{m0dul4r_4r17hm371c_15_fun_y34h}
Notes for writeup reuse#
- Keep solver deterministic; avoid manual extraction.
- If similar tasks fail initially, brute-force combinations over:
- traversal order (row/column),
- channel order (RGB permutations),
- bit indices (0..7 per channel),
- pack bit order (big/little).
- Search extracted byte streams for known flag prefix to quickly rank candidates.