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.pyretrieve_kernel.pyimages/flower.jpgimages/flower_processed.jpgimages/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}}}"pythonSo 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 = Yvia 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.jpgbashRecovered kernels:
Red
[[ 1 -1 0]
[-1 5 -1]
[ 2 -1 0]]textGreen
[[ 1 2 1]
[-1 8 -1]
[-3 -1 1]]textBlue
[[-1 -4 1]
[ 1 4 4]
[-1 3 1]]textStep 2: Infer what “matrix scalar” likely means#
From process_scalars.py argument names:
<matrix-scalar-1> <matrix-scalar-2> <matrix-scalar-3>pythonIn 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)))pythonResults:
det(kr) = 1det(kg) = 40det(kb) = 35
So:
d1 = 1d2 = 40d3 = 35
Final Flag#
apoorvctf{1_40_35}textWhy 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:
- Install dependency if needed:
bashpython3 -m pip install --user pillow - Recover kernels:
bashpython3 retrieve_kernel.py flower.jpg flower_processed.jpg - Compute determinants (any calculator / Python / NumPy).
- Build flag as
apoorvctf{detR_detG_detB}.
Files Referenced#
process_scalars.pyretrieve_kernel.pyimages/flower.jpgimages/flower_processed.npy
Kernel Recovery
https://nahil.xyz/vault/writeups/apoorvctf2026/ai/kernel-recovery/
Author Nahil Rasheed
Published at March 24, 2026
Copyright
CC BY-NC-SA 4.0
Disclaimer This content is provided strictly for educational purposes only.