Update analysers (WIP)
This commit is contained in:
@@ -3,75 +3,27 @@
|
|||||||
# mypy: disable-error-code=unused-awaitable
|
# mypy: disable-error-code=unused-awaitable
|
||||||
"""Binwalk Analyzer for Image Submissions."""
|
"""Binwalk Analyzer for Image Submissions."""
|
||||||
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from .utils import MAX_PENDING_TIME, update_data
|
from .base_analyzer import SubprocessAnalyzer
|
||||||
|
|
||||||
|
|
||||||
|
class BinwalkAnalyzer(SubprocessAnalyzer):
|
||||||
|
"""Analyzer for binwalk."""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
super().__init__("binwalk", *args, has_archive=True)
|
||||||
|
self.cmd = ["binwalk", "--matryoshka", "-e", self.img, "--run-as=root"]
|
||||||
|
self.make_folder = False # Binwalk already create its own folder
|
||||||
|
|
||||||
|
def get_extracted_dir(self) -> Path:
|
||||||
|
return self.output_dir / f"_{self.input_img.name}.extracted"
|
||||||
|
|
||||||
|
def is_error(self, returncode: int, stdout: str, stderr: str, zip_exist: bool) -> bool:
|
||||||
|
return len(stderr) > 0 and not zip_exist and False
|
||||||
|
|
||||||
|
|
||||||
def analyze_binwalk(input_img: Path, output_dir: Path) -> None:
|
def analyze_binwalk(input_img: Path, output_dir: Path) -> None:
|
||||||
"""Analyze an image submission using binwalk."""
|
"""Analyze an image submission using binwalk."""
|
||||||
|
analyzer = BinwalkAnalyzer(input_img, output_dir)
|
||||||
image_name = input_img.name
|
analyzer.analyze()
|
||||||
extracted_dir = output_dir / f"_{image_name}.extracted"
|
|
||||||
|
|
||||||
try:
|
|
||||||
stderr = ""
|
|
||||||
# Run binwalk
|
|
||||||
data = subprocess.run(
|
|
||||||
["binwalk", "--matryoshka", "-e", "../" + str(image_name), "--run-as=root"],
|
|
||||||
cwd=output_dir,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
check=False,
|
|
||||||
timeout=MAX_PENDING_TIME,
|
|
||||||
)
|
|
||||||
stderr += data.stderr
|
|
||||||
|
|
||||||
zip_exist = False
|
|
||||||
if extracted_dir.exists():
|
|
||||||
# Zip extracted files
|
|
||||||
zip_data = subprocess.run(
|
|
||||||
["7z", "a", "../binwalk.7z", "*"],
|
|
||||||
cwd=extracted_dir,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
check=False,
|
|
||||||
timeout=MAX_PENDING_TIME,
|
|
||||||
)
|
|
||||||
zip_exist = True
|
|
||||||
stderr += zip_data.stderr
|
|
||||||
|
|
||||||
# Remove the extracted directory
|
|
||||||
if extracted_dir.exists():
|
|
||||||
shutil.rmtree(extracted_dir)
|
|
||||||
|
|
||||||
# Only report error if stderr exists AND extraction failed.
|
|
||||||
# If zip_exist is True, binwalk worked (despite warnings), so we proceed.
|
|
||||||
if len(stderr) > 0 and not zip_exist:
|
|
||||||
err: dict[str, dict[str, Any]] = {
|
|
||||||
"binwalk": {
|
|
||||||
"status": "error",
|
|
||||||
"output": data.stdout.split("\n") if data else [],
|
|
||||||
"error": stderr,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
update_data(output_dir, err)
|
|
||||||
return None
|
|
||||||
|
|
||||||
output_data: dict[str, dict[str, Any]] = {
|
|
||||||
"binwalk": {
|
|
||||||
"status": "ok",
|
|
||||||
"output": data.stdout.split("\n") if data else [],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if zip_exist:
|
|
||||||
output_data["binwalk"]["download"] = f"/download/{output_dir.name}/binwalk"
|
|
||||||
|
|
||||||
update_data(output_dir, output_data)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
update_data(output_dir, {"binwalk": {"status": "error", "error": str(e)}})
|
|
||||||
return None
|
|
||||||
|
|||||||
@@ -3,46 +3,29 @@
|
|||||||
# mypy: disable-error-code=unused-awaitable
|
# mypy: disable-error-code=unused-awaitable
|
||||||
"""Exiftool Analyzer for Image Submissions."""
|
"""Exiftool Analyzer for Image Submissions."""
|
||||||
|
|
||||||
import subprocess
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from .utils import MAX_PENDING_TIME, update_data
|
from .base_analyzer import SubprocessAnalyzer
|
||||||
|
|
||||||
|
|
||||||
|
class ExiftoolAnalyzer(SubprocessAnalyzer):
|
||||||
|
"""Analyzer for exiftool."""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
super().__init__("exiftool", *args)
|
||||||
|
self.cmd = ["exiftool", "-a", "-u", "-g1", self.img]
|
||||||
|
|
||||||
|
def process_output(self, stdout: str, stderr: str) -> dict[str, str]:
|
||||||
|
"""Process the stdout into a list of lines."""
|
||||||
|
metadata: dict[str, str] = {}
|
||||||
|
for line in stdout.split("\n"):
|
||||||
|
if ":" in line:
|
||||||
|
key, value = line.split(":", 1)
|
||||||
|
metadata[key.strip()] = value.strip()
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
|
||||||
def analyze_exiftool(input_img: Path, output_dir: Path) -> None:
|
def analyze_exiftool(input_img: Path, output_dir: Path) -> None:
|
||||||
"""Analyze an image submission using exiftool."""
|
"""Analyze an image submission using exiftool."""
|
||||||
|
analyzer = ExiftoolAnalyzer(input_img, output_dir)
|
||||||
try:
|
analyzer.analyze()
|
||||||
data = subprocess.run(
|
|
||||||
["exiftool", input_img],
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
check=True,
|
|
||||||
timeout=MAX_PENDING_TIME,
|
|
||||||
)
|
|
||||||
|
|
||||||
metadata: dict[str, str] = {}
|
|
||||||
for line in data.stdout.split("\n"):
|
|
||||||
if ":" in line:
|
|
||||||
key, value = line.split(":", 1)
|
|
||||||
metadata[key.strip()] = value.strip()
|
|
||||||
|
|
||||||
if data.stderr:
|
|
||||||
update_data(
|
|
||||||
output_dir,
|
|
||||||
{
|
|
||||||
"exiftool": {
|
|
||||||
"status": "error",
|
|
||||||
"output": metadata,
|
|
||||||
"error": data.stderr,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
update_data(
|
|
||||||
output_dir,
|
|
||||||
{"exiftool": {"status": "ok", "output": metadata}},
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
update_data(output_dir, {"exiftool": {"status": "error", "error": str(e)}})
|
|
||||||
|
|||||||
@@ -3,60 +3,30 @@
|
|||||||
# mypy: disable-error-code=unused-awaitable
|
# mypy: disable-error-code=unused-awaitable
|
||||||
"""Foremost Analyzer for Image Submissions."""
|
"""Foremost Analyzer for Image Submissions."""
|
||||||
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from .utils import MAX_PENDING_TIME, update_data
|
from .base_analyzer import SubprocessAnalyzer
|
||||||
|
|
||||||
|
|
||||||
|
class ForemostAnalyzer(SubprocessAnalyzer):
|
||||||
|
"""Analyzer for foremost."""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
super().__init__("foremost", *args, has_archive=True)
|
||||||
|
self.cmd = ["foremost", "-o", str(self.get_extracted_dir()), "-i", self.img]
|
||||||
|
|
||||||
|
def is_error(self, returncode: int, stdout: str, stderr: str, zip_exist: bool) -> bool:
|
||||||
|
"""Check if the result is an error."""
|
||||||
|
return len(stderr) > 60
|
||||||
|
|
||||||
|
def process_output(self, stdout: str, stderr: str) -> str | list[str] | dict[str, str]:
|
||||||
|
"""Process the stdout/stderr."""
|
||||||
|
if "Processing" in stderr and "|*|" in stderr:
|
||||||
|
return stderr.strip().split("\n")
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
def analyze_foremost(input_img: Path, output_dir: Path) -> None:
|
def analyze_foremost(input_img: Path, output_dir: Path) -> None:
|
||||||
"""Analyze an image submission using foremost."""
|
"""Analyze an image submission using foremost."""
|
||||||
|
analyzer = ForemostAnalyzer(input_img, output_dir)
|
||||||
try:
|
analyzer.analyze()
|
||||||
foremost_dir = output_dir / "foremost"
|
|
||||||
foremost_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
stderr = ""
|
|
||||||
# Run foremost
|
|
||||||
data = subprocess.run(
|
|
||||||
["foremost", "-o", foremost_dir, "-i", input_img],
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
check=False,
|
|
||||||
timeout=MAX_PENDING_TIME,
|
|
||||||
)
|
|
||||||
# Note: foremost use stderr as standard output for results :o)
|
|
||||||
# So we can't use it as error detection :
|
|
||||||
# stderr += data.stderr
|
|
||||||
|
|
||||||
# Zip extracted files
|
|
||||||
zip_data = subprocess.run(
|
|
||||||
["7z", "a", "../foremost.7z", "*"],
|
|
||||||
cwd=foremost_dir,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
check=False,
|
|
||||||
timeout=MAX_PENDING_TIME,
|
|
||||||
)
|
|
||||||
|
|
||||||
stderr += zip_data.stderr
|
|
||||||
|
|
||||||
# Remove the extracted directory
|
|
||||||
shutil.rmtree(foremost_dir)
|
|
||||||
|
|
||||||
data_strings = data.stdout.split("\n") if data else []
|
|
||||||
data_strings = [s for s in data_strings if s]
|
|
||||||
|
|
||||||
update_data(
|
|
||||||
output_dir,
|
|
||||||
{
|
|
||||||
"foremost": {
|
|
||||||
"status": "ok",
|
|
||||||
"output": data_strings,
|
|
||||||
"download": f"/download/{output_dir.name}/foremost",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
update_data(output_dir, {"foremost": {"status": "error", "error": str(e)}})
|
|
||||||
|
|||||||
@@ -3,97 +3,32 @@
|
|||||||
# mypy: disable-error-code=unused-awaitable
|
# mypy: disable-error-code=unused-awaitable
|
||||||
"""Outguess Analyzer for Image Submissions."""
|
"""Outguess Analyzer for Image Submissions."""
|
||||||
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from .utils import MAX_PENDING_TIME, update_data
|
from .base_analyzer import SubprocessAnalyzer
|
||||||
|
|
||||||
|
|
||||||
def analyze_outguess(
|
class OutguessAnalyzer(SubprocessAnalyzer):
|
||||||
input_img: Path, output_dir: Path, password: Optional[str] = None
|
"""Analyzer for outguess."""
|
||||||
) -> None:
|
|
||||||
"""Analyze an image submission using outguess."""
|
|
||||||
|
|
||||||
image_name = input_img.name
|
def __init__(self, *args):
|
||||||
try:
|
super().__init__("outguess", *args, has_archive=True)
|
||||||
outguess_dir = output_dir / "outguess"
|
|
||||||
outguess_dir.mkdir(parents=True, exist_ok=True)
|
def build_cmd(self, password: Optional[str] = None) -> list[str]:
|
||||||
outguess_file = "outguess.data"
|
extracted_dir = self.get_extracted_dir()
|
||||||
stderr = ""
|
assert extracted_dir is not None # since has_archive is True
|
||||||
cmd: list[str]
|
out = str(extracted_dir / "outguess.data")
|
||||||
if password:
|
if password:
|
||||||
cmd = [
|
return ["outguess", "-k", password, "-r", self.img, out]
|
||||||
"outguess",
|
|
||||||
"-k",
|
|
||||||
password,
|
|
||||||
"-r",
|
|
||||||
"../../" + str(image_name),
|
|
||||||
outguess_file,
|
|
||||||
]
|
|
||||||
else:
|
else:
|
||||||
cmd = ["outguess", "-r", "../../" + str(image_name), outguess_file]
|
return ["outguess", "-r", self.img, out]
|
||||||
|
|
||||||
data = subprocess.run(
|
|
||||||
cmd,
|
|
||||||
cwd=outguess_dir,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
check=False,
|
|
||||||
timeout=MAX_PENDING_TIME,
|
|
||||||
)
|
|
||||||
|
|
||||||
if data.returncode != 0:
|
def analyze_outguess(input_img: Path, output_dir: Path, password: Optional[str] = None) -> None:
|
||||||
stderr += data.stderr
|
"""Analyze an image submission using outguess."""
|
||||||
err = {
|
analyzer = OutguessAnalyzer(input_img, output_dir)
|
||||||
"outguess": {
|
if password:
|
||||||
"status": "error",
|
analyzer.analyze(password)
|
||||||
"error": stderr,
|
else:
|
||||||
}
|
analyzer.analyze()
|
||||||
}
|
|
||||||
update_data(output_dir, err)
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Zip extracted files
|
|
||||||
zip_data = subprocess.run(
|
|
||||||
["7z", "a", "../outguess.7z", "*"],
|
|
||||||
cwd=outguess_dir,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
check=False,
|
|
||||||
timeout=MAX_PENDING_TIME,
|
|
||||||
)
|
|
||||||
|
|
||||||
stderr += zip_data.stderr
|
|
||||||
|
|
||||||
if len(stderr) > 0:
|
|
||||||
err = {
|
|
||||||
"outguess": {
|
|
||||||
"status": "error",
|
|
||||||
"error": stderr,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
update_data(output_dir, err)
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Remove the extracted directory
|
|
||||||
shutil.rmtree(outguess_dir)
|
|
||||||
|
|
||||||
data_strings = data.stderr.split("\n") if data else []
|
|
||||||
data_strings = [s for s in data_strings if s]
|
|
||||||
|
|
||||||
update_data(
|
|
||||||
output_dir,
|
|
||||||
{
|
|
||||||
"outguess": {
|
|
||||||
"status": "ok",
|
|
||||||
"output": data_strings,
|
|
||||||
"download": f"/download/{output_dir.name}/outguess",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
update_data(output_dir, {"outguess": {"status": "error", "error": str(e)}})
|
|
||||||
return None
|
|
||||||
|
|||||||
@@ -3,54 +3,23 @@
|
|||||||
# mypy: disable-error-code=unused-awaitable
|
# mypy: disable-error-code=unused-awaitable
|
||||||
"""Pngcheck Analyzer for Image Submissions."""
|
"""Pngcheck Analyzer for Image Submissions."""
|
||||||
|
|
||||||
import subprocess
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from .utils import MAX_PENDING_TIME, update_data
|
from .base_analyzer import SubprocessAnalyzer
|
||||||
|
|
||||||
|
|
||||||
|
class PngcheckAnalyzer(SubprocessAnalyzer):
|
||||||
|
"""Analyzer for pngcheck."""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
super().__init__("pngcheck", *args)
|
||||||
|
self.cmd = ["pngcheck", "-v", self.img]
|
||||||
|
|
||||||
|
def is_error(self, returncode: int, stdout: str, stderr: str, zip_exist: bool) -> bool:
|
||||||
|
return "this is neither a PNG or JNG image nor a MNG stream" in stdout
|
||||||
|
|
||||||
|
|
||||||
def analyze_pngcheck(input_img: Path, output_dir: Path) -> None:
|
def analyze_pngcheck(input_img: Path, output_dir: Path) -> None:
|
||||||
"""Analyze an image submission using pngcheck."""
|
"""Analyze an image submission using pngcheck."""
|
||||||
|
analyzer = PngcheckAnalyzer(input_img, output_dir)
|
||||||
try:
|
analyzer.analyze()
|
||||||
data = subprocess.run(
|
|
||||||
["pngcheck", "-v", input_img],
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
check=False,
|
|
||||||
timeout=MAX_PENDING_TIME,
|
|
||||||
)
|
|
||||||
# Error based on https://github.com/pnggroup/pngcheck/issues/59
|
|
||||||
# if data.stderr:
|
|
||||||
# update_data(
|
|
||||||
# output_dir, {"pngcheck": {"status": "error", "error": data.stderr}}
|
|
||||||
# )
|
|
||||||
# return
|
|
||||||
|
|
||||||
if "this is neither a PNG or JNG image nor a MNG stream" in data.stdout:
|
|
||||||
update_data(
|
|
||||||
output_dir,
|
|
||||||
{
|
|
||||||
"pngcheck": {
|
|
||||||
"status": "error",
|
|
||||||
"error": "ERRORS DETECTED: this is neither a PNG or JNG image nor a "
|
|
||||||
"MNG stream.",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
data_pngcheck = data.stdout.split("\n") if data else []
|
|
||||||
data_pngcheck = [s for s in data_pngcheck if s]
|
|
||||||
|
|
||||||
update_data(
|
|
||||||
output_dir,
|
|
||||||
{
|
|
||||||
"pngcheck": {
|
|
||||||
"status": "ok",
|
|
||||||
"output": data_pngcheck,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
update_data(output_dir, {"pngcheck": {"status": "error", "error": str(e)}})
|
|
||||||
|
|||||||
@@ -4,127 +4,67 @@
|
|||||||
"""Steghide Analyzer for Image Submissions."""
|
"""Steghide Analyzer for Image Submissions."""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from .utils import MAX_PENDING_TIME, update_data
|
from .base_analyzer import SubprocessAnalyzer
|
||||||
|
|
||||||
|
|
||||||
def analyze_steghide(
|
class SteghideAnalyzer(SubprocessAnalyzer):
|
||||||
input_img: Path, output_dir: Path, password: Optional[str] = None
|
"""Analyzer for steghide."""
|
||||||
) -> None:
|
|
||||||
"""Analyze an image submission using steghide."""
|
|
||||||
|
|
||||||
image_name = input_img.name
|
def __init__(self, *args):
|
||||||
try:
|
super().__init__("steghide", *args, has_archive=True)
|
||||||
stderr = ""
|
|
||||||
# Get embedded file information first
|
|
||||||
cmd: list[str] = ["steghide", "info", "../" + str(image_name)]
|
|
||||||
if password:
|
|
||||||
cmd += ["-p", password]
|
|
||||||
else:
|
|
||||||
cmd += ["-p", ""]
|
|
||||||
|
|
||||||
data = subprocess.run(
|
def build_cmd(self, password: Optional[str] = None) -> list[str]:
|
||||||
cmd,
|
if password is None:
|
||||||
cwd=output_dir,
|
password = ""
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
# First, get the embedded file name, and return the test cmd if failed
|
||||||
check=False,
|
cmd_test = ["steghide", "info", self.img, "-p", password]
|
||||||
timeout=MAX_PENDING_TIME,
|
data = self.run_command(cmd_test, cwd=self.output_dir)
|
||||||
)
|
err = self.is_error(data.returncode, data.stdout, data.stderr, False)
|
||||||
|
if err:
|
||||||
|
return cmd_test
|
||||||
|
|
||||||
match = re.search(r'embedded file "([^"]+)"', data.stdout)
|
match = re.search(r'embedded file "([^"]+)"', data.stdout)
|
||||||
|
assert match is not None
|
||||||
|
hidden = match.group(1)
|
||||||
|
cmd = ["steghide", "extract", "-sf", self.img, "-xf", hidden, "-p", password]
|
||||||
|
return cmd
|
||||||
|
|
||||||
|
def is_error(self, returncode: int, stdout: str, stderr: str, zip_exist: bool) -> bool:
|
||||||
|
"""Check if the result is an error."""
|
||||||
|
|
||||||
|
match = re.search(r'embedded file "([^"]+)"', stdout)
|
||||||
embedded_filename = match.group(1) if match else None
|
embedded_filename = match.group(1) if match else None
|
||||||
|
|
||||||
# if bad file format or wrong password, raise an error
|
# if bad file format or wrong password, raise an error
|
||||||
if data.returncode != 0 or embedded_filename is None:
|
return returncode != 0 or embedded_filename is None
|
||||||
stderr += data.stderr.replace(f'"../{image_name}" ', "") # hide file name
|
|
||||||
# the file format of the file \"../foobar.png\" is not supported.
|
|
||||||
# become
|
|
||||||
# the file format of the file is not supported.
|
|
||||||
err = {
|
|
||||||
"steghide": {
|
|
||||||
"status": "error",
|
|
||||||
"error": stderr,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
update_data(output_dir, err)
|
|
||||||
return None
|
|
||||||
|
|
||||||
# The passphrase is correct, we can extract the file
|
def process_output(self, stdout: str, stderr: str) -> str | list[str] | dict[str, str]:
|
||||||
# prevent path traversal
|
"""Process the stdout into a list of lines."""
|
||||||
embedded_file = output_dir / "steghide" / str(Path(embedded_filename).name)
|
out = []
|
||||||
extracted_dir = embedded_file.parent
|
for line in stderr.split("\n"):
|
||||||
extracted_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
cmd_ext: list[str] = [
|
|
||||||
"steghide",
|
|
||||||
"extract",
|
|
||||||
"-sf",
|
|
||||||
"../../" + str(image_name),
|
|
||||||
"-xf",
|
|
||||||
str(embedded_file.name),
|
|
||||||
]
|
|
||||||
if password:
|
|
||||||
cmd_ext += ["-p", password]
|
|
||||||
else:
|
|
||||||
cmd_ext += ["-p", ""]
|
|
||||||
|
|
||||||
data = subprocess.run(
|
|
||||||
cmd_ext,
|
|
||||||
cwd=extracted_dir,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
check=False,
|
|
||||||
timeout=MAX_PENDING_TIME,
|
|
||||||
)
|
|
||||||
# Filter out lines that indicate success
|
|
||||||
stdout = []
|
|
||||||
for line in data.stderr.split("\n"):
|
|
||||||
if line.startswith('wrote extracted data to "'):
|
if line.startswith('wrote extracted data to "'):
|
||||||
stdout.append(line)
|
out.append(line)
|
||||||
elif line:
|
return out
|
||||||
stderr += line + "\n"
|
|
||||||
|
|
||||||
# Zip extracted files
|
def process_error(self, stdout: str, stderr: str) -> str:
|
||||||
zip_data = subprocess.run(
|
"""Process stderr."""
|
||||||
["7z", "a", "../steghide.7z", "*"],
|
if "the file format of the file" in stderr and "not supported" in stderr:
|
||||||
cwd=extracted_dir,
|
return "The file format of the file is not supported (JPEG or BMP only)."
|
||||||
capture_output=True,
|
out = ""
|
||||||
text=True,
|
for line in stderr.split("\n"):
|
||||||
check=False,
|
if line and not line.startswith('wrote extracted data to "'):
|
||||||
timeout=MAX_PENDING_TIME,
|
out += line + "\n"
|
||||||
)
|
return out
|
||||||
|
|
||||||
stderr += zip_data.stderr
|
|
||||||
|
|
||||||
if len(stderr) > 0:
|
def analyze_steghide(input_img: Path, output_dir: Path, password: Optional[str] = None) -> None:
|
||||||
err = {
|
"""Analyze an image submission using steghide."""
|
||||||
"steghide": {
|
analyzer = SteghideAnalyzer(input_img, output_dir)
|
||||||
"status": "error",
|
if password:
|
||||||
"error": stderr,
|
analyzer.analyze(password)
|
||||||
}
|
else:
|
||||||
}
|
analyzer.analyze()
|
||||||
update_data(output_dir, err)
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Remove the extracted directory
|
|
||||||
shutil.rmtree(extracted_dir)
|
|
||||||
|
|
||||||
update_data(
|
|
||||||
output_dir,
|
|
||||||
{
|
|
||||||
"steghide": {
|
|
||||||
"status": "ok",
|
|
||||||
"output": stdout,
|
|
||||||
"download": f"/download/{output_dir.name}/steghide",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
update_data(output_dir, {"steghide": {"status": "error", "error": str(e)}})
|
|
||||||
return None
|
|
||||||
|
|||||||
@@ -3,45 +3,20 @@
|
|||||||
# mypy: disable-error-code=unused-awaitable
|
# mypy: disable-error-code=unused-awaitable
|
||||||
"""Strings Analyzer for Image Submissions."""
|
"""Strings Analyzer for Image Submissions."""
|
||||||
|
|
||||||
import subprocess
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from .utils import MAX_PENDING_TIME, update_data
|
from .base_analyzer import SubprocessAnalyzer
|
||||||
|
|
||||||
|
|
||||||
|
class StringsAnalyzer(SubprocessAnalyzer):
|
||||||
|
"""Analyzer for strings."""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
super().__init__("strings", *args)
|
||||||
|
self.cmd = ["strings", self.img]
|
||||||
|
|
||||||
|
|
||||||
def analyze_strings(input_img: Path, output_dir: Path) -> None:
|
def analyze_strings(input_img: Path, output_dir: Path) -> None:
|
||||||
"""Analyze an image submission using strings."""
|
"""Analyze an image submission using strings."""
|
||||||
|
analyzer = StringsAnalyzer(input_img, output_dir)
|
||||||
try:
|
analyzer.analyze()
|
||||||
data = subprocess.run(
|
|
||||||
["strings", input_img],
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
check=False,
|
|
||||||
timeout=MAX_PENDING_TIME,
|
|
||||||
)
|
|
||||||
if data.stderr:
|
|
||||||
update_data(
|
|
||||||
output_dir, {"strings": {"status": "error", "error": data.stderr}}
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
# strings_dir = output_dir / "strings"
|
|
||||||
# strings_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
# with open(strings_dir / "strings.txt", "w", encoding="utf-8") as f:
|
|
||||||
# subprocess.run(["strings", input_img], stdout=f, check=True)
|
|
||||||
|
|
||||||
data_strings = data.stdout.split("\n") if data else []
|
|
||||||
data_strings = [s for s in data_strings if s]
|
|
||||||
|
|
||||||
update_data(
|
|
||||||
output_dir,
|
|
||||||
{
|
|
||||||
"strings": {
|
|
||||||
"status": "ok",
|
|
||||||
"output": data_strings,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
update_data(output_dir, {"strings": {"status": "error", "error": str(e)}})
|
|
||||||
|
|||||||
@@ -1,79 +1,30 @@
|
|||||||
# flake8: noqa: E203,E501,W503
|
# flake8: noqa: E203,E501,W503
|
||||||
# pylint: disable=C0413,W0718,R0903,R0801
|
# pylint: disable=C0413,W0718,R0903,R0801
|
||||||
# mypy: disable-error-code=unused-awaitable
|
# mypy: disable-error-code=unused-awaitable
|
||||||
"""Custom Analyzer for Image Submissions."""
|
"""<toolname> Analyzer for Image Submissions."""
|
||||||
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from .utils import MAX_PENDING_TIME, update_data
|
from .base_analyzer import SubprocessAnalyzer
|
||||||
|
|
||||||
|
|
||||||
def analyze_myanalyzer(input_img: Path, output_dir: Path) -> None:
|
class TemplateAnalyze(SubprocessAnalyzer):
|
||||||
"""Analyze an image submission using myanalyzer."""
|
"""Analyzer for <toolname>."""
|
||||||
|
|
||||||
image_name = input_img.name
|
def __init__(self, *args):
|
||||||
|
super().__init__("<toolname>", *args, has_archive=True)
|
||||||
|
|
||||||
# Dir generated by the tool
|
def build_cmd(self, password: Optional[str] = None) -> list[str]:
|
||||||
extracted_dir = output_dir / "myanalyzer_output_dir"
|
if password:
|
||||||
|
return ["<toolname>", "-p", password, "-i", self.img]
|
||||||
|
return ["<toolname>", "-i", self.img]
|
||||||
|
|
||||||
try:
|
|
||||||
stderr, stdout = "", ""
|
|
||||||
|
|
||||||
# Run myanalyzer
|
def analyze_template(input_img: Path, output_dir: Path, password: Optional[str] = None) -> None:
|
||||||
data = subprocess.run(
|
"""Analyze an image submission using <toolname>."""
|
||||||
["myanalyzer", "-e", "../" + str(image_name)],
|
analyzer = TemplateAnalyze(input_img, output_dir)
|
||||||
cwd=output_dir,
|
if password:
|
||||||
capture_output=True,
|
analyzer.analyze(password)
|
||||||
text=True,
|
else:
|
||||||
check=False,
|
analyzer.analyze()
|
||||||
timeout=MAX_PENDING_TIME,
|
|
||||||
)
|
|
||||||
|
|
||||||
stderr += data.stderr
|
|
||||||
stdout += data.stdout
|
|
||||||
|
|
||||||
if len(stderr) > 0: # Return error if error has been raised
|
|
||||||
err = {
|
|
||||||
"myanalyzer": {
|
|
||||||
"status": "error",
|
|
||||||
"error": stderr,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
update_data(output_dir, err)
|
|
||||||
return None
|
|
||||||
|
|
||||||
zip_exist = False
|
|
||||||
if extracted_dir.exists(): # If result file(s) exist, zip them
|
|
||||||
subprocess.run(
|
|
||||||
["7z", "a", "../myanalyzer.7z", "*"],
|
|
||||||
cwd=extracted_dir,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
check=False,
|
|
||||||
timeout=MAX_PENDING_TIME,
|
|
||||||
)
|
|
||||||
zip_exist = True
|
|
||||||
|
|
||||||
if extracted_dir.exists(): # Remove the extracted directory
|
|
||||||
shutil.rmtree(extracted_dir)
|
|
||||||
|
|
||||||
output = data.stdout.split("\n") if data else []
|
|
||||||
output_data = {
|
|
||||||
"myanalyzer": {
|
|
||||||
"status": "ok",
|
|
||||||
"output": output,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if zip_exist:
|
|
||||||
output_data["myanalyzer"][
|
|
||||||
"download"
|
|
||||||
] = f"/download/{output_dir.name}/myanalyzer"
|
|
||||||
|
|
||||||
update_data(output_dir, output_data)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
update_data(output_dir, {"myanalyzer": {"status": "error", "error": str(e)}})
|
|
||||||
return None
|
|
||||||
|
|||||||
@@ -1,43 +1,31 @@
|
|||||||
# flake8: noqa: E203,E501,W503
|
# flake8: noqa: E203,E501,W503
|
||||||
# pylint: disable=C0413,W0718,R0903,R0801
|
# pylint: disable=C0413,W0718,R0903,R0801
|
||||||
# mypy: disable-error-code=unused-awaitable
|
# mypy: disable-error-code=unused-awaitable
|
||||||
"""Zsteg Analyzer for Image Submissions."""
|
"""Strings Analyzer for Image Submissions."""
|
||||||
|
|
||||||
import subprocess
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from .utils import MAX_PENDING_TIME, update_data
|
from .base_analyzer import SubprocessAnalyzer
|
||||||
|
|
||||||
|
|
||||||
|
class ZstegAnalyzer(SubprocessAnalyzer):
|
||||||
|
"""Analyzer for zsteg."""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__("zsteg", *args, **kwargs)
|
||||||
|
self.cmd = ["zsteg", self.img]
|
||||||
|
|
||||||
|
def is_error(self, returncode: int, stdout: str, stderr: str, zip_exist: bool) -> bool:
|
||||||
|
return bool(stderr) or "PNG::NotSupported" in stdout[:100]
|
||||||
|
|
||||||
|
def process_error(self, stdout: str, stderr: str) -> str:
|
||||||
|
"""Process the stderr."""
|
||||||
|
if "PNG::NotSupported" in stdout[:100]:
|
||||||
|
return "The file format of the file is not supported (PNG only)."
|
||||||
|
return stderr
|
||||||
|
|
||||||
|
|
||||||
def analyze_zsteg(input_img: Path, output_dir: Path) -> None:
|
def analyze_zsteg(input_img: Path, output_dir: Path) -> None:
|
||||||
"""Analyze an image submission using zsteg."""
|
"""Analyze an image submission using zsteg."""
|
||||||
|
analyzer = ZstegAnalyzer(input_img, output_dir)
|
||||||
try:
|
analyzer.analyze()
|
||||||
data = subprocess.run(
|
|
||||||
["zsteg", input_img],
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
check=False,
|
|
||||||
timeout=MAX_PENDING_TIME,
|
|
||||||
)
|
|
||||||
if data.stderr or "PNG::NotSupported" in data.stdout[:100]:
|
|
||||||
error_msg = data.stderr or data.stdout
|
|
||||||
update_data(output_dir, {"zsteg": {"status": "error", "error": error_msg}})
|
|
||||||
return
|
|
||||||
|
|
||||||
# zsteg_dir = output_dir / "zsteg"
|
|
||||||
# zsteg_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
# with open(zsteg_dir / "zsteg.txt", "w", encoding="utf-8") as f:
|
|
||||||
# subprocess.run(["zsteg", input_img], stdout=f, check=True)
|
|
||||||
|
|
||||||
update_data(
|
|
||||||
output_dir,
|
|
||||||
{
|
|
||||||
"zsteg": {
|
|
||||||
"status": "ok",
|
|
||||||
"output": data.stdout.split("\n") if data else [],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
update_data(output_dir, {"zsteg": {"status": "error", "error": str(e)}})
|
|
||||||
|
|||||||
Reference in New Issue
Block a user