diff --git a/forensics/c0rrupt/flag.txt b/forensics/c0rrupt/flag.txt new file mode 100644 index 0000000..181922a --- /dev/null +++ b/forensics/c0rrupt/flag.txt @@ -0,0 +1 @@ +picoCTF{c0rrupt10n_1847995} diff --git a/forensics/c0rrupt/mystery b/forensics/c0rrupt/mystery new file mode 100644 index 0000000..de852f3 Binary files /dev/null and b/forensics/c0rrupt/mystery differ diff --git a/forensics/c0rrupt/mystery.png b/forensics/c0rrupt/mystery.png new file mode 100644 index 0000000..96a1c72 Binary files /dev/null and b/forensics/c0rrupt/mystery.png differ diff --git a/forensics/c0rrupt/solve.py b/forensics/c0rrupt/solve.py new file mode 100755 index 0000000..a4a5140 --- /dev/null +++ b/forensics/c0rrupt/solve.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 + +from binascii import crc32 +from pathlib import Path + +def overwrite_at_pos(original: bytes, offset: int, replacement: bytes) -> bytes: + return original[:offset] + replacement + original[offset + len(replacement):] + +def num_to_4b(i: int) -> bytes: + return i.to_bytes(4, byteorder='big') + +def main(): + with (Path(__file__).parent / "mystery").open('rb') as file: + content = file.read() + + # From https://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html: + # > The first eight bytes of a PNG file always contain the following (decimal) values: + # > 137 80 78 71 13 10 26 10 + + PNG_MAGIC_BYTES = bytes([137, 80, 78, 71, 13, 10, 26, 10]) + + # https://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html: + # + # > 3.2. Chunk layout + # > Each chunk consists of four parts: + # > + # > Length + # > A 4-byte unsigned integer giving the number of bytes in the chunk's data field. + # > The length counts only the data field, not itself, the chunk type code, or the CRC. + # > Zero is a valid length. Although encoders and decoders should treat the length as unsigned, + # > its value must not exceed 231 bytes. + # > + # > Chunk Type + # > A 4-byte chunk type code. For convenience in description and in examining PNG files, + # > type codes are restricted to consist of uppercase and lowercase ASCII letters + # > (A-Z and a-z, or 65-90 and 97-122 decimal). However, encoders and decoders must treat the + # > codes as fixed binary values, not character strings. For example, it would not be correct + # > to represent the type code IDAT by the EBCDIC equivalents of those letters. + # > Additional naming conventions for chunk types are discussed in the next section. + # > + # > Chunk Data + # > The data bytes appropriate to the chunk type, if any. This field can be of zero length. + # > + # > CRC + # > A 4-byte CRC (Cyclic Redundancy Check) calculated on the preceding bytes in the chunk, + # > including the chunk type code and chunk data fields, but not including the length field. + # > The CRC is always present, even for chunks containing no data. See CRC algorithm. + + S_LENGTH = 4 + S_TYPE = 4 + S_CRC = 4 + + # https://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html: + # > IHDR length is static: + # > Width: 4 bytes + # > Height: 4 bytes + # > Bit depth: 1 byte + # > Color type: 1 byte + # > Compression method: 1 byte + # > Filter method: 1 byte + # > Interlace method: 1 byte + + IHDR_LENGTH = 4 + 4 + 1 * 5 + IHDR_CHUNK_TYPE = b"IHDR" + + PREFIX = PNG_MAGIC_BYTES + num_to_4b(IHDR_LENGTH) + IHDR_CHUNK_TYPE + new_content = overwrite_at_pos(content, 0 , PREFIX) + + # $ pngcheck mystery.png + # mystery.png CRC error in chunk pHYs (computed 38d82c82, expected 495224f0) + + checksum_location = new_content.find(b'\x49\x52\x24\xf0') + new_content = overwrite_at_pos(new_content, checksum_location, b'\x38\xd8\x2c\x82') + + # $ pngcheck -v mystery.png + # File: mystery.png (202940 bytes) + # chunk IHDR at offset 0x0000c, length 13 + # 1642 x 1095 image, 24-bit RGB, non-interlaced + # chunk sRGB at offset 0x00025, length 1 + # rendering intent = perceptual + # chunk gAMA at offset 0x00032, length 4: 0.45455 + # chunk pHYs at offset 0x00042, length 9: 2852132389x5669 pixels/meter + # : invalid chunk length (too large) + # ERRORS DETECTED in mystery.png + + # https://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html: + # > pHYs length is static: + # > Pixels per unit, X axis: 4 bytes (unsigned integer) + # > Pixels per unit, Y axis: 4 bytes (unsigned integer) + # > Unit specifier: 1 byte + + PHYS_SIZE = 4 + 4 + 1 + + # Next chunk seems broken, both the length and the type, maybe type is ?DET -> IDAT? + + phys_type_location = new_content.find(b'pHYs') + next_chunk_length_pos = phys_type_location + S_TYPE + PHYS_SIZE + S_CRC + new_content = overwrite_at_pos(new_content, next_chunk_length_pos + S_LENGTH, b'IDAT') + + # https://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html: + # > There can be multiple IDAT chunks; if so, they must appear consecutively with no other intervening chunks. + # So we can calculate the expected size by finding the next idat chunk + + chunk_data_length = new_content[next_chunk_length_pos + S_LENGTH + S_TYPE:].find(b'IDAT') - S_LENGTH - S_CRC + new_content = overwrite_at_pos(new_content, next_chunk_length_pos, num_to_4b(chunk_data_length)) + + + with (Path(__file__).parent / "mystery.png").open('wb') as file: + file.write(new_content) + +if __name__ == '__main__': + main()