# N0PSctf - Selfie Memory Writeup **Title:** Selfie Memory **Description:** At the end of this lovely moment, n00psy gives you a picture of him. "I will never forget this moment, thank you for coming with me!", he says. "Also, sorry if the quality of this picture is a bit poor on the edges, this is an old picture that my friend Joseph took in 1807." **Author:** volca_ky and Jusdepommes **File:** - [noopsy_selfie.png](https://github.com/xtasy94/CTFW/blob/main/CTF_Files/N0PSctf/noopsy_selfie.png) **Hint:** You have something, but can it be transformed ? ## Solution: ### 1. Challenge Overview At first glance, `noopsy_selfie.png` looks like a normal, slightly degraded grayscale portrait. Typical steganography tricks like LSBs, EXIF data, or appended payloads—reveal nothing. Instead, the flag is hidden in the image’s frequency domain. By computing a 2D FFT, centering the zero-frequency, and viewing the log-magnitude spectrum, faint letters spelling the flag become visible. This challenge tests frequency-domain steganography rather than pixel-level techniques. --- ### 2. Exploitation This is how we go from “I have `noopsy_selfie.png`” to “I have the flag.” #### 2.1 Load the image in grayscale We begin by reading `noopsy_selfie.png` as an 8-bit grayscale array. Converting to grayscale isolates luminance, which is sufficient for Fourier analysis: ```python import cv2 img = cv2.imread("noopsy_selfie.png", cv2.IMREAD_GRAYSCALE) if img is None: print("Failed to load image.") exit(1) print("Loaded image shape:", img.shape) ``` #### 2.2 Compute the 2D FFT and center the zero‐frequency term A 2D FFT transforms spatial pixel data into frequency space. By default, the DC (lowest‐frequency) component sits in the top‐left corner; we shift it to the center so that any hidden pattern around the origin becomes easier to spot: ```python import numpy as np # 2D FFT f = np.fft.fft2(img) # Shift zero-frequency (DC) to center fshift = np.fft.fftshift(f) ``` At this point, `fshift` is a complex‐valued array and its magnitude tells us how much each frequency contributes. #### 2.3 Compute a log‐scaled magnitude spectrum Directly viewing `|fshift|` will cause really large values (near‐DC) to dominate. Taking a logarithm compresses the dynamic range, here: ```python magnitude = np.abs(fshift) magnitude_spectrum = np.log(magnitude + 1) ``` To save as an 8-bit image, we normalize it to [0,255]: ```python import cv2 # Normalize to 0–255 and convert to uint8 mag = cv2.normalize(magnitude_spectrum, None, 0, 255, cv2.NORM_MINMAX) mag = np.uint8(mag) cv2.imwrite("spectrum.png", mag) ``` If we open `spectrum.png`, we see a swirl of gray pixels, but faint letters (the flag) appear near the bright center — specifically above it, with the readable text `N0PS{i_love_fourier}` positioned just above the central bright spot. A mirrored version of the same text also appears below the center: ![spectru](https://hackmd.io/_uploads/r1G1Yihfeg.jpg) ``` Flag: N0PS{i_love_fourier} ``` --- ## PoC Below is a complete Python script (`solve_noopsy_fft.py`) that automates every step leaving you with two images (`spectrum.png` and `spectrum_thresh.png`) to inspect manually: ```python #!/usr/bin/env python3 import cv2 import numpy as np import argparse import os import sys def main(): parser = argparse.ArgumentParser( description="Generate FFT magnitude images for noopsy_selfie.png" ) parser.add_argument("image", help="Path to noopsy_selfie.png") args = parser.parse_args() # 1) Verify the file exists if not os.path.isfile(args.image): print(f"File not found: {args.image}") sys.exit(1) # 2) Load as grayscale img = cv2.imread(args.image, cv2.IMREAD_GRAYSCALE) if img is None: print("Failed to load image.") sys.exit(1) # 3) Compute 2D FFT and shift DC component to center f = np.fft.fft2(img) fshift = np.fft.fftshift(f) # 4) Compute log-scaled magnitude spectrum magnitude = np.abs(fshift) magnitude_spectrum = np.log(magnitude + 1) # 5) Normalize to 0–255 (uint8) for saving mag = cv2.normalize(magnitude_spectrum, None, 0, 255, cv2.NORM_MINMAX) mag = np.uint8(mag) cv2.imwrite("spectrum.png", mag) print("[+] Saved spectrum.png (log-scaled FFT magnitude).") # 6) Apply Otsu's threshold to accentuate letters _, mag_thresh = cv2.threshold(mag, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) cv2.imwrite("spectrum_thresh.png", mag_thresh) print("[+] Saved spectrum_thresh.png (thresholded).") if __name__ == "__main__": main() ``` ```bash └╼$ python3 solve.py noopsy_selfie.png [+] Saved spectrum.png (log-scaled FFT magnitude). [+] Saved spectrum_thresh.png (thresholded). ``` After running, we’ll have: - `spectrum.png` - `spectrum_thresh.png` Open `spectrum.png` file and we see the flag above the bright center: ``` N0PS{i_love_fourier} ``` That is our final flag, I've also uploaded the [spectrum.png](https://github.com/xtasy94/CTFW/blob/main/CTF_Files/N0PSctf/spectrum.png) file on my GitHub.