Seregon/PkgToolBox

Toolbox for analyzing and editing pkg application files for psp,ps3, ps4 and ps5, includes the most useful functions you might need.

Python/57.3 KB/No license
GraphicUserInterface/widgets/bruteforce_tab.py
PkgToolBox / GraphicUserInterface / widgets / bruteforce_tab.py
1"""
2Bruteforce tab widget for PS4 passcode bruteforcing functionality
3"""
4import os
5import logging
6from PyQt5.QtWidgets import (QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
7 QPushButton, QTextEdit, QGroupBox, QSpinBox,
8 QListWidget, QMessageBox)
9from PyQt5.QtCore import Qt, QThread, QObject, pyqtSignal
10from .base_tab import BaseTab
11from tools.PS4_Passcode_Bruteforcer import PS4PasscodeBruteforcer
12import re
13 
14class BruteforceTab(BaseTab):
15 """Tab for PS4 passcode bruteforcing operations"""
16
17 def setup_ui(self):
18 """Setup the bruteforce tab UI"""
19 # Output directory selection
20 output_group = QGroupBox("Output Directory")
21 output_layout = QVBoxLayout()
22
23 output_dir_layout = QHBoxLayout()
24 self.bruteforce_out_entry = QLineEdit()
25 self.bruteforce_out_entry.setPlaceholderText("Select output directory")
26 browse_button = QPushButton("Browse")
27 browse_button.clicked.connect(self.browse_output_directory)
28
29 output_dir_layout.addWidget(self.bruteforce_out_entry)
30 output_dir_layout.addWidget(browse_button)
31 output_layout.addLayout(output_dir_layout)
32 output_group.setLayout(output_layout)
33
34 # Passcode input group
35 passcode_group = QGroupBox("Passcode Operations")
36 passcode_layout = QVBoxLayout()
37
38 # Manual passcode input
39 manual_layout = QHBoxLayout()
40 self.passcode_entry = QLineEdit()
41 self.passcode_entry.setPlaceholderText("Enter 32-character passcode (optional)")
42 self.passcode_entry.setMaxLength(32)
43 manual_layout.addWidget(self.passcode_entry)
44
45 # Try passcode button
46 try_button = QPushButton("Try Passcode")
47 try_button.clicked.connect(self.try_manual_passcode)
48 try_button.setStyleSheet("""
49 QPushButton {
50 background-color: #3498db;
51 color: white;
52 font-weight: bold;
53 padding: 8px 16px;
54 border-radius: 4px;
55 }
56 QPushButton:hover {
57 background-color: #2980b9;
58 }
59 """)
60 manual_layout.addWidget(try_button)
61 passcode_layout.addLayout(manual_layout)
62
63 # Control layout for threads, seed, and buttons
64 control_layout = QHBoxLayout()
65
66 # Threads
67 control_layout.addWidget(QLabel("Threads:"))
68 self.brute_threads_spin = QSpinBox()
69 self.brute_threads_spin.setRange(1, 32)
70 self.brute_threads_spin.setValue(1)
71 self.brute_threads_spin.setToolTip("Number of parallel workers")
72 control_layout.addWidget(self.brute_threads_spin)
73 
74 # Seed
75 control_layout.addWidget(QLabel("Seed:"))
76 self.brute_seed_edit = QLineEdit()
77 self.brute_seed_edit.setPlaceholderText("optional integer")
78 self.brute_seed_edit.setToolTip("Optional integer seed for deterministic traversal")
79 self.brute_seed_edit.setMaximumWidth(160)
80 control_layout.addWidget(self.brute_seed_edit)
81 
82 # Control buttons
83 button_layout = QHBoxLayout()
84
85 self.brute_start_button = QPushButton("Start Bruteforce")
86 self.brute_start_button.clicked.connect(self.run_bruteforce)
87 self.brute_start_button.setStyleSheet("""
88 QPushButton {
89 background-color: #e74c3c;
90 color: white;
91 font-weight: bold;
92 padding: 10px 20px;
93 border-radius: 6px;
94 font-size: 14px;
95 }
96 QPushButton:hover {
97 background-color: #c0392b;
98 }
99 QPushButton:disabled {
100 background-color: #95a5a6;
101 }
102 """)
103
104 self.brute_stop_button = QPushButton("Stop")
105 self.brute_stop_button.setEnabled(False)
106 self.brute_stop_button.clicked.connect(self.stop_bruteforce)
107 self.brute_stop_button.setStyleSheet("""
108 QPushButton {
109 background-color: #f39c12;
110 color: white;
111 font-weight: bold;
112 padding: 8px 16px;
113 border-radius: 4px;
114 }
115 QPushButton:hover {
116 background-color: #e67e22;
117 }
118 """)
119
120 self.brute_reset_button = QPushButton("Reset")
121 self.brute_reset_button.setToolTip("Stop and clear progress (.brutestate/.success)")
122 self.brute_reset_button.clicked.connect(self.reset_bruteforce)
123 self.brute_reset_button.setStyleSheet("""
124 QPushButton {
125 background-color: #95a5a6;
126 color: white;
127 font-weight: bold;
128 padding: 8px 16px;
129 border-radius: 4px;
130 }
131 QPushButton:hover {
132 background-color: #7f8c8d;
133 }
134 """)
135
136 button_layout.addWidget(self.brute_start_button)
137 button_layout.addWidget(self.brute_stop_button)
138 button_layout.addWidget(self.brute_reset_button)
139
140 control_layout.addStretch()
141 control_layout.addLayout(button_layout)
142 passcode_layout.addLayout(control_layout)
143
144 passcode_group.setLayout(passcode_layout)
145
146 # Statistics layout
147 stats_group = QGroupBox("Live Statistics")
148 stats_layout = QVBoxLayout()
149
150 stats_info_layout = QHBoxLayout()
151 self.brute_attempts_label = QLabel("Attempts: 0")
152 self.brute_rate_label = QLabel("Rate: 0/s")
153 stats_info_layout.addWidget(self.brute_attempts_label)
154 stats_info_layout.addWidget(self.brute_rate_label)
155 stats_info_layout.addStretch()
156 stats_layout.addLayout(stats_info_layout)
157
158 # Tested keys list
159 self.tested_keys_list = QListWidget()
160 self.tested_keys_list.setAlternatingRowColors(True)
161 self.tested_keys_list.setMaximumHeight(150)
162 stats_layout.addWidget(QLabel("Recently Tested Keys:"))
163 stats_layout.addWidget(self.tested_keys_list)
164
165 self.tested_count_label = QLabel("Shown: 0 (max 1000)")
166 stats_layout.addWidget(self.tested_count_label)
167 stats_group.setLayout(stats_layout)
168
169 # Log display
170 log_group = QGroupBox("Bruteforce Log")
171 log_layout = QVBoxLayout()
172 self.bruteforce_log = QTextEdit()
173 self.bruteforce_log.setReadOnly(True)
174 self.bruteforce_log.setMaximumHeight(200)
175 log_layout.addWidget(self.bruteforce_log)
176 log_group.setLayout(log_layout)
177
178 # Add to main layout
179 self.layout.addWidget(output_group)
180 self.layout.addWidget(passcode_group)
181 self.layout.addWidget(stats_group)
182 self.layout.addWidget(log_group)
183 self.layout.addStretch()
184
185 def browse_output_directory(self):
186 """Browse for output directory"""
187 from PyQt5.QtWidgets import QFileDialog
188 directory = QFileDialog.getExistingDirectory(
189 self,
190 "Select Output Directory"
191 )
192 if directory:
193 self.bruteforce_out_entry.setText(directory)
194
195 def try_manual_passcode(self):
196 """Try decrypting with manual passcode"""
197 package = self.get_package()
198 if not package:
199 QMessageBox.warning(self, "Warning", "Please load a PKG file first")
200 return
201 
202 output_dir = self.bruteforce_out_entry.text()
203 if not output_dir:
204 QMessageBox.warning(self, "Warning", "Please select an output directory")
205 return
206
207 passcode = self.passcode_entry.text()
208 if not passcode:
209 QMessageBox.warning(self, "Warning", "Please enter a passcode")
210 return
211
212 try:
213 bruteforcer = PS4PasscodeBruteforcer()
214 result = bruteforcer.brute_force_passcode(
215 package.original_file,
216 output_dir,
217 lambda msg: self.bruteforce_log.append(msg),
218 manual_passcode=passcode
219 )
220 self.bruteforce_log.append(result)
221 if "successfully" in result.lower():
222 QMessageBox.information(self, "Success", result)
223 else:
224 QMessageBox.warning(self, "Warning", result)
225 except Exception as e:
226 error_msg = f"Failed to try passcode: {str(e)}"
227 self.bruteforce_log.append(error_msg)
228 QMessageBox.critical(self, "Error", error_msg)
229 
230 def run_bruteforce(self):
231 """Run passcode bruteforcer"""
232 package = self.get_package()
233 if not package:
234 QMessageBox.warning(self, "Warning", "Please load a PKG file first")
235 return
236 
237 output_dir = self.bruteforce_out_entry.text()
238 if not output_dir:
239 QMessageBox.warning(self, "Warning", "Please select an output directory")
240 return
241 
242 try:
243 # Prepare UI state
244 self.brute_start_button.setEnabled(False)
245 self.brute_stop_button.setEnabled(True)
246 self.bruteforce_log.clear()
247 self.tested_keys_list.clear()
248 self.tested_count_label.setText("Shown: 0 (max 1000)")
249 
250 # Create bruteforcer and thread
251 self.bruteforcer = PS4PasscodeBruteforcer()
252 
253 class BruteforceWorker(QObject):
254 progress = pyqtSignal(str)
255 tested = pyqtSignal(str)
256 finished = pyqtSignal(str)
257 
258 def __init__(self, bruteforcer, input_file, output_dir, threads, seed_val):
259 super().__init__()
260 self._bf = bruteforcer
261 self._in = input_file
262 self._out = output_dir
263 self._threads = threads
264 self._seed = seed_val
265 
266 def run(self):
267 try:
268 result = self._bf.brute_force_passcode(
269 self._in,
270 self._out,
271 progress_callback=self.progress.emit,
272 manual_passcode=None,
273 num_workers=self._threads,
274 tested_callback=self.tested.emit,
275 seed=self._seed
276 )
277 self.finished.emit(result)
278 except Exception as e:
279 self.finished.emit(f"[-] Error: {str(e)}")
280 
281 # Parse seed (optional)
282 seed_text = self.brute_seed_edit.text().strip()
283 seed_val = None
284 if seed_text:
285 try:
286 seed_val = int(seed_text)
287 except ValueError:
288 QMessageBox.warning(self, "Seed", "Seed must be an integer")
289 self.brute_start_button.setEnabled(True)
290 self.brute_stop_button.setEnabled(False)
291 return
292 
293 self.brute_thread = QThread(self)
294 self.brute_worker = BruteforceWorker(
295 self.bruteforcer,
296 package.original_file,
297 output_dir,
298 self.brute_threads_spin.value(),
299 seed_val
300 )
301 self.brute_worker.moveToThread(self.brute_thread)
302 self.brute_thread.started.connect(self.brute_worker.run)
303 self.brute_worker.progress.connect(self.bruteforce_log.append)
304 self.brute_worker.progress.connect(self.on_bruteforce_progress)
305 self.brute_worker.tested.connect(self.on_tested_key)
306 self.brute_worker.finished.connect(self.on_bruteforce_finished)
307 self.brute_worker.finished.connect(self.brute_thread.quit)
308 self.brute_thread.finished.connect(self.brute_thread.deleteLater)
309 self.brute_thread.start()
310 except Exception as e:
311 QMessageBox.critical(self, "Error", f"Failed to start bruteforce: {str(e)}")
312 
313 def stop_bruteforce(self):
314 """Stop bruteforce operation"""
315 try:
316 if hasattr(self, 'bruteforcer') and self.bruteforcer:
317 if hasattr(self.bruteforcer, 'stop') and callable(self.bruteforcer.stop):
318 self.bruteforcer.stop()
319 else:
320 setattr(self.bruteforcer, '_stop', True)
321 self.brute_stop_button.setEnabled(False)
322 except Exception as e:
323 logging.error(f"Failed to stop bruteforce: {e}")
324 
325 def reset_bruteforce(self):
326 """Stop any running bruteforce, delete saved state/success files, and reset UI."""
327 try:
328 # Stop current run if any
329 self.stop_bruteforce()
330 
331 # Determine current input file
332 input_file = None
333 package = self.get_package()
334 if package and hasattr(package, 'original_file'):
335 input_file = package.original_file
336
337 if not input_file and hasattr(self.parent_window, 'pkg_entry'):
338 input_file = self.parent_window.pkg_entry.text().strip()
339 
340 # Delete state and success files
341 if input_file:
342 state_path = f"{input_file}.brutestate.json"
343 success_path = f"{input_file}.success"
344 try:
345 if os.path.exists(state_path):
346 os.remove(state_path)
347 self.bruteforce_log.append(f"[+] Removed state file: {state_path}")
348 except Exception as e:
349 self.bruteforce_log.append(f"[-] Could not remove state file: {e}")
350 try:
351 if os.path.exists(success_path):
352 os.remove(success_path)
353 self.bruteforce_log.append(f"[+] Removed success file: {success_path}")
354 except Exception as e:
355 self.bruteforce_log.append(f"[-] Could not remove success file: {e}")
356 
357 # Reset UI elements
358 self.bruteforce_log.clear()
359 self.tested_keys_list.clear()
360 self.tested_count_label.setText("Shown: 0 (max 1000)")
361 self.brute_attempts_label.setText("Attempts: 0")
362 self.brute_rate_label.setText("Rate: 0/s")
363 self.brute_start_button.setEnabled(True)
364 self.brute_stop_button.setEnabled(False)
365 
366 QMessageBox.information(self, "Reset", "Bruteforce state has been reset.")
367 except Exception as e:
368 logging.error(f"Failed to reset bruteforce: {e}")
369 QMessageBox.critical(self, "Reset", f"Failed to reset: {e}")
370 
371 def on_tested_key(self, key: str):
372 """Handle tested key update"""
373 MAX_ITEMS = 1000
374 self.tested_keys_list.addItem(key)
375 if self.tested_keys_list.count() > MAX_ITEMS:
376 item = self.tested_keys_list.takeItem(0)
377 del item
378 self.tested_count_label.setText(f"Shown: {self.tested_keys_list.count()} (max {MAX_ITEMS})")
379 
380 def on_bruteforce_finished(self, result: str):
381 """Handle bruteforce completion"""
382 self.brute_start_button.setEnabled(True)
383 self.brute_stop_button.setEnabled(False)
384 if result:
385 self.bruteforce_log.append(result)
386 if "successfully" in result.lower() or "[+]" in result:
387 QMessageBox.information(self, "Success", result)
388 elif result.lower().startswith("[-]"):
389 QMessageBox.warning(self, "Bruteforce", result)
390 
391 def on_bruteforce_progress(self, msg: str):
392 """Handle progress updates"""
393 try:
394 m = re.search(r"Attempts:\s*(\d+).*Rate:\s*([0-9]+(?:\.[0-9]+)?)", msg)
395 if m:
396 self.brute_attempts_label.setText(f"Attempts: {m.group(1)}")
397 self.brute_rate_label.setText(f"Rate: {m.group(2)}/s")
398 except Exception:
399 pass
400