跳过正文
  1. Ctfs/

PNG CRC32 修复工具

·3 分钟· loading · loading · ·
曙光磁铁
作者
曙光磁铁
A little bit about you
目录

PNG CRC32 修复工具
#

这是一个用于修复 PNG 图片 CRC32 校验的工具,主要通过调整图片的宽度和高度来实现。

功能特性
#

  1. CRC32 检测

    • 自动检测 PNG 文件的 IHDR 块 CRC32 值
    • 对比实际 CRC32 和期望值是否匹配
  2. 自动修复

    • 支持三种修复模式:
      • 仅修复高度
      • 仅修复宽度
      • 同时修复高度和宽度
    • 自动生成修复后的文件(文件名后缀为 _fix.png)
  3. 可视化输出

    • 使用彩色输出显示处理过程
    • 显示当前和修复后的图片尺寸
    • 显示详细的 CRC32 信息

使用方法
#

python break.py <png_file_path>

例如:

python break.py test.png

输出说明
#

  • 绿色: 成功信息
  • 红色: 错误信息
  • 黄色: 预期值
  • 青色: 信息提示

技术细节
#

  1. IHDR 类

    • 处理 PNG 文件的 IHDR 块
    • 提供宽度/高度的读取和修改方法
    • 计算 CRC32 校验值
  2. 修复策略

    • 优先尝试仅修改高度(最快)
    • 其次尝试仅修改宽度
    • 最后尝试同时修改高度和宽度(最慢)
  3. 文件处理

    • 支持二进制文件读写
    • 自动备份原始文件
    • 错误处理和异常捕获

注意事项
#

  1. 输入文件必须是有效的 PNG 文件
  2. 工具会在原文件同目录下创建修复后的文件
  3. 修复过程可能需要一定时间,特别是在尝试同时修复宽度和高度时
# -*- coding: utf-8 -*-
import binascii
import struct
import sys
from pathlib import Path
from typing import Optional, Tuple


# ANSI escape codes for colors
class Colors:
    RED = "\033[91m"
    GREEN = "\033[92m"
    YELLOW = "\033[93m"
    BLUE = "\033[94m"
    CYAN = "\033[96m"
    RESET = "\033[0m"


class IHDR:
    def __init__(self, data: bytes):
        self.raw_data = data
        self._header = data[:4]  # IHDR chunk type
        self._height_bytes = data[8:12]
        self._width_bytes = data[4:8]
        self._ends = data[12:]  # bit depth, color type, etc

    @property
    def width(self) -> int:
        return int.from_bytes(self._width_bytes, "big")

    @property
    def height(self) -> int:
        return int.from_bytes(self._height_bytes, "big")

    def get_data(self, width=None, height=None) -> bytes:
        return (
            bytes(self._header)
            + (bytes(self._width_bytes) if width is None else struct.pack(">i", width))
            + (
                bytes(self._height_bytes)
                if height is None
                else struct.pack(">i", height)
            )
            + bytes(self._ends)
        )

    def crc32(self, width=None, height=None) -> int:
        return binascii.crc32(self.get_data(width, height)) & 0xFFFFFFFF

    @staticmethod
    def from_png(png_data: bytes) -> Tuple["IHDR", int]:
        """Extract IHDR from PNG file data and return (IHDR, expected_crc32)"""
        ihdr_data = png_data[12:29]
        expected_crc32 = int.from_bytes(png_data[29:33], "big")
        return IHDR(ihdr_data), expected_crc32


def try_fix_height(ihdr: IHDR, expected_crc32: int) -> Optional[int]:
    for height in range(0, 65535):
        if ihdr.crc32(height=height) == expected_crc32:
            return height
    return None


def try_fix_width(ihdr: IHDR, expected_crc32: int) -> Optional[int]:
    for width in range(0, 65535):
        if ihdr.crc32(width=width) == expected_crc32:
            return width
    return None


def try_fix_both(
    ihdr: IHDR, expected_crc32: int
) -> Tuple[Optional[int], Optional[int]]:
    for height in range(0, 65535):
        for width in range(0, 65535):
            if ihdr.crc32(width=width, height=height) == expected_crc32:
                return height, width
    return None, None


