corrupt (misc)

i thought this challenge was about some people. jk

We were given a PNG file that only has 1x1 pixels. But the file has 837 bytes, so there might be more stuff actually.

❯ wc corrupt.png
       5      24     837 corrupt.png

Some useful tools:

apt install -y pngcheck
apt install -y pngtools

First, I see what is in the PNG file:

$ pnginfo corrupt.png
corrupt.png...
  Image Width: 1 Image Length: 1
  Bitdepth (Bits/Sample): 8
  Channels (Samples/Pixel): 1
  Pixel depth (Pixel Depth): 8
  Colour Type (Photometric Interpretation): GRAYSCALE
  Image filter: Single row per byte filter
  Interlacing: No interlacing
  Compression Scheme: Deflate method 8, 32k window
  Resolution: 96, 96 (pixels per meter)
  FillOrder: msb-to-lsb
  Byte Order: Network (Big Endian)
  Number of text strings: 1
    Software (xTXt deflate compressed): www.inkscape.org
$ pngchunks corrupt.png
Chunk: Data Length 13 (max 2147483647), Type 1380206665 [IHDR]
  Critical, public, PNG 1.2 compliant, unsafe to copy
  IHDR Width: 1
  IHDR Height: 1
  IHDR Bitdepth: 8
  IHDR Colortype: 0
  IHDR Compression: 0
  IHDR Filter: 0
  IHDR Interlace: 0
  IHDR Compression algorithm is Deflate
  IHDR Filter method is type zero (None, Sub, Up, Average, Paeth)
  IHDR Interlacing is disabled
  Chunk CRC: 981375829
Chunk: Data Length 9 (max 2147483647), Type 1935231088 [pHYs]
  Ancillary, public, PNG 1.2 compliant, safe to copy
  ... Unknown chunk type
  Chunk CRC: 949104502
Chunk: Data Length 25 (max 2147483647), Type 1951942004 [tEXt]
  Ancillary, public, PNG 1.2 compliant, safe to copy
  ... Unknown chunk type
  Chunk CRC: -1678885862
Chunk: Data Length 722 (max 2147483647), Type 1413563465 [IDAT]
  Critical, public, PNG 1.2 compliant, unsafe to copy
  IDAT contains image data
  Chunk CRC: 105581587
Chunk: Data Length 0 (max 2147483647), Type 1145980233 [IEND]
  Critical, public, PNG 1.2 compliant, unsafe to copy
  IEND contains no data
  Chunk CRC: -1371381630

In particular, in the header (IHDR) chunk:

Chunk: Data Length 13 (max 2147483647), Type 1380206665 [IHDR]
  Critical, public, PNG 1.2 compliant, unsafe to copy
  IHDR Width: 1
  IHDR Height: 1

And the data (IDAT) chunk:

Chunk: Data Length 722 (max 2147483647), Type 1413563465 [IDAT]
  Critical, public, PNG 1.2 compliant, unsafe to copy
  IDAT contains image data
  Chunk CRC: 105581587

The width and height values in the header must be corrupted, and should be values other than 1. But what should they be?

To find out more, I need to know how much pixel data is in the IDAT chunk. To do so, I had to decompress/deflate the IDAT contents.

Since the IDAT magic constant is located at offset 0x5f, the chunk contents start at 0x5f+4.

00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR
00000010: 0000 0001 0000 0001 0800 0000 003a 7e9b  .............:~.
00000020: 5500 0000 0970 4859 7300 000e c700 000e  U....pHYs.......
00000030: c701 3892 2f76 0000 0019 7445 5874 536f  ..8./v....tEXtSo
00000040: 6674 7761 7265 0077 7777 2e69 6e6b 7363  ftware.www.inksc
00000050: 6170 652e 6f72 679b ee3c 1a00 0002 d249  ape.org..<.....I
00000060: 4441 5478 daed 5adb 6eec 200c 6447 fbff  DATx..Z.n. .dG..
00000070: 9f3c ea43 4ec0 e00b e46c 2b55 d5f8 a52b  .<.CN....l+U...+
00000080: 02be 8d3d 3851 5f6c 92df 2450 0a04 8844  ...=8Q_l..$P...D
00000090: 8008 1089 0011 2012 0122 4024 0244 8048  ...... .."@$.D.H
import zlib

idat = open("corrupt.png", "rb").read()[0x5f+4:0x5f+722+4]
open("idat", "wb").write(idat)
deflated = zlib.decompress(idat)
open("deflated", "wb").write(deflated)
print(len(deflated))
$ python3 deflate.py
20050

The size of the deflated chunk is 20050. Since the image is greyscale, according to the pnginfo output earlier, each pixel has one byte. Now, to find out the possible values of width and height, I factorize 20050 to get 20050 = 2*5*5*401.

According to this writeup, each line in the image will have an extra byte to specify the filter type. It is then very logical to guess that the 1 in 401 is that filter type. This leaves me with 50*400 pixel values. Since the flag should be long, it makes sense for width to be 400 and height to be 50.

So I opened up my hex editor and changed the width and height values, at the following offsets in the IHDR chunk:

  • Width (0x0)
  • Height (0x4)

We can see the 0000 0001 for both width and height in the hexdump below:

$ xxd corrupt.png | head -n 3
00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR
00000010: 0000 0001 0000 0001 0800 0000 003a 7e9b  .............:~.
00000020: 5500 0000 0970 4859 7300 000e c700 000e  U....pHYs.......

Change them to:

00000000:   8950 4e47 0d0a 1a0a   0000 000d 4948 4452  .PNG........IHDR
00000010: **0000 0190 0000 0032** 0800 0000 003a 7e9b  .............:~.
00000020:   5500 0000 0970 4859   7300 000e c700 000e  U....pHYs.......

But changing this will break the checksum. Not a big issue anyways, pngcheck can tell us the correct checksum.

$ pngcheck corrupt.png
corrupt.png  CRC error in chunk IHDR (computed 5777d241, expected 3a7e9b55)
ERROR: corrupt.png

So just replace the 3a7e9b55 above to 5777d1241:

00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR
00000010: 0000 0190 0000 0032 0800 0000 0057 77d2  .......2.....Ww.
00000020: 4100 0000 0970 4859 7300 000e c700 000e  A....pHYs.......

And here we see the recovered image:

fixed

References:

  • https://pyokagan.name/blog/2019-10-14-png/#:~:text=Reconstructing%20pixel%20data