Adding jphide analyzer
This commit is contained in:
+10
-4
@@ -251,10 +251,16 @@ RUN apt-get update && apt-get install -y \
|
|||||||
|
|
||||||
Or for tools requiring compilation:
|
Or for tools requiring compilation:
|
||||||
```dockerfile
|
```dockerfile
|
||||||
RUN git clone https://github.com/tool/repo.git /tmp/tool && \
|
# Builder stage example (jphide)
|
||||||
cd /tmp/tool && \
|
RUN git clone https://github.com/h3xx/jphs.git /tmp/jphs && \
|
||||||
make && make install && \
|
cd /tmp/jphs && \
|
||||||
rm -rf /tmp/tool
|
make && \
|
||||||
|
cp jphide jpseek /usr/local/bin/ && \
|
||||||
|
rm -rf /tmp/jphs
|
||||||
|
|
||||||
|
# Runtime stage - copy compiled binaries
|
||||||
|
COPY --from=builder /usr/local/bin/jphide /usr/local/bin/jphide
|
||||||
|
COPY --from=builder /usr/local/bin/jpseek /usr/local/bin/jpseek
|
||||||
```
|
```
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
|
|||||||
+19
-1
@@ -5,13 +5,26 @@ FROM python:3.14-slim AS builder
|
|||||||
|
|
||||||
# Install wget only to download OpenStego
|
# Install wget only to download OpenStego
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
wget \
|
wget git build-essential \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Download OpenStego
|
# Download OpenStego
|
||||||
RUN wget https://github.com/syvaidya/openstego/releases/download/openstego-0.8.6/openstego_0.8.6-1_all.deb \
|
RUN wget https://github.com/syvaidya/openstego/releases/download/openstego-0.8.6/openstego_0.8.6-1_all.deb \
|
||||||
-O /tmp/openstego.deb
|
-O /tmp/openstego.deb
|
||||||
|
|
||||||
|
# Builder stage - add after other tool compilations
|
||||||
|
RUN git clone https://github.com/h3xx/jphs.git /tmp/jphs
|
||||||
|
RUN cd /tmp/jphs/jpeg-8a && ./configure && make
|
||||||
|
RUN cd /tmp/jphs && sed -i 's/open(seekfilename,O_WRONLY|O_TRUNC|O_CREAT)/open(seekfilename,O_WRONLY|O_TRUNC|O_CREAT, 0644)/' jpseek.c
|
||||||
|
RUN cd /tmp/jphs \
|
||||||
|
&& sed -i 's/^LIBS = .*/JPEG_OBJS = $(filter-out jpeg-8a\/cjpeg.o jpeg-8a\/djpeg.o jpeg-8a\/jpegtran.o jpeg-8a\/rdjpgcom.o jpeg-8a\/wrjpgcom.o,$(wildcard jpeg-8a\/\*.o))/' Makefile \
|
||||||
|
&& sed -i 's/^LDFLAGS = .*/LDFLAGS =/' Makefile \
|
||||||
|
&& sed -i 's/^jphide: \(.*\)$/jphide: \1 $(JPEG_OBJS)/' Makefile \
|
||||||
|
&& sed -i 's/^jpseek: \(.*\)$/jpseek: \1 $(JPEG_OBJS)/' Makefile
|
||||||
|
RUN cd /tmp/jphs && make all
|
||||||
|
RUN cd /tmp/jphs && cp jphide jpseek /usr/local/bin/
|
||||||
|
RUN rm -rf /tmp/jphs
|
||||||
|
|
||||||
# ==========================
|
# ==========================
|
||||||
# Stage 2 : Image runtime minimale
|
# Stage 2 : Image runtime minimale
|
||||||
# ==========================
|
# ==========================
|
||||||
@@ -21,6 +34,7 @@ WORKDIR /app
|
|||||||
|
|
||||||
# Install runtime + stego tools
|
# Install runtime + stego tools
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
expect \
|
||||||
default-jre \
|
default-jre \
|
||||||
file \
|
file \
|
||||||
ruby \
|
ruby \
|
||||||
@@ -48,5 +62,9 @@ COPY aperisolve/ /app/aperisolve/
|
|||||||
# Install Python dependencies
|
# Install Python dependencies
|
||||||
RUN pip install --no-cache-dir -r /app/aperisolve/requirements.txt
|
RUN pip install --no-cache-dir -r /app/aperisolve/requirements.txt
|
||||||
|
|
||||||
|
# Copy jphide binaries
|
||||||
|
COPY --from=builder /usr/local/bin/jphide /usr/local/bin/jphide
|
||||||
|
COPY --from=builder /usr/local/bin/jpseek /usr/local/bin/jpseek
|
||||||
|
|
||||||
# Commande de lancement
|
# Commande de lancement
|
||||||
CMD ["gunicorn", "-w", "8", "-b", "0.0.0.0:5000", "--access-logfile", "-", "--error-logfile", "-", "--log-level", "info", "--capture-output", "aperisolve.utils.wsgi:application"]
|
CMD ["gunicorn", "-w", "8", "-b", "0.0.0.0:5000", "--access-logfile", "-", "--error-logfile", "-", "--log-level", "info", "--capture-output", "aperisolve.utils.wsgi:application"]
|
||||||
|
|||||||
@@ -45,7 +45,8 @@ Aperi'Solve is an open-source steganalysis web platform that performs automated
|
|||||||
- [steghide](https://steghide.sourceforge.net/) (extraction with password)
|
- [steghide](https://steghide.sourceforge.net/) (extraction with password)
|
||||||
- [strings](https://pubs.opengroup.org/onlinepubs/9799919799/utilities/strings.html)
|
- [strings](https://pubs.opengroup.org/onlinepubs/9799919799/utilities/strings.html)
|
||||||
- [zsteg](https://github.com/zed-0xff/zsteg) (LSB text/data extraction)
|
- [zsteg](https://github.com/zed-0xff/zsteg) (LSB text/data extraction)
|
||||||
- [file](https://www.darwinsys.com/file/) (MIME type and format detection)
|
- [file](https://manned.org/file.1) (MIME type and format detection)
|
||||||
|
- [jpseek](https://github.com/h3xx/jphs) (JPEG steganography detection and extraction)
|
||||||
- Worker queue architecture for offloading heavy/slow analyzers (Redis + background workers).
|
- Worker queue architecture for offloading heavy/slow analyzers (Redis + background workers).
|
||||||
- Results stored for later browsing and download.
|
- Results stored for later browsing and download.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# flake8: noqa: E203,E501,W503
|
||||||
|
# pylint: disable=C0413,W0718,R0903,R0801
|
||||||
|
# mypy: disable-error-code=unused-awaitable
|
||||||
|
"""JPSeek Analyzer for Image Submissions."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from .base_analyzer import SubprocessAnalyzer
|
||||||
|
|
||||||
|
|
||||||
|
class JpseekAnalyzer(SubprocessAnalyzer):
|
||||||
|
"""Analyzer for jpseek."""
|
||||||
|
|
||||||
|
def __init__(self, input_img: Path, output_dir: Path) -> None:
|
||||||
|
super().__init__("jpseek", input_img, output_dir, has_archive=True)
|
||||||
|
|
||||||
|
def build_cmd(self, password: Optional[str] = None) -> list[str]:
|
||||||
|
extracted_dir = self.get_extracted_dir()
|
||||||
|
assert extracted_dir is not None # since has_archive is True
|
||||||
|
out = str(extracted_dir / "jpseek.out")
|
||||||
|
jpseek_cmd = f"jpseek {self.img} {out}"
|
||||||
|
if password is None:
|
||||||
|
password = ""
|
||||||
|
expect_cmd = (
|
||||||
|
f"expect -c 'spawn {jpseek_cmd}; "
|
||||||
|
f"expect \"Passphrase:\"; "
|
||||||
|
f"send \"{password}\\r\"; "
|
||||||
|
f"expect eof'"
|
||||||
|
)
|
||||||
|
command = ["bash", "-c", expect_cmd]
|
||||||
|
print("Built command:", command)
|
||||||
|
return command
|
||||||
|
|
||||||
|
def _remove_output_artifacts(self, output: str) -> str:
|
||||||
|
extracted_dir = self.get_extracted_dir()
|
||||||
|
out_file = str(extracted_dir / "jpseek.out")
|
||||||
|
o = output
|
||||||
|
o = o.replace("jpseek, version 0.3 (c) 1998 Allan Latham <alatham@flexsys-group.com>", "")
|
||||||
|
o = o.replace("This is licenced software but no charge is made for its use.", "")
|
||||||
|
o = o.replace("NO WARRANTY whatsoever is offered with this product.", "")
|
||||||
|
o = o.replace("NO LIABILITY whatsoever is accepted for its use.", "")
|
||||||
|
o = o.replace("You are using this entirely at your OWN RISK.", "")
|
||||||
|
o = o.replace("See the GNU Public Licence for full details.", "")
|
||||||
|
o = o.replace("Passphrase:", "")
|
||||||
|
o = o.replace(f"spawn jpseek {self.img} {out_file}", "")
|
||||||
|
return o.strip()
|
||||||
|
|
||||||
|
def is_error(self, returncode: int, stdout: str, stderr: str, zip_exist: bool) -> bool:
|
||||||
|
"""Check if the result is an error."""
|
||||||
|
return bool(returncode) and "File not completely recovered" not in "".join([stdout, stderr])
|
||||||
|
|
||||||
|
def process_error(self, stdout: str, stderr: str) -> str:
|
||||||
|
"""Process stderr."""
|
||||||
|
output = stdout + stderr
|
||||||
|
output = self._remove_output_artifacts(output)
|
||||||
|
return output
|
||||||
|
|
||||||
|
def process_output(self, stdout: str, stderr: str) -> str:
|
||||||
|
"""Process stdout."""
|
||||||
|
output = stdout + stderr
|
||||||
|
output = self._remove_output_artifacts(output)
|
||||||
|
if output == "":
|
||||||
|
output = "File completely recovered."
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def analyze_jpseek(input_img: Path, output_dir: Path, password: Optional[str] = None) -> None:
|
||||||
|
"""Analyze an image submission using jpseek."""
|
||||||
|
analyzer = JpseekAnalyzer(input_img, output_dir)
|
||||||
|
if password:
|
||||||
|
analyzer.analyze(password)
|
||||||
|
else:
|
||||||
|
analyzer.analyze()
|
||||||
@@ -7,7 +7,7 @@ from os import getenv
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
IMAGE_EXTENSIONS = [".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp", ".tiff"]
|
IMAGE_EXTENSIONS = [".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp", ".tiff"]
|
||||||
WORKER_FILES = ["binwalk", "foremost", "steghide", "zsteg", "openstego", "pcrt"]
|
WORKER_FILES = ["binwalk", "foremost", "steghide", "zsteg", "openstego", "pcrt", "jpseek"]
|
||||||
|
|
||||||
RESULT_FOLDER = Path(__file__).parent.resolve() / "results"
|
RESULT_FOLDER = Path(__file__).parent.resolve() / "results"
|
||||||
REMOVED_IMAGES_FOLDER = Path(__file__).parent.resolve() / "removed_images"
|
REMOVED_IMAGES_FOLDER = Path(__file__).parent.resolve() / "removed_images"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const TOOL_ORDER = [
|
|||||||
"pngcheck",
|
"pngcheck",
|
||||||
"pcrt",
|
"pcrt",
|
||||||
"steghide",
|
"steghide",
|
||||||
|
"jpseek",
|
||||||
"openstego",
|
"openstego",
|
||||||
"zsteg",
|
"zsteg",
|
||||||
"strings"
|
"strings"
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from .analyzers.decomposer import analyze_decomposer
|
|||||||
from .analyzers.exiftool import analyze_exiftool
|
from .analyzers.exiftool import analyze_exiftool
|
||||||
from .analyzers.file import analyze_file
|
from .analyzers.file import analyze_file
|
||||||
from .analyzers.foremost import analyze_foremost
|
from .analyzers.foremost import analyze_foremost
|
||||||
|
from .analyzers.jpseek import analyze_jpseek
|
||||||
from .analyzers.openstego import analyze_openstego
|
from .analyzers.openstego import analyze_openstego
|
||||||
from .analyzers.outguess import analyze_outguess
|
from .analyzers.outguess import analyze_outguess
|
||||||
from .analyzers.pcrt import analyze_pcrt
|
from .analyzers.pcrt import analyze_pcrt
|
||||||
@@ -106,6 +107,7 @@ def analyze_image(submission_hash: str) -> None:
|
|||||||
(analyze_exiftool, img_path, result_path),
|
(analyze_exiftool, img_path, result_path),
|
||||||
(analyze_file, img_path, result_path),
|
(analyze_file, img_path, result_path),
|
||||||
(analyze_foremost, img_path, result_path),
|
(analyze_foremost, img_path, result_path),
|
||||||
|
(analyze_jpseek, img_path, result_path, submission.password),
|
||||||
(analyze_openstego, img_path, result_path, submission.password),
|
(analyze_openstego, img_path, result_path, submission.password),
|
||||||
(analyze_pngcheck, img_path, result_path),
|
(analyze_pngcheck, img_path, result_path),
|
||||||
(analyze_pcrt, img_path, result_path),
|
(analyze_pcrt, img_path, result_path),
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ This guide shows you how to add a new image analysis tool to the system.
|
|||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
1. Create a new file in `analyzers/` from template file `template_analyzer.py`.
|
1. Create a new file in `analyzers/` from template file `template_analyzer.py`.
|
||||||
2. Register it in `workers.py`, `config.py` and `aperisolve.js`.
|
2. Register it in `workers.py`, `config.py` (if it extracts files) and `aperisolve.js`.
|
||||||
|
|
||||||
## Basic Template
|
## Basic Template
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ def analyze_mytool(input_img: Path, output_dir: Path) -> None:
|
|||||||
analyzer.analyze()
|
analyzer.analyze()
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also see `template_analyzer.py`.
|
You can also see `template_analyzer.py` or check real examples like `jpseek.py`, `steghide.py`, or `strings.py`.
|
||||||
|
|
||||||
## Common Scenarios
|
## Common Scenarios
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user