fix 109
This commit is contained in:
@@ -161,6 +161,7 @@ class SubprocessAnalyzer(ABC):
|
|||||||
self.update_result(result)
|
self.update_result(result)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.update_result({"status": "error", "error": str(e)})
|
self.update_result({"status": "error", "error": str(e)})
|
||||||
|
raise
|
||||||
|
|
||||||
def is_error(self, returncode: int, stdout: str, stderr: str, zip_exist: bool) -> bool:
|
def is_error(self, returncode: int, stdout: str, stderr: str, zip_exist: bool) -> bool:
|
||||||
"""Check if the result is an error."""
|
"""Check if the result is an error."""
|
||||||
|
|||||||
@@ -20,73 +20,70 @@ class DecomposerAnalyzer(SubprocessAnalyzer):
|
|||||||
|
|
||||||
def get_results(self, _: Optional[str] = None) -> dict[str, Any]:
|
def get_results(self, _: Optional[str] = None) -> dict[str, Any]:
|
||||||
"""Analyze an image submission using bits decomposition."""
|
"""Analyze an image submission using bits decomposition."""
|
||||||
try:
|
img = Image.open(self.input_img)
|
||||||
img = Image.open(self.input_img)
|
converted = False
|
||||||
converted = False
|
|
||||||
|
|
||||||
if img.mode == "P":
|
if img.mode == "P":
|
||||||
img = img.convert("RGB")
|
img = img.convert("RGB")
|
||||||
converted = True
|
converted = True
|
||||||
|
|
||||||
img_np = np.array(img)
|
img_np = np.array(img)
|
||||||
|
|
||||||
# Handle grayscale, RGB, RGBA, etc.
|
# Handle grayscale, RGB, RGBA, etc.
|
||||||
if len(img_np.shape) == 2:
|
if len(img_np.shape) == 2:
|
||||||
channels = 1
|
channels = 1
|
||||||
else:
|
else:
|
||||||
channels = img_np.shape[2]
|
channels = img_np.shape[2]
|
||||||
|
|
||||||
if channels > 1:
|
if channels > 1:
|
||||||
channel_names = ["Red", "Green", "Blue", "Alpha"]
|
channel_names = ["Red", "Green", "Blue", "Alpha"]
|
||||||
else:
|
else:
|
||||||
channel_names = ["Grayscale"]
|
channel_names = ["Grayscale"]
|
||||||
|
|
||||||
image_json = {}
|
image_json = {}
|
||||||
|
|
||||||
# Add Superimposed RGB bit planes
|
# Add Superimposed RGB bit planes
|
||||||
if channels >= 3:
|
if channels >= 3:
|
||||||
superimposed_json = []
|
superimposed_json = []
|
||||||
for bit in range(8):
|
for bit in range(8):
|
||||||
bit_mask = 1 << bit
|
bit_mask = 1 << bit
|
||||||
rgb_planes = [((img_np[..., c] & bit_mask) >> bit) * 255 for c in range(3)]
|
rgb_planes = [((img_np[..., c] & bit_mask) >> bit) * 255 for c in range(3)]
|
||||||
rgb_image = np.stack(rgb_planes, axis=-1).astype(np.uint8)
|
rgb_image = np.stack(rgb_planes, axis=-1).astype(np.uint8)
|
||||||
rgb_img = Image.fromarray(rgb_image, mode="RGB")
|
rgb_img = Image.fromarray(rgb_image, mode="RGB")
|
||||||
img_name = f"superimposed_bit_{bit}.png"
|
img_name = f"superimposed_bit_{bit}.png"
|
||||||
out_path = self.output_dir / img_name
|
out_path = self.output_dir / img_name
|
||||||
dl_path = Path(self.output_dir.name) / img_name
|
dl_path = Path(self.output_dir.name) / img_name
|
||||||
superimposed_json.append("/image/" + str(dl_path))
|
superimposed_json.append("/image/" + str(dl_path))
|
||||||
rgb_img.save(out_path)
|
rgb_img.save(out_path)
|
||||||
|
|
||||||
image_json["Superimposed"] = superimposed_json
|
image_json["Superimposed"] = superimposed_json
|
||||||
|
|
||||||
# Process individual channels
|
# Process individual channels
|
||||||
for c in range(channels):
|
for c in range(channels):
|
||||||
channel_data = img_np[..., c] if channels > 1 else img_np
|
channel_data = img_np[..., c] if channels > 1 else img_np
|
||||||
channel_label = channel_names[c]
|
channel_label = channel_names[c]
|
||||||
channel_json = []
|
channel_json = []
|
||||||
for bit in range(8):
|
for bit in range(8):
|
||||||
bit_mask = 1 << bit
|
bit_mask = 1 << bit
|
||||||
bit_plane = ((channel_data & bit_mask) >> bit) * 255
|
bit_plane = ((channel_data & bit_mask) >> bit) * 255
|
||||||
bit_img = Image.fromarray(bit_plane.astype(np.uint8), mode="L")
|
bit_img = Image.fromarray(bit_plane.astype(np.uint8), mode="L")
|
||||||
img_name = f"{channel_label}_bit_{bit}.png"
|
img_name = f"{channel_label}_bit_{bit}.png"
|
||||||
out_path = self.output_dir / img_name
|
out_path = self.output_dir / img_name
|
||||||
dl_path = Path(self.output_dir.name) / img_name
|
dl_path = Path(self.output_dir.name) / img_name
|
||||||
channel_json.append("/image/" + str(dl_path))
|
channel_json.append("/image/" + str(dl_path))
|
||||||
bit_img.save(out_path)
|
bit_img.save(out_path)
|
||||||
image_json[channel_label] = channel_json
|
image_json[channel_label] = channel_json
|
||||||
|
|
||||||
output = {
|
output = {
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
"images": image_json,
|
"images": image_json,
|
||||||
}
|
}
|
||||||
if converted:
|
if converted:
|
||||||
output["note"] = (
|
output["note"] = (
|
||||||
"Image contains a color palette and was converted to RGB for processing."
|
"Image contains a color palette and was converted to RGB for processing."
|
||||||
)
|
)
|
||||||
|
|
||||||
return output
|
return output
|
||||||
except Exception as e:
|
|
||||||
return {"status": "error", "error": str(e)}
|
|
||||||
|
|
||||||
|
|
||||||
def analyze_decomposer(input_img: Path, output_dir: Path) -> None:
|
def analyze_decomposer(input_img: Path, output_dir: Path) -> None:
|
||||||
|
|||||||
@@ -115,53 +115,50 @@ class ResizeAnalyzer(SubprocessAnalyzer):
|
|||||||
"""
|
"""
|
||||||
logs = []
|
logs = []
|
||||||
|
|
||||||
try:
|
with self.input_img.open("rb") as image:
|
||||||
with self.input_img.open("rb") as image:
|
img_bytes = bytearray(image.read())
|
||||||
img_bytes = bytearray(image.read())
|
try:
|
||||||
try:
|
self.png = PNG.from_bytearray(img_bytes)
|
||||||
self.png = PNG.from_bytearray(img_bytes)
|
except ValueError:
|
||||||
except ValueError:
|
|
||||||
return {
|
|
||||||
"status": "error",
|
|
||||||
"error": "IHDR chunk is not the first chunk, or PNG has invalid structure.",
|
|
||||||
}
|
|
||||||
|
|
||||||
# Already correct
|
|
||||||
if self.png.crc == self.png.compute_crc():
|
|
||||||
return {
|
|
||||||
"status": "ok",
|
|
||||||
"note": f"PNG is already valid with dimensions "
|
|
||||||
f"{self.png.width}x{self.png.height} and crc 0x{self.png.crc:08x}.",
|
|
||||||
}
|
|
||||||
|
|
||||||
saved_img_urls = []
|
|
||||||
|
|
||||||
# No match found
|
|
||||||
if not (db_matches := self._lookup_crc()) and not (
|
|
||||||
db_matches := self._search_height_crc()
|
|
||||||
):
|
|
||||||
return {
|
return {
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"error": "Failure: No matching dimensions found.",
|
"error": "IHDR chunk is not the first chunk, or PNG has invalid structure.",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Match found, saving image(s)
|
# Already correct
|
||||||
logs.append(f"Target CRC found: 0x{self.png.crc:08x}")
|
if self.png.crc == self.png.compute_crc():
|
||||||
self.output_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
for png_match in db_matches:
|
|
||||||
img_name = self._write_recovered_png(img_bytes, png_match)
|
|
||||||
logs.append(f"Image saved: {img_name}")
|
|
||||||
# Append the formatted URL to our list
|
|
||||||
saved_img_urls.append("/image/" + str(Path(self.output_dir.name) / img_name))
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
"output": logs,
|
"note": f"PNG is already valid with dimensions "
|
||||||
"png_images": saved_img_urls,
|
f"{self.png.width}x{self.png.height} and crc 0x{self.png.crc:08x}.",
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
saved_img_urls = []
|
||||||
return {"status": "error", "error": str(e)}
|
|
||||||
|
# No match found
|
||||||
|
if not (db_matches := self._lookup_crc()) and not (
|
||||||
|
db_matches := self._search_height_crc()
|
||||||
|
):
|
||||||
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"error": "Failure: No matching dimensions found.",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Match found, saving image(s)
|
||||||
|
logs.append(f"Target CRC found: 0x{self.png.crc:08x}")
|
||||||
|
self.output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
for png_match in db_matches:
|
||||||
|
img_name = self._write_recovered_png(img_bytes, png_match)
|
||||||
|
logs.append(f"Image saved: {img_name}")
|
||||||
|
# Append the formatted URL to our list
|
||||||
|
saved_img_urls.append("/image/" + str(Path(self.output_dir.name) / img_name))
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": "ok",
|
||||||
|
"output": logs,
|
||||||
|
"png_images": saved_img_urls,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def analyze_image_resize(input_img: Path, output_dir: Path) -> None:
|
def analyze_image_resize(input_img: Path, output_dir: Path) -> None:
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ class OpenStegoAnalyzer(SubprocessAnalyzer):
|
|||||||
self.update_result(result)
|
self.update_result(result)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.update_result({"status": "error", "error": str(e)})
|
self.update_result({"status": "error", "error": str(e)})
|
||||||
|
raise
|
||||||
|
|
||||||
def process_output(self, stdout: str, stderr: str) -> str | list[str] | dict[str, str]:
|
def process_output(self, stdout: str, stderr: str) -> str | list[str] | dict[str, str]:
|
||||||
"""Process the stdout/stderr."""
|
"""Process the stdout/stderr."""
|
||||||
|
|||||||
+17
-3
@@ -77,12 +77,26 @@ def analyze_image(submission_hash: str) -> None:
|
|||||||
|
|
||||||
def run_analyzer(analyzer_func: Any, *args: Any) -> None:
|
def run_analyzer(analyzer_func: Any, *args: Any) -> None:
|
||||||
"""Run an analyzer function in a separate thread."""
|
"""Run an analyzer function in a separate thread."""
|
||||||
|
analyzer_name = analyzer_func.__name__.replace("analyze_", "")
|
||||||
try:
|
try:
|
||||||
analyzer_func(*args)
|
analyzer_func(*args)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
sentry_sdk.capture_exception(e)
|
print(f"Error in {analyzer_name}: {e}")
|
||||||
print(f"Error in {analyzer_func.__name__}: {e}")
|
with sentry_sdk.push_scope() as scope:
|
||||||
sentry_sdk.capture_exception(e)
|
scope.set_tag("analyzer", analyzer_name)
|
||||||
|
scope.set_tag("submission_hash", submission_hash)
|
||||||
|
scope.set_context(
|
||||||
|
"analyzer_info",
|
||||||
|
{
|
||||||
|
"tool": analyzer_name,
|
||||||
|
"image_path": str(img_path),
|
||||||
|
"result_path": str(result_path),
|
||||||
|
"filename": submission.filename,
|
||||||
|
"deep_analysis": submission.deep_analysis,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
scope.fingerprint = ["analyzer-error", analyzer_name]
|
||||||
|
sentry_sdk.capture_exception(e)
|
||||||
|
|
||||||
analyzers = [
|
analyzers = [
|
||||||
(analyze_binwalk, img_path, result_path),
|
(analyze_binwalk, img_path, result_path),
|
||||||
|
|||||||
Reference in New Issue
Block a user