def save_fixed_png(
    original_data: bytes,
    width: Optional[int] = None,
    height: Optional[int] = None,
    output_path: Path | None = None,
) -> None:
    """Save a new PNG with fixed dimensions"""
    if output_path is None:
        output_path = Path(str(png_path).replace(".png", "_fix.png"))

    # Only modify if we have new dimensions
    if width is None and height is None:
        return

    # Copy original data
    new_data = bytearray(original_data)

    # Update width if provided (at offset 16)
    if width is not None:
        new_data[16:20] = struct.pack(">i", width)

    # Update height if provided (at offset 20)
    if height is not None:
        new_data[20:24] = struct.pack(">i", height)

    # Calculate new CRC32 for IHDR chunk
    new_crc = binascii.crc32(new_data[12:29]) & 0xFFFFFFFF
    new_data[29:33] = struct.pack(">I", new_crc)

    # Save new file
    with open(output_path, "wb") as f:
        f.write(new_data)
    print(f"{Colors.GREEN}Saved fixed PNG to: {output_path}{Colors.RESET}")


def try_fix_crc32(img_data: bytes, png_path: Path) -> None:
    ihdr, expected_crc32 = IHDR.from_png(img_data)
    real_crc32 = ihdr.crc32()

    if expected_crc32 == real_crc32:
        print(f"{Colors.GREEN}CRC32 is correct!{Colors.RESET}")
        return

    print(f"{Colors.CYAN}IHDR data:{Colors.RESET} {ihdr.get_data()}")
    print(f"{Colors.YELLOW}Expected CRC32: 0x{expected_crc32:X}")
    print(f"{Colors.RED}Current CRC32: 0x{real_crc32:X}{Colors.RESET}")
    print(f"{Colors.CYAN}Current dimensions: {ihdr.width}x{ihdr.height}{Colors.RESET}")

    # Try fixing height
    height = try_fix_height(ihdr, expected_crc32)
    if height is not None:
        print(f"{Colors.GREEN}Found height fix: {height} (0x{height:X})")
        print(f"Real dimensions: {ihdr.width}x{height}{Colors.RESET}")
        save_fixed_png(
            img_data,
            height=height,
            output_path=png_path.parent / f"{png_path.stem}_fix.png",
        )
        return

    # Try fixing width
    width = try_fix_width(ihdr, expected_crc32)
    if width is not None:
        print(f"{Colors.GREEN}Found width fix: {width} (0x{width:X})")
        print(f"Real dimensions: {width}x{ihdr.height}{Colors.RESET}")
        save_fixed_png(
            img_data,
            width=width,
            output_path=png_path.parent / f"{png_path.stem}_fix.png",
        )
        return

    # Try fixing both
    height, width = try_fix_both(ihdr, expected_crc32)
    if height is not None and width is not None:
        print(
            f"{Colors.GREEN}Found height/width fix: {height}x{width} (0x{height:X}x0x{width:X}){Colors.RESET}"
        )
        save_fixed_png(
            img_data,
            width=width,
            height=height,
            output_path=png_path.parent / f"{png_path.stem}_fix.png",
        )
        return

    print(f"{Colors.RED}No fix found{Colors.RESET}")


if __name__ == "__main__":
    if len(sys.argv) != 2:
        print(f"{Colors.RED}Usage: python {sys.argv[0]} <png_file_path>{Colors.RESET}")
        sys.exit(1)

    png_path = Path(sys.argv[1])
    if not png_path.exists():
        print(f"{Colors.RED}Error: File {png_path} does not exist{Colors.RESET}")
        sys.exit(1)

    if not png_path.is_file():
        print(f"{Colors.RED}Error: {png_path} is not a file{Colors.RESET}")
        sys.exit(1)

    try:
        with open(png_path, "rb") as f:
            img_data = f.read()
        try_fix_crc32(img_data, png_path)
    except Exception as e:
        print(f"{Colors.RED}Error: {str(e)}{Colors.RESET}")
        sys.exit(1)
f