Tags: steganography python misc
Rating:
```
The CIA has been tracking a group of hackers who communicate using PNG files embedded with a custom steganography algorithm.
An insider spy was able to obtain the encoder, but it is not the original code.
You have been tasked with reversing the encoder file and creating a decoder as soon as possible in order to read the most recent PNG file they have sent.
solves: 110
```
[encoder.c (From googleCTF github)](https://github.com/google/google-ctf/blob/master/2023/misc-symatrix/challenge/encoder.c)
The challenge comes with a large encoder.c file, this seems intimidating initially. However, reading through the code a little bit, we quickly found out that there are part of the original python file written in the comment. Using a simple parser I extracted the python source from encoder.c file. (My parser actually missed a else: line, and I manuelly added that afterward)
```
f = open("encoder.c")
source = ["" for i in range(70)]
for line in f:
if "encoder.py" in line:
# print(line)
try:
line_number = int(line.split(':')[-1].split(" ")[0])
except Exception:
continue
# print(line_number)
for af in range(5):
temp = f.readline()
if "<<<<<" in temp:
source[line_number] = temp[3:-1].split("#")[0]
with open("encoder.py", "w") as f:
f.write("\n".join(source))
```
```
from PIL import Image
from random import randint
import binascii
def hexstr_to_binstr(hexstr):
n = int(hexstr, 16)
bstr = ''
while n > 0:
bstr = str(n % 2) + bstr
n = n >> 1
if len(bstr) % 8 != 0:
bstr = '0' + bstr
return bstr
def pixel_bit(b):
return tuple((0, 1, b))
def embed(t1, t2):
return tuple((t1[0] + t2[0], t1[1] + t2[1], t1[2] + t2[2]))
def full_pixel(pixel):
return pixel[1] == 255 or pixel[2] == 255
print("Embedding file...")
bin_data = open("./flag.txt", 'rb').read()
data_to_hide = binascii.hexlify(bin_data).decode('utf-8')
base_image = Image.open("./original.png")
x_len, y_len = base_image.size
nx_len = x_len * 2
new_image = Image.new("RGB", (nx_len, y_len))
base_matrix = base_image.load()
new_matrix = new_image.load()
binary_string = hexstr_to_binstr(data_to_hide)
remaining_bits = len(binary_string)
nx_len = nx_len - 1
next_position = 0
for i in range(0, y_len):
for j in range(0, x_len):
pixel = new_matrix[j, i] = base_matrix[j, i]
if remaining_bits > 0 and next_position <= 0 and not full_pixel(pixel):
new_matrix[nx_len - j, i] = embed(pixel_bit(int(binary_string[0])),pixel)
next_position = randint(1, 17)
binary_string = binary_string[1:]
remaining_bits -= 1
else:
new_matrix[nx_len - j, i] = pixel
next_position -= 1
new_image.save("./symatrix.png")
new_image.close()
base_image.close()
print("Work done!")
exit(1)
```
Base on the encoder.py file, it’s clear that the original image is mirrored, and the flag bit and embedded to the right half of the image. The pixel that are modifyed are always increamented by either (0, 1, 0) or (0, 1, 1), with the formar encoding 0 and the latter encoding 1. To decode the flag, we simply go through all the bytes, and extract those that are different left side v.s. right side. We can ignore the random offset since only the pixels that have data encoded are changed.
```
from PIL import Image
import binascii
from Crypto.Util.number import long_to_bytes
encoded_image = Image.open("./symatrix.png")
x_len, y_len = encoded_image.size
encoded_matrix = encoded_image.load()
center = x_len//2
flag_bits = ""
for i in range(0, y_len):
for j in range(0, x_len//2):
if encoded_matrix[j, i] == encoded_matrix[x_len-1-j, i]:
continue
else:
flag_bits += str(encoded_matrix[x_len-1-j, i][2] - encoded_matrix[j, i][2])
# print(flag_bits)
flag = long_to_bytes(int(flag_bits, 2))
print(flag)
```
`CTF{W4ke_Up_Ne0+Th1s_I5_Th3_Fl4g!}`
[link to blog](https://bronson113.github.io/2023/06/26/googlectf-2023-writeup.html#symatrix)