# 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:

```
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.