
Found a 0-Day in Ghidra: Shared Project File Became a Code Execution Vector
Advisory: GHSA-pcfh-853f-q3gh · Severity: High (CVSS 7.5) · Affected: Ghidra ≤ 12.1.2 · Fixed: c03a70d (Ghidra 12.1.3)
Opening someone else's Ghidra project was enough to execute their code.
Ghidra 12.1.2 stores analyzer options inside the program database, and one of those options is a filesystem path to a Swift toolchain directory. When analysis runs, Ghidra joins that path with swift-demangle and executes it no signature check, no integrity check, no prompt. A shared project, an imported .gzf, or a cloned research repo silently carries the attacker's path with it.
The result is arbitrary code execution in the Ghidra user's context. On headless and CI setups, there is no human in the loop at all.
📖 Full technical writeup: https://sn0xs-organization.gitbook.io/sn0x-order.org/bb-web-hunt/critical/how-i-found-a-0-day-in-ghidra-shared-project-file-became-a-code-execution-vector.
The Swift demangler analyzer restores a configurable Swift tool directory from saved program state, and the native demangler wrapper executes whatever binary resolves under it.
// SwiftDemanglerAnalyzer.java — value restored from program-persisted state
String swiftDir = options.getString(SWIFT_TOOL_DIR_OPTION, null);
SwiftNativeDemangler demangler = new SwiftNativeDemangler(new File(swiftDir));
// SwiftNativeDemangler.java — the resolved binary is executed twice
private File swiftDemanglerPath = new File(swiftToolDir, "swift-demangle");
new ProcessBuilder(swiftDemanglerPath.getAbsolutePath(), "--version").start(); // SINK 1
new ProcessBuilder(swiftDemanglerPath.getAbsolutePath(), mangled).start(); // SINK 2
SINK 1 is a validation probe that fires during initialization attacker code runs before a single symbol is demangled.
CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H → 7.5 High
CWE-427 Uncontrolled Search Path Element (primary)
CWE-494 Execution of Code Without Integrity Check (related)
A single auditable research tool packaging the code-execution conditions found while reviewing Ghidra 12.1.2. The tool is deliberately precise about classification three reviewed surfaces, three different risk levels (full detail in docs/classification.md):
Source-to-sink evidence for each is in evidence/source-evidence.md.
Responsible-disclosure tool. Nothing here is networked or destructive. Every process launch is opt-in. The traditional harmless calculator is the only "arbitrary process" it will ever spawn, and only when you explicitly ask for it. The vulnerability it demonstrates is patched.
git clone https://github.com/sn0x-sharma/CVE-2026-18718.git
cd CVE-2026-18718
python3 CVE-2026-18718-POC.py --mode swift --execute --launch-calc
10:15:09 | INFO | swift-ace: launching fake demangler as Ghidra would
Swift demangler calc PoC (sn0x-sharma)
10:15:09 | INFO | [PASS ] swift-ace: Swift demangler sink reproduced; attacker binary executed.
10:15:09 | INFO | - Execution marker: artifacts/swift-demangler-calc/swift_demangler_calc_marker.txt
cat artifacts/swift-demangler-calc/swift_demangler_calc_marker.txt
ran with: --version
That marker is the whole advisory. --version is the argument Ghidra passes at SINK 1, so the fake binary executed in exactly the position the real attacker binary would.
Python 3.9+, standard library only. No third-party dependencies.
A Ghidra 12.1.2 source tree is optional needed only for the tracermi and sevenzip modes. The swift mode is fully self-contained and fabricates its own fake toolchain.
python3 CVE-2026-18718-POC.py --help
With no flags every applicable mode runs in dry-run it stages the fake Swift tool and scans source, but launches nothing:
python3 CVE-2026-18718-POC.py
--execute runs the fake swift-demangle exactly as Ghidra would, and drops a marker proving the attacker binary ran. --launch-calc makes that fake binary spawn the platform calculator:
python3 CVE-2026-18718-POC.py --mode swift --execute # marker only
python3 CVE-2026-18718-POC.py --mode swift --execute --launch-calc # marker + calc
Note that the calculator is spawned by the fake tool, not by the harness in the real bug the attacker's binary is what executes, so in the PoC the attacker's binary is what pops calc.
Resolved from --ghidra-source, then $GHIDRA_SOURCE, then a nearby ghidra-12.1.2/ directory:
python3 CVE-2026-18718-POC.py --ghidra-source /path/to/ghidra-12.1.2
export GHIDRA_SOURCE=/path/to/ghidra-12.1.2
python3 CVE-2026-18718-POC.py --mode tracermi
python3 CVE-2026-18718-POC.py --mode sevenzip --ghidra-source /path/to/ghidra-12.1.2 --harmless-zip
Everything lives in one entrypoint, CVE-2026-18718-POC.py, split into focused, independently-testable classes:
CVE-2026-18718-POC.py
├── PlatformProfile OS-specific behaviour (calc command, script header, chmod), resolved once
├── ResearchConfig run configuration + Ghidra-source resolver
├── SourceScanner read-only substring scanner over a Ghidra tree (file:line)
├── EvidenceCollector owns artifacts/, records every file written
├── ResearchComponent base contract for a reviewed surface
│ ├── SwiftAnalyzer swift-ace — conditional ACE (self-contained)
│ ├── TraceManager tracermi-rce — conditional RCE evidence + calc-only payload shapes
│ └── SevenZipProbe sevenzip-reachability — native parser reachability chain
├── EnvironmentValidator pre-flight checks (Python version, source tree, calc availability)
├── ReportGenerator deterministic summary → log + artifacts/summary.txt
└── ResearchRunner orchestration: validate → run → report → exit code
Why this shape: each surface has a distinct precondition and risk level, so each is a separate component returning structured ComponentResult data. The runner owns orchestration and the reporter owns presentation, which keeps the summary deterministic and the exit code meaningful. Collapsing three different risk levels into one "RCE" headline would have misrepresented what is actually reachable.
The optional JVM runtime probe, probes/SevenZipReachabilityProbe.java, stays a standalone Java artifact — it opens a benign ZIP through SevenZipJBinding when you supply the dependency jars.
CVE-2026-18718/
├── CVE-2026-18718-POC.py unified research framework
├── README.md
├── docs/
│ └── classification.md per-surface classification and preconditions
├── evidence/
│ └── source-evidence.md source-to-sink evidence with file:line references
├── probes/
│ └── SevenZipReachabilityProbe.java
└── artifacts/ generated at runtime, git-ignored
Human-readable timestamped logs go to stderr; machine artifacts are written under artifacts/ (git-ignored):
| Path | Contents |
|---|---|
artifacts/summary.txt | Deterministic run summary |
Exit codes: 0 all selected components passed or cleanly skipped · 2 a component failed or returned partial evidence · 3 environment validation failed.
The calculator is best-effort per platform: calc.exe on Windows, open -a Calculator on macOS, and the first available of xcalc / gnome-calculator / kcalc / qalculate-gtk on Linux. If none is installed, the on-disk marker still proves execution.
c03a70d restricts the analyzer to resolving swift from the system PATH and drops the configurable tool directory entirely.
// before — attacker-influenceable join from restored program state
new File(swiftToolDir, "swift-demangle")
// after — PATH-only resolution
findOnPath("swift")
Even if a malicious project still carries the option, it no longer affects which binary gets executed.
If you are on an unpatched version: update to Ghidra 12.1.3. Until then, disable the Swift Demangler analyzer before opening projects you did not create, and check Analysis → Auto Analyze → Analyzers on anything you import.
This repository is published after coordinated disclosure, against a version that has been patched upstream. It is intended for defensive validation, reproduction notes, detection engineering, and hardening discussion under the stated preconditions.
c03a70dCVE-2026-18718MIT see LICENSE.
| Mode | Surface | Classification |
|---|
swift | Swift demangler process-launch sink (the accepted advisory) | Conditional ACE local code execution when a configured/restored Swift tool directory holds attacker content |
tracermi | TraceRMI debugger-agent execute / pyeval sinks | Conditional RCE code execution once an untrusted peer can drive an agent channel |
sevenzip | SevenZipJBinding native archive parser reachability | RCE-class parser exposure source reachability only, no code-execution claim |
| Flag | Effect |
|---|
--mode {swift,tracermi,sevenzip,all} | Select a surface (default all) |
--ghidra-source PATH | Ghidra 12.1.2 source tree |
--artifacts-dir PATH | Evidence output directory (default ./artifacts) |
--execute | Actually launch local sinks (else dry-run) |
--launch-calc | Allow the benign calculator to spawn (implies --execute) |
--harmless-zip | Emit a benign ZIP during sevenzip mode |
-v, --verbose | Debug-level logging |
--version | Print tool version |
artifacts/swift-demangler-calc/ |
| Fake tool + execution marker |
artifacts/tracermi-conditional-rce/tracermi_calc_payload_shapes.txt | Calc-only payload shapes |
artifacts/sevenzip-reachability/harmless-sevenzip-sample.zip | Benign ZIP sample |
| Date | Event |
|---|
| June 6, 2026 | Reported privately via GitHub Security Advisory (GHSA-pcfh-853f-q3gh) |
| Week 2 | Initially assessed as working as intended |
| Week 3 | Silent project-import reproduction path demonstrated |
| Week 4 | Finding acknowledged · fix committed in c03a70d |
| Week 5 | Report accepted |
| Week 7 | CVE-2026-18718 assigned |