0xnhl

Kernel Recovery

/ Update
2 min read

Kernel Recovery Challenge Writeup (ApoorvCTF)#

Challenge Overview#

  • Category (inferred): Reverse / Forensics (image processing + linear algebra)
  • Given files:
    • process_scalars.py
    • retrieve_kernel.py
    • images/flower.jpg
    • images/flower_processed.jpg
    • images/flower_processed.npy
  • Flag format: apoorvctf{...}

The challenge provides a script that builds the flag from three integer scalars:

flag = f"apoorvctf{{{d1}_{d2}_{d3}}}"
python

So the main task is to recover these three integers.

Initial Triage#

1) Inspect helper script for flag structure#

Looking at process_scalars.py immediately reveals:

  • It expects exactly 3 integer inputs.
  • It outputs apoorvctf{d1_d2_d3}.

This means we need to extract three integers from the other artifacts.

2) Inspect kernel recovery script#

retrieve_kernel.py does the following:

  • Loads original image (flower.jpg) as RGB.
  • Loads processed RGB array from flower_processed.npy.
  • For each channel (R, G, B), reconstructs a 3x3 convolution kernel by solving:
    • X * k = Y via least squares (np.linalg.lstsq), where:
      • X = flattened 3x3 input patches,
      • Y = corresponding processed pixel value.
  • Prints recovered kernels for red/green/blue channels.

So there are exactly three matrices, and the remaining step is to derive one scalar from each.

Solving Steps#

Step 1: Run kernel recovery#

Command used:

python3 retrieve_kernel.py flower.jpg flower_processed.jpg
bash

Recovered kernels:

Red

[[ 1 -1  0]
 [-1  5 -1]
 [ 2 -1  0]]
text

Green

[[ 1  2  1]
 [-1  8 -1]
 [-3 -1  1]]
text

Blue

[[-1 -4  1]
 [ 1  4  4]
 [-1  3  1]]
text

Step 2: Infer what “matrix scalar” likely means#

From process_scalars.py argument names:

<matrix-scalar-1> <matrix-scalar-2> <matrix-scalar-3>
python

In linear algebra, the most standard singular “matrix scalar” is the determinant (single scalar derived from a square matrix).

Because each recovered object is a 3x3 matrix, determinant is a natural fit.

Step 3: Compute determinants#

Python snippet used:

import numpy as np

kr=np.array([[1,-1,0],[-1,5,-1],[2,-1,0]])
kg=np.array([[1,2,1],[-1,8,-1],[-3,-1,1]])
kb=np.array([[-1,-4,1],[1,4,4],[-1,3,1]])

print(round(np.linalg.det(kr)))
print(round(np.linalg.det(kg)))
print(round(np.linalg.det(kb)))
python

Results:

  • det(kr) = 1
  • det(kg) = 40
  • det(kb) = 35

So:

  • d1 = 1
  • d2 = 40
  • d3 = 35

Final Flag#

apoorvctf{1_40_35}
text

Why This Works#

  • The provided script explicitly builds flag from 3 integers.
  • The second script reliably reconstructs 3 channel-wise 3x3 kernels from original vs processed data.
  • Determinant is the canonical scalar for square matrices and cleanly produces integers.
  • Plugging those values into the known flag template yields a valid-format flag.

Reproducibility Checklist#

From a clean copy of the challenge:

  1. Install dependency if needed:
    python3 -m pip install --user pillow
    bash
  2. Recover kernels:
    python3 retrieve_kernel.py flower.jpg flower_processed.jpg
    bash
  3. Compute determinants (any calculator / Python / NumPy).
  4. Build flag as apoorvctf{detR_detG_detB}.

Files Referenced#

  • process_scalars.py
  • retrieve_kernel.py
  • images/flower.jpg
  • images/flower_processed.npy
Kernel Recovery
https://nahil.xyz/vault/writeups/apoorvctf2026/ai/kernel-recovery/
Author Nahil Rasheed
Published at March 24, 2026
Disclaimer This content is provided strictly for educational purposes only.