printerxpl-forge 6.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nse/README.md +204 -0
- nse/__init__.py +6 -0
- nse/install_nse.py +412 -0
- nse/lib/printerxpl.lua +238 -0
- nse/scripts/cups-info.nse +74 -0
- nse/scripts/cups-queue-info.nse +43 -0
- nse/scripts/hp-printers-cve-2022-1026.nse +121 -0
- nse/scripts/http-device-mac.nse +107 -0
- nse/scripts/http-hp-ilo-info.nse +121 -0
- nse/scripts/http-info-xerox-enum.nse +101 -0
- nse/scripts/http-vuln-cve2022-1026.nse +158 -0
- nse/scripts/lexmark-config.nse +89 -0
- nse/scripts/pjl-ready-message.nse +106 -0
- nse/scripts/printer-banner.nse +217 -0
- nse/scripts/printer-cups-rce.nse +189 -0
- nse/scripts/printer-cve-detect.nse +279 -0
- nse/scripts/printer-discover.nse +205 -0
- nse/scripts/printer-firmware-exposed.nse +219 -0
- nse/scripts/printer-hp-pjl.nse +192 -0
- nse/scripts/printer-http-ews.nse +293 -0
- nse/scripts/printer-ipp-info.nse +235 -0
- nse/scripts/printer-lexmark-ipp.nse +203 -0
- nse/scripts/printer-passback.nse +204 -0
- nse/scripts/printer-pjl-info.nse +146 -0
- nse/scripts/printer-printnightmare.nse +211 -0
- nse/scripts/printer-snmp-info.nse +176 -0
- nse/scripts/printer-vuln-check.nse +256 -0
- nse/scripts/snmp-device-mac.nse +93 -0
- nse/scripts/snmp-info.nse +146 -0
- nse/scripts/snmp-sysdescr.nse +70 -0
- printerxpl_forge-6.2.0.dist-info/METADATA +919 -0
- printerxpl_forge-6.2.0.dist-info/RECORD +97 -0
- printerxpl_forge-6.2.0.dist-info/WHEEL +5 -0
- printerxpl_forge-6.2.0.dist-info/entry_points.txt +4 -0
- printerxpl_forge-6.2.0.dist-info/licenses/LICENSE +21 -0
- printerxpl_forge-6.2.0.dist-info/top_level.txt +4 -0
- src/assets/fonts/gunplay.pfa +1671 -0
- src/assets/fonts/kshandwrt.pfa +315 -0
- src/assets/fonts/laksoner.pfa +2402 -0
- src/assets/fonts/paintcans.pfa +9699 -0
- src/assets/fonts/stencilod.pfa +4076 -0
- src/assets/fonts/takecover.pfa +26138 -0
- src/assets/fonts/topsecret.pfa +6652 -0
- src/assets/fonts/whoa.pfa +773 -0
- src/assets/mibs/HOST-RESOURCES-MIB +1540 -0
- src/assets/mibs/Printer-MIB +4389 -0
- src/assets/mibs/README.md +9 -0
- src/assets/mibs/SNMPv2-MIB +854 -0
- src/assets/overlays/hacker.eps +596 -0
- src/assets/overlays/smiley.eps +214 -0
- src/assets/overlays/smiley2.eps +240 -0
- src/core/attack_orchestrator.py +1025 -0
- src/core/capabilities.py +323 -0
- src/core/destructive_audit.py +430 -0
- src/core/discovery.py +488 -0
- src/core/osdetect.py +74 -0
- src/core/poly_runner.py +579 -0
- src/core/printer.py +1426 -0
- src/main.py +2134 -0
- src/modules/install_printer.py +318 -0
- src/modules/login_bruteforce.py +852 -0
- src/modules/pcl.py +506 -0
- src/modules/pjl.py +3575 -0
- src/modules/print_job.py +1290 -0
- src/modules/ps.py +1102 -0
- src/payloads/__init__.py +98 -0
- src/payloads/assets/overlays/notice.eps +9 -0
- src/protocols/__init__.py +19 -0
- src/protocols/firmware.py +738 -0
- src/protocols/ipp.py +216 -0
- src/protocols/ipp_attacks.py +609 -0
- src/protocols/lpd.py +141 -0
- src/protocols/network_map.py +1004 -0
- src/protocols/raw.py +173 -0
- src/protocols/smb.py +359 -0
- src/protocols/ssrf_pivot.py +427 -0
- src/protocols/storage.py +587 -0
- src/ui/__init__.py +6 -0
- src/ui/interactive.py +742 -0
- src/ui/spinner.py +112 -0
- src/ui/tables.py +132 -0
- src/utils/banner_grabber.py +852 -0
- src/utils/codebook.py +456 -0
- src/utils/config.py +522 -0
- src/utils/cve_loader.py +158 -0
- src/utils/default_creds.py +134 -0
- src/utils/discovery_online.py +1327 -0
- src/utils/exploit_manager.py +805 -0
- src/utils/fuzzer.py +220 -0
- src/utils/helper.py +732 -0
- src/utils/local_printers.py +307 -0
- src/utils/ml_engine.py +491 -0
- src/utils/operators.py +474 -0
- src/utils/ports.py +234 -0
- src/utils/vuln_scanner.py +823 -0
- src/utils/wordlist_loader.py +412 -0
- src/version.py +36 -0
src/core/poly_runner.py
ADDED
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
"""
|
|
2
|
+
poly_runner — Orquestrador multi-linguagem para execução de exploits em
|
|
3
|
+
qualquer linguagem (Python, C/C++, Ruby/Metasploit, Node.js, Go, Rust, PHP, Perl).
|
|
4
|
+
|
|
5
|
+
Suporta compilação (C/C++/Go/Rust) e execução de runtimes externos
|
|
6
|
+
(Ruby/MSF, Node, PHP, Perl, PowerShell).
|
|
7
|
+
|
|
8
|
+
Todos os builds usam .tmp/build/ do projeto (nunca diretórios externos).
|
|
9
|
+
|
|
10
|
+
Funções públicas principais:
|
|
11
|
+
detect(lang) → caminho do compilador/runtime ou None
|
|
12
|
+
detect_msf() → caminho do msfconsole ou None
|
|
13
|
+
available_langs() → dict {lang: path_or_None} para todas as linguagens
|
|
14
|
+
build(src, lang, ...) → compila source → binário em .tmp/build/
|
|
15
|
+
run(src, lang, ...) → detect + build (se compilado) + executa + retorna dict
|
|
16
|
+
run_msf(module, ...) → gera RC script + msfconsole -r → retorna dict
|
|
17
|
+
run_from_dir(dir, ...) → auto-detecta source no diretório + despacha para run()
|
|
18
|
+
|
|
19
|
+
# Autor: André Henrique (@mrhenrike) | União Geek
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import os
|
|
24
|
+
import re
|
|
25
|
+
import shutil
|
|
26
|
+
import subprocess
|
|
27
|
+
import sys
|
|
28
|
+
import time
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import Any, Optional
|
|
31
|
+
|
|
32
|
+
# ── Build directory inside the project workspace ──────────────────────────────
|
|
33
|
+
_PROJECT_ROOT = Path(__file__).parent.parent.parent
|
|
34
|
+
_BUILD_TMP = _PROJECT_ROOT / ".tmp" / "build"
|
|
35
|
+
|
|
36
|
+
# ── Compiler/runtime detection tables ─────────────────────────────────────────
|
|
37
|
+
_COMPILERS: dict[str, list[str]] = {
|
|
38
|
+
"c": ["gcc", "clang", "cc"],
|
|
39
|
+
"cpp": ["g++", "clang++", "c++"],
|
|
40
|
+
"go": ["go"],
|
|
41
|
+
"rust": ["cargo"],
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
_RUNTIMES: dict[str, list[str]] = {
|
|
45
|
+
"ruby": ["ruby"],
|
|
46
|
+
"node": ["node", "nodejs"],
|
|
47
|
+
"php": ["php"],
|
|
48
|
+
"perl": ["perl"],
|
|
49
|
+
"powershell": ["pwsh", "powershell"],
|
|
50
|
+
"python": [sys.executable],
|
|
51
|
+
"sh": ["sh", "bash"],
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
_MSF_BINARIES = ["msfconsole", "msfexec"]
|
|
55
|
+
|
|
56
|
+
# WSL binary (Windows only) — used as fallback compiler/runtime host
|
|
57
|
+
_WSL_BIN: Optional[str] = shutil.which("wsl") if sys.platform == "win32" else None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# ── Source file extension → language key ──────────────────────────────────────
|
|
61
|
+
_EXT_TO_LANG: dict[str, str] = {
|
|
62
|
+
".c": "c",
|
|
63
|
+
".cpp": "cpp",
|
|
64
|
+
".cc": "cpp",
|
|
65
|
+
".go": "go",
|
|
66
|
+
".rs": "rust",
|
|
67
|
+
".rb": "ruby",
|
|
68
|
+
".js": "node",
|
|
69
|
+
".php": "php",
|
|
70
|
+
".pl": "perl",
|
|
71
|
+
".ps1": "powershell",
|
|
72
|
+
".sh": "sh",
|
|
73
|
+
".py": "python",
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
# Source file names considered as exploit entry points when auto-discovering
|
|
77
|
+
_SOURCE_CANDIDATES = ["source.c", "source.cpp", "exploit.c", "exploit.cpp",
|
|
78
|
+
"exploit.rb", "exploit.go", "exploit.rs",
|
|
79
|
+
"exploit.js", "exploit.php", "exploit.pl", "exploit.ps1"]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# ── Public API ─────────────────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
def detect(lang: str) -> Optional[str]:
|
|
85
|
+
"""Return path to first available compiler/runtime for *lang*, or None.
|
|
86
|
+
|
|
87
|
+
On Windows, if no native compiler is found for C/C++/Go/Rust, falls back
|
|
88
|
+
to checking inside WSL via ``wsl <compiler> --version``. When the WSL
|
|
89
|
+
fallback succeeds, returns the string ``"wsl:<compiler>"`` as a sentinel
|
|
90
|
+
that ``build()`` and ``run()`` recognise and dispatch via ``wsl``.
|
|
91
|
+
"""
|
|
92
|
+
lang = lang.lower()
|
|
93
|
+
candidates: list[str] = []
|
|
94
|
+
if lang in _COMPILERS:
|
|
95
|
+
candidates = _COMPILERS[lang]
|
|
96
|
+
elif lang in _RUNTIMES:
|
|
97
|
+
candidates = _RUNTIMES[lang]
|
|
98
|
+
else:
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
for name in candidates:
|
|
102
|
+
found = shutil.which(name)
|
|
103
|
+
if found:
|
|
104
|
+
return found
|
|
105
|
+
|
|
106
|
+
# WSL fallback on Windows for compiled languages
|
|
107
|
+
if sys.platform == "win32" and _WSL_BIN and lang in _COMPILERS:
|
|
108
|
+
wsl_compiler = _COMPILERS[lang][0] # e.g. "gcc"
|
|
109
|
+
probe = subprocess.run(
|
|
110
|
+
[_WSL_BIN, wsl_compiler, "--version"],
|
|
111
|
+
capture_output=True, text=True,
|
|
112
|
+
)
|
|
113
|
+
if probe.returncode == 0:
|
|
114
|
+
return f"wsl:{wsl_compiler}"
|
|
115
|
+
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def detect_msf() -> Optional[str]:
|
|
120
|
+
"""Return path to msfconsole binary, or None if Metasploit is not installed."""
|
|
121
|
+
for name in _MSF_BINARIES:
|
|
122
|
+
found = shutil.which(name)
|
|
123
|
+
if found:
|
|
124
|
+
return found
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def available_langs() -> dict[str, Optional[str]]:
|
|
129
|
+
"""Return a mapping of every supported language to its detected binary path.
|
|
130
|
+
|
|
131
|
+
Keys include all compiler languages (c, cpp, go, rust) and all runtime
|
|
132
|
+
languages (ruby, node, php, perl, powershell, python, sh). The value is
|
|
133
|
+
the resolved binary path or ``None`` when not installed.
|
|
134
|
+
|
|
135
|
+
Useful for ``--xpl-list`` output to flag exploits with unmet dependencies
|
|
136
|
+
and for the exploit_manager dependency check.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
dict: ``{"c": "/usr/bin/gcc", "ruby": None, ...}``
|
|
140
|
+
"""
|
|
141
|
+
result: dict[str, Optional[str]] = {}
|
|
142
|
+
for lang in list(_COMPILERS) + list(_RUNTIMES):
|
|
143
|
+
result[lang] = detect(lang)
|
|
144
|
+
return result
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def run_from_dir(
|
|
148
|
+
module_dir: Path,
|
|
149
|
+
host: str,
|
|
150
|
+
port: int,
|
|
151
|
+
timeout: float = 30.0,
|
|
152
|
+
dry_run: bool = False,
|
|
153
|
+
extra_args: Optional[list[str]] = None,
|
|
154
|
+
build_flags: Optional[list[str]] = None,
|
|
155
|
+
) -> dict[str, Any]:
|
|
156
|
+
"""Auto-detect a non-Python source file in *module_dir* and execute it.
|
|
157
|
+
|
|
158
|
+
Searches for candidate source files in *module_dir* following the priority
|
|
159
|
+
order defined in ``_SOURCE_CANDIDATES``. The first match determines the
|
|
160
|
+
language and is passed to :func:`run`.
|
|
161
|
+
|
|
162
|
+
This helper lets ``exploit.py`` wrappers delegate execution to poly_runner
|
|
163
|
+
without needing to know the exact source filename or language key::
|
|
164
|
+
|
|
165
|
+
from src.core import poly_runner
|
|
166
|
+
from pathlib import Path
|
|
167
|
+
|
|
168
|
+
_DIR = Path(__file__).parent
|
|
169
|
+
|
|
170
|
+
def run(host, port=9100, timeout=15, dry_run=True, **opts):
|
|
171
|
+
return poly_runner.run_from_dir(_DIR, host, port,
|
|
172
|
+
timeout=timeout, dry_run=dry_run)
|
|
173
|
+
|
|
174
|
+
Parameters
|
|
175
|
+
----------
|
|
176
|
+
module_dir : Directory that contains the source file (usually the same
|
|
177
|
+
directory as the calling ``exploit.py``).
|
|
178
|
+
host : Target host.
|
|
179
|
+
port : Target port.
|
|
180
|
+
timeout : Execution timeout in seconds.
|
|
181
|
+
dry_run : If True, return metadata without executing.
|
|
182
|
+
extra_args : Extra CLI arguments forwarded to the exploit binary/script.
|
|
183
|
+
build_flags : Compiler flags forwarded to :func:`build` (compiled langs).
|
|
184
|
+
|
|
185
|
+
Returns
|
|
186
|
+
-------
|
|
187
|
+
dict with keys: success, vulnerable, output, evidence, error, lang, runner.
|
|
188
|
+
Returns an error dict when no source file is found.
|
|
189
|
+
"""
|
|
190
|
+
module_dir = Path(module_dir)
|
|
191
|
+
|
|
192
|
+
for candidate in _SOURCE_CANDIDATES:
|
|
193
|
+
src = module_dir / candidate
|
|
194
|
+
if src.exists():
|
|
195
|
+
lang = _EXT_TO_LANG.get(src.suffix.lower(), "")
|
|
196
|
+
if not lang:
|
|
197
|
+
return _error_result("unknown",
|
|
198
|
+
f"run_from_dir: unsupported extension '{src.suffix}'")
|
|
199
|
+
artifact = module_dir.name or src.stem
|
|
200
|
+
return run(
|
|
201
|
+
src=src,
|
|
202
|
+
lang=lang,
|
|
203
|
+
host=host,
|
|
204
|
+
port=port,
|
|
205
|
+
timeout=timeout,
|
|
206
|
+
dry_run=dry_run,
|
|
207
|
+
extra_args=extra_args,
|
|
208
|
+
build_flags=build_flags,
|
|
209
|
+
artifact_name=artifact,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
return _error_result(
|
|
213
|
+
"unknown",
|
|
214
|
+
f"run_from_dir: no supported source file found in '{module_dir}' "
|
|
215
|
+
f"(searched: {_SOURCE_CANDIDATES})",
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def build(
|
|
220
|
+
src: Path,
|
|
221
|
+
lang: str,
|
|
222
|
+
artifact_name: str,
|
|
223
|
+
build_flags: Optional[list[str]] = None,
|
|
224
|
+
) -> Path:
|
|
225
|
+
"""
|
|
226
|
+
Compile *src* → .tmp/build/<artifact_name>/exploit (or .exe on Windows).
|
|
227
|
+
|
|
228
|
+
Uses a **compilation cache**: if the binary already exists and its
|
|
229
|
+
modification time is newer than *src*, the existing binary is returned
|
|
230
|
+
immediately without recompiling.
|
|
231
|
+
|
|
232
|
+
Parameters
|
|
233
|
+
----------
|
|
234
|
+
src : Path to source file (.c, .cpp, .go, etc.)
|
|
235
|
+
lang : Language key — "c", "cpp", "go", "rust"
|
|
236
|
+
artifact_name : Directory name under .tmp/build/ for isolation
|
|
237
|
+
build_flags : Extra compiler flags (e.g. ["-lpthread"])
|
|
238
|
+
|
|
239
|
+
Returns
|
|
240
|
+
-------
|
|
241
|
+
Path to the compiled binary.
|
|
242
|
+
|
|
243
|
+
Raises
|
|
244
|
+
------
|
|
245
|
+
RuntimeError : If compiler not found or compilation fails.
|
|
246
|
+
"""
|
|
247
|
+
lang = lang.lower()
|
|
248
|
+
if lang not in _COMPILERS:
|
|
249
|
+
raise RuntimeError(f"poly_runner: no compiler registered for lang='{lang}'")
|
|
250
|
+
|
|
251
|
+
compiler = detect(lang)
|
|
252
|
+
if not compiler:
|
|
253
|
+
raise RuntimeError(
|
|
254
|
+
f"poly_runner: no compiler found for lang='{lang}' "
|
|
255
|
+
f"(tried: {_COMPILERS[lang]})"
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
out_dir = _BUILD_TMP / artifact_name
|
|
259
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
260
|
+
|
|
261
|
+
binary_name = "exploit.exe" if sys.platform == "win32" else "exploit"
|
|
262
|
+
binary_path = out_dir / binary_name
|
|
263
|
+
flags = build_flags or []
|
|
264
|
+
|
|
265
|
+
# Compilation cache: skip rebuild if binary is newer than source
|
|
266
|
+
if binary_path.exists() and src.exists():
|
|
267
|
+
if binary_path.stat().st_mtime >= src.stat().st_mtime:
|
|
268
|
+
return binary_path
|
|
269
|
+
|
|
270
|
+
# Detect WSL-based compilation (compiler path starts with "wsl:")
|
|
271
|
+
using_wsl = compiler.startswith("wsl:")
|
|
272
|
+
wsl_compiler = compiler.split(":", 1)[1] if using_wsl else compiler
|
|
273
|
+
|
|
274
|
+
if using_wsl:
|
|
275
|
+
# Convert Windows path to WSL Linux path (/mnt/X/ convention)
|
|
276
|
+
def _win_to_wsl(p: Path) -> str:
|
|
277
|
+
import re as _re
|
|
278
|
+
s = str(p.resolve()) # always absolute before converting
|
|
279
|
+
m = _re.match(r'^([A-Za-z]):[/\\](.*)', s)
|
|
280
|
+
if m:
|
|
281
|
+
drive = m.group(1).lower()
|
|
282
|
+
rest = m.group(2).replace("\\", "/")
|
|
283
|
+
return f"/mnt/{drive}/{rest}"
|
|
284
|
+
return s.replace("\\", "/")
|
|
285
|
+
|
|
286
|
+
wsl_src = _win_to_wsl(src)
|
|
287
|
+
wsl_out = _win_to_wsl(binary_path)
|
|
288
|
+
|
|
289
|
+
if lang == "go":
|
|
290
|
+
cmd = [_WSL_BIN, wsl_compiler, "build", "-o", wsl_out, wsl_src] # type: ignore[list-item]
|
|
291
|
+
elif lang == "rust":
|
|
292
|
+
cmd = [_WSL_BIN, "rustc", wsl_src, "-o", wsl_out] + flags # type: ignore[list-item]
|
|
293
|
+
else:
|
|
294
|
+
cmd = [_WSL_BIN, wsl_compiler, wsl_src, "-o", wsl_out] + flags # type: ignore[list-item]
|
|
295
|
+
elif lang == "go":
|
|
296
|
+
cmd = [compiler, "build", "-o", str(binary_path), str(src)]
|
|
297
|
+
elif lang == "rust":
|
|
298
|
+
rustc = shutil.which("rustc")
|
|
299
|
+
if not rustc:
|
|
300
|
+
raise RuntimeError("poly_runner: rustc not found")
|
|
301
|
+
cmd = [rustc, str(src), "-o", str(binary_path)] + flags
|
|
302
|
+
else:
|
|
303
|
+
# C / C++
|
|
304
|
+
cmd = [compiler, str(src), "-o", str(binary_path)] + flags
|
|
305
|
+
|
|
306
|
+
result = subprocess.run(
|
|
307
|
+
cmd,
|
|
308
|
+
capture_output=True,
|
|
309
|
+
text=True,
|
|
310
|
+
cwd=str(out_dir),
|
|
311
|
+
)
|
|
312
|
+
if result.returncode != 0:
|
|
313
|
+
raise RuntimeError(
|
|
314
|
+
f"poly_runner: compilation failed ({compiler}):\n"
|
|
315
|
+
f" STDOUT: {result.stdout}\n"
|
|
316
|
+
f" STDERR: {result.stderr}"
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
return binary_path
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def run(
|
|
323
|
+
src: Path,
|
|
324
|
+
lang: str,
|
|
325
|
+
host: str,
|
|
326
|
+
port: int,
|
|
327
|
+
timeout: float = 30.0,
|
|
328
|
+
dry_run: bool = False,
|
|
329
|
+
extra_args: Optional[list[str]] = None,
|
|
330
|
+
build_flags: Optional[list[str]] = None,
|
|
331
|
+
artifact_name: Optional[str] = None,
|
|
332
|
+
) -> dict[str, Any]:
|
|
333
|
+
"""
|
|
334
|
+
Full pipeline: detect → build (if compiled) → execute → parse → return result.
|
|
335
|
+
|
|
336
|
+
For compiled languages (C/C++/Go/Rust), the source is compiled first via
|
|
337
|
+
`build()`, then the binary is executed with `host port [extra_args]`.
|
|
338
|
+
|
|
339
|
+
For interpreted languages (Python/Ruby/Node/PHP/Perl), the runtime is
|
|
340
|
+
invoked directly: `runtime src host port [extra_args]`.
|
|
341
|
+
|
|
342
|
+
Parameters
|
|
343
|
+
----------
|
|
344
|
+
src : Path to source or script file
|
|
345
|
+
lang : Language key
|
|
346
|
+
host : Target host
|
|
347
|
+
port : Target port
|
|
348
|
+
timeout : Execution timeout in seconds
|
|
349
|
+
dry_run : If True, skip actual execution and return metadata only
|
|
350
|
+
extra_args : Additional CLI arguments passed to the exploit
|
|
351
|
+
build_flags : Compiler flags (compiled langs only)
|
|
352
|
+
artifact_name : Override build dir name (defaults to src.stem)
|
|
353
|
+
|
|
354
|
+
Returns
|
|
355
|
+
-------
|
|
356
|
+
dict with keys: success, vulnerable, output, evidence, error, lang, runner
|
|
357
|
+
"""
|
|
358
|
+
lang = lang.lower()
|
|
359
|
+
extra_args = extra_args or []
|
|
360
|
+
artifact = artifact_name or src.stem
|
|
361
|
+
|
|
362
|
+
if dry_run:
|
|
363
|
+
return {
|
|
364
|
+
"success": True,
|
|
365
|
+
"vulnerable": None,
|
|
366
|
+
"output": f"[DRY-RUN] poly_runner: would execute {lang} exploit {src.name} against {host}:{port}",
|
|
367
|
+
"evidence": "",
|
|
368
|
+
"error": "",
|
|
369
|
+
"lang": lang,
|
|
370
|
+
"runner": "poly_runner",
|
|
371
|
+
"dry_run": True,
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
try:
|
|
375
|
+
if lang in _COMPILERS:
|
|
376
|
+
binary = build(src, lang, artifact, build_flags)
|
|
377
|
+
# If compiled via WSL, the binary is an ELF — run it via wsl
|
|
378
|
+
if _WSL_BIN and sys.platform == "win32":
|
|
379
|
+
probe = subprocess.run(
|
|
380
|
+
["file", str(binary)] if shutil.which("file") else
|
|
381
|
+
[_WSL_BIN, "file", _win_to_wsl_safe(binary)],
|
|
382
|
+
capture_output=True, text=True,
|
|
383
|
+
)
|
|
384
|
+
is_elf = "ELF" in probe.stdout
|
|
385
|
+
else:
|
|
386
|
+
is_elf = False
|
|
387
|
+
|
|
388
|
+
if is_elf and _WSL_BIN:
|
|
389
|
+
wsl_bin_path = _win_to_wsl_safe(binary)
|
|
390
|
+
cmd = [_WSL_BIN, wsl_bin_path, host, str(port)] + extra_args
|
|
391
|
+
else:
|
|
392
|
+
cmd = [str(binary), host, str(port)] + extra_args
|
|
393
|
+
elif lang in _RUNTIMES:
|
|
394
|
+
runtime = detect(lang)
|
|
395
|
+
if not runtime:
|
|
396
|
+
return _error_result(lang, f"runtime not found for '{lang}'")
|
|
397
|
+
cmd = [runtime, str(src), host, str(port)] + extra_args
|
|
398
|
+
else:
|
|
399
|
+
return _error_result(lang, f"unknown language '{lang}'")
|
|
400
|
+
|
|
401
|
+
result = subprocess.run(
|
|
402
|
+
cmd,
|
|
403
|
+
capture_output=True,
|
|
404
|
+
text=True,
|
|
405
|
+
timeout=timeout,
|
|
406
|
+
)
|
|
407
|
+
return _normalize(result.stdout, result.stderr, result.returncode, lang)
|
|
408
|
+
|
|
409
|
+
except subprocess.TimeoutExpired:
|
|
410
|
+
return _error_result(lang, f"timeout after {timeout}s")
|
|
411
|
+
except RuntimeError as exc:
|
|
412
|
+
return _error_result(lang, str(exc))
|
|
413
|
+
except Exception as exc:
|
|
414
|
+
return _error_result(lang, f"unexpected error: {exc}")
|
|
415
|
+
finally:
|
|
416
|
+
_cleanup_build(artifact)
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def run_msf(
|
|
420
|
+
msf_module: str,
|
|
421
|
+
host: str,
|
|
422
|
+
port: int,
|
|
423
|
+
payload: Optional[str] = None,
|
|
424
|
+
lhost: Optional[str] = None,
|
|
425
|
+
lport: Optional[int] = None,
|
|
426
|
+
extra_options: Optional[dict[str, str]] = None,
|
|
427
|
+
timeout: float = 120.0,
|
|
428
|
+
dry_run: bool = False,
|
|
429
|
+
) -> dict[str, Any]:
|
|
430
|
+
"""
|
|
431
|
+
Execute a Metasploit module via msfconsole -q -r <rc_script>.
|
|
432
|
+
|
|
433
|
+
Parameters
|
|
434
|
+
----------
|
|
435
|
+
msf_module : MSF module path (e.g. "exploit/windows/local/cve_2020_1048_printerdemon")
|
|
436
|
+
host : RHOST value
|
|
437
|
+
port : RPORT value
|
|
438
|
+
payload : Optional payload string
|
|
439
|
+
lhost : LHOST for reverse shells
|
|
440
|
+
lport : LPORT for reverse shells
|
|
441
|
+
extra_options : Dict of additional MSF options
|
|
442
|
+
timeout : Execution timeout in seconds
|
|
443
|
+
dry_run : If True, return script without executing
|
|
444
|
+
|
|
445
|
+
Returns
|
|
446
|
+
-------
|
|
447
|
+
dict with keys: success, vulnerable, output, evidence, error, lang, runner
|
|
448
|
+
"""
|
|
449
|
+
msf_bin = detect_msf()
|
|
450
|
+
|
|
451
|
+
# Build resource script
|
|
452
|
+
rc_lines = [
|
|
453
|
+
f"use {msf_module}",
|
|
454
|
+
f"set RHOSTS {host}",
|
|
455
|
+
f"set RPORT {port}",
|
|
456
|
+
]
|
|
457
|
+
if payload:
|
|
458
|
+
rc_lines.append(f"set PAYLOAD {payload}")
|
|
459
|
+
if lhost:
|
|
460
|
+
rc_lines.append(f"set LHOST {lhost}")
|
|
461
|
+
if lport:
|
|
462
|
+
rc_lines.append(f"set LPORT {lport}")
|
|
463
|
+
for k, v in (extra_options or {}).items():
|
|
464
|
+
rc_lines.append(f"set {k} {v}")
|
|
465
|
+
rc_lines.extend(["run", "exit"])
|
|
466
|
+
rc_content = "\n".join(rc_lines)
|
|
467
|
+
|
|
468
|
+
if dry_run or not msf_bin:
|
|
469
|
+
reason = "DRY-RUN" if dry_run else "msfconsole not found"
|
|
470
|
+
return {
|
|
471
|
+
"success": True if dry_run else False,
|
|
472
|
+
"vulnerable": None,
|
|
473
|
+
"output": f"[{reason}] MSF RC script:\n{rc_content}",
|
|
474
|
+
"evidence": "",
|
|
475
|
+
"error": "" if dry_run else "Metasploit not installed",
|
|
476
|
+
"lang": "ruby",
|
|
477
|
+
"runner": "poly_runner_msf",
|
|
478
|
+
"dry_run": dry_run,
|
|
479
|
+
"msf_module": msf_module,
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
# Write RC script to project .tmp (never system /tmp)
|
|
483
|
+
rc_path = _BUILD_TMP / f"msf_{int(time.time())}.rc"
|
|
484
|
+
rc_path.parent.mkdir(parents=True, exist_ok=True)
|
|
485
|
+
rc_path.write_text(rc_content, encoding="utf-8")
|
|
486
|
+
|
|
487
|
+
try:
|
|
488
|
+
result = subprocess.run(
|
|
489
|
+
[msf_bin, "-q", "-r", str(rc_path)],
|
|
490
|
+
capture_output=True,
|
|
491
|
+
text=True,
|
|
492
|
+
timeout=timeout,
|
|
493
|
+
)
|
|
494
|
+
return _normalize_msf(result.stdout, result.stderr, result.returncode)
|
|
495
|
+
except subprocess.TimeoutExpired:
|
|
496
|
+
return _error_result("ruby", f"msfconsole timeout after {timeout}s")
|
|
497
|
+
except Exception as exc:
|
|
498
|
+
return _error_result("ruby", f"msfconsole error: {exc}")
|
|
499
|
+
finally:
|
|
500
|
+
if rc_path.exists():
|
|
501
|
+
rc_path.unlink(missing_ok=True)
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
# ── Internal helpers ───────────────────────────────────────────────────────────
|
|
505
|
+
|
|
506
|
+
def _win_to_wsl_safe(p: Path) -> str:
|
|
507
|
+
"""Convert a Windows path to a WSL Linux path string (best-effort).
|
|
508
|
+
|
|
509
|
+
Converts ``D:\\foo\\bar`` → ``/mnt/d/foo/bar`` using the standard WSL
|
|
510
|
+
drive-mount convention. Falls back to a forward-slash replacement when
|
|
511
|
+
the path does not match a Windows drive letter pattern.
|
|
512
|
+
"""
|
|
513
|
+
s = str(p)
|
|
514
|
+
# Windows absolute path: e.g. D:\foo\bar or d:/foo/bar
|
|
515
|
+
import re as _re
|
|
516
|
+
m = _re.match(r'^([A-Za-z]):[/\\](.*)', s)
|
|
517
|
+
if m:
|
|
518
|
+
drive = m.group(1).lower()
|
|
519
|
+
rest = m.group(2).replace("\\", "/")
|
|
520
|
+
return f"/mnt/{drive}/{rest}"
|
|
521
|
+
# Already a WSL/POSIX path or relative
|
|
522
|
+
return s.replace("\\", "/")
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def _normalize(stdout: str, stderr: str, rc: int, lang: str) -> dict[str, Any]:
|
|
526
|
+
success = rc == 0
|
|
527
|
+
vulnerable = success and bool(stdout.strip())
|
|
528
|
+
evidence = stdout
|
|
529
|
+
if stderr and rc != 0:
|
|
530
|
+
evidence += f"\n[STDERR]: {stderr}"
|
|
531
|
+
return {
|
|
532
|
+
"success": success,
|
|
533
|
+
"vulnerable": vulnerable,
|
|
534
|
+
"output": stdout,
|
|
535
|
+
"evidence": evidence,
|
|
536
|
+
"error": stderr if rc != 0 else "",
|
|
537
|
+
"returncode": rc,
|
|
538
|
+
"lang": lang,
|
|
539
|
+
"runner": "poly_runner",
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def _normalize_msf(stdout: str, stderr: str, rc: int) -> dict[str, Any]:
|
|
544
|
+
"""Parse msfconsole output for session/success indicators."""
|
|
545
|
+
lines = stdout.lower()
|
|
546
|
+
session_opened = bool(re.search(r"session \d+ opened", lines))
|
|
547
|
+
exploited = bool(re.search(r"\[\+\]", stdout))
|
|
548
|
+
failed = bool(re.search(r"\[-\].*exploit failed|no session", lines))
|
|
549
|
+
vulnerable = session_opened or exploited
|
|
550
|
+
return {
|
|
551
|
+
"success": rc == 0 and not failed,
|
|
552
|
+
"vulnerable": vulnerable,
|
|
553
|
+
"output": stdout,
|
|
554
|
+
"evidence": stdout if vulnerable else "",
|
|
555
|
+
"error": stderr if rc != 0 else "",
|
|
556
|
+
"returncode": rc,
|
|
557
|
+
"lang": "ruby",
|
|
558
|
+
"runner": "poly_runner_msf",
|
|
559
|
+
"session_opened": session_opened,
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
def _error_result(lang: str, msg: str) -> dict[str, Any]:
|
|
564
|
+
return {
|
|
565
|
+
"success": False,
|
|
566
|
+
"vulnerable": False,
|
|
567
|
+
"output": "",
|
|
568
|
+
"evidence": "",
|
|
569
|
+
"error": msg,
|
|
570
|
+
"lang": lang,
|
|
571
|
+
"runner": "poly_runner",
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def _cleanup_build(artifact_name: str) -> None:
|
|
576
|
+
"""Remove compiled artifacts from .tmp/build/<artifact_name>/."""
|
|
577
|
+
build_dir = _BUILD_TMP / artifact_name
|
|
578
|
+
if build_dir.exists():
|
|
579
|
+
shutil.rmtree(build_dir, ignore_errors=True)
|