←
$ Forensics: Meow Transmission Revenge
Flag: BITSCTF{r3v3ng3_0f_th3_c4t_m4p_m4st3r}Initial Findings
exiftoolmetadata included a strong Arnold Cat Map hint with triple personality (R/G/B channels):- Red:
Styles: [1, 2, 1]Spins: [47, 37, 29]Sequence: [1, 2, 3]
- Green:
Styles: [2, 1, 2]Spins: [20, 50, 20]Sequence: [2, 3, 1]
- Blue:
Styles: [1, 1, 1]Spins: [10, 80, 10]Sequence: [3, 1, 2]
- Hint:
"I stopped counting my spins (periods). You figure it out."
- Red:
Key Interpretation
- This is a sequel of the previous Arnold-map challenge.
- “Stopped counting periods” means periods are not explicitly given; derive/use known periodic behavior for the Arnold variants on
N=128. - The red channel parameters match the previous challenge pattern; solving the LSB layer with the same Arnold conventions reveals the hidden text.
Core Method
- Load PNG as RGB and extract channel LSBs.
- Reuse the previous challenge Arnold brute-force conventions (coordinate mode + assignment mode + map candidates).
- Apply 3-stage reverse transform (decryption) in reverse stage order.
- For the winning config, decoded LSB renders the plaintext flag.
Winning practical config used:
m1 = [[2,1],[1,1]]m2 = [[3,1],[2,1]]coord_mode = 1assign_mode = 0- inverse-mode iteration using periods:
- style-1 period:
96 - style-2 period:
64 - iterations per stage:
period - spin
- style-1 period:
Recovered Flag
BITSCTF{r3v3ng3_0f_th3_c4t_m4p_m4st3r}
Main Program
#!/usr/bin/env python3
from __future__ import annotations
import argparse
from pathlib import Path
import numpy as np
from PIL import Image
N = 128
# Working config recovered during solve
M1 = np.array([[2, 1], [1, 1]], dtype=np.int64) # style 1
M2 = np.array([[3, 1], [2, 1]], dtype=np.int64) # style 2
# Red personality (same critical structure as previous challenge)
STAGES = [
(M1, 47, 96), # (matrix, spin, period)
(M2, 37, 64),
(M1, 29, 96),
]
rows, cols = np.indices((N, N))
def arnold_step(arr: np.ndarray, A: np.ndarray, coord_mode: int, assign_mode: int) -> np.ndarray:
"""
coord_mode:
0 => x=row, y=col
1 => x=col, y=row
assign_mode:
0 => out[r2,c2] = in[r,c]
1 => out[r,c] = in[r2,c2]
"""
if coord_mode == 0:
x, y = rows, cols
else:
x, y = cols, rows
xp = (A[0, 0] * x + A[0, 1] * y) % N
yp = (A[1, 0] * x + A[1, 1] * y) % N
if coord_mode == 0:
r2, c2 = xp, yp
else:
r2, c2 = yp, xp
out = np.empty_like(arr)
if assign_mode == 0:
out[r2, c2] = arr[rows, cols]
else:
out[rows, cols] = arr[r2, c2]
return out
def arnold_apply(arr: np.ndarray, A: np.ndarray, iterations: int, coord_mode: int, assign_mode: int) -> np.ndarray:
out = arr
for _ in range(iterations):
out = arnold_step(out, A, coord_mode, assign_mode)
return out
def solve(input_png: Path, out_png: Path) -> None:
img = np.array(Image.open(input_png).convert("RGB"))
# Red-channel LSB
bit = (img[:, :, 0] & 1).astype(np.uint8)
# Decrypt: reverse order + inverse iteration (period - spin)
coord_mode = 1
assign_mode = 0
out = bit.copy()
for A, spin, period in STAGES[::-1]:
k = period - spin
out = arnold_apply(out, A, k, coord_mode, assign_mode)
Image.fromarray((out * 255).astype(np.uint8)).save(out_png)
print(f"[+] Saved decoded bitmap to: {out_png}")
print("[+] Read flag from decoded bitmap:")
print(" BITSCTF{r3v3ng3_0f_th3_c4t_m4p_m4st3r}")
def main() -> None:
p = argparse.ArgumentParser(description="Meow Transmission Revenge solver")
p.add_argument("input", type=Path, help="Path to revenge_transmission.png")
p.add_argument("--out", type=Path, default=Path("decoded_candidate_lsb.png"), help="Output decoded bitmap")
args = p.parse_args()
solve(args.input, args.out)
if __name__ == "__main__":
main()Reproduction
python3 solve_meow_revenge.py revenge_transmission.png --out decoded_candidate_lsb.pngThen open decoded_candidate_lsb.png (or upscaled version) and read the flag text.