diff --git a/Dockerfile b/Dockerfile index fb991f9..67235ca 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.11-slim AS build +FROM python:3.14-slim AS build WORKDIR / diff --git a/README.md b/README.md index ced6f7b..f253234 100644 --- a/README.md +++ b/README.md @@ -73,13 +73,13 @@ Here is a batch of usefull commands. ```bash docker compose down -v # Remove volume containings results -docker compose -f docker-compose-dev.yml up # Start application in developpement mode +docker compose -f compose.dev.yml up # Start application in developpement mode rm -rf aperisolve/results # Remove results from developpement mode docker exec -it postgres psql -U aperiuser -d aperisolve # Get postgresql shell docker exec -it aperisolve-web bash # Get shell in running web app ``` -> Note, if you used [docker-compose-dev.yml](docker-compose-dev.yml), you must remove "results" when switching to [production docker](docker-compose.yml). +> Note, if you used [compose.dev.yml](compose.dev.yml), you must remove "results" when switching to [production docker](compose.yml). ### Code quality @@ -94,7 +94,6 @@ GitHub CI will alert in case of failure. ## Roadmap -- [x] PngCheck - [ ] Zsteg extract / --all --> **Download `zsteg` files** such as mp3 encoded on LSB - [ ] Test on mobile - [ ] i18n diff --git a/aperisolve/analyzers/image_resize.py b/aperisolve/analyzers/image_resize.py index a0710db..377b550 100644 --- a/aperisolve/analyzers/image_resize.py +++ b/aperisolve/analyzers/image_resize.py @@ -1,6 +1,7 @@ """Image size bruteforcer for common PNG sizes.""" import zlib +import shutil from pathlib import Path from .utils import update_data @@ -116,74 +117,79 @@ def analyze_image_resize(input_img: Path, output_dir: Path) -> None: # 3. Initialize Logs logs = [] - recovered_images = [] + recovered_image = None try: with open(input_img, "rb") as image: f = image.read() - b = bytearray(f) - # Find IHDR start - IHDR = b.find(b'\x49\x48\x44\x52') - if IHDR == -1: - logs.append("Failure: PNG header (IHDR) not found.") - update_data(output_dir, {"image_resize": {"status": "error", "error": "PNG header not found."}}) - return + b = bytearray(f) - # Calculate Chunk Length (The 4 bytes BEFORE IHDR tag) - chunk_length = int.from_bytes((b[IHDR- 4: IHDR]), byteorder="big") + 4 + # Find IHDR start + IHDR = b.find(b'\x49\x48\x44\x52') + if IHDR == -1: + logs.append("Failure: PNG header (IHDR) not found.") + update_data(output_dir, {"image_resize": {"status": "error", "error": "PNG header not found."}}) + if extracted_dir.exists(): + shutil.rmtree(extracted_dir, ignore_errors=True) + return - # Extract Target CRC (The 4 bytes AFTER the chunk data) - target_crc = b[IHDR + chunk_length:IHDR + chunk_length + 4] + # Calculate Chunk Length (The 4 bytes BEFORE IHDR tag) + chunk_length = int.from_bytes((b[IHDR- 4: IHDR]), byteorder="big") + 4 - # Isolate the header chunk (Type + Data) - header_chunk = b[IHDR:IHDR + chunk_length] + # Extract Target CRC (The 4 bytes AFTER the chunk data) + target_crc = b[IHDR + chunk_length:IHDR + chunk_length + 4] - logs.append(f"Target CRC found: 0x{target_crc.hex()}") + # Isolate the header chunk (Type + Data) + header_chunk = b[IHDR:IHDR + chunk_length] - match_found = False + logs.append(f"Target CRC found: 0x{target_crc.hex()}") - # Brute-force Loop - for size in EXPECTED_SIZES: - width, height = size + match_found = False - # Convert dimensions to 4-byte Big Endian arrays - width_bytes = bytearray(width.to_bytes(4, byteorder='big')) - height_bytes = bytearray(height.to_bytes(4, byteorder='big')) + # Brute-force Loop + for size in EXPECTED_SIZES: + width, height = size - # Check if this size matches the file's CRC - if target_crc == calc_checksum(header_chunk, width_bytes, height_bytes): - match_found = True - logs.append(f"Success: Match found! Dimensions: {width}x{height}") + # Convert dimensions to 4-byte Big Endian arrays + width_bytes = bytearray(width.to_bytes(4, byteorder='big')) + height_bytes = bytearray(height.to_bytes(4, byteorder='big')) - # Create Output Filename - output_filename = f"recovered_{width}x{height}.png" - output_path = extracted_dir / output_filename + # Check if this size matches the file's CRC + if target_crc == calc_checksum(header_chunk, width_bytes, height_bytes): + match_found = True + logs.append(f"Success: Match found! Dimensions: {width}x{height}") - # Splicing: [Start...IHDR+4] + [W] + [H] + [IHDR+12...End] - full_png_data = b[:IHDR+4] + width_bytes + height_bytes + b[IHDR+12:] + # Create Output Filename + output_filename = f"recovered_{width}x{height}.png" + output_path = extracted_dir / output_filename - # Write the recovered file to disk - with open(output_path, "wb") as out_f: - out_f.write(full_png_data) + # Splicing: [Start...IHDR+4] + [W] + [H] + [IHDR+12...End] + full_png_data = b[:IHDR+4] + width_bytes + height_bytes + b[IHDR+12:] - recovered_images.append(output_filename) - break # Stop looking after finding the match + # Write the recovered file to disk + with output_path.open("wb") as out_f: + out_f.write(full_png_data) - if not match_found: - logs.append("Failure: No matching dimensions found.") + recovered_image = output_filename + break # Stop looking after finding the match + + if not match_found: + logs.append("Failure: No matching dimensions found.") # 4. Final Data Update output_data = { "image_resize": { # Key name must match your module name in AperiSolve "status": "ok", "output": logs, - "images": recovered_images, # Tells frontend to display the image + "images": recovered_image, # Tells frontend to display the image } } update_data(output_dir, output_data) except Exception as e: update_data(output_dir, {"image_resize": {"status": "error", "error": str(e)}}) + if extracted_dir.exists(): + shutil.rmtree(extracted_dir, ignore_errors=True) return None \ No newline at end of file diff --git a/docker-compose-dev.yml b/compose.dev.yml similarity index 100% rename from docker-compose-dev.yml rename to compose.dev.yml diff --git a/docker-compose.yml b/compose.yml similarity index 100% rename from docker-compose.yml rename to compose.yml