Rating:

# File contents
 

The file "steg.zip" contained two interesting files:

* **crypt.pyc** - Some Python code that is used to obfuscate an image file, in bytecode format.
* **res.png** - An obfuscated image file.

-----

res.png looks like this:

[![https://imma.gr/68015x071f5.jpg](https://imma.gr/68015x071f5.jpg)](https://imma.gr/l/68015x071f5)

and clearly has some mirroring going on along both axes.

-----

Looking closely at the image and comparing dimensions, we found the original image to be this one:

[![https://imma.gr/68016x1ed39.jpg](https://imma.gr/68016x1ed39.jpg)](https://imma.gr/p/68016x1ed39)

And although it was not necessary to solve the task, it helped to verify our reversing efforts in the next step.

-----

# Reversing crypt.pyc
 

We ran the pyc through various decompilers, but had best results with Decompyle++. The bytecode was from Python 3.6 and contained fairly new control flow opcodes, which were poorly supported in the other decompilers. After cleaning up the code slightly, and replacing some repeated list comprehensions with a function, we ended up with this:

```
from PIL import Image

def xor(A,B):
return tuple([a ^ b for (a, b) in zip(A, B)])

def obfuscate(inputfilename, outputfilename):
img = Image.open(inputfilename)
img_rgb = img.convert('RGB')
(w, h) = img.size # 736, 618
prev_pixel = (0, 0, 0)
for x in range(w):
for y in range(h):
if (x + y + 1) % 203 % 130 % 53 % 37 % 23 % 17 % 6 == 0:
img_rgb.putpixel((x, y), tuple(img_rgb.getpixel((w - x - 1, h - y - 1))))
img_rgb.putpixel((w - x - 1, h - y - 1), xor(prev_pixel, img_rgb.getpixel((x,y))))
elif (x + y + 1) % 203 % 130 % 53 % 37 % 23 % 17 % 6 == 1:
img_rgb.putpixel((x, y), tuple(img_rgb.getpixel((x, h - y - 1))))
img_rgb.putpixel((x, h - y - 1), xor(prev_pixel, img_rgb.getpixel((x, y))))
elif (x + y + 1) % 203 % 130 % 53 % 37 % 23 % 17 % 6 == 2:
img_rgb.putpixel((x, y), tuple(img_rgb.getpixel((w - x - 1, y))))
img_rgb.putpixel((w - x - 1, y), xor(prev_pixel, img_rgb.getpixel((x, y))))
else:
img_rgb.putpixel((x, y), xor(prev_pixel, img_rgb.getpixel((x, y))))

prev_pixel = xor(prev_pixel, img_rgb.getpixel((x, y)))
img_rgb.save(outputfilename)
```
 

In short, this loops through every pixel with `prev_pixel` initially set to (0, 0, 0). Then, depending on if some long modulo chain is 0, 1, or 2 it either swaps the pixel with the one mirrored along the diagonal, the y-axis or x-axis. If the result is 4 or 5, it just XORs the current pixel with `prev_pixel`. When swapping pixels, it also XORs the mirrored pixel with `prev_pixel`. Ultimately, `prev_pixel` is set to be the the current pixel XOR `prev_pixel`.

We were not able to fully reverse this function, but that was not strictly needed either. Simply looping backwards and changing the order of execution inside each if condition produced a readable image.
 

```
def deobfuscate(inputfilename, outputfilename):
img = Image.open(inputfilename)
img_rgb = img.convert('RGB')
(w, h) = img.size # 736, 618
prev_pixel = (98, 21, 65) # Values chosen so that the final prev_pixel = (0,0,0)
for x in range(w-1,-1,-1):
for y in range(h-1,-1,-1):
prev_pixel = xor(prev_pixel, img_rgb.getpixel((x, y)))

if (x + y + 1) % 203 % 130 % 53 % 37 % 23 % 17 % 6 == 0:
img_rgb.putpixel((w - x - 1, h - y - 1), img_rgb.getpixel((x,y)))
img_rgb.putpixel((x, y), xor(prev_pixel, img_rgb.getpixel((w - x - 1, h - y - 1))))
elif (x + y + 1) % 203 % 130 % 53 % 37 % 23 % 17 % 6 == 1:
img_rgb.putpixel((x, h - y - 1), img_rgb.getpixel((x, y)))
img_rgb.putpixel((x, y), xor(prev_pixel, img_rgb.getpixel((x, h - y - 1))))
elif (x + y + 1) % 203 % 130 % 53 % 37 % 23 % 17 % 6 == 2:
img_rgb.putpixel((w - x - 1, y), img_rgb.getpixel((x, y)))
img_rgb.putpixel((x, y), xor(prev_pixel, img_rgb.getpixel((w - x - 1, y))))
else:
img_rgb.putpixel((x, y), xor(prev_pixel, img_rgb.getpixel((x, y))))


img_rgb.save(outputfilename)
```

-----

# Result
 

[![https://imma.gr/68017xe1ebd.jpg](https://imma.gr/68017xe1ebd.jpg)](https://imma.gr/l/68017xe1ebd)

-----

Which is readable enough to see the flag

h4ck1t{PyC_steg0_rev3rseR}