What have you done to my flag?
We're given an encoded image of the flag
and an encoder script that looks like this:
import numpy as np
import cv2
import random
from datetime import datetime
img = cv2.imread('flag.png')
size_x, size_y = img.shape[:2]
enc_negpos = np.zeros_like(img)
random.seed(datetime.now().timestamp())
for i in range(size_x):
for j in range(size_y):
for rgb in range(3):
negpos = random.random()
if negpos < 0.5:
enc_negpos[i, j, rgb] = img[i, j, rgb]
else:
enc_negpos[i, j, rgb] = img[i, j, rgb] ^ 255
enc_shuffle = enc_negpos.copy()
for i in range(size_x):
for j in range(size_y):
shuffle = random.randint(1, 6)
if shuffle == 1:
enc_shuffle[i, j] = enc_negpos[i, j]
elif shuffle == 2:
enc_shuffle[i, j] = enc_negpos[i, j][[0, 2, 1]]
elif shuffle == 3:
enc_shuffle[i, j] = enc_negpos[i, j][[1, 0, 2]]
elif shuffle == 4:
enc_shuffle[i, j] = enc_negpos[i, j][[1, 2, 0]]
elif shuffle == 5:
enc_shuffle[i, j] = enc_negpos[i, j][[2, 0, 1]]
else:
enc_shuffle[i, j] = enc_negpos[i, j][[2, 1, 0]]
cv2.imwrite('enc.png', enc_shuffle)
At first glance, it looks like the image encoding is done in two steps:
- Randomly XOR random channels of each pixel in the image with 255.
- Randomly swap channels for each pixel.
With no further information, there doesn't seem to be a mathematical way to perfectly reverse this encoding. However, we can conjecture that most pixels in the original image are white (e.g. (255, 255, 255)
). Running these pixels through the series of XORs and swaps should create permutations of channels that are either 0
or 255
, and indeed we see the majority of border pixels follow this pattern:
Then, we should filter out all of these background pixels and examine only the gray band in the middle. Here's a Python script that does just that:
import cv2
import numpy as np
img = cv2.imread('./enc.png')
res = np.zeros_like(img)
for i in range(len(img)):
for j in range(len(img[i])):
if all(30 < x < 225 for x in img[i, j]):
res[i, j] = img[i, j] - 128
else:
res[i, j] = [255, 255, 255]
cv2.namedWindow('res', cv2.WINDOW_NORMAL)
cv2.imshow('res', res)
cv2.waitKey()
cv2.imshow('res', cv2.cvtColor(res, cv2.COLOR_BGR2GRAY))
cv2.waitKey()
CSCTF{why_SKK_image_encryption_sooo.weak?}
(or a simpler solution using inRange
:
import cv2
img = cv2.imread('./enc.png')
mask = cv2.inRange(img, (20, 20, 20), (235, 235, 235))
cv2.namedWindow('res', cv2.WINDOW_NORMAL)
cv2.imshow('res', mask)
cv2.waitKey()