Merge pull request #117 from Zeecka/main
## What's Changed * CRC Database lookup for image_resize.py, fix #96 by @Philip-Zimmermann in https://github.com/Zeecka/AperiSolve/pull/106 * OpenStego by @Zeecka in https://github.com/Zeecka/AperiSolve/pull/111 * fix #77 threejs on cpu by @Zeecka in https://github.com/Zeecka/AperiSolve/pull/112 * fix 69 by @Zeecka in https://github.com/Zeecka/AperiSolve/pull/116 **Full Changelog**: https://github.com/Zeecka/AperiSolve/compare/3.0.7...3.1.0
This commit is contained in:
@@ -2,3 +2,4 @@
|
|||||||
__pycache__/
|
__pycache__/
|
||||||
.mypy_cache/
|
.mypy_cache/
|
||||||
results/
|
results/
|
||||||
|
.env
|
||||||
|
|||||||
+19
-5
@@ -2,20 +2,34 @@ FROM python:3.14-slim AS build
|
|||||||
|
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
|
|
||||||
# Install dependencies
|
# Install build tools and dependencies
|
||||||
RUN apt-get update && apt-get install -y \
|
RUN apt-get update && apt-get install -y \
|
||||||
|
wget \
|
||||||
|
default-jre \
|
||||||
|
ruby \
|
||||||
zip \
|
zip \
|
||||||
7zip \
|
7zip
|
||||||
|
|
||||||
|
# Install steganography and forensics tools
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
binwalk \
|
binwalk \
|
||||||
foremost \
|
foremost \
|
||||||
exiftool \
|
exiftool \
|
||||||
steghide \
|
steghide \
|
||||||
ruby \
|
|
||||||
binutils \
|
binutils \
|
||||||
outguess \
|
outguess \
|
||||||
pngcheck \
|
pngcheck \
|
||||||
&& gem install zsteg \
|
&& gem install zsteg
|
||||||
&& apt-get clean
|
|
||||||
|
# Install OpenStego
|
||||||
|
RUN wget https://github.com/syvaidya/openstego/releases/download/openstego-0.8.6/openstego_0.8.6-1_all.deb -O /tmp/openstego.deb && \
|
||||||
|
dpkg -i /tmp/openstego.deb && \
|
||||||
|
rm /tmp/openstego.deb
|
||||||
|
|
||||||
|
# Clean up apt cache and remove useless packages
|
||||||
|
RUN apt-get remove -y wget && \
|
||||||
|
apt-get clean && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
COPY aperisolve/ /aperisolve/
|
COPY aperisolve/ /aperisolve/
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ Support Aperi'Solve:
|
|||||||
<img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" height="40" />
|
<img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" height="40" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Aperi'Solve is an open-source steganalysis web platform that performs automated analysis on images to detect and extract hidden data using common steganography tools and techniques.
|
Aperi'Solve is an open-source steganalysis web platform that performs automated analysis on images to detect and extract hidden data using common steganography tools and techniques.
|
||||||
@@ -41,14 +40,15 @@ Table of contents
|
|||||||
- Visualize each bit layer (LSB and other layers) per image channel (R/G/B/Alpha).
|
- Visualize each bit layer (LSB and other layers) per image channel (R/G/B/Alpha).
|
||||||
- Browse and download each bit-layer image.
|
- Browse and download each bit-layer image.
|
||||||
- Integrates and displays outputs from:
|
- Integrates and displays outputs from:
|
||||||
- zsteg (LSB text/data extraction)
|
- [zsteg](https://github.com/zed-0xff/zsteg) (LSB text/data extraction)
|
||||||
- steghide (extraction with password)
|
- [steghide](https://steghide.sourceforge.net/) (extraction with password)
|
||||||
- outguess (extraction with password)
|
- [outguess](https://www.rbcafe.com/software/outguess/) (extraction with password)
|
||||||
- exiftool (metadata and geolocation)
|
- [openstego](https://www.openstego.com/) (extraction with password)
|
||||||
- binwalk (embedded archives)
|
- [exiftool](https://exiftool.org/) (metadata and geolocation)
|
||||||
- foremost (carved files)
|
- [binwalk](https://github.com/ReFirmLabs/binwalk) (embedded archives)
|
||||||
- pngcheck
|
- [foremost](https://foremost.sourceforge.net/) (carved files)
|
||||||
- strings
|
- [pngcheck](https://www.libpng.org/pub/png/apps/pngcheck.html)
|
||||||
|
- [strings](https://pubs.opengroup.org/onlinepubs/9799919799/utilities/strings.html)
|
||||||
- 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.
|
||||||
|
|
||||||
@@ -86,10 +86,12 @@ docker exec -it aperisolve-web bash
|
|||||||
docker exec -it postgres psql -U aperiuser -d aperisolve
|
docker exec -it postgres psql -U aperiuser -d aperisolve
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: If switching between dev and production compose files, remove the `results` directory or mounted volume to avoid conflicts:
|
> [!WARNING]
|
||||||
```bash
|
> If switching between dev and production compose files, remove the `results` directory or mounted volume to avoid conflicts:
|
||||||
rm -rf aperisolve/results
|
> ```bash
|
||||||
```
|
> rm -rf aperisolve/results
|
||||||
|
> ```
|
||||||
|
|
||||||
### Adding a new analyzer
|
### Adding a new analyzer
|
||||||
|
|
||||||
Adding a custom analyzer is straightforward:
|
Adding a custom analyzer is straightforward:
|
||||||
@@ -128,9 +130,9 @@ Adding a custom analyzer is straightforward:
|
|||||||
|
|
||||||
4. Test locally: run the worker and submit jobs to ensure outputs are produced and displayed.
|
4. Test locally: run the worker and submit jobs to ensure outputs are produced and displayed.
|
||||||
|
|
||||||
Tips:
|
> [!TIP]
|
||||||
- Keep analyzers idempotent and write outputs to the provided `results_dir`.
|
> - Keep analyzers idempotent and write outputs to the provided `results_dir`.
|
||||||
- Return structured JSON so the frontend can render links/downloads automatically.
|
> - Return structured JSON so the frontend can render links/downloads automatically.
|
||||||
|
|
||||||
## Configuration & environment variables
|
## Configuration & environment variables
|
||||||
|
|
||||||
@@ -146,7 +148,8 @@ Main environment variables (examples):
|
|||||||
- SECRET_KEY=change_me
|
- SECRET_KEY=change_me
|
||||||
- FLASK_ENV=production/development
|
- FLASK_ENV=production/development
|
||||||
|
|
||||||
If using Docker Compose, defaults are set in the compose files. For production deployments, set secure secrets via your orchestrator or environment.
|
> [!NOTE]
|
||||||
|
> If using Docker Compose, defaults are set in the compose files. For production deployments, set secure secrets via your orchestrator or environment.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
@@ -168,11 +171,17 @@ This separation keeps heavy tools (binwalk, foremost, zsteg, etc.) isolated and
|
|||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
- [ ] zsteg: full extraction (--all) and download of discovered files (mp3, etc.)
|
- [ ] **[Bug]** Error 500 when file already exist on disc
|
||||||
- [ ] Mobile-friendly UI / test on mobile
|
- [ ] **[Bug]** Multiple bug on buggy Jpeg filebugSomething isn't working
|
||||||
- [ ] i18n (internationalization)
|
- [ ] **[Bug]** Binwalk Extractor errorbugSomething isn't working
|
||||||
- [ ] Rootless / unprivileged analyzers
|
- [ ] **[Bug]** Grayscale on a RGB image ?bugSomething isn't working
|
||||||
- [ ] Improve analyzer sandboxing (e.g., per-analyzer containers)
|
- [ ] **[Feature]** Implement healthcheck and auto reboot
|
||||||
|
- [ ] **[Feature]** Implement Sentry for better error handling
|
||||||
|
- [ ] **[Feature]** Zsteg: full extraction (--all) and download of discovered files (mp3, etc.)
|
||||||
|
- [ ] **[Feature]** Mobile-friendly UI / test on mobile
|
||||||
|
- [ ] **[Feature]** i18n (internationalization)
|
||||||
|
- [ ] **[Feature]** Rootless / unprivileged analyzers
|
||||||
|
- [ ] **[Feature]** Improve analyzer sandboxing (e.g., per-analyzer containers)
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
@@ -181,20 +190,21 @@ Contributions are welcome! Please:
|
|||||||
2. Create a feature branch
|
2. Create a feature branch
|
||||||
3. Open a pull request describing your change
|
3. Open a pull request describing your change
|
||||||
|
|
||||||
Please follow the code style and run linters before submitting.
|
> [!IMPORTANT]
|
||||||
The project adheres to:
|
> Please follow the code style and run linters before submitting. The project adheres to:
|
||||||
- Black
|
> - Black
|
||||||
- Flake8 (ignoring E203, E501, W503)
|
> - Flake8 (ignoring E203, E501, W503)
|
||||||
- Pylint (ignoring W0718, R0903, R0801)
|
> - Pylint (ignoring W0718, R0903, R0801)
|
||||||
- Mypy (ignoring unused-awaitable)
|
> - Mypy (ignoring unused-awaitable)
|
||||||
|
|
||||||
CI will run these checks on each PR.
|
CI will run these checks on each PR.
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
- If you discover a security issue, please report it privately to the repository owner instead of opening a public issue.
|
> [!WARNING]
|
||||||
|
> If you discover a security issue, please report it privately to the repository owner instead of opening a public issue.
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
Acknowledgements:
|
Acknowledgements:
|
||||||
- Thanks to contributors and the open-source community for the tools integrated (zsteg, steghide, binwalk, foremost, exiftool).
|
- Thanks to contributors and the open-source community for the tools integrated ([zsteg](https://github.com/zed-0xff/zsteg), [steghide](https://steghide.sourceforge.net/), [outguess](https://www.rbcafe.com/software/outguess/), [openstego](https://www.openstego.com/), [exiftool](https://exiftool.org/), [binwalk](https://github.com/ReFirmLabs/binwalk), [foremost](https://foremost.sourceforge.net/), [pngcheck](https://www.libpng.org/pub/png/apps/pngcheck.html), [strings](https://pubs.opengroup.org/onlinepubs/9799919799/utilities/strings.html), ...).
|
||||||
|
|||||||
@@ -11,6 +11,12 @@ from .utils import update_data
|
|||||||
def analyze_decomposer(input_img: Path, output_dir: Path) -> None:
|
def analyze_decomposer(input_img: Path, output_dir: Path) -> None:
|
||||||
"""Analyze an image submission using bits decomposition."""
|
"""Analyze an image submission using bits decomposition."""
|
||||||
img = Image.open(input_img)
|
img = Image.open(input_img)
|
||||||
|
converted = False
|
||||||
|
|
||||||
|
if img.mode == "P":
|
||||||
|
img = img.convert("RGB")
|
||||||
|
converted = True
|
||||||
|
|
||||||
img_np = np.array(img)
|
img_np = np.array(img)
|
||||||
|
|
||||||
# Handle grayscale, RGB, RGBA, etc.
|
# Handle grayscale, RGB, RGBA, etc.
|
||||||
@@ -58,12 +64,15 @@ def analyze_decomposer(input_img: Path, output_dir: Path) -> None:
|
|||||||
bit_img.save(out_path)
|
bit_img.save(out_path)
|
||||||
image_json[channel_label] = channel_json
|
image_json[channel_label] = channel_json
|
||||||
|
|
||||||
update_data(
|
output = {
|
||||||
output_dir,
|
"decomposer": {
|
||||||
{
|
"status": "ok",
|
||||||
"decomposer": {
|
"images": image_json,
|
||||||
"status": "ok",
|
}
|
||||||
"images": image_json,
|
}
|
||||||
}
|
if converted:
|
||||||
},
|
output["decomposer"][
|
||||||
)
|
"note"
|
||||||
|
] = "Image contains a color palette and was converted to RGB for processing."
|
||||||
|
|
||||||
|
update_data(output_dir, output)
|
||||||
|
|||||||
@@ -18,21 +18,24 @@ def analyze_exiftool(input_img: Path, output_dir: Path) -> None:
|
|||||||
timeout=MAX_PENDING_TIME,
|
timeout=MAX_PENDING_TIME,
|
||||||
)
|
)
|
||||||
|
|
||||||
if data.stderr:
|
|
||||||
update_data(
|
|
||||||
output_dir, {"exiftool": {"status": "error", "error": data.stderr}}
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
metadata: dict[str, str] = {}
|
metadata: dict[str, str] = {}
|
||||||
for line in data.stdout.split("\n"):
|
for line in data.stdout.split("\n"):
|
||||||
if ":" in line:
|
if ":" in line:
|
||||||
key, value = line.split(":", 1)
|
key, value = line.split(":", 1)
|
||||||
metadata[key.strip()] = value.strip()
|
metadata[key.strip()] = value.strip()
|
||||||
|
|
||||||
# exiftool_dir = output_dir / "exiftool"
|
if data.stderr:
|
||||||
# exiftool_dir.mkdir(parents=True, exist_ok=True)
|
update_data(
|
||||||
# update_data(exiftool_dir, metadata, "data.json")
|
output_dir,
|
||||||
|
{
|
||||||
|
"exiftool": {
|
||||||
|
"status": "error",
|
||||||
|
"output": metadata,
|
||||||
|
"error": data.stderr,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
update_data(
|
update_data(
|
||||||
output_dir,
|
output_dir,
|
||||||
|
|||||||
@@ -146,6 +146,17 @@ def analyze_image_resize(input_img: Path, output_dir: Path) -> None:
|
|||||||
|
|
||||||
logs.append(f"Target CRC found: 0x{png.crc:08x}")
|
logs.append(f"Target CRC found: 0x{png.crc:08x}")
|
||||||
|
|
||||||
|
if png.crc == png.compute_crc():
|
||||||
|
note = (
|
||||||
|
f"PNG is already valid with dimensions {png.width}x{png.height} "
|
||||||
|
f"and crc 0x{png.crc:08x}."
|
||||||
|
)
|
||||||
|
output_no_compute: dict[str, Any] = {
|
||||||
|
"image_resize": {"status": "ok", "note": note}
|
||||||
|
}
|
||||||
|
update_data(output_dir, output_no_compute)
|
||||||
|
return
|
||||||
|
|
||||||
saved_img_urls = []
|
saved_img_urls = []
|
||||||
|
|
||||||
db_matches = lookup_crc(png)
|
db_matches = lookup_crc(png)
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
"""OpenStego Analyzer for Image Submissions."""
|
||||||
|
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from .utils import MAX_PENDING_TIME, update_data
|
||||||
|
|
||||||
|
|
||||||
|
def analyze_openstego(input_img: Path, output_dir: Path, password: str = "") -> None:
|
||||||
|
"""Analyze an image submission using openstego."""
|
||||||
|
|
||||||
|
image_name = input_img.name
|
||||||
|
try:
|
||||||
|
stderr = ""
|
||||||
|
# Try to extract the embedded file
|
||||||
|
extracted_dir = output_dir / "openstego"
|
||||||
|
extracted_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
for algo in ["AES128", "AES256"]:
|
||||||
|
cmd: list[str] = [
|
||||||
|
"openstego",
|
||||||
|
"extract",
|
||||||
|
"-a",
|
||||||
|
"randomlsb",
|
||||||
|
"--cryptalgo",
|
||||||
|
algo,
|
||||||
|
"-sf",
|
||||||
|
"../" + str(image_name),
|
||||||
|
"-xd",
|
||||||
|
str(extracted_dir),
|
||||||
|
"-p",
|
||||||
|
password,
|
||||||
|
]
|
||||||
|
|
||||||
|
data = subprocess.run(
|
||||||
|
cmd,
|
||||||
|
cwd=output_dir,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=False,
|
||||||
|
timeout=MAX_PENDING_TIME,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check if extraction was successful
|
||||||
|
if data.returncode != 0:
|
||||||
|
stderr += data.stderr.replace(
|
||||||
|
f'"../{image_name}" ', ""
|
||||||
|
) # hide file name
|
||||||
|
err = {
|
||||||
|
"openstego": {
|
||||||
|
"status": "error",
|
||||||
|
"error": stderr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
update_data(output_dir, err)
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Find the extracted file
|
||||||
|
extracted_files = list(extracted_dir.glob("*"))
|
||||||
|
if not extracted_files:
|
||||||
|
err = {
|
||||||
|
"openstego": {
|
||||||
|
"status": "error",
|
||||||
|
"error": "No file extracted, password may be incorrect.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
update_data(output_dir, err)
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Filter stdout for success messages
|
||||||
|
stdout = []
|
||||||
|
for f in extracted_files:
|
||||||
|
stdout.append(f"Recovered file: {f.name}")
|
||||||
|
|
||||||
|
# Zip extracted files
|
||||||
|
zip_data = subprocess.run(
|
||||||
|
["7z", "a", "../openstego.7z", "*"],
|
||||||
|
cwd=extracted_dir,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=False,
|
||||||
|
timeout=MAX_PENDING_TIME,
|
||||||
|
)
|
||||||
|
|
||||||
|
stderr += zip_data.stderr
|
||||||
|
|
||||||
|
if len(stderr) > 0:
|
||||||
|
err = {
|
||||||
|
"openstego": {
|
||||||
|
"status": "error",
|
||||||
|
"error": stderr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
update_data(output_dir, err)
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Remove the extracted directory
|
||||||
|
shutil.rmtree(extracted_dir)
|
||||||
|
|
||||||
|
update_data(
|
||||||
|
output_dir,
|
||||||
|
{
|
||||||
|
"openstego": {
|
||||||
|
"status": "ok",
|
||||||
|
"output": stdout,
|
||||||
|
"download": f"/download/{output_dir.name}/openstego",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
update_data(output_dir, {"openstego": {"status": "error", "error": str(e)}})
|
||||||
|
return None
|
||||||
@@ -24,6 +24,19 @@ def analyze_pngcheck(input_img: Path, output_dir: Path) -> None:
|
|||||||
# )
|
# )
|
||||||
# return
|
# 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 = data.stdout.split("\n") if data else []
|
||||||
data_pngcheck = [s for s in data_pngcheck if s]
|
data_pngcheck = [s for s in data_pngcheck if s]
|
||||||
|
|
||||||
|
|||||||
@@ -180,17 +180,24 @@ class PNG:
|
|||||||
self.height = size[1]
|
self.height = size[1]
|
||||||
self.bit_depth = bit_depth
|
self.bit_depth = bit_depth
|
||||||
self.color_type = color_type
|
self.color_type = color_type
|
||||||
self.crc = (
|
self.interlace = interlace
|
||||||
crc
|
self.crc = crc or self.compute_crc()
|
||||||
or zlib.crc32(
|
|
||||||
|
def compute_crc(self) -> int:
|
||||||
|
"""Compute the CRC checksum for the PNG IHDR chunk.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: The computed CRC checksum.
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
zlib.crc32(
|
||||||
b"IHDR"
|
b"IHDR"
|
||||||
+ self.width.to_bytes(4, "big")
|
+ self.width.to_bytes(4, "big")
|
||||||
+ self.height.to_bytes(4, "big")
|
+ self.height.to_bytes(4, "big")
|
||||||
+ bytes((bit_depth, color_type, 0, 0, interlace))
|
+ bytes((self.bit_depth, self.color_type, 0, 0, self.interlace))
|
||||||
)
|
)
|
||||||
& 0xFFFFFFFF
|
& 0xFFFFFFFF
|
||||||
)
|
)
|
||||||
self.interlace = interlace
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_packed(cls, packed: int, crc: int | None = None) -> Self:
|
def from_packed(cls, packed: int, crc: int | None = None) -> Self:
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
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", "image_resize"]
|
WORKER_FILES = ["binwalk", "foremost", "steghide", "zsteg", "image_resize", "openstego"]
|
||||||
|
|
||||||
RESULT_FOLDER = Path(__file__).parent.resolve() / "results"
|
RESULT_FOLDER = Path(__file__).parent.resolve() / "results"
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const TOOL_ORDER = [
|
|||||||
"outguess",
|
"outguess",
|
||||||
"pngcheck",
|
"pngcheck",
|
||||||
"steghide",
|
"steghide",
|
||||||
|
"openstego",
|
||||||
"zsteg",
|
"zsteg",
|
||||||
"strings",
|
"strings",
|
||||||
"image_resize",
|
"image_resize",
|
||||||
@@ -326,9 +327,15 @@ function parseResult(result) {
|
|||||||
result[tool]["download"]
|
result[tool]["download"]
|
||||||
)}" target="_blank" class="btn btn-primary mt-2"><i class="fa fa-download"></i> Download file</a>`;
|
)}" target="_blank" class="btn btn-primary mt-2"><i class="fa fa-download"></i> Download file</a>`;
|
||||||
}
|
}
|
||||||
} else if (result[tool]["status"] === "error") {
|
}
|
||||||
|
|
||||||
|
if (result[tool]["error"] && (/[^\s]/.test(result[tool]["error"]))) {
|
||||||
showDanger(result[tool]["error"]);
|
showDanger(result[tool]["error"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (result[tool]["note"] && (/[^\s]/.test(result[tool]["note"]))) {
|
||||||
|
showInfo(result[tool]["note"]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,27 @@ const tmpQ = new THREE.Quaternion();
|
|||||||
const tmpM = new THREE.Matrix4();
|
const tmpM = new THREE.Matrix4();
|
||||||
const currentM = new THREE.Matrix4();
|
const currentM = new THREE.Matrix4();
|
||||||
|
|
||||||
init();
|
|
||||||
|
function isSoftwareRenderer() {
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
const gl =
|
||||||
|
canvas.getContext("webgl") ||
|
||||||
|
canvas.getContext("experimental-webgl");
|
||||||
|
|
||||||
|
if (!gl) return true;
|
||||||
|
|
||||||
|
const ext = gl.getExtension("WEBGL_debug_renderer_info");
|
||||||
|
if (!ext) return false;
|
||||||
|
|
||||||
|
const renderer = gl.getParameter(ext.UNMASKED_RENDERER_WEBGL);
|
||||||
|
return /swiftshader|software/i.test(renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSoftwareRenderer()) {
|
||||||
|
console.warn("Software rendering detected — disabling Three.js");
|
||||||
|
} else {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
container = document.getElementById("three-bg");
|
container = document.getElementById("three-bg");
|
||||||
@@ -163,5 +183,5 @@ function animate() {
|
|||||||
|
|
||||||
renderer.render(scene, camera);
|
renderer.render(scene, camera);
|
||||||
|
|
||||||
stats.update();
|
//stats.update();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from .analyzers.decomposer import analyze_decomposer
|
|||||||
from .analyzers.exiftool import analyze_exiftool
|
from .analyzers.exiftool import analyze_exiftool
|
||||||
from .analyzers.foremost import analyze_foremost
|
from .analyzers.foremost import analyze_foremost
|
||||||
from .analyzers.image_resize import analyze_image_resize
|
from .analyzers.image_resize import analyze_image_resize
|
||||||
|
from .analyzers.openstego import analyze_openstego
|
||||||
from .analyzers.outguess import analyze_outguess
|
from .analyzers.outguess import analyze_outguess
|
||||||
from .analyzers.pngcheck import analyze_pngcheck
|
from .analyzers.pngcheck import analyze_pngcheck
|
||||||
from .analyzers.steghide import analyze_steghide
|
from .analyzers.steghide import analyze_steghide
|
||||||
@@ -77,6 +78,7 @@ def analyze_image(submission_hash: str) -> None:
|
|||||||
(analyze_strings, img_path, result_path),
|
(analyze_strings, img_path, result_path),
|
||||||
(analyze_pngcheck, img_path, result_path),
|
(analyze_pngcheck, img_path, result_path),
|
||||||
(analyze_steghide, img_path, result_path, submission.password),
|
(analyze_steghide, img_path, result_path, submission.password),
|
||||||
|
(analyze_openstego, img_path, result_path, submission.password),
|
||||||
(analyze_zsteg, img_path, result_path),
|
(analyze_zsteg, img_path, result_path),
|
||||||
(analyze_image_resize, img_path, result_path),
|
(analyze_image_resize, img_path, result_path),
|
||||||
]
|
]
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 62 KiB |
Reference in New Issue
Block a user