Link to archived challenge

Category Difficulty Solves Author
forensics medium 6 sudoBash418

Description

We’ve managed to capture some data from an adversary - but they seem to have encoded the data in an image?

Players are given an archive containing two files: generate.py and result.png.

Analysis

The player is meant to deduce that the generate.py script was used to encode some input into the given result.png. In this case, that input was the flag.

I’ve added a few comments to generate.py here for clarity:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/usr/bin/env python3

from PIL import Image  # requires Pillow: `pip install pillow`

def encode(data: bytes) -> Image.Image:
	"""Encode a bytestring into a new Pillow image."""
	img = Image.new("RGB", (len(data), 1))
	buf = bytearray(len(data) * 3)  # contains the raw RGB values
	for i in range(len(data)):
		# split the byte into 3 parts using separate bitmasks
		# each part is assigned to a different channel of the pixel
		buf[i*3+1] = data[i] & 0x70  # 0b01110000
		buf[i*3]   = data[i] & 0x8a  # 0b10001010
		buf[i*3+2] = data[i] & 0x05  # 0b00000101
	img.frombytes(bytes(buf))  # load raw RGB values into Image
	return img

if __name__ == "__main__":
	import argparse

	# `generate.py "clubeh{...}" result.png`
	parser = argparse.ArgumentParser()
	parser.add_argument("flag")
	parser.add_argument("filename")
	args = parser.parse_args()

	encode(args.flag.encode()).save(args.filename)

Each output pixel maps to a single byte of the flag, split across the three RGB channels.
The bitmasks were chosen to ensure that the original bytes can be recovered without any loss of information.

Solution

Here’s my solve script:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/env python3

from PIL import Image

def decode(img: Image.Image) -> bytes:
	buf = bytearray()
	for pixel in img.getdata():
		# works because 0x70 | 0x8a | 0x05 == 0xff
		buf.append(pixel[0] | pixel[1] | pixel[2])
	return bytes(buf)

if __name__ == "__main__":
	import argparse

	parser = argparse.ArgumentParser()
	parser.add_argument("filepath", help="Path to the image containing encoded data.")
	args = parser.parse_args()

	with Image.open(args.filepath) as img:
		flag = decode(img)

	print(f"Recovered flag: {flag!r}")

Run solve.py result.png to obtain the flag.