Deflate algorithm on an example of PNG format
As part of the next laboratory work, my colleagues and I were faced with the task of parsing the hexadecimal dump of the PNG file . According to the RFC 2083 standard, the PNG format stores pixel data compressed by the Deflate algorithm . Therefore, when parsing the dump, we needed to decompress the compressed data using the Inflate algorithm.
The analysis is carried out on the following 4x4 pixel image:
Data streams compressed using Deflate standard RFC 1951 are stored in PNG in zlib format of RFC 1950 standard , we will use this standard for parsing. The zlib header defines Deflate settings. It has the following structure:
1 byte | 1 byte | 4 bytes | |||
CMF | Flg | Dictid | |||
4 bits | 4 bits | 2 bits | 1 bit | 5 bit | |
Cinfo | CM | FLEVEL | Fdict | Fcheck |
More about fields:
- CMF (Compression method and flags). This byte is divided into 2 cells with 4 bits each: CM (Compression method), CINFO (Compression info).
- CM (Compression method). This field defines the compression method used in the file. CM = 8 means Deflate is used with window sizes up to 32 kilobytes.
- CINFO (Compression info). When CM = 8, CINFO is the base 2 logarithm that sets the window size to minus 8 (CINFO = 7, sets the window size to 32 kilobytes).
- FLG (Flags). This byte is divided into parts as follows: FCHECK, FDICT, FLEVEL.
- FCHECK (check bits for CMF and FLG). Consider the value of the expression sum = (CMF * 256 + FLG) as a sixteen-bit unsigned integer. This field complements FLG so that sum is a multiple of 31.
- FDICT (Preset dictionary). If this flag is set, then the DICT dictionary descriptor immediately follows the FLG byte. The unpacker can use the value of this field to determine the dictionary used during compression.
- FLEVEL (Compression level). This field is used by special compression algorithms. Deflate (CM = 8): 0 - the fastest compression, 1 - fast compression, 2 - default compression, 3 - maximum compression (most slowly). The value of this field is not taken into account when unpacking. Used to indicate whether additional compression makes sense.
After DICT (if FDICT is installed), there is a stream of compressed data, the length of which for PNG depends on the CHUNK_LENGTH field of the IDAT structure. At the end of the zlib batch is the ADLER-32 checksum calculated from the uncompressed data using the Adler-32 algorithm.
In our case, the zlib header looks like this:
78 DA | 0111 | 1000 | eleven | 0 | 11010 |
Window size = 32K | Compression method = Deflate | Compression level = fastest | Dictionary is used = false | Check bits |
From the header we find out that the dictionary is not used (FDICT = 0).
Compressed data for our picture:
63 F8 3F 93 E1 3F 03 C3 CC FF 20 1A C8 00 22 24 0E 58 12 85 33 D3 F8 3F 03 32 07 44 03 00 AA 05 23 77 The
bits are then read continuously by bytes. In the byte, we go from the last bit to the first (for example, in the data stream 2 bytes go sequentially: 63 F8 (0b01100011 0b11111000), then you should get the following bit sequence: 1100011000011111).
The first bit in the read sequence is the BFINAL flag, indicating whether this piece of data is the last. The next 2 BTYPE bits indicate the type of compression: 00 - without compression, 01 - compression with fixed Huffman codes, 10 - compression with dynamic Huffman codes, 11 - the value is reserved.
Table for fixed Huffman codes:
Unpacked value | Number of bits | Codes | base |
0 - 143 | 8 | From 00110000 to 10111111 | 00110000 |
144 - 255 | 9 | From 110010000 to 111111111 | 110010000 |
256 - 279 | 7 | From 0000000 to 0010111 | 0000000 |
280 - 287 | 8 | 11000000 to 11000111 | 11000000 |
Offset table:
Codes | Add. bits | Distance | Codes | Add. bits | Distance | Codes | Add. bits | Distance |
0 | 0 | 1 | 10 | 4 | 33 - 48 | 20 | 9 | 1025 - 1536 |
1 | 0 | 2 | eleven | 4 | 49 - 64 | 21 | 9 | 1537 2048 |
2 | 0 | 3 | 12 | 5 | 65 - 96 | 22 | 10 | 2049 - 3072 |
3 | 0 | 4 | thirteen | 5 | 97 - 128 | 23 | 10 | 3073 - 4096 |
4 | 1 | 5, 6 | 14 | 6 | 129 - 192 | 24 | eleven | 4097 - 6144 |
5 | 1 | 7, 8 | fifteen | 6 | 193 - 256 | 25 | eleven | 6145 - 8192 |
6 | 2 | 9 - 12 | 16 | 7 | 257 - 384 | 26 | 12 | 8193 - 12288 |
7 | 2 | 13 - 16 | 17 | 7 | 385 - 512 | 27 | 12 | 12289 - 16384 |
8 | 3 | 17-24 | 18 | 8 | 513 - 768 | 28 | thirteen | 16385 - 24576 |
9 | 3 | 25 - 32 | 19 | 8 | 769 - 1024 | 29th | thirteen | 24577 - 32768 |
Length table:
Codes | Add. Bits | Length | Codes | Add. Bits | Length | Codes | Add. Bits | Length |
257 | 0 | 3 | 267 | 1 | 15, 16 | 277 | 4 | 67 - 82 |
258 | 0 | 4 | 268 | 1 | 17, 18 | 278 | 4 | 83 - 98 |
259 | 0 | 5 | 269 | 2 | 19 - 22 | 279 | 4 | 99 - 114 |
260 | 0 | 6 | 270 | 2 | 23 - 26 | 280 | 4 | 115 - 130 |
261 | 0 | 7 | 271 | 2 | 27 - 30 | 281 | 5 | 131 - 162 |
262 | 0 | 8 | 272 | 2 | 31 - 34 | 282 | 5 | 163 - 194 |
263 | 0 | 9 | 273 | 3 | 35 - 42 | 283 | 5 | 195 - 226 |
264 | 0 | 10 | 274 | 3 | 43 - 50 | 284 | 5 | 227 - 257 |
265 | 1 | 11, 12 | 275 | 3 | 51 - 58 | 285 | 0 | 258 |
266 | 1 | 13, 14 | 276 | 3 | 59 - 66 | | | |
When unpacking, data can be represented by two types: a fixed value and the length of the reverse bias.
The data that we read must be converted to fixed values. The translation formula is presented below:
lit value = data - base + left bound, where
lit value is a fixed value,
base is a column from table 1,
left bound is a left number from a column of unpacked values from table 1.
When reading data, we can unambiguously determine to which interval of fixed values from table 1 it corresponds to the fact that the prefixes of each interval are different.
For example, let's choose 2 bytes F8 and 3F from our sequence. The sequence of bits that turned out to be 000 1111111111100. Let the current read position be 4, then read 7 bits (the minimum number of bits), we get the prefix 1111111, which corresponds to the interval 2. From this it turns out that the length of the read code is 9.
0b111111111 = 0d511
Using the formula above, we get that the number that was encoded is 255 = 511 - 400 (0b110010000) + 144.
Consider the case when instead of a fixed value, the data stream contains information about the length and reverse bias, i.e. the read value falls into the 3rd interval. In our version, this will be a subsequence:
20 1A C8 ( 0000010 0 01011000 00010011).
We read the first 7 bits (0b0000010), which fall into the interval 3. By the formula, translate into a number. This will be the number 257 = 1 - 0 + 256. Next, we use table 3. The code 257 means that the number of additional bits that must be counted is 0. And the length along the third column is 3. If there are additional bits, then they are read in the reverse order . These bits determine the number we add to the length.
Next, read 5 bits (0b00101 = 0d5), which determine the reverse bias. In our case, this is the number 5. From table 2 it is clear that we need to read 1 additional bit. We read it in the reverse order. It turned out 1 (0d1). We add this number to the minimum distance from column 3. And it follows that our reverse bias is 7 + 1 = 8.
What does this mean? For example, we counted 9 values: 0 255 153 0 255 0 0 153 255. The return distance shows how many values we need to shift back in this stream, that is, we will be at the second value - 255. The length, which is equal to 3, indicates that we need to take 3 values from the data stream, starting from the value on which we stand, ie 255 153 0. If the length is greater than the offset, then we return to the starting position and read again in the original order until we count the number of values equal to the length. Suppose we have a length of 7, and the reverse bias is 2. We are shifted by 2 values, i.e. our position on the penultimate number is 153. The sequence that turned out is 153 255 153 255 153 255 153. Ultimately, when the unpacker reads the next fixed value, equal to zero,
Full dump analysis:
01100011 FINAL CHUNK, FIXED HUFFMAN 11111000 48 - 48 + 0 = 0 - FILTER: NONE 00111111 511 - 400 + 144 = 255 10010011 409 - 400 + 144 = 153 11100001 48 - 48 + 0 = 0 00111111 511 - 400 + 144 = 255 00000011 48 - 48 + 0 = 0 11000011 48 - 48 + 0 = 0 11001100 409 - 400 + 144 = 153 11111111 511 - 400 + 144 = 255 00100000 2 - 0 + 256 = 258 LENGTH = 4 00011010 5 DISTANCE = 7 + 1 = 8 11001000 1 - 0 + 256 = 257 LENGTH = 3 00000000 6 DISTANCE = 9 + 0 = 9 00100001 1 - 0 + 256 = 257 LENGTH = 3, 2 DISTANCE = 3 00100100 9 - 0 + 256 = 265 LENGTH = 11 + 0 = 11 00001110 7 DISTANCE = 13 + 0 = 13 01011000 3 - 0 + 256 = 259 LENGTH = 5 00010010 9 DISTANCE = 25 + 4 = 29 10000101 10 - 0 + 256 = 266 LENGTH = 13 + 0 = 13 00110011 7 DISTANCE = 13 + 0 = 13 11010011 409 - 400 + 144 = 153 11111000 99 - 48 + 0 = 51 00111111 511 - 400 + 144 = 255 00000011 48 - 48 + 0 = 0 00110010 9 - 0 + 256 = 265 LENGTH = 11 + 1 = 12 00000111 7 DISTANCE = 13 + 0 = 13 01000100 2 - 0 + 256 = 258 LENGTH = 4 00000011 5 DISTANCE = 7 + 1 = 8 00000000 0 - 0 + 256 = 256 END 10101010 ADLER 00000101 ADLER 00100011 ADLER 01110111 ADLER | 0 255 153 0 255 0 0 153 255 255 153 0 255 255 0 0 255 0 0 0 153 255 255 153 0 255 255 0 0 255 255 153 0 255 0 255 153 0 255 255 0 0 255 255 153 0 255 0 153 51 255 0 255 0 0 255 255 153 0 255 0 153 51 255 255 153 0 255 |
The search for similar materials ultimately led to standards. We hope that this article will already help those in need in their own endeavors in their native and understandable Russian language.
Source image