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:
References:
- https://pyokagan.name/blog/2019-10-14-png/#:~:text=Reconstructing%20pixel%20data