def run_statistical_crack_mode(pJTR: JohnTheRipper, pPercentile: float, pMaxAllowedCharactersToBruteForce: int) -> None: # The JTR POT file is the source of passwords Printer.print("Parsing JTR POT file at {}".format(pJTR.jtr_pot_file_path), Level.INFO) lListOfPasswords = pJTR.parse_passwords_from_pot() if pJTR.verbose: lCountPasswords = lListOfPasswords.__len__() Printer.print( "Using {} passwords in statistical analysis: ".format( str(lCountPasswords)), Level.INFO) if lCountPasswords > 1000000: Printer.print( "That is a lot of passwords. Statistical analysis may take a while.", Level.WARNING) # Let PasswordStats class analyze most likely masks Printer.print("Beginning statistical analysis", Level.INFO) lPasswordStats = PasswordStats(lListOfPasswords) Printer.print( "Parsed {} passwords into {} masks".format( lPasswordStats.count_passwords, lPasswordStats.count_masks), Level.INFO) # Calculate masks most likely need to crack X% of the password hashes lMasks = lPasswordStats.get_popular_masks(pPercentile) Printer.print( "Password masks ({} percentile): {}".format(pPercentile, lMasks), Level.INFO) run_smart_mask_mode( pJTR=pJTR, pMasks=lMasks, pMaxAllowedCharactersToBruteForce=pMaxAllowedCharactersToBruteForce)
def perform_statistical_cracking(pHashFile: str, pPercentile: float, pHashFormat: str, pVerbose: bool, pDebug: bool, pPassThrough: str, pNumberHashes: int) -> None: # The JTR POT file is the source of passwords if pVerbose: print("[*] Parsing JTR POT file at {}".format(JTR_POT_FILE_PATH)) lListOfPasswords = parse_jtr_pot(pVerbose, True) if pVerbose: lCountPasswords = lListOfPasswords.__len__() print("[*] Using {} passwords in statistical analysis: ".format( str(lCountPasswords))) if lCountPasswords > 1000000: print( "[*] That is a lot of passwords. Statistical analysis may take a while." ) # Let PasswordStats class analyze most likely masks if pVerbose: print("[*] Beginning statistical analysis") lPasswordStats = PasswordStats(lListOfPasswords) if pVerbose: print("[*] Parsed {} passwords into {} masks".format( lPasswordStats.count_passwords, lPasswordStats.count_masks)) # Calculate masks most likely need to crack X% of the password hashes lMasks = lPasswordStats.get_popular_masks(lPercentile) if pVerbose: print("[*] Password masks ({} percentile): {}".format( pPercentile, lMasks)) # For each mask, try high probability guesses lUndefinedMasks = [] for lMask in lMasks: if pVerbose: print("[*] Processing mask: {}".format(lMask)) # If the number of characters in the mask is "small" as defined by # MAX_CHARS_TO_BRUTEFORCE, then use brute-force on the pattern. # If there are more characters than the limit, use "smart brute-force" # which is a hybrid between dictionary and mask mode. lCountCharacters = int(len(lMask) / 2) if lCountCharacters <= MAX_CHARS_TO_BRUTEFORCE: lWordlist = "" run_jtr_mask_mode(pHashFile=pHashFile, pMask=lMask, pWordlist=lWordlist, pHashFormat=pHashFormat, pVerbose=pVerbose, pDebug=pDebug, pPassThrough=pPassThrough, pNumberHashes=pNumberHashes) else: # All lowercase letters if re.match('^(\?l)+$', lMask): lCountLetters = lMask.count('?l') if lCountLetters > MAX_CHARS_TO_BRUTEFORCE: lWordlist = "dictionaries/{}-character-words.txt".format( str(lCountLetters)) lRule = "" run_jtr_wordlist_mode(pHashFile=pHashFile, pWordlist=lWordlist, pRule=lRule, pHashFormat=pHashFormat, pVerbose=pVerbose, pDebug=pDebug, pPassThrough=pPassThrough, pNumberHashes=pNumberHashes) # All uppercase elif re.match('^(\?u)+$', lMask): lCountLetters = lMask.count('?u') lWordlist = "dictionaries/{}-character-words.txt".format( str(lCountLetters)) lRule = "uppercase" run_jtr_wordlist_mode(pHashFile=pHashFile, pWordlist=lWordlist, pRule=lRule, pHashFormat=pHashFormat, pVerbose=pVerbose, pDebug=pDebug, pPassThrough=pPassThrough, pNumberHashes=pNumberHashes) # Uppercase followed by lowercase (assume only leading letter is uppercase) elif re.match('^(\?u)(\?l)+$', lMask): lCountLetters = lMask.count('?u') + lMask.count('?l') lWordlist = "dictionaries/{}-character-words.txt".format( str(lCountLetters)) lRule = "capitalize" run_jtr_wordlist_mode(pHashFile=pHashFile, pWordlist=lWordlist, pRule=lRule, pHashFormat=pHashFormat, pVerbose=pVerbose, pDebug=pDebug, pPassThrough=pPassThrough, pNumberHashes=pNumberHashes) # Lowercase ending with digits elif re.match('^(\?l)+(\?d)+$', lMask): lCountLetters = lMask.count('?l') lCountDigits = lMask.count('?d') lWordlist = "dictionaries/{}-character-words.txt".format( str(lCountLetters)) lRule = "append{}digits".format(str(lCountDigits)) run_jtr_wordlist_mode(pHashFile=pHashFile, pWordlist=lWordlist, pRule=lRule, pHashFormat=pHashFormat, pVerbose=pVerbose, pDebug=pDebug, pPassThrough=pPassThrough, pNumberHashes=pNumberHashes) # Uppercase followed by digits elif re.match('^(\?u)+(\?d)+$', lMask): lCountLetters = lMask.count('?u') lCountDigits = lMask.count('?d') lWordlist = "dictionaries/{}-character-words.txt".format( str(lCountLetters)) lRule = "uppercaseappend{}digits".format(str(lCountDigits)) run_jtr_wordlist_mode(pHashFile=pHashFile, pWordlist=lWordlist, pRule=lRule, pHashFormat=pHashFormat, pVerbose=pVerbose, pDebug=pDebug, pPassThrough=pPassThrough, pNumberHashes=pNumberHashes) # Uppercase, lowercase, then digits (assume only leading letter is uppercase) elif re.match('^(\?u)(\?l)+(\?d)+$', lMask): lCountLetters = lMask.count('?u') + lMask.count('?l') lCountDigits = lMask.count('?d') lWordlist = "dictionaries/{}-character-words.txt".format( str(lCountLetters)) lRule = "capitalizeappend{}digits".format(str(lCountDigits)) run_jtr_wordlist_mode(pHashFile=pHashFile, pWordlist=lWordlist, pRule=lRule, pHashFormat=pHashFormat, pVerbose=pVerbose, pDebug=pDebug, pPassThrough=pPassThrough, pNumberHashes=pNumberHashes) # Low number of digits. We do not cover large numbers of digits because # precomputing dictionary files would be huge and running mask mode takes a long time. # Right now we support 4-6 digits only. Recall we cover 4 and 6 digits specifically in # "prayer" mode so we do not repeat those two masks here. elif re.match('^(\?d)+$', lMask): lCountDigits = lMask.count('?d') if lCountDigits == 5: lWordlist = "dictionaries/{}-digit-numbers.txt".format( str(lCountDigits)) run_jtr_wordlist_mode(pHashFile=pHashFile, pWordlist=lWordlist, pRule="", pHashFormat=pHashFormat, pVerbose=pVerbose, pDebug=pDebug, pPassThrough=pPassThrough, pNumberHashes=pNumberHashes) else: print( "[*] WARNING: Did not process mask {} because it is out of policy" .format(lMask)) # Lowercase ending with something other than the masks already accounted for. If the # ending pattern is longer than 4 characters, we do not try because it takes a long time # to test that many hashes elif re.match('^(\?l)+', lMask): lPrefix = re.search('^(\?l)+', lMask).group() lCountLetters = lPrefix.count("?l") lSuffix = lMask[lCountLetters * 2:] if len(lSuffix) <= 4: lWordlist = "dictionaries/{}-character-words.txt".format( str(lCountLetters)) lMaskParam = "--mask=?w{}".format(lSuffix) run_jtr_mask_mode(pHashFile=pHashFile, pMask=lMaskParam, pWordlist=lWordlist, pHashFormat=pHashFormat, pVerbose=pVerbose, pDebug=pDebug, pPassThrough=pPassThrough, pNumberHashes=pNumberHashes) else: print( "[*] WARNING: Did not process mask {} because it is out of policy" .format(lMask)) else: lUndefinedMasks.append(lMask) print( "[*] WARNING: No policy defined for mask {}".format(lMask)) # List masks that did not match a pattern so that a pattern can be added if lUndefinedMasks: print( "[*] WARNING: There was no policy defined for the following masks: {}" .format(lUndefinedMasks))
if lArgs.input_file: with open(lArgs.input_file, READ_BYTES) as lFile: lListOfPasswords = lFile.readlines() if lArgs.verbose: print("[*] Finished reading input file " + lArgs.input_file) # Count passwords imported if lArgs.verbose: lCountPasswords = lListOfPasswords.__len__() print("[*] Passwords imported: " + str(lCountPasswords)) if lCountPasswords > 1000000: print("[*] That is a lot of passwords. Parsing may take a while.") # Calculate password statistics and store in PasswordStats object if lArgs.verbose: print("[*] Parsing input file " + lArgs.input_file) lPasswordStats = PasswordStats(lListOfPasswords) if lArgs.verbose: print("[*] Finished parsing input file " + lArgs.input_file) print("[*] Parsed {} passwords into {} masks".format( lPasswordStats.count_passwords, lPasswordStats.count_masks)) if lArgs.analyze_passwords: lPasswordStats.get_analysis(lPercentile) if lArgs.list_masks: if lArgs.verbose: print("[*] Password masks ({} percentile):".format(lPercentile), end='') print(lPasswordStats.get_popular_masks(lPercentile)) if lArgs.output_file: