This commit is contained in:
Zeecka
2025-12-10 22:23:54 +01:00
parent 83bce75b2f
commit d1fb8b3760
2 changed files with 117 additions and 57 deletions
+1 -1
View File
@@ -43,7 +43,7 @@ def analyze_binwalk(input_img: Path, output_dir: Path) -> None:
# Remove the extracted directory # Remove the extracted directory
if extracted_dir.exists(): if extracted_dir.exists():
shutil.rmtree(extracted_dir) shutil.rmtree(extracted_dir)
if len(stderr) > 0: if len(stderr) > 0:
err = { err = {
"binwalk": { "binwalk": {
+116 -56
View File
@@ -1,7 +1,6 @@
"""Image size bruteforcer for common PNG sizes.""" """Image size bruteforcer for common PNG sizes."""
import zlib import zlib
import shutil
from pathlib import Path from pathlib import Path
from .utils import update_data from .utils import update_data
@@ -10,75 +9,128 @@ EXPECTED_SIZES = [
# ------------------------- # -------------------------
# ICONS (Square) # ICONS (Square)
# ------------------------- # -------------------------
(16, 16), (20, 20), (24, 24), (28, 28), (16, 16),
(32, 32), (48, 48), (64, 64), (72, 72), (20, 20),
(96, 96), (128, 128), (192, 192), (256, 256), (24, 24),
(384, 384), (512, 512), (768, 768), (1024, 1024), (28, 28),
(1536, 1536), (2048, 2048), (4096, 4096), (32, 32),
(48, 48),
(64, 64),
(72, 72),
(96, 96),
(128, 128),
(192, 192),
(256, 256),
(384, 384),
(512, 512),
(768, 768),
(1024, 1024),
(1536, 1536),
(2048, 2048),
(4096, 4096),
# ------------------------- # -------------------------
# 1:1 Square (General) # 1:1 Square (General)
# ------------------------- # -------------------------
(300, 300), (500, 500), (800, 800), (1000, 1000), (300, 300),
(1500, 1500), (2000, 2000), (2500, 2500), (3000, 3000), (500, 500),
(800, 800),
(1000, 1000),
(1500, 1500),
(2000, 2000),
(2500, 2500),
(3000, 3000),
# ------------------------- # -------------------------
# 4:3 Aspect (Classic Screens) # 4:3 Aspect (Classic Screens)
# ------------------------- # -------------------------
(320, 240), (640, 480), (800, 600), (960, 720), (320, 240),
(1024, 768), (1280, 960), (1600, 1200), (2048, 1536), (640, 480),
(2560, 1920), (3200, 2400), (4096, 3072), (800, 600),
(960, 720),
(1024, 768),
(1280, 960),
(1600, 1200),
(2048, 1536),
(2560, 1920),
(3200, 2400),
(4096, 3072),
# ------------------------- # -------------------------
# 3:2 Aspect (Photography) # 3:2 Aspect (Photography)
# ------------------------- # -------------------------
(600, 400), (900, 600), (1200, 800), (1500, 1000), (600, 400),
(1800, 1200), (2400, 1600), (3000, 2000), (3600, 2400), (900, 600),
(4500, 3000), (6000, 4000), (1200, 800),
(1500, 1000),
(1800, 1200),
(2400, 1600),
(3000, 2000),
(3600, 2400),
(4500, 3000),
(6000, 4000),
# ------------------------- # -------------------------
# 16:9 Aspect (Video / Web) # 16:9 Aspect (Video / Web)
# ------------------------- # -------------------------
(426, 240), (640, 360), (854, 480), (960, 540), (426, 240),
(1280, 720), (1366, 768), (1600, 900), (1920, 1080), (640, 360),
(2560, 1440), (3200, 1800), (3440, 1935), (3840, 2160), (854, 480),
(5120, 2880), (7680, 4320), (10240, 5760), (960, 540),
(1280, 720),
(1366, 768),
(1600, 900),
(1920, 1080),
(2560, 1440),
(3200, 1800),
(3440, 1935),
(3840, 2160),
(5120, 2880),
(7680, 4320),
(10240, 5760),
# ------------------------- # -------------------------
# 16:10 Aspect (Laptops) # 16:10 Aspect (Laptops)
# ------------------------- # -------------------------
(1280, 800), (1440, 900), (1680, 1050), (1280, 800),
(1920, 1200), (2560, 1600), (2880, 1800), (1440, 900),
(3840, 2400), (5120, 3200), (1680, 1050),
(1920, 1200),
(2560, 1600),
(2880, 1800),
(3840, 2400),
(5120, 3200),
# ------------------------- # -------------------------
# Mobile Portrait (9:16) # Mobile Portrait (9:16)
# ------------------------- # -------------------------
(720, 1280), (1080, 1920), (1440, 2560), (720, 1280),
(2160, 3840), (1440, 2960), (1080, 2400), (1080, 1920),
(1170, 2532), (1284, 2778), (1290, 2796), (1440, 2560),
(2160, 3840),
(1440, 2960),
(1080, 2400),
(1170, 2532),
(1284, 2778),
(1290, 2796),
# ------------------------- # -------------------------
# Social Media Sizes # Social Media Sizes
# ------------------------- # -------------------------
# Instagram # Instagram
(1080, 1080), (1080, 1350), (1080, 1920), (1080, 1080),
(1080, 1350),
(1080, 1920),
# Facebook # Facebook
(1200, 628), (1200, 1200), (1200, 628),
(1200, 1200),
# Twitter/X # Twitter/X
(1200, 675), (1500, 500), (1200, 675),
(1500, 500),
# YouTube Thumbnails # YouTube Thumbnails
(1280, 720), (1920, 1080), (1280, 720),
(1920, 1080),
# ------------------------- # -------------------------
# Banners / Headers # Banners / Headers
# ------------------------- # -------------------------
(1600, 500), (1920, 500), (2560, 700), (1600, 500),
(3000, 1000), (3840, 1200), (1920, 500),
(2560, 700),
(3000, 1000),
(3840, 1200),
# ------------------------- # -------------------------
# Print-like Digital Sizes # Print-like Digital Sizes
# ------------------------- # -------------------------
@@ -87,10 +139,12 @@ EXPECTED_SIZES = [
(3508, 4961), # A3 @ 300 DPI (3508, 4961), # A3 @ 300 DPI
(4961, 7016), # A2 @ 300 DPI (4961, 7016), # A2 @ 300 DPI
(7016, 9933), # A1 @ 300 DPI (7016, 9933), # A1 @ 300 DPI
] ]
def calc_checksum(header_chunk, width_bytes, height_bytes):
def calc_checksum(
header_chunk: bytearray, width_bytes: bytearray, height_bytes: bytearray
) -> bytearray:
""" """
Calculates the CRC32 of the IHDR chunk with new dimensions. Calculates the CRC32 of the IHDR chunk with new dimensions.
header_chunk: The full IHDR chunk (Type + Data). header_chunk: The full IHDR chunk (Type + Data).
@@ -99,7 +153,8 @@ def calc_checksum(header_chunk, width_bytes, height_bytes):
new_header = header_chunk[:4] + width_bytes + height_bytes + header_chunk[12:] new_header = header_chunk[:4] + width_bytes + height_bytes + header_chunk[12:]
# Calculate CRC and return as 4 bytes (Big Endian) # Calculate CRC and return as 4 bytes (Big Endian)
return bytearray((zlib.crc32(new_header) & 0xffffffff).to_bytes(4, byteorder='big')) return bytearray((zlib.crc32(new_header) & 0xFFFFFFFF).to_bytes(4, byteorder="big"))
def analyze_image_resize(input_img: Path, output_dir: Path) -> None: def analyze_image_resize(input_img: Path, output_dir: Path) -> None:
""" """
@@ -122,20 +177,23 @@ def analyze_image_resize(input_img: Path, output_dir: Path) -> None:
b = bytearray(f) b = bytearray(f)
# Find IHDR start # Find IHDR start
IHDR = b.find(b'\x49\x48\x44\x52') IHDR = b.find(b"\x49\x48\x44\x52")
if IHDR == -1: if IHDR == -1:
logs.append("Failure: PNG header (IHDR) not found.") logs.append("Failure: PNG header (IHDR) not found.")
update_data(output_dir, {"image_resize": {"status": "error", "error": "PNG header not found."}}) update_data(
output_dir,
{"image_resize": {"status": "error", "error": "PNG header not found."}},
)
return return
# Calculate Chunk Length (The 4 bytes BEFORE IHDR tag) # Calculate Chunk Length (The 4 bytes BEFORE IHDR tag)
chunk_length = int.from_bytes((b[IHDR- 4: IHDR]), byteorder="big") + 4 chunk_length = int.from_bytes((b[IHDR - 4 : IHDR]), byteorder="big") + 4
# Extract Target CRC (The 4 bytes AFTER the chunk data) # Extract Target CRC (The 4 bytes AFTER the chunk data)
target_crc = b[IHDR + chunk_length:IHDR + chunk_length + 4] target_crc = b[IHDR + chunk_length : IHDR + chunk_length + 4]
# Isolate the header chunk (Type + Data) # Isolate the header chunk (Type + Data)
header_chunk = b[IHDR:IHDR + chunk_length] header_chunk = b[IHDR : IHDR + chunk_length]
logs.append(f"Target CRC found: 0x{target_crc.hex()}") logs.append(f"Target CRC found: 0x{target_crc.hex()}")
@@ -146,8 +204,8 @@ def analyze_image_resize(input_img: Path, output_dir: Path) -> None:
width, height = size width, height = size
# Convert dimensions to 4-byte Big Endian arrays # Convert dimensions to 4-byte Big Endian arrays
width_bytes = bytearray(width.to_bytes(4, byteorder='big')) width_bytes = bytearray(width.to_bytes(4, byteorder="big"))
height_bytes = bytearray(height.to_bytes(4, byteorder='big')) height_bytes = bytearray(height.to_bytes(4, byteorder="big"))
# Check if this size matches the file's CRC # Check if this size matches the file's CRC
if target_crc == calc_checksum(header_chunk, width_bytes, height_bytes): if target_crc == calc_checksum(header_chunk, width_bytes, height_bytes):
@@ -162,14 +220,16 @@ def analyze_image_resize(input_img: Path, output_dir: Path) -> None:
output_path = output_dir / img_name output_path = output_dir / img_name
# Splicing: [Start...IHDR+4] + [W] + [H] + [IHDR+12...End] # Splicing: [Start...IHDR+4] + [W] + [H] + [IHDR+12...End]
full_png_data = b[:IHDR+4] + width_bytes + height_bytes + b[IHDR+12:] full_png_data = (
b[: IHDR + 4] + width_bytes + height_bytes + b[IHDR + 12 :]
)
# Write the recovered file to disk # Write the recovered file to disk
with output_path.open("wb") as out_f: with output_path.open("wb") as out_f:
out_f.write(full_png_data) out_f.write(full_png_data)
recovered_image = Path(output_dir.name) / img_name recovered_image = Path(output_dir.name) / img_name
break # Stop looking after finding the match break # Stop looking after finding the match
if not match_found: if not match_found:
logs.append("Failure: No matching dimensions found.") logs.append("Failure: No matching dimensions found.")
@@ -178,7 +238,7 @@ def analyze_image_resize(input_img: Path, output_dir: Path) -> None:
output_data = { output_data = {
"image_resize": { # Key name must match your module name in AperiSolve "image_resize": { # Key name must match your module name in AperiSolve
"status": "ok", "status": "ok",
"output": logs "output": logs,
} }
} }
if recovered_image: if recovered_image:
@@ -187,4 +247,4 @@ def analyze_image_resize(input_img: Path, output_dir: Path) -> None:
except Exception as e: except Exception as e:
update_data(output_dir, {"image_resize": {"status": "error", "error": str(e)}}) update_data(output_dir, {"image_resize": {"status": "error", "error": str(e)}})
return None return None