1 package edu.jiangxin.apktoolbox.file.password.recovery; 2 3 import edu.jiangxin.apktoolbox.file.password.recovery.category.CategoryFactory; 4 import edu.jiangxin.apktoolbox.file.password.recovery.category.CategoryType; 5 import edu.jiangxin.apktoolbox.file.password.recovery.category.ICategory; 6 import edu.jiangxin.apktoolbox.file.password.recovery.checker.*; 7 import edu.jiangxin.apktoolbox.file.password.recovery.checker.thirdparty.ThirdParty7ZipChecker; 8 import edu.jiangxin.apktoolbox.file.password.recovery.checker.thirdparty.ThirdPartyRarChecker; 9 import edu.jiangxin.apktoolbox.file.password.recovery.checker.thirdparty.ThirdPartyWinRarChecker; 10 import edu.jiangxin.apktoolbox.swing.extend.EasyPanel; 11 import edu.jiangxin.apktoolbox.swing.extend.filepanel.FilePanel; 12 import edu.jiangxin.apktoolbox.utils.Constants; 13 import org.apache.commons.collections4.CollectionUtils; 14 import org.apache.commons.io.FilenameUtils; 15 import org.apache.commons.lang3.ArrayUtils; 16 import org.apache.commons.lang3.StringUtils; 17 18 import javax.swing.*; 19 import java.awt.*; 20 import java.awt.datatransfer.Clipboard; 21 import java.awt.datatransfer.StringSelection; 22 import java.io.File; 23 import java.io.Serial; 24 import java.text.NumberFormat; 25 import java.util.ArrayList; 26 import java.util.Arrays; 27 import java.util.HashSet; 28 import java.util.List; 29 import java.util.Set; 30 import java.util.concurrent.ExecutorService; 31 import java.util.concurrent.Executors; 32 33 public final class RecoveryPanel extends EasyPanel { 34 @Serial 35 private static final long serialVersionUID = 1L; 36 37 private JPanel optionPanel; 38 39 private FilePanel recoveryFilePanel; 40 41 private JTabbedPane categoryTabbedPane; 42 43 private JPanel bruteForceCategoryPanel; 44 45 private JPanel dictionaryCategoryPanel; 46 47 private FilePanel dictionaryFilePanel; 48 49 private CategoryType currentCategoryType = CategoryType.UNKNOWN; 50 51 private JPanel operationPanel; 52 53 private JCheckBox numberCheckBox; 54 private JCheckBox lowercaseLetterCheckBox; 55 private JCheckBox uppercaseLetterCheckBox; 56 57 private JCheckBox userIncludedCheckBox; 58 59 private JTextField userIncludedTextField; 60 61 private JCheckBox userExcludedCheckBox; 62 63 private JTextField userExcludedTextField; 64 65 private JSpinner minSpinner; 66 private JSpinner maxSpinner; 67 68 private JCheckBox isUseMultiThreadCheckBox; 69 70 private JProgressBar progressBar; 71 72 private JComboBox<FileChecker> checkerTypeComboBox; 73 74 private transient FileChecker currentFileChecker; 75 76 private JButton startButton; 77 private JButton stopButton; 78 79 private JLabel currentStateLabel; 80 81 private JLabel currentPasswordLabel; 82 83 private JLabel currentSpeedLabel; 84 85 private int passwordTryCount = 0; 86 87 private NumberFormat numberFormat; 88 89 private State currentState = State.IDLE; 90 91 private Timer timer; 92 93 private final transient ExecutorService startExecutorService = Executors.newFixedThreadPool(1); 94 95 private final transient ExecutorService stopExecutorService = Executors.newFixedThreadPool(1); 96 97 public RecoveryPanel() { 98 super(); 99 initBase(); 100 } 101 102 private void initBase() { 103 numberFormat = NumberFormat.getPercentInstance(); 104 numberFormat.setMinimumFractionDigits(3); 105 } 106 107 @Override 108 public void initUI() { 109 BoxLayout boxLayout = new BoxLayout(this, BoxLayout.Y_AXIS); 110 setLayout(boxLayout); 111 112 createOptionPanel(); 113 add(optionPanel); 114 add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER)); 115 116 createOperationPanel(); 117 add(operationPanel); 118 119 timer = new Timer(1000, e -> { 120 if (currentState == State.WORKING) { 121 int currentValue = progressBar.getValue(); 122 int speed = currentValue - passwordTryCount; 123 passwordTryCount = currentValue; 124 currentSpeedLabel.setText("Speed: " + speed + " passwords/s"); 125 } 126 }); 127 } 128 129 private void createOptionPanel() { 130 optionPanel = new JPanel(); 131 optionPanel.setLayout(new BoxLayout(optionPanel, BoxLayout.Y_AXIS)); 132 133 checkerTypeComboBox = new JComboBox<>(); 134 checkerTypeComboBox.addItem(new ThirdParty7ZipChecker()); 135 checkerTypeComboBox.addItem(new ThirdPartyWinRarChecker()); 136 checkerTypeComboBox.addItem(new ThirdPartyRarChecker()); 137 checkerTypeComboBox.addItem(new ZipChecker()); 138 checkerTypeComboBox.addItem(new RarChecker()); 139 checkerTypeComboBox.addItem(new SevenZipChecker()); 140 checkerTypeComboBox.addItem(new PdfChecker()); 141 checkerTypeComboBox.addItem(new XmlBasedOfficeChecker()); 142 checkerTypeComboBox.addItem(new BinaryOfficeChecker()); 143 checkerTypeComboBox.setSelectedIndex(0); 144 checkerTypeComboBox.addItemListener(e -> { 145 FileChecker fileChecker = (FileChecker) e.getItem(); 146 if (fileChecker == null) { 147 logger.error("fileChecker is null"); 148 return; 149 } 150 recoveryFilePanel.setDescriptionAndFileExtensions( 151 fileChecker.getFileDescription(), fileChecker.getFileExtensions()); 152 }); 153 154 FileChecker fileChecker = (FileChecker) checkerTypeComboBox.getSelectedItem(); 155 if (fileChecker == null) { 156 logger.error("fileChecker is null"); 157 return; 158 } 159 160 recoveryFilePanel = new FilePanel("Choose Recovery File"); 161 recoveryFilePanel.initialize(); 162 recoveryFilePanel.setDescriptionAndFileExtensions(fileChecker.getFileDescription(), fileChecker.getFileExtensions()); 163 164 categoryTabbedPane = new JTabbedPane(); 165 166 createBruteForcePanel(); 167 categoryTabbedPane.addTab("Brute Force", null, bruteForceCategoryPanel, "Brute Force"); 168 categoryTabbedPane.setSelectedIndex(0); 169 170 createDictionaryPanel(); 171 categoryTabbedPane.addTab("Dictionary", null, dictionaryCategoryPanel, "Dictionary"); 172 173 progressBar = new JProgressBar(); 174 progressBar.setStringPainted(true); 175 String text = numberFormat.format(0); 176 progressBar.setString(text); 177 178 optionPanel.add(checkerTypeComboBox); 179 optionPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER)); 180 optionPanel.add(recoveryFilePanel); 181 optionPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER)); 182 optionPanel.add(categoryTabbedPane); 183 optionPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER)); 184 optionPanel.add(progressBar); 185 } 186 187 private void createBruteForcePanel() { 188 bruteForceCategoryPanel = new JPanel(); 189 190 JPanel topLevelPanel = new JPanel(); 191 topLevelPanel.setLayout(new BoxLayout(topLevelPanel, BoxLayout.Y_AXIS)); 192 bruteForceCategoryPanel.add(topLevelPanel); 193 194 JPanel charsetPanel = new JPanel(); 195 charsetPanel.setLayout(new BoxLayout(charsetPanel, BoxLayout.X_AXIS)); 196 197 JPanel passwordLengthPanel = new JPanel(); 198 passwordLengthPanel.setLayout(new BoxLayout(passwordLengthPanel, BoxLayout.X_AXIS)); 199 200 topLevelPanel.add(charsetPanel); 201 topLevelPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER)); 202 topLevelPanel.add(passwordLengthPanel); 203 204 numberCheckBox = new JCheckBox("Number"); 205 numberCheckBox.setSelected(true); 206 lowercaseLetterCheckBox = new JCheckBox("Lowercase Letter"); 207 uppercaseLetterCheckBox = new JCheckBox("Uppercase Letter"); 208 userIncludedCheckBox = new JCheckBox("User-defined"); 209 userIncludedTextField = new JTextField(10); 210 userExcludedCheckBox = new JCheckBox("User-excluded"); 211 userExcludedTextField = new JTextField(10); 212 213 214 charsetPanel.add(numberCheckBox); 215 charsetPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER)); 216 charsetPanel.add(lowercaseLetterCheckBox); 217 charsetPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER)); 218 charsetPanel.add(uppercaseLetterCheckBox); 219 charsetPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER)); 220 charsetPanel.add(userIncludedCheckBox); 221 charsetPanel.add(userIncludedTextField); 222 charsetPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER)); 223 charsetPanel.add(userExcludedCheckBox); 224 charsetPanel.add(userExcludedTextField); 225 226 JLabel minLabel = new JLabel("Minimum Length: "); 227 JLabel maxLabel = new JLabel("Maximum Length: "); 228 229 minSpinner = new JSpinner(); 230 minSpinner.setModel(new SpinnerNumberModel(1, 1, 9, 1)); 231 minSpinner.setToolTipText("Minimum Length"); 232 233 maxSpinner = new JSpinner(); 234 maxSpinner.setModel(new SpinnerNumberModel(6, 1, 9, 1)); 235 maxSpinner.setToolTipText("Maximum Length"); 236 237 passwordLengthPanel.add(minLabel); 238 passwordLengthPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER)); 239 passwordLengthPanel.add(minSpinner); 240 passwordLengthPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER)); 241 passwordLengthPanel.add(maxLabel); 242 passwordLengthPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER)); 243 passwordLengthPanel.add(maxSpinner); 244 } 245 246 private void createDictionaryPanel() { 247 dictionaryCategoryPanel = new JPanel(); 248 249 JPanel topLevelPanel = new JPanel(); 250 topLevelPanel.setLayout(new BoxLayout(topLevelPanel, BoxLayout.Y_AXIS)); 251 dictionaryCategoryPanel.add(topLevelPanel); 252 253 dictionaryFilePanel = new FilePanel("Choose Dictionary File"); 254 dictionaryFilePanel.initialize(); 255 dictionaryFilePanel.setDescriptionAndFileExtensions("*.dic;*.txt", new String[]{"dic", "txt"}); 256 257 JPanel threadPanel = new JPanel(); 258 threadPanel.setLayout(new BoxLayout(threadPanel, BoxLayout.X_AXIS)); 259 260 topLevelPanel.add(dictionaryFilePanel); 261 topLevelPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER)); 262 topLevelPanel.add(threadPanel); 263 264 isUseMultiThreadCheckBox = new JCheckBox("Use Multi-thread"); 265 isUseMultiThreadCheckBox.setSelected(true); 266 267 threadPanel.add(isUseMultiThreadCheckBox); 268 } 269 270 private void createOperationPanel() { 271 operationPanel = new JPanel(); 272 operationPanel.setLayout(new BoxLayout(operationPanel, BoxLayout.X_AXIS)); 273 274 startButton = new JButton("Start"); 275 stopButton = new JButton("Stop"); 276 currentStateLabel = new JLabel("State: IDLE"); 277 currentPasswordLabel = new JLabel("Trying: "); 278 currentSpeedLabel = new JLabel("Speed: 0 passwords/s"); 279 280 operationPanel.add(startButton); 281 operationPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER)); 282 operationPanel.add(stopButton); 283 operationPanel.add(Box.createHorizontalStrut(2 * Constants.DEFAULT_X_BORDER)); 284 operationPanel.add(currentStateLabel); 285 operationPanel.add(Box.createHorizontalStrut(2 * Constants.DEFAULT_X_BORDER)); 286 operationPanel.add(currentPasswordLabel); 287 operationPanel.add(Box.createHorizontalStrut(2 * Constants.DEFAULT_X_BORDER)); 288 operationPanel.add(currentSpeedLabel); 289 operationPanel.add(Box.createHorizontalGlue()); 290 291 startButton.addActionListener(e -> startExecutorService.submit(this::onStart)); 292 stopButton.addActionListener(e -> stopExecutorService.submit(this::onStop)); 293 294 setCurrentState(State.IDLE); 295 } 296 297 private void onStart() { 298 FileChecker fileChecker = (FileChecker) checkerTypeComboBox.getSelectedItem(); 299 if (fileChecker == null) { 300 JOptionPane.showMessageDialog(this, "fileChecker is null!"); 301 return; 302 } 303 if (!fileChecker.prepareChecker()) { 304 JOptionPane.showMessageDialog(this, "onStart failed: Checker condition does not been prepared!"); 305 return; 306 } 307 308 File selectedFile = recoveryFilePanel.getFile(); 309 if (!selectedFile.isFile()) { 310 JOptionPane.showMessageDialog(this, "file is not a file!"); 311 return; 312 } 313 314 String extension = FilenameUtils.getExtension(recoveryFilePanel.getFile().getName()); 315 if (!ArrayUtils.contains(fileChecker.getFileExtensions(), StringUtils.toRootLowerCase(extension))) { 316 JOptionPane.showMessageDialog(this, "invalid file!"); 317 return; 318 } 319 320 fileChecker.attachFile(selectedFile); 321 currentFileChecker = fileChecker; 322 323 Component selectedPanel = categoryTabbedPane.getSelectedComponent(); 324 if (selectedPanel.equals(bruteForceCategoryPanel)) { 325 currentCategoryType = CategoryType.BRUTE_FORCE; 326 } else if (selectedPanel.equals(dictionaryCategoryPanel)) { 327 if (isUseMultiThreadCheckBox.isSelected()) { 328 currentCategoryType = CategoryType.DICTIONARY_MULTI_THREAD; 329 } else { 330 currentCategoryType = CategoryType.DICTIONARY_SINGLE_THREAD; 331 } 332 } else { 333 currentCategoryType = CategoryType.UNKNOWN; 334 } 335 logger.info("onStart: {}", currentCategoryType); 336 if (currentCategoryType == CategoryType.UNKNOWN) { 337 JOptionPane.showMessageDialog(this, "onStart failed: Invalid category!"); 338 return; 339 } 340 setCurrentState(State.WORKING); 341 passwordTryCount = 0; 342 timer.start(); 343 ICategory category = CategoryFactory.getCategoryInstance(currentCategoryType); 344 category.start(this); 345 if (currentState == State.WORKING) { 346 onStop(); 347 } 348 } 349 350 private void onStop() { 351 logger.info("onStop: currentState: {}, currentCategoryType: {}", currentState, currentCategoryType); 352 if (currentState != State.WORKING) { 353 logger.error("onStop failed: Not in working state!"); 354 return; 355 } 356 if (currentCategoryType == CategoryType.UNKNOWN) { 357 logger.error("onStop failed: Invalid category!"); 358 return; 359 } 360 timer.stop(); 361 setCurrentState(State.STOPPING); 362 ICategory category = CategoryFactory.getCategoryInstance(currentCategoryType); 363 category.cancel(); 364 setCurrentState(State.IDLE); 365 currentCategoryType = CategoryType.UNKNOWN; 366 } 367 368 public void showResultWithDialog(String password) { 369 if (password == null) { 370 logger.error("Can not find password"); 371 JOptionPane.showMessageDialog(this, "Can not find password"); 372 } else { 373 logger.info("Find out the password: {}", password); 374 JPanel panel = createPasswordShowPanel(password); 375 JOptionPane.showMessageDialog(this, panel, "Find out the password", JOptionPane.INFORMATION_MESSAGE); 376 } 377 } 378 379 private static JPanel createPasswordShowPanel(String password) { 380 JPanel panel = new JPanel(); 381 panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); 382 JTextField textField = new JTextField(password); 383 textField.setEditable(false); 384 textField.setColumns(20); 385 JButton button = new JButton("Copy"); 386 button.addActionListener(e -> { 387 StringSelection stringSelection = new StringSelection(password); 388 Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); 389 clipboard.setContents(stringSelection, null); 390 }); 391 panel.add(textField); 392 panel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER)); 393 panel.add(button); 394 return panel; 395 } 396 397 public void setCurrentState(State currentState) { 398 SwingUtilities.invokeLater(() -> { 399 this.currentState = currentState; 400 if (currentStateLabel != null) { 401 currentStateLabel.setText("State: " + currentState.toString()); 402 } 403 if (currentState == State.WORKING) { 404 setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); 405 startButton.setEnabled(false); 406 stopButton.setEnabled(true); 407 updateUiComponent(false); 408 } else if (currentState == State.STOPPING) { 409 setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); 410 startButton.setEnabled(false); 411 stopButton.setEnabled(false); 412 updateUiComponent(false); 413 } else if (currentState == State.IDLE) { 414 setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); 415 startButton.setEnabled(true); 416 stopButton.setEnabled(false); 417 updateUiComponent(true); 418 } 419 }); 420 } 421 422 private void updateUiComponent(boolean enable) { 423 for (Component component : getComponents(optionPanel)) { 424 component.setEnabled(enable); 425 } 426 } 427 428 private Component[] getComponents(Component container) { 429 List<Component> list; 430 431 try { 432 list = new ArrayList<>(Arrays.asList( 433 ((Container) container).getComponents())); 434 for (int index = 0; index < list.size(); index++) { 435 list.addAll(Arrays.asList(getComponents(list.get(index)))); 436 } 437 } catch (ClassCastException e) { 438 list = new ArrayList<>(); 439 } 440 441 return list.toArray(new Component[0]); 442 } 443 444 public State getCurrentState() { 445 return currentState; 446 } 447 448 public void resetProgressMaxValue(int maxValue) { 449 SwingUtilities.invokeLater(() -> { 450 progressBar.setMaximum(maxValue); 451 setProgressBarValue(0); 452 }); 453 } 454 455 public void increaseProgressBarValue() { 456 SwingUtilities.invokeLater(() -> setProgressBarValue(progressBar.getValue() + 1)); 457 } 458 459 private void setProgressBarValue(int value) { 460 int properValue = Math.min(value, progressBar.getMaximum()); 461 progressBar.setValue(properValue); 462 String text = numberFormat.format(((double) properValue) / progressBar.getMaximum()); 463 progressBar.setString(text); 464 } 465 466 public void setCurrentPassword(String password) { 467 SwingUtilities.invokeLater(() -> currentPasswordLabel.setText("Trying: " + password)); 468 } 469 470 public String getCharset() { 471 Set<Character> charsetSet = new HashSet<>(); 472 if (numberCheckBox.isSelected()) { 473 CollectionUtils.addAll(charsetSet, ArrayUtils.toObject("0123456789".toCharArray())); 474 } 475 if (lowercaseLetterCheckBox.isSelected()) { 476 CollectionUtils.addAll(charsetSet, ArrayUtils.toObject("abcdefghijklmnopqrstuvwxyz".toCharArray())); 477 } 478 if (uppercaseLetterCheckBox.isSelected()) { 479 CollectionUtils.addAll(charsetSet, ArrayUtils.toObject("ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray())); 480 } 481 if (userIncludedCheckBox.isSelected()) { 482 CollectionUtils.addAll(charsetSet, ArrayUtils.toObject(userIncludedTextField.getText().toCharArray())); 483 } 484 if (userExcludedCheckBox.isSelected()) { 485 for (char ch : userExcludedTextField.getText().toCharArray()) { 486 charsetSet.remove(ch); 487 } 488 } 489 return String.valueOf(ArrayUtils.toPrimitive(charsetSet.toArray(new Character[0]))); 490 } 491 492 public File getDictionaryFile() { 493 return dictionaryFilePanel.getFile(); 494 } 495 496 public int getMinLength() { 497 return (Integer) minSpinner.getValue(); 498 } 499 500 public int getMaxLength() { 501 return (Integer) maxSpinner.getValue(); 502 } 503 504 public FileChecker getCurrentFileChecker() { 505 return currentFileChecker; 506 } 507 }