4

I am trying to solve the following puzzle from CryptoHack involving two images encrypted with the same key:

I've hidden two cool images by XOR with the same secret key so you can't see them!

If I compare both images using an online image comparison tool I can see traces of a new image containing what seems to be the solution to the puzzle. However, I don't think this is the intended way to solve this puzzle.

So I tried to write a program to XOR the two encrypted images together, because the XORed keys should cancel out that way. I XORed both files byte by byte and saved the result as a PNG file. But this new PNG won't even open - my image viewer says it's a faulty file.

This is my Python code:

from binascii import unhexlify

with open("lemur.png", mode='rb') as fl: lemur = fl.read()

with open("flag.png", mode='rb') as ff: flag = ff.read()

d = b'' for b1, b2 in zip(lemur, flag): d += bytes([b1^b2])

with open("new.png", mode='wb') as fn: fn.write(d)

One thing I noticed is that the lemur.png file is one byte longer than the other one. The above program stops XOR at the length of the smaller file. I also tried appending the last byte of the bigger file to the end. But even that doesn't help.

Is my approach incorrect, and if not, what is the right way to compare the two images?

Ilmari Karonen
  • 46,700
  • 5
  • 112
  • 189
user93353
  • 2,348
  • 3
  • 28
  • 49

2 Answers2

10

PNG is a compressed image format. This means that, unlike with uncompressed formats like BMP or netpbm, the bytes stored on disk in the PNG file do not correspond directly to the bytes of actual image data shown on the screen when you view the image.

In addition to the compressed image data, PNG files also contain a header with distinctive "signature" byte sequence, allowing programs to recognize the file as a PNG image regardless of its name, and several metadata chunks storing additional information about the image such as its width and height, and possibly e.g. a color profile. Each chunk, including the actual image data chunk, also includes a CRC-32 checksum to verify its correctness.

What all this means is that if you XOR a PNG file with something (anything!) else byte by byte, or modify its bytes directly in any other way, the result will almost certainly not be a valid PNG file. If the modification was very minor, some image viewers might still recognize the file as PNG and attempt to display it even though the checksums don't match. But if you do something like XOR the entire file with another PNG file, the result will not even be recognizable as PNG.

So, given that:

  • the files you've been given are clearly valid PNG image files,
  • the hint you've been given implies that they were valid images also before encryption, and
  • the hint says that they've been "XORed with the same key",

what could this possibly mean?

Well, I see only one likely possibility: the encryption must've been done by XORing the raw RGB image data with a (pseudo)random key, and only then saving the resulting image in PNG format.

So, basically, what you'll need to do is to use an image loading library to load and decode the PNG images into a 2D array of pixel values first, and then XOR those pixel values together. And then you'll either need to save the XORed pixel data as PNG (or in some other image format) again for viewing or just make your program display it directly on the screen.

I'll leave actually implementing this in Python as an exercise. If you have any questions about that part, Stack Overflow is the right place for those.

Ilmari Karonen
  • 46,700
  • 5
  • 112
  • 189
3

As mentioned by @r3mainer, I get this result from the code below using PIL library.enter image description here

from PIL import Image, ImageChops
im1 = Image.open('lemur.png')
im2 = Image.open('flag.png')

im3 = ImageChops.add(ImageChops.subtract(im2, im1), ImageChops.subtract(im1, im2)) im3.show() #im3.save("Your_path/im3.png")

I alter the code found on this link to be more explainable.

My code calculate abs(a-b) while the description of a XOR b using arithmetic in this link is (a-b)^2. They produce the same value when a and b is either 0 or 1.

Normally, a picture has RGB color channel which each of them is treated as 8-bit integer. This code, ofcourse, doesn't give the result as R3 = R1 XOR R2 (if R1 from R channel of picture 1 and so on). But I code this way because I couldn't find any decent arithmetic expression that can really substitue the XOR operation and that I check with this link and it can produce the same result so I think it should works.

Advin Saz
  • 91
  • 4