From 9c5bef9e50d90edffa4a17ee4e3bda315efcd691 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Haidar-Bachminska Date: Sun, 19 Jun 2016 00:29:27 +0200 Subject: [PATCH 01/27] Applied 2to3 to each python file --- maskgen.py | 46 ++++++++-------- policygen.py | 28 +++++----- rulegen.py | 148 +++++++++++++++++++++++++-------------------------- statsgen.py | 44 +++++++-------- 4 files changed, 133 insertions(+), 133 deletions(-) diff --git a/maskgen.py b/maskgen.py index 2b9a592..131afa7 100755 --- a/maskgen.py +++ b/maskgen.py @@ -51,7 +51,7 @@ def getcomplexity(self, mask): elif char == "d": count *= 10 elif char == "s": count *= 33 elif char == "a": count *= 95 - else: print "[!] Error, unknown mask ?%s in a mask %s" % (char,mask) + else: print("[!] Error, unknown mask ?%s in a mask %s" % (char,mask)) return count @@ -97,12 +97,12 @@ def generate_masks(self,sorting_mode): # Group by length 1,2,3,4,5,6,7,8,9,10.... # Group by occurrence 10%, 20%, 30%, 40%, 50%.... - if self.showmasks: print "[L:] Mask: [ Occ: ] [ Time: ]" - for mask in sorted(self.masks.keys(), key=lambda mask: self.masks[mask][sorting_mode], reverse=True): + if self.showmasks: print("[L:] Mask: [ Occ: ] [ Time: ]") + for mask in sorted(list(self.masks.keys()), key=lambda mask: self.masks[mask][sorting_mode], reverse=True): if self.showmasks: time_human = ">1 year" if self.masks[mask]['time'] > 60*60*24*365 else str(datetime.timedelta(seconds=self.masks[mask]['time'])) - print "[{:>2}] {:<30} [{:<7}] [{:>8}] ".format(self.masks[mask]['length'], mask, self.masks[mask]['occurrence'], time_human) + print("[{:>2}] {:<30} [{:<7}] [{:>8}] ".format(self.masks[mask]['length'], mask, self.masks[mask]['occurrence'], time_human)) if self.output_file: self.output_file.write("%s\n" % mask) @@ -112,14 +112,14 @@ def generate_masks(self,sorting_mode): sample_count += 1 if self.target_time and sample_time > self.target_time: - print "[!] Target time exceeded." + print("[!] Target time exceeded.") break - print "[*] Finished generating masks:" - print " Masks generated: %s" % sample_count - print " Masks coverage: %d%% (%d/%d)" % (sample_occurrence*100/self.total_occurrence,sample_occurrence,self.total_occurrence) + print("[*] Finished generating masks:") + print(" Masks generated: %s" % sample_count) + print(" Masks coverage: %d%% (%d/%d)" % (sample_occurrence*100/self.total_occurrence,sample_occurrence,self.total_occurrence)) time_human = ">1 year" if sample_time > 60*60*24*365 else str(datetime.timedelta(seconds=sample_time)) - print " Masks runtime: %s" % time_human + print(" Masks runtime: %s" % time_human) def getmaskscoverage(self, checkmasks): @@ -128,7 +128,7 @@ def getmaskscoverage(self, checkmasks): total_complexity = 0 - if self.showmasks: print "[L:] Mask: [ Occ: ] [ Time: ]" + if self.showmasks: print("[L:] Mask: [ Occ: ] [ Time: ]") for mask in checkmasks: mask = mask.strip() mask_complexity = self.getcomplexity(mask) @@ -139,7 +139,7 @@ def getmaskscoverage(self, checkmasks): if self.showmasks: time_human = ">1 year" if self.masks[mask]['time'] > 60*60*24*365 else str(datetime.timedelta(seconds=self.masks[mask]['time'])) - print "[{:>2}] {:<30} [{:<7}] [{:>8}] ".format(self.masks[mask]['length'], mask, self.masks[mask]['occurrence'], time_human) + print("[{:>2}] {:<30} [{:<7}] [{:>8}] ".format(self.masks[mask]['length'], mask, self.masks[mask]['occurrence'], time_human)) if self.output_file: self.output_file.write("%s\n" % mask) @@ -148,16 +148,16 @@ def getmaskscoverage(self, checkmasks): sample_count += 1 if self.target_time and total_complexity/self.pps > self.target_time: - print "[!] Target time exceeded." + print("[!] Target time exceeded.") break # TODO: Something wrong here, complexity and time doesn't match with estimated from policygen total_time = total_complexity/self.pps time_human = ">1 year" if total_time > 60*60*24*365 else str(datetime.timedelta(seconds=total_time)) - print "[*] Finished matching masks:" - print " Masks matched: %s" % sample_count - print " Masks coverage: %d%% (%d/%d)" % (sample_occurrence*100/self.total_occurrence,sample_occurrence,self.total_occurrence) - print " Masks runtime: %s" % time_human + print("[*] Finished matching masks:") + print(" Masks matched: %s" % sample_count) + print(" Masks coverage: %d%% (%d/%d)" % (sample_occurrence*100/self.total_occurrence,sample_occurrence,self.total_occurrence)) + print(" Masks runtime: %s" % time_human) if __name__ == "__main__": @@ -210,20 +210,20 @@ def getmaskscoverage(self, checkmasks): # Print program header if not options.quiet: - print header + print(header) if len(args) < 1: parser.error("no masks file specified! Please provide statsgen output.") exit(1) - print "[*] Analyzing masks in [%s]" % args[0] + print("[*] Analyzing masks in [%s]" % args[0]) maskgen = MaskGen() # Settings if options.target_time: maskgen.target_time = options.target_time if options.output_masks: - print "[*] Saving generated masks to [%s]" % options.output_masks + print("[*] Saving generated masks to [%s]" % options.output_masks) maskgen.output_file = open(options.output_masks, 'w') # Filters @@ -240,7 +240,7 @@ def getmaskscoverage(self, checkmasks): if options.pps: maskgen.pps = options.pps if options.showmasks: maskgen.showmasks = options.showmasks - print "[*] Using {:,d} keys/sec for calculations.".format(maskgen.pps) + print("[*] Using {:,d} keys/sec for calculations.".format(maskgen.pps)) # Load masks for arg in args: @@ -249,13 +249,13 @@ def getmaskscoverage(self, checkmasks): # Matching masks from the command-line if options.checkmasks: checkmasks = [m.strip() for m in options.checkmasks.split(',')] - print "[*] Checking coverage of the these masks [%s]" % ", ".join(checkmasks) + print("[*] Checking coverage of the these masks [%s]" % ", ".join(checkmasks)) maskgen.getmaskscoverage(checkmasks) # Matching masks from a file elif options.checkmasks_file: checkmasks_file = open(options.checkmasks_file, 'r') - print "[*] Checking coverage of masks in [%s]" % options.checkmasks_file + print("[*] Checking coverage of masks in [%s]" % options.checkmasks_file) maskgen.getmaskscoverage(checkmasks_file) # Printing masks in a file @@ -268,5 +268,5 @@ def getmaskscoverage(self, checkmasks): else: sorting_mode = "optindex" - print "[*] Sorting masks by their [%s]." % sorting_mode + print("[*] Sorting masks by their [%s]." % sorting_mode) maskgen.generate_masks(sorting_mode) \ No newline at end of file diff --git a/policygen.py b/policygen.py index e96d5cf..65f2f5e 100755 --- a/policygen.py +++ b/policygen.py @@ -45,7 +45,7 @@ def getcomplexity(self, mask): elif char == "d": count *= 10 elif char == "s": count *= 33 elif char == "a": count *= 95 - else: print "[!] Error, unknown mask ?%s in a mask %s" % (char,mask) + else: print("[!] Error, unknown mask ?%s in a mask %s" % (char,mask)) return count @@ -61,8 +61,8 @@ def generate_masks(self, noncompliant): sample_complexity = 0 # TODO: Randomize or even statistically arrange matching masks - for length in xrange(self.minlength, self.maxlength+1): - print "[*] Generating %d character password masks." % length + for length in range(self.minlength, self.maxlength+1): + print("[*] Generating %d character password masks." % length) total_length_count = 0 sample_length_count = 0 @@ -109,7 +109,7 @@ def generate_masks(self, noncompliant): if self.showmasks: mask_time = mask_complexity/self.pps time_human = ">1 year" if mask_time > 60*60*24*365 else str(datetime.timedelta(seconds=mask_time)) - print "[{:>2}] {:<30} [l:{:>2} u:{:>2} d:{:>2} s:{:>2}] [{:>8}] ".format(length, mask, lowercount,uppercount,digitcount,specialcount, time_human) + print("[{:>2}] {:<30} [l:{:>2} u:{:>2} d:{:>2} s:{:>2}] [{:>8}] ".format(length, mask, lowercount,uppercount,digitcount,specialcount, time_human)) if self.output_file: self.output_file.write("%s\n" % mask) @@ -123,11 +123,11 @@ def generate_masks(self, noncompliant): total_time = total_complexity/self.pps total_time_human = ">1 year" if total_time > 60*60*24*365 else str(datetime.timedelta(seconds=total_time)) - print "[*] Total Masks: %d Time: %s" % (total_count, total_time_human) + print("[*] Total Masks: %d Time: %s" % (total_count, total_time_human)) sample_time = sample_complexity/self.pps sample_time_human = ">1 year" if sample_time > 60*60*24*365 else str(datetime.timedelta(seconds=sample_time)) - print "[*] Policy Masks: %d Time: %s" % (sample_count, sample_time_human) + print("[*] Policy Masks: %d Time: %s" % (sample_count, sample_time_human)) if __name__ == "__main__": @@ -168,13 +168,13 @@ def generate_masks(self, noncompliant): # Print program header if not options.quiet: - print header + print(header) policygen = PolicyGen() # Settings if options.output_masks: - print "[*] Saving generated masks to [%s]" % options.output_masks + print("[*] Saving generated masks to [%s]" % options.output_masks) policygen.output_file = open(options.output_masks, 'w') @@ -194,13 +194,13 @@ def generate_masks(self, noncompliant): if options.pps: policygen.pps = options.pps if options.showmasks: policygen.showmasks = options.showmasks - print "[*] Using {:,d} keys/sec for calculations.".format(policygen.pps) + print("[*] Using {:,d} keys/sec for calculations.".format(policygen.pps)) # Print current password policy - print "[*] Password policy:" - print " Pass Lengths: min:%d max:%d" % (policygen.minlength, policygen.maxlength) - print " Min strength: l:%s u:%s d:%s s:%s" % (policygen.minlower, policygen.minupper, policygen.mindigit, policygen.minspecial) - print " Max strength: l:%s u:%s d:%s s:%s" % (policygen.maxlower, policygen.maxupper, policygen.maxdigit, policygen.maxspecial) + print("[*] Password policy:") + print(" Pass Lengths: min:%d max:%d" % (policygen.minlength, policygen.maxlength)) + print(" Min strength: l:%s u:%s d:%s s:%s" % (policygen.minlower, policygen.minupper, policygen.mindigit, policygen.minspecial)) + print(" Max strength: l:%s u:%s d:%s s:%s" % (policygen.maxlower, policygen.maxupper, policygen.maxdigit, policygen.maxspecial)) - print "[*] Generating [%s] masks." % ("compliant" if not options.noncompliant else "non-compliant") + print("[*] Generating [%s] masks." % ("compliant" if not options.noncompliant else "non-compliant")) policygen.generate_masks(options.noncompliant) \ No newline at end of file diff --git a/rulegen.py b/rulegen.py index 389d578..21f549c 100755 --- a/rulegen.py +++ b/rulegen.py @@ -172,9 +172,9 @@ def levenshtein(self,word,password): matrix = [] # Generate and populate the initial matrix - for i in xrange(len(password) + 1): + for i in range(len(password) + 1): matrix.append([]) - for j in xrange(len(word) + 1): + for j in range(len(word) + 1): if i == 0: matrix[i].append(j) elif j == 0: @@ -183,8 +183,8 @@ def levenshtein(self,word,password): matrix[i].append(0) # Calculate edit distance for each substring - for i in xrange(1,len(password) + 1): - for j in xrange(1,len(word) + 1): + for i in range(1,len(password) + 1): + for j in range(1,len(word) + 1): if password[i-1] == word[j-1]: matrix[i][j] = matrix[i-1][j-1] else: @@ -205,7 +205,7 @@ def levenshtein_distance(self, s1, s2): if not s1: return len(s2) - previous_row = xrange(len(s2) + 1) + previous_row = range(len(s2) + 1) for i, c1 in enumerate(s1): current_row = [i + 1] for j, c2 in enumerate(s2): @@ -219,11 +219,11 @@ def levenshtein_distance(self, s1, s2): def levenshtein_print(self,matrix,word,password): """ Print word X password matrix """ - print " %s" % " ".join(list(word)) + print(" %s" % " ".join(list(word))) for i,row in enumerate(matrix): - if i == 0: print " ", - else: print password[i-1], - print " ".join("%2d" % col for col in row) + if i == 0: print(" ", end=' ') + else: print(password[i-1], end=' ') + print(" ".join("%2d" % col for col in row)) def generate_levenshtein_rules(self, word, password): """ Generates levenshtein rules. Returns a list of lists of levenshtein rules. """ @@ -254,7 +254,7 @@ def levenshtein_reverse_recursive(self,matrix,i,j,path_len): cost = matrix[i][j] # Calculate minimum cost of each operation - cost_delete = cost_insert = cost_equal_or_replace = sys.maxint + cost_delete = cost_insert = cost_equal_or_replace = sys.maxsize if i > 0: cost_insert = matrix[i-1][j] if j > 0: cost_delete = matrix[i][j-1] if i > 0 and j > 0: cost_equal_or_replace = matrix[i-1][j-1] @@ -285,7 +285,7 @@ def load_custom_wordlist(self,wordlist_file): def generate_words(self,password): """ Generate source word candidates.""" - if self.debug: print "[*] Generating source words for %s" % password + if self.debug: print("[*] Generating source words for %s" % password) words = list() words_collection = list() @@ -319,8 +319,8 @@ def generate_words(self,password): suggestions.append(suggestion) if len(suggestions) != len(set(suggestions)): - print sorted(suggestions) - print sorted(set(suggestions)) + print(sorted(suggestions)) + print(sorted(set(suggestions))) for suggestion in suggestions: @@ -347,22 +347,22 @@ def generate_words(self,password): elif word["distance"] > best_found_distance: if self.verbose: - print "[-] %s => {edit distance suboptimal: %d (%d)} => %s" % \ - (word["suggestion"], word["distance"], best_found_distance, word["password"]) + print("[-] %s => {edit distance suboptimal: %d (%d)} => %s" % \ + (word["suggestion"], word["distance"], best_found_distance, word["password"])) break # Filter words with too big edit distance if word["distance"] <= self.max_word_dist: if self.debug: - print "[+] %s => {edit distance: %d (%d)} = > %s" % \ - (word["suggestion"], word["distance"],best_found_distance, word["password"]) + print("[+] %s => {edit distance: %d (%d)} = > %s" % \ + (word["suggestion"], word["distance"],best_found_distance, word["password"])) words_collection.append(word) else: if self.verbose: - print "[-] %s => {max distance exceeded: %d (%d)} => %s" % \ - (word["suggestion"], word["distance"], self.max_word_dist, word["password"]) + print("[-] %s => {max distance exceeded: %d (%d)} => %s" % \ + (word["suggestion"], word["distance"], self.max_word_dist, word["password"])) if self.max_words: words_collection = words_collection[:self.max_words] @@ -433,9 +433,9 @@ def generate_hashcat_rules(self, suggestion, password): hashcat_rule = self.generate_advanced_hashcat_rules(suggestion, lev_rule, password) if hashcat_rule == None: - print "[!] Processing FAILED: %s => ;( => %s" % (suggestion,password) - print " Sorry about that, please report this failure to" - print " the developer: iphelix [at] thesprawl.org" + print("[!] Processing FAILED: %s => ;( => %s" % (suggestion,password)) + print(" Sorry about that, please report this failure to") + print(" the developer: iphelix [at] thesprawl.org") else: hashcat_rules.append(hashcat_rule) @@ -454,8 +454,8 @@ def generate_hashcat_rules(self, suggestion, password): elif rule_length > best_found_rule_length: if self.verbose: - print "[-] %s => {best rule length exceeded: %d (%d)} => %s" % \ - (suggestion, rule_length, best_found_rule_length, password) + print("[-] %s => {best rule length exceeded: %d (%d)} => %s" % \ + (suggestion, rule_length, best_found_rule_length, password)) break if rule_length <= self.max_rule_len: @@ -467,7 +467,7 @@ def generate_simple_hashcat_rules(self,word,rules,password): """ Generate basic hashcat rules using only basic insert,delete,replace rules. """ hashcat_rules = [] - if self.debug: print "[*] Simple Processing %s => %s" % (word,password) + if self.debug: print("[*] Simple Processing %s => %s" % (word,password)) # Dynamically apply rules to the source word # NOTE: Special case were word == password this would work as well. @@ -475,7 +475,7 @@ def generate_simple_hashcat_rules(self,word,rules,password): for (op,p,w) in rules: - if self.debug: print "\t[*] Simple Processing Started: %s - %s" % (word_rules, " ".join(hashcat_rules)) + if self.debug: print("\t[*] Simple Processing Started: %s - %s" % (word_rules, " ".join(hashcat_rules))) if op == 'insert': hashcat_rules.append("i%s%s" % (self.int_to_hashcat(p),password[p])) @@ -489,20 +489,20 @@ def generate_simple_hashcat_rules(self,word,rules,password): hashcat_rules.append("o%s%s" % (self.int_to_hashcat(p),password[p])) word_rules = self.hashcat_rule['o'](word_rules,p,password[p]) - if self.debug: print "\t[*] Simple Processing Ended: %s => %s => %s" % (word_rules, " ".join(hashcat_rules),password) + if self.debug: print("\t[*] Simple Processing Ended: %s => %s => %s" % (word_rules, " ".join(hashcat_rules),password)) # Check if rules result in the correct password if word_rules == password: return hashcat_rules else: - if self.debug: print "[!] Simple Processing FAILED: %s => %s => %s (%s)" % (word," ".join(hashcat_rules),password,word_rules) + if self.debug: print("[!] Simple Processing FAILED: %s => %s => %s (%s)" % (word," ".join(hashcat_rules),password,word_rules)) return None def generate_advanced_hashcat_rules(self,word,rules,password): """ Generate advanced hashcat rules using full range of available rules. """ hashcat_rules = [] - if self.debug: print "[*] Advanced Processing %s => %s" % (word,password) + if self.debug: print("[*] Advanced Processing %s => %s" % (word,password)) # Dynamically apply and store rules in word_rules variable. # NOTE: Special case where word == password this would work as well. @@ -514,7 +514,7 @@ def generate_advanced_hashcat_rules(self,word,rules,password): for i,(op,p,w) in enumerate(rules): - if self.debug: print "\t[*] Advanced Processing Started: %s - %s" % (word_rules, " ".join(hashcat_rules)) + if self.debug: print("\t[*] Advanced Processing Started: %s - %s" % (word_rules, " ".join(hashcat_rules))) if op == 'insert': hashcat_rules.append("i%s%s" % (self.int_to_hashcat(p),password[p])) @@ -538,7 +538,7 @@ def generate_advanced_hashcat_rules(self,word,rules,password): # This rule was made obsolete by a prior global replacement if word_rules[p] == password[p]: - if self.debug: print "\t[*] Advanced Processing Obsolete Rule: %s - %s" % (word_rules, " ".join(hashcat_rules)) + if self.debug: print("\t[*] Advanced Processing Obsolete Rule: %s - %s" % (word_rules, " ".join(hashcat_rules))) # Swapping rules elif p < len(password)-1 and p < len(word_rules)-1 and word_rules[p] == password[p+1] and word_rules[p+1] == password[p]: @@ -649,7 +649,7 @@ def generate_advanced_hashcat_rules(self,word,rules,password): hashcat_rules.append("o%s%s" % (self.int_to_hashcat(p),password[p])) word_rules = self.hashcat_rule['o'](word_rules,p,password[p]) - if self.debug: print "\t[*] Advanced Processing Ended: %s %s" % (word_rules, " ".join(hashcat_rules)) + if self.debug: print("\t[*] Advanced Processing Ended: %s %s" % (word_rules, " ".join(hashcat_rules))) ######################################################################## # Prefix rules @@ -718,7 +718,7 @@ def generate_advanced_hashcat_rules(self,word,rules,password): if word_rules == password: return hashcat_rules else: - if self.debug: print "[!] Advanced Processing FAILED: %s => %s => %s (%s)" % (word," ".join(hashcat_rules),password,word_rules) + if self.debug: print("[!] Advanced Processing FAILED: %s => %s => %s (%s)" % (word," ".join(hashcat_rules),password,word_rules)) return None @@ -727,21 +727,21 @@ def check_reversible_password(self, password): # Skip all numeric passwords if password.isdigit(): - if self.verbose and not self.quiet: print "[!] %s => {skipping numeric} => %s" % (password,password) + if self.verbose and not self.quiet: print("[!] %s => {skipping numeric} => %s" % (password,password)) self.numeric_stats_total += 1 return False # Skip passwords with less than 25% of alpha character # TODO: Make random word detection more reliable based on word entropy. elif len([c for c in password if c.isalpha()]) < len(password)/4: - if self.verbose and not self.quiet:print "[!] %s => {skipping alpha less than 25%%} => %s" % (password,password) + if self.verbose and not self.quiet:print("[!] %s => {skipping alpha less than 25%%} => %s" % (password,password)) self.special_stats_total += 1 return False # Only check english ascii passwords for now # TODO: Add support for more languages. elif [c for c in password if ord(c) < 32 or ord(c) > 126]: - if self.verbose and not self.quiet: print "[!] %s => {skipping non ascii english} => %s" % (password,password) + if self.verbose and not self.quiet: print("[!] %s => {skipping non ascii english} => %s" % (password,password)) self.foreign_stats_total += 1 return False @@ -751,7 +751,7 @@ def check_reversible_password(self, password): def analyze_password(self,password, rules_queue=multiprocessing.Queue(), words_queue=multiprocessing.Queue()): """ Analyze a single password. """ - if self.verbose: print "[*] Analyzing password: %s" % password + if self.verbose: print("[*] Analyzing password: %s" % password) words = [] @@ -800,20 +800,20 @@ def print_hashcat_rules(self, words, password, rules_queue, words_queue): elif rule_length > best_found_rule_length: if self.verbose: - print "[-] %s => {best rule length exceeded: %d (%d)} => %s" % \ - (word["suggestion"], rule_length, best_found_rule_length, password) + print("[-] %s => {best rule length exceeded: %d (%d)} => %s" % \ + (word["suggestion"], rule_length, best_found_rule_length, password)) break if rule_length <= self.max_rule_len: hashcat_rule_str = " ".join(hashcat_rule + word["pre_rule"] or [':']) - if self.verbose: print "[+] %s => %s => %s" % (word["suggestion"], hashcat_rule_str, password) + if self.verbose: print("[+] %s => %s => %s" % (word["suggestion"], hashcat_rule_str, password)) rules_queue.put(hashcat_rule_str) def password_worker(self,i, passwords_queue, rules_queue, words_queue): - if self.debug: print "[*] Password analysis worker [%d] started." % i + if self.debug: print("[*] Password analysis worker [%d] started." % i) try: while True: password = passwords_queue.get() @@ -823,16 +823,16 @@ def password_worker(self,i, passwords_queue, rules_queue, words_queue): self.analyze_password(password, rules_queue, words_queue) except (KeyboardInterrupt, SystemExit): - if self.debug: print "[*] Password analysis worker [%d] terminated." % i + if self.debug: print("[*] Password analysis worker [%d] terminated." % i) - if self.debug: print "[*] Password analysis worker [%d] stopped." % i + if self.debug: print("[*] Password analysis worker [%d] stopped." % i) def rule_worker(self, rules_queue, output_rules_filename): """ Worker to store generated rules. """ - print "[*] Saving rules to %s" % output_rules_filename + print("[*] Saving rules to %s" % output_rules_filename) f = open(output_rules_filename, 'w') - if self.debug: print "[*] Rule worker started." + if self.debug: print("[*] Rule worker started.") try: while True: rule = rules_queue.get() @@ -844,17 +844,17 @@ def rule_worker(self, rules_queue, output_rules_filename): f.flush() except (KeyboardInterrupt, SystemExit): - if self.debug: print "[*] Rule worker terminated." + if self.debug: print("[*] Rule worker terminated.") f.close() - if self.debug: print "[*] Rule worker stopped." + if self.debug: print("[*] Rule worker stopped.") def word_worker(self, words_queue, output_words_filename): """ Worker to store generated rules. """ - print "[*] Saving words to %s" % output_words_filename + print("[*] Saving words to %s" % output_words_filename) f = open(output_words_filename, 'w') - if self.debug: print "[*] Word worker started." + if self.debug: print("[*] Word worker started.") try: while True: word = words_queue.get() @@ -866,17 +866,17 @@ def word_worker(self, words_queue, output_words_filename): f.flush() except (KeyboardInterrupt, SystemExit): - if self.debug: print "[*] Word worker terminated." + if self.debug: print("[*] Word worker terminated.") f.close() - if self.debug: print "[*] Word worker stopped." + if self.debug: print("[*] Word worker stopped.") # Analyze passwords file def analyze_passwords_file(self,passwords_file): """ Analyze provided passwords file. """ - print "[*] Analyzing passwords file: %s:" % passwords_file - print "[*] Press Ctrl-C to end execution and generate statistical analysis." + print("[*] Analyzing passwords file: %s:" % passwords_file) + print("[*] Press Ctrl-C to end execution and generate statistical analysis.") # Setup queues passwords_queue = multiprocessing.Queue(self.threads) @@ -904,8 +904,8 @@ def analyze_passwords_file(self,passwords_file): # Provide analysis time feedback to the user if not self.quiet and password_count != 0 and password_count % 5000 == 0: segment_time = time.time() - segment_start - print "[*] Processed %d passwords in %.2f seconds at the rate of %.2f p/sec" % \ - (password_count, segment_start - analysis_start, 5000/segment_time ) + print("[*] Processed %d passwords in %.2f seconds at the rate of %.2f p/sec" % \ + (password_count, segment_start - analysis_start, 5000/segment_time )) segment_start = time.time() password_count += 1 @@ -915,7 +915,7 @@ def analyze_passwords_file(self,passwords_file): passwords_queue.put(password) except (KeyboardInterrupt, SystemExit): - print "\n[!] Rulegen was interrupted." + print("\n[!] Rulegen was interrupted.") else: # Signal workers to stop. @@ -933,15 +933,15 @@ def analyze_passwords_file(self,passwords_file): f.close() analysis_time = time.time() - analysis_start - print "[*] Finished processing %d passwords in %.2f seconds at the rate of %.2f p/sec" % (password_count, analysis_time, float(password_count)/analysis_time ) + print("[*] Finished processing %d passwords in %.2f seconds at the rate of %.2f p/sec" % (password_count, analysis_time, float(password_count)/analysis_time )) - print "[*] Generating statistics for [%s] rules and words." % self.basename - print "[-] Skipped %d all numeric passwords (%0.2f%%)" % \ - (self.numeric_stats_total, float(self.numeric_stats_total)*100.0/float(password_count)) - print "[-] Skipped %d passwords with less than 25%% alpha characters (%0.2f%%)" % \ - (self.special_stats_total, float(self.special_stats_total)*100.0/float(password_count)) - print "[-] Skipped %d passwords with non ascii characters (%0.2f%%)" % \ - (self.foreign_stats_total, float(self.foreign_stats_total)*100.0/float(password_count)) + print("[*] Generating statistics for [%s] rules and words." % self.basename) + print("[-] Skipped %d all numeric passwords (%0.2f%%)" % \ + (self.numeric_stats_total, float(self.numeric_stats_total)*100.0/float(password_count))) + print("[-] Skipped %d passwords with less than 25%% alpha characters (%0.2f%%)" % \ + (self.special_stats_total, float(self.special_stats_total)*100.0/float(password_count))) + print("[-] Skipped %d passwords with non ascii characters (%0.2f%%)" % \ + (self.foreign_stats_total, float(self.foreign_stats_total)*100.0/float(password_count))) # TODO: Counter breaks on large files. uniq -c | sort -rn is still the most # optimal way. @@ -950,11 +950,11 @@ def analyze_passwords_file(self,passwords_file): rules_counter = Counter(rules_file) rule_counter_total = sum(rules_counter.values()) - print "\n[*] Top 10 rules" + print("\n[*] Top 10 rules") rules_i = 0 for (rule, count) in rules_counter.most_common(): rules_sorted_file.write(rule) - if rules_i < 10: print "[+] %s - %d (%0.2f%%)" % (rule.rstrip('\r\n'), count, count*100/rule_counter_total) + if rules_i < 10: print("[+] %s - %d (%0.2f%%)" % (rule.rstrip('\r\n'), count, count*100/rule_counter_total)) rules_i += 1 rules_file.close() @@ -966,11 +966,11 @@ def analyze_passwords_file(self,passwords_file): words_counter = Counter(words_file) word_counter_total = sum(rules_counter.values()) - print "\n[*] Top 10 words" + print("\n[*] Top 10 words") words_i = 0 for (word, count) in words_counter.most_common(): words_sorted_file.write(word) - if words_i < 10: print "[+] %s - %d (%0.2f%%)" % (word.rstrip('\r\n'), count, count*100/word_counter_total) + if words_i < 10: print("[+] %s - %d (%0.2f%%)" % (word.rstrip('\r\n'), count, count*100/word_counter_total)) words_i += 1 words_file.close() @@ -993,10 +993,10 @@ def verify_hashcat_rules(self,word, rules, password): if out == password: hashcat_rules_str = " ".join(rules or [':']) - if self.verbose: print "[+] %s => %s => %s" % (word, hashcat_rules_str, password) + if self.verbose: print("[+] %s => %s => %s" % (word, hashcat_rules_str, password)) else: - print "[!] Hashcat Verification FAILED: %s => %s => %s (%s)" % (word," ".join(rules or [':']),password,out) + print("[!] Hashcat Verification FAILED: %s => %s => %s (%s)" % (word," ".join(rules or [':']),password,out)) if __name__ == "__main__": @@ -1049,7 +1049,7 @@ def verify_hashcat_rules(self,word, rules, password): # Print program header if not options.quiet: - print header + print(header) if len(args) < 1: parser.error("no passwords file specified") @@ -1069,7 +1069,7 @@ def verify_hashcat_rules(self,word, rules, password): rulegen.more_rules=options.morerules rulegen.simple_rules=options.simplerules rulegen.brute_rules=options.bruterules - if rulegen.brute_rules: print "[!] Bruteforcing reversal and rotation rules. (slower)" + if rulegen.brute_rules: print("[!] Bruteforcing reversal and rotation rules. (slower)") # Debugging options rulegen.word = options.word @@ -1081,8 +1081,8 @@ def verify_hashcat_rules(self,word, rules, password): # Custom wordlist if not options.word: if options.wordlist: rulegen.load_custom_wordlist(options.wordlist) - print "[*] Using Enchant '%s' module. For best results please install" % rulegen.enchant.provider.name - print " '%s' module language dictionaries." % rulegen.enchant.provider.name + print("[*] Using Enchant '%s' module. For best results please install" % rulegen.enchant.provider.name) + print(" '%s' module language dictionaries." % rulegen.enchant.provider.name) # Analyze a single password or several passwords in a file if options.password: diff --git a/statsgen.py b/statsgen.py index 6765f9e..b67a82f 100755 --- a/statsgen.py +++ b/statsgen.py @@ -177,33 +177,33 @@ def generate_stats(self, filename): def print_stats(self): """ Print password statistics. """ - print "[+] Analyzing %d%% (%d/%d) of passwords" % (self.filter_counter*100/self.total_counter, self.filter_counter, self.total_counter) - print " NOTE: Statistics below is relative to the number of analyzed passwords, not total number of passwords" - print "\n[*] Length:" - for (length,count) in sorted(self.stats_length.iteritems(), key=operator.itemgetter(1), reverse=True): + print("[+] Analyzing %d%% (%d/%d) of passwords" % (self.filter_counter*100/self.total_counter, self.filter_counter, self.total_counter)) + print(" NOTE: Statistics below is relative to the number of analyzed passwords, not total number of passwords") + print("\n[*] Length:") + for (length,count) in sorted(iter(self.stats_length.items()), key=operator.itemgetter(1), reverse=True): if self.hiderare and not count*100/self.filter_counter > 0: continue - print "[+] %25d: %02d%% (%d)" % (length, count*100/self.filter_counter, count) + print("[+] %25d: %02d%% (%d)" % (length, count*100/self.filter_counter, count)) - print "\n[*] Character-set:" - for (char,count) in sorted(self.stats_charactersets.iteritems(), key=operator.itemgetter(1), reverse=True): + print("\n[*] Character-set:") + for (char,count) in sorted(iter(self.stats_charactersets.items()), key=operator.itemgetter(1), reverse=True): if self.hiderare and not count*100/self.filter_counter > 0: continue - print "[+] %25s: %02d%% (%d)" % (char, count*100/self.filter_counter, count) + print("[+] %25s: %02d%% (%d)" % (char, count*100/self.filter_counter, count)) - print "\n[*] Password complexity:" - print "[+] digit: min(%s) max(%s)" % (self.mindigit, self.maxdigit) - print "[+] lower: min(%s) max(%s)" % (self.minlower, self.maxlower) - print "[+] upper: min(%s) max(%s)" % (self.minupper, self.maxupper) - print "[+] special: min(%s) max(%s)" % (self.minspecial, self.maxspecial) + print("\n[*] Password complexity:") + print("[+] digit: min(%s) max(%s)" % (self.mindigit, self.maxdigit)) + print("[+] lower: min(%s) max(%s)" % (self.minlower, self.maxlower)) + print("[+] upper: min(%s) max(%s)" % (self.minupper, self.maxupper)) + print("[+] special: min(%s) max(%s)" % (self.minspecial, self.maxspecial)) - print "\n[*] Simple Masks:" - for (simplemask,count) in sorted(self.stats_simplemasks.iteritems(), key=operator.itemgetter(1), reverse=True): + print("\n[*] Simple Masks:") + for (simplemask,count) in sorted(iter(self.stats_simplemasks.items()), key=operator.itemgetter(1), reverse=True): if self.hiderare and not count*100/self.filter_counter > 0: continue - print "[+] %25s: %02d%% (%d)" % (simplemask, count*100/self.filter_counter, count) + print("[+] %25s: %02d%% (%d)" % (simplemask, count*100/self.filter_counter, count)) - print "\n[*] Advanced Masks:" - for (advancedmask,count) in sorted(self.stats_advancedmasks.iteritems(), key=operator.itemgetter(1), reverse=True): + print("\n[*] Advanced Masks:") + for (advancedmask,count) in sorted(iter(self.stats_advancedmasks.items()), key=operator.itemgetter(1), reverse=True): if count*100/self.filter_counter > 0: - print "[+] %25s: %02d%% (%d)" % (advancedmask, count*100/self.filter_counter, count) + print("[+] %25s: %02d%% (%d)" % (advancedmask, count*100/self.filter_counter, count)) if self.output_file: self.output_file.write("%s,%d\n" % (advancedmask,count)) @@ -237,13 +237,13 @@ def print_stats(self): # Print program header if not options.quiet: - print header + print(header) if len(args) != 1: parser.error("no passwords file specified") exit(1) - print "[*] Analyzing passwords in [%s]" % args[0] + print("[*] Analyzing passwords in [%s]" % args[0]) statsgen = StatsGen() @@ -255,7 +255,7 @@ def print_stats(self): if options.hiderare: statsgen.hiderare = options.hiderare if options.output_file: - print "[*] Saving advanced masks and occurrences to [%s]" % options.output_file + print("[*] Saving advanced masks and occurrences to [%s]" % options.output_file) statsgen.output_file = open(options.output_file, 'w') statsgen.generate_stats(args[0]) From 67ae93041d9a53b5f1736d34e9007b8cbd6a3a07 Mon Sep 17 00:00:00 2001 From: Haidar-Bachminska Pierre-Antoine Date: Sun, 19 Jun 2016 00:36:48 +0200 Subject: [PATCH 02/27] deleted unused imports --- maskgen.py | 2 -- policygen.py | 1 - rulegen.py | 1 - statsgen.py | 4 +--- 4 files changed, 1 insertion(+), 7 deletions(-) diff --git a/maskgen.py b/maskgen.py index 131afa7..5af494b 100755 --- a/maskgen.py +++ b/maskgen.py @@ -10,10 +10,8 @@ # # Please see the attached LICENSE file for additional licensing information. -import sys import csv import datetime -from operator import itemgetter from optparse import OptionParser, OptionGroup VERSION = "0.0.3" diff --git a/policygen.py b/policygen.py index 65f2f5e..fd7b15f 100755 --- a/policygen.py +++ b/policygen.py @@ -10,7 +10,6 @@ # # Please see the attached LICENSE file for additional licensing information. -import sys, string, random import datetime from optparse import OptionParser, OptionGroup import itertools diff --git a/rulegen.py b/rulegen.py index 21f549c..8560ae5 100755 --- a/rulegen.py +++ b/rulegen.py @@ -15,7 +15,6 @@ import sys import re import time -import operator import enchant from optparse import OptionParser, OptionGroup diff --git a/statsgen.py b/statsgen.py index b67a82f..f378fb8 100755 --- a/statsgen.py +++ b/statsgen.py @@ -10,10 +10,8 @@ # # Please see the attached LICENSE file for additional licensing information. -import sys -import re, operator, string +import operator, string from optparse import OptionParser, OptionGroup -import time VERSION = "0.0.3" From 7b148df2d84c218d048951064e7b2a8d33dc82f1 Mon Sep 17 00:00:00 2001 From: Haidar-Bachminska Pierre-Antoine Date: Sun, 19 Jun 2016 01:46:08 +0200 Subject: [PATCH 03/27] fix statsgen.py's file opening mode and prints of masks --- statsgen.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/statsgen.py b/statsgen.py index f378fb8..dc213c6 100755 --- a/statsgen.py +++ b/statsgen.py @@ -72,13 +72,13 @@ def analyze_password(self, password): advancedmask_string += "?d" if not simplemask or not simplemask[-1] == 'digit': simplemask.append('digit') - elif letter in string.lowercase: + elif letter in string.ascii_lowercase: lower += 1 advancedmask_string += "?l" if not simplemask or not simplemask[-1] == 'string': simplemask.append('string') - elif letter in string.uppercase: + elif letter in string.ascii_uppercase: upper += 1 advancedmask_string += "?u" if not simplemask or not simplemask[-1] == 'string': simplemask.append('string') @@ -119,8 +119,7 @@ def analyze_password(self, password): def generate_stats(self, filename): """ Generate password statistics. """ - f = open(filename,'r') - + f = open(filename, 'r', encoding="latin-1", errors='ignore') for password in f: password = password.rstrip('\r\n') @@ -175,16 +174,16 @@ def generate_stats(self, filename): def print_stats(self): """ Print password statistics. """ - print("[+] Analyzing %d%% (%d/%d) of passwords" % (self.filter_counter*100/self.total_counter, self.filter_counter, self.total_counter)) + print("[+] Analyzing %d%% (%d/%d) of passwords" % (self.filter_counter*100//self.total_counter, self.filter_counter, self.total_counter)) print(" NOTE: Statistics below is relative to the number of analyzed passwords, not total number of passwords") print("\n[*] Length:") for (length,count) in sorted(iter(self.stats_length.items()), key=operator.itemgetter(1), reverse=True): - if self.hiderare and not count*100/self.filter_counter > 0: continue + if self.hiderare and not count*100//self.filter_counter > 0: continue print("[+] %25d: %02d%% (%d)" % (length, count*100/self.filter_counter, count)) print("\n[*] Character-set:") for (char,count) in sorted(iter(self.stats_charactersets.items()), key=operator.itemgetter(1), reverse=True): - if self.hiderare and not count*100/self.filter_counter > 0: continue + if self.hiderare and not count*100//self.filter_counter > 0: continue print("[+] %25s: %02d%% (%d)" % (char, count*100/self.filter_counter, count)) print("\n[*] Password complexity:") @@ -195,13 +194,13 @@ def print_stats(self): print("\n[*] Simple Masks:") for (simplemask,count) in sorted(iter(self.stats_simplemasks.items()), key=operator.itemgetter(1), reverse=True): - if self.hiderare and not count*100/self.filter_counter > 0: continue - print("[+] %25s: %02d%% (%d)" % (simplemask, count*100/self.filter_counter, count)) + if self.hiderare and not count*100//self.filter_counter > 0: continue + print("[+] %25s: %02d%% (%d)" % (simplemask, count*100//self.filter_counter, count)) print("\n[*] Advanced Masks:") for (advancedmask,count) in sorted(iter(self.stats_advancedmasks.items()), key=operator.itemgetter(1), reverse=True): - if count*100/self.filter_counter > 0: - print("[+] %25s: %02d%% (%d)" % (advancedmask, count*100/self.filter_counter, count)) + if count*100//self.filter_counter > 0: + print("[+] %25s: %02d%% (%d)" % (advancedmask, count*100//self.filter_counter, count)) if self.output_file: self.output_file.write("%s,%d\n" % (advancedmask,count)) From 39620d461acd91f98ceab0005d5b2df79720cc04 Mon Sep 17 00:00:00 2001 From: Haidar-Bachminska Pierre-Antoine Date: Sun, 19 Jun 2016 02:16:01 +0200 Subject: [PATCH 04/27] fix divisions in maskgen --- maskgen.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/maskgen.py b/maskgen.py index 5af494b..cacd0ff 100755 --- a/maskgen.py +++ b/maskgen.py @@ -62,9 +62,9 @@ def loadmasks(self, filename): if mask == "": continue mask_occurrence = int(occurrence) - mask_length = len(mask)/2 + mask_length = len(mask)//2 mask_complexity = self.getcomplexity(mask) - mask_time = mask_complexity/self.pps + mask_time = mask_complexity//self.pps self.total_occurrence += mask_occurrence @@ -83,7 +83,7 @@ def loadmasks(self, filename): self.masks[mask]['occurrence'] = mask_occurrence self.masks[mask]['complexity'] = 1 - mask_complexity self.masks[mask]['time'] = mask_time - self.masks[mask]['optindex'] = 1 - mask_complexity/mask_occurrence + self.masks[mask]['optindex'] = 1 - (mask_complexity//mask_occurrence) def generate_masks(self,sorting_mode): """ Generate optimal password masks sorted by occurrence, complexity or optindex """ @@ -115,7 +115,7 @@ def generate_masks(self,sorting_mode): print("[*] Finished generating masks:") print(" Masks generated: %s" % sample_count) - print(" Masks coverage: %d%% (%d/%d)" % (sample_occurrence*100/self.total_occurrence,sample_occurrence,self.total_occurrence)) + print(" Masks coverage: %d%% (%d/%d)" % (sample_occurrence*100//self.total_occurrence,sample_occurrence,self.total_occurrence)) time_human = ">1 year" if sample_time > 60*60*24*365 else str(datetime.timedelta(seconds=sample_time)) print(" Masks runtime: %s" % time_human) From 1f8243c81cd2609be22a1afecdb661d10c354044 Mon Sep 17 00:00:00 2001 From: Haidar-Bachminska Pierre-Antoine Date: Sun, 19 Jun 2016 02:33:10 +0200 Subject: [PATCH 05/27] fix divisions in policygen --- policygen.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/policygen.py b/policygen.py index fd7b15f..df2c43c 100755 --- a/policygen.py +++ b/policygen.py @@ -106,7 +106,7 @@ def generate_masks(self, noncompliant): sample_length_complexity += mask_complexity if self.showmasks: - mask_time = mask_complexity/self.pps + mask_time = mask_complexity//self.pps time_human = ">1 year" if mask_time > 60*60*24*365 else str(datetime.timedelta(seconds=mask_time)) print("[{:>2}] {:<30} [l:{:>2} u:{:>2} d:{:>2} s:{:>2}] [{:>8}] ".format(length, mask, lowercount,uppercount,digitcount,specialcount, time_human)) @@ -120,11 +120,11 @@ def generate_masks(self, noncompliant): sample_complexity += sample_length_complexity - total_time = total_complexity/self.pps + total_time = total_complexity//self.pps total_time_human = ">1 year" if total_time > 60*60*24*365 else str(datetime.timedelta(seconds=total_time)) print("[*] Total Masks: %d Time: %s" % (total_count, total_time_human)) - sample_time = sample_complexity/self.pps + sample_time = sample_complexity//self.pps sample_time_human = ">1 year" if sample_time > 60*60*24*365 else str(datetime.timedelta(seconds=sample_time)) print("[*] Policy Masks: %d Time: %s" % (sample_count, sample_time_human)) From 3c93ef6484f641845941502bab4ec0ad53caf1da Mon Sep 17 00:00:00 2001 From: Haidar-Bachminska Pierre-Antoine Date: Sun, 19 Jun 2016 02:34:01 +0200 Subject: [PATCH 06/27] fix small spelling typo --- policygen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/policygen.py b/policygen.py index df2c43c..711612e 100755 --- a/policygen.py +++ b/policygen.py @@ -83,7 +83,7 @@ def generate_masks(self, noncompliant): total_length_count += 1 total_length_complexity += mask_complexity - # Count charachter types in a mask + # Count character types in a mask for char in mask[1:].split("?"): if char == "l": lowercount += 1 elif char == "u": uppercount += 1 From 1d73c2a753c7ddc72ffeb55701ac59bf68f96db6 Mon Sep 17 00:00:00 2001 From: Haidar-Bachminska Pierre-Antoine Date: Sun, 19 Jun 2016 02:43:21 +0200 Subject: [PATCH 07/27] first pass of cleanup: modified None comparisons to use the is keyword --- policygen.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/policygen.py b/policygen.py index 711612e..36dff3b 100755 --- a/policygen.py +++ b/policygen.py @@ -93,14 +93,14 @@ def generate_masks(self, noncompliant): # Filter according to password policy # NOTE: Perform exact opposite (XOR) operation if noncompliant # flag was set when calling the function. - if ((self.minlower == None or lowercount >= self.minlower) and \ - (self.maxlower == None or lowercount <= self.maxlower) and \ - (self.minupper == None or uppercount >= self.minupper) and \ - (self.maxupper == None or uppercount <= self.maxupper) and \ - (self.mindigit == None or digitcount >= self.mindigit) and \ - (self.maxdigit == None or digitcount <= self.maxdigit) and \ - (self.minspecial == None or specialcount >= self.minspecial) and \ - (self.maxspecial == None or specialcount <= self.maxspecial)) ^ noncompliant : + if ((self.minlower is None or lowercount >= self.minlower) and + (self.maxlower is None or lowercount <= self.maxlower) and + (self.minupper is None or uppercount >= self.minupper) and + (self.maxupper is None or uppercount <= self.maxupper) and + (self.mindigit is None or digitcount >= self.mindigit) and + (self.maxdigit is None or digitcount <= self.maxdigit) and + (self.minspecial is None or specialcount >= self.minspecial) and + (self.maxspecial is None or specialcount <= self.maxspecial)) ^ noncompliant: sample_length_count += 1 sample_length_complexity += mask_complexity @@ -119,7 +119,6 @@ def generate_masks(self, noncompliant): total_complexity += total_length_complexity sample_complexity += sample_length_complexity - total_time = total_complexity//self.pps total_time_human = ">1 year" if total_time > 60*60*24*365 else str(datetime.timedelta(seconds=total_time)) print("[*] Total Masks: %d Time: %s" % (total_count, total_time_human)) From 1c94727d872337e7676fc428462f306fcd9f426d Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Haidar-Bachminska Date: Tue, 21 Jun 2016 19:01:17 +0200 Subject: [PATCH 08/27] Add an exception handler in case the provider dict does not exist --- rulegen.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/rulegen.py b/rulegen.py index 8560ae5..15a663f 100755 --- a/rulegen.py +++ b/rulegen.py @@ -1054,7 +1054,12 @@ def verify_hashcat_rules(self,word, rules, password): parser.error("no passwords file specified") exit(1) - rulegen = RuleGen(language="en", providers=options.providers, basename=options.basename, threads=options.threads) + try: + rulegen = RuleGen(language="en", providers=options.providers, basename=options.basename, threads=options.threads) + except enchant.errors.DictNotFoundError: + print("[-] Cannot find a dictionary for specified language. Please install it and try again.") + print("[*] Hint: Usually this dictionary resides within an aspell / myspell package.") + exit(-1) # Finetuning word generation rulegen.max_word_dist=options.maxworddist From 602342d9cbdbe53380c3a264c41b6f2d05b8a4b0 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Haidar-Bachminska Date: Tue, 21 Jun 2016 19:16:37 +0200 Subject: [PATCH 09/27] Fix file opening to mimic old behaviour --- rulegen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rulegen.py b/rulegen.py index 15a663f..dc20a7a 100755 --- a/rulegen.py +++ b/rulegen.py @@ -890,7 +890,7 @@ def analyze_passwords_file(self,passwords_file): # Continue with the main thread - f = open(passwords_file,'r') + f = open(passwords_file, 'r', encoding="latin-1", errors='ignore') password_count = 0 analysis_start = time.time() From 6fc2ad815d5ceb43caea5218df660b44cd87977b Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Haidar-Bachminska Date: Tue, 21 Jun 2016 19:47:54 +0200 Subject: [PATCH 10/27] Use a python3 integer division instead of the floating one --- rulegen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rulegen.py b/rulegen.py index dc20a7a..aadb249 100755 --- a/rulegen.py +++ b/rulegen.py @@ -732,7 +732,7 @@ def check_reversible_password(self, password): # Skip passwords with less than 25% of alpha character # TODO: Make random word detection more reliable based on word entropy. - elif len([c for c in password if c.isalpha()]) < len(password)/4: + elif len([c for c in password if c.isalpha()]) < len(password)//4: if self.verbose and not self.quiet:print("[!] %s => {skipping alpha less than 25%%} => %s" % (password,password)) self.special_stats_total += 1 return False From a208d84e86a4d71ea3b1661c076167ebe815572f Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Haidar-Bachminska Date: Tue, 21 Jun 2016 20:36:52 +0200 Subject: [PATCH 11/27] PEP8 compliance for statsgen.py --- statsgen.py | 187 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 114 insertions(+), 73 deletions(-) diff --git a/statsgen.py b/statsgen.py index dc213c6..438d335 100755 --- a/statsgen.py +++ b/statsgen.py @@ -10,20 +10,22 @@ # # Please see the attached LICENSE file for additional licensing information. -import operator, string +import operator +import string from optparse import OptionParser, OptionGroup VERSION = "0.0.3" + class StatsGen: def __init__(self): self.output_file = None # Filters - self.minlength = None - self.maxlength = None + self.minlength = None + self.maxlength = None self.simplemasks = None - self.charsets = None + self.charsets = None self.quiet = False self.debug = True @@ -40,14 +42,14 @@ def __init__(self): self.total_counter = 0 # Minimum password complexity counters - self.mindigit = None - self.minupper = None - self.minlower = None + self.mindigit = None + self.minupper = None + self.minlower = None self.minspecial = None - self.maxdigit = None - self.maxupper = None - self.maxlower = None + self.maxdigit = None + self.maxupper = None + self.maxlower = None self.maxspecial = None def analyze_password(self, password): @@ -66,53 +68,68 @@ def analyze_password(self, password): # Detect simple and advanced masks for letter in password: - + if letter in string.digits: digit += 1 advancedmask_string += "?d" - if not simplemask or not simplemask[-1] == 'digit': simplemask.append('digit') + if not simplemask or not simplemask[-1] == 'digit': + simplemask.append('digit') elif letter in string.ascii_lowercase: lower += 1 advancedmask_string += "?l" - if not simplemask or not simplemask[-1] == 'string': simplemask.append('string') - + if not simplemask or not simplemask[-1] == 'string': + simplemask.append('string') elif letter in string.ascii_uppercase: upper += 1 advancedmask_string += "?u" - if not simplemask or not simplemask[-1] == 'string': simplemask.append('string') + if not simplemask or not simplemask[-1] == 'string': + simplemask.append('string') else: special += 1 advancedmask_string += "?s" - if not simplemask or not simplemask[-1] == 'special': simplemask.append('special') - + if not simplemask or not simplemask[-1] == 'special': + simplemask.append('special') # String representation of masks simplemask_string = ''.join(simplemask) if len(simplemask) <= 3 else 'othermask' # Policy - policy = (digit,lower,upper,special) + policy = (digit, lower, upper, special) # Determine character-set - if digit and not lower and not upper and not special: charset = 'numeric' - elif not digit and lower and not upper and not special: charset = 'loweralpha' - elif not digit and not lower and upper and not special: charset = 'upperalpha' - elif not digit and not lower and not upper and special: charset = 'special' - - elif not digit and lower and upper and not special: charset = 'mixedalpha' - elif digit and lower and not upper and not special: charset = 'loweralphanum' - elif digit and not lower and upper and not special: charset = 'upperalphanum' - elif not digit and lower and not upper and special: charset = 'loweralphaspecial' - elif not digit and not lower and upper and special: charset = 'upperalphaspecial' - elif digit and not lower and not upper and special: charset = 'specialnum' - - elif not digit and lower and upper and special: charset = 'mixedalphaspecial' - elif digit and not lower and upper and special: charset = 'upperalphaspecialnum' - elif digit and lower and not upper and special: charset = 'loweralphaspecialnum' - elif digit and lower and upper and not special: charset = 'mixedalphanum' - else: charset = 'all' + if digit and not lower and not upper and not special: + charset = 'numeric' + elif not digit and lower and not upper and not special: + charset = 'loweralpha' + elif not digit and not lower and upper and not special: + charset = 'upperalpha' + elif not digit and not lower and not upper and special: + charset = 'special' + elif not digit and lower and upper and not special: + charset = 'mixedalpha' + elif digit and lower and not upper and not special: + charset = 'loweralphanum' + elif digit and not lower and upper and not special: + charset = 'upperalphanum' + elif not digit and lower and not upper and special: + charset = 'loweralphaspecial' + elif not digit and not lower and upper and special: + charset = 'upperalphaspecial' + elif digit and not lower and not upper and special: + charset = 'specialnum' + elif not digit and lower and upper and special: + charset = 'mixedalphaspecial' + elif digit and not lower and upper and special: + charset = 'upperalphaspecialnum' + elif digit and lower and not upper and special: + charset = 'loweralphaspecialnum' + elif digit and lower and upper and not special: + charset = 'mixedalphanum' + else: + charset = 'all' return (pass_length, charset, simplemask_string, advancedmask_string, policy) @@ -123,31 +140,40 @@ def generate_stats(self, filename): for password in f: password = password.rstrip('\r\n') - if len(password) == 0: continue + if len(password) == 0: + continue - self.total_counter += 1 + self.total_counter += 1 - (pass_length,characterset,simplemask,advancedmask, policy) = self.analyze_password(password) - (digit,lower,upper,special) = policy + (pass_length, characterset, simplemask, advancedmask, policy) = self.analyze_password(password) + (digit, lower, upper, special) = policy - if (self.charsets == None or characterset in self.charsets) and \ - (self.simplemasks == None or simplemask in self.simplemasks) and \ - (self.maxlength == None or pass_length <= self.maxlength) and \ - (self.minlength == None or pass_length >= self.minlength): + if (self.charsets is None or characterset in self.charsets) and \ + (self.simplemasks is None or simplemask in self.simplemasks) and \ + (self.maxlength is None or pass_length <= self.maxlength) and \ + (self.minlength is None or pass_length >= self.minlength): self.filter_counter += 1 - if self.mindigit == None or digit < self.mindigit: self.mindigit = digit - if self.maxdigit == None or digit > self.maxdigit: self.maxdigit = digit + if self.mindigit is None or digit < self.mindigit: + self.mindigit = digit + if self.maxdigit is None or digit > self.maxdigit: + self.maxdigit = digit - if self.minupper == None or upper < self.minupper: self.minupper = upper - if self.maxupper == None or upper > self.maxupper: self.maxupper = upper + if self.minupper is None or upper < self.minupper: + self.minupper = upper + if self.maxupper is None or upper > self.maxupper: + self.maxupper = upper - if self.minlower == None or lower < self.minlower: self.minlower = lower - if self.maxlower == None or lower > self.maxlower: self.maxlower = lower + if self.minlower is None or lower < self.minlower: + self.minlower = lower + if self.maxlower is None or lower > self.maxlower: + self.maxlower = lower - if self.minspecial == None or special < self.minspecial: self.minspecial = special - if self.maxspecial == None or special > self.maxspecial: self.maxspecial = special + if self.minspecial is None or special < self.minspecial: + self.minspecial = special + if self.maxspecial is None or special > self.maxspecial: + self.maxspecial = special if pass_length in self.stats_length: self.stats_length[pass_length] += 1 @@ -174,16 +200,19 @@ def generate_stats(self, filename): def print_stats(self): """ Print password statistics. """ - print("[+] Analyzing %d%% (%d/%d) of passwords" % (self.filter_counter*100//self.total_counter, self.filter_counter, self.total_counter)) - print(" NOTE: Statistics below is relative to the number of analyzed passwords, not total number of passwords") + print("[+] Analyzing %d%% (%d/%d) of passwords" % (self.filter_counter*100//self.total_counter, + self.filter_counter, self.total_counter)) + print("[*] Statistics below is relative to the number of analyzed passwords, not total number of passwords") print("\n[*] Length:") - for (length,count) in sorted(iter(self.stats_length.items()), key=operator.itemgetter(1), reverse=True): - if self.hiderare and not count*100//self.filter_counter > 0: continue + for (length, count) in sorted(iter(self.stats_length.items()), key=operator.itemgetter(1), reverse=True): + if self.hiderare and not count*100//self.filter_counter > 0: + continue print("[+] %25d: %02d%% (%d)" % (length, count*100/self.filter_counter, count)) print("\n[*] Character-set:") - for (char,count) in sorted(iter(self.stats_charactersets.items()), key=operator.itemgetter(1), reverse=True): - if self.hiderare and not count*100//self.filter_counter > 0: continue + for (char, count) in sorted(iter(self.stats_charactersets.items()), key=operator.itemgetter(1), reverse=True): + if self.hiderare and not count*100//self.filter_counter > 0: + continue print("[+] %25s: %02d%% (%d)" % (char, count*100/self.filter_counter, count)) print("\n[*] Password complexity:") @@ -193,22 +222,25 @@ def print_stats(self): print("[+] special: min(%s) max(%s)" % (self.minspecial, self.maxspecial)) print("\n[*] Simple Masks:") - for (simplemask,count) in sorted(iter(self.stats_simplemasks.items()), key=operator.itemgetter(1), reverse=True): - if self.hiderare and not count*100//self.filter_counter > 0: continue + for (simplemask, count) in sorted(iter(self.stats_simplemasks.items()), key=operator.itemgetter(1), + reverse=True): + if self.hiderare and not count*100//self.filter_counter > 0: + continue print("[+] %25s: %02d%% (%d)" % (simplemask, count*100//self.filter_counter, count)) print("\n[*] Advanced Masks:") - for (advancedmask,count) in sorted(iter(self.stats_advancedmasks.items()), key=operator.itemgetter(1), reverse=True): + for (advancedmask, count) in sorted(iter(self.stats_advancedmasks.items()), key=operator.itemgetter(1), + reverse=True): if count*100//self.filter_counter > 0: print("[+] %25s: %02d%% (%d)" % (advancedmask, count*100//self.filter_counter, count)) if self.output_file: - self.output_file.write("%s,%d\n" % (advancedmask,count)) + self.output_file.write("%s,%d\n" % (advancedmask, count)) if __name__ == "__main__": - header = " _ \n" - header += " StatsGen %s | |\n" % VERSION + header = " _ \n" + header += " StatsGen %s | |\n" % VERSION header += " _ __ __ _ ___| | _\n" header += " | '_ \ / _` |/ __| |/ /\n" header += " | |_) | (_| | (__| < \n" @@ -222,12 +254,16 @@ def print_stats(self): filters = OptionGroup(parser, "Password Filters") filters.add_option("--minlength", dest="minlength", type="int", metavar="8", help="Minimum password length") filters.add_option("--maxlength", dest="maxlength", type="int", metavar="8", help="Maximum password length") - filters.add_option("--charset", dest="charsets", help="Password charset filter (comma separated)", metavar="loweralpha,numeric") - filters.add_option("--simplemask", dest="simplemasks",help="Password mask filter (comma separated)", metavar="stringdigit,allspecial") + filters.add_option("--charset", dest="charsets", help="Password charset filter (comma separated)", + metavar="loweralpha,numeric") + filters.add_option("--simplemask", dest="simplemasks", help="Password mask filter (comma separated)", + metavar="stringdigit,allspecial") parser.add_option_group(filters) - parser.add_option("-o", "--output", dest="output_file",help="Save masks and stats to a file", metavar="password.masks") - parser.add_option("--hiderare", action="store_true", dest="hiderare", default=False, help="Hide statistics covering less than 1% of the sample") + parser.add_option("-o", "--output", dest="output_file", help="Save masks and stats to a file", + metavar="password.masks") + parser.add_option("--hiderare", action="store_true", dest="hiderare", default=False, + help="Hide statistics covering less than 1% of the sample") parser.add_option("-q", "--quiet", action="store_true", dest="quiet", default=False, help="Don't show headers.") (options, args) = parser.parse_args() @@ -244,12 +280,17 @@ def print_stats(self): statsgen = StatsGen() - if not options.minlength == None: statsgen.minlength = options.minlength - if not options.maxlength == None: statsgen.maxlength = options.maxlength - if not options.charsets == None: statsgen.charsets = [x.strip() for x in options.charsets.split(',')] - if not options.simplemasks == None: statsgen.simplemasks = [x.strip() for x in options.simplemasks.split(',')] - - if options.hiderare: statsgen.hiderare = options.hiderare + if options.minlength is not None: + statsgen.minlength = options.minlength + if options.maxlength is not None: + statsgen.maxlength = options.maxlength + if options.charsets is not None: + statsgen.charsets = [x.strip() for x in options.charsets.split(',')] + if options.simplemasks is not None: + statsgen.simplemasks = [x.strip() for x in options.simplemasks.split(',')] + + if options.hiderare: + statsgen.hiderare = options.hiderare if options.output_file: print("[*] Saving advanced masks and occurrences to [%s]" % options.output_file) From 996fdf844cce51050993756cadf5d4d8f6acb663 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Haidar-Bachminska Date: Tue, 21 Jun 2016 20:37:55 +0200 Subject: [PATCH 12/27] make analyze_password a static method --- statsgen.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/statsgen.py b/statsgen.py index 438d335..a230602 100755 --- a/statsgen.py +++ b/statsgen.py @@ -52,7 +52,8 @@ def __init__(self): self.maxlower = None self.maxspecial = None - def analyze_password(self, password): + @staticmethod + def analyze_password(password): # Password length pass_length = len(password) From 8f7ccfcab0e1271bf814613f09cf5bafe55bf816 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Haidar-Bachminska Date: Tue, 21 Jun 2016 23:24:28 +0200 Subject: [PATCH 13/27] remove an useless call to len() --- statsgen.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/statsgen.py b/statsgen.py index a230602..d1d4376 100755 --- a/statsgen.py +++ b/statsgen.py @@ -141,7 +141,8 @@ def generate_stats(self, filename): for password in f: password = password.rstrip('\r\n') - if len(password) == 0: + # if the password is empty, continue + if not password: continue self.total_counter += 1 From 8abf9a2a764e667b92311ec96f998edd72b7b4b3 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Haidar-Bachminska Date: Tue, 21 Jun 2016 23:51:29 +0200 Subject: [PATCH 14/27] cleanup of maskgen.py (PEP8 compliance) --- maskgen.py | 177 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 107 insertions(+), 70 deletions(-) diff --git a/maskgen.py b/maskgen.py index cacd0ff..9844d71 100755 --- a/maskgen.py +++ b/maskgen.py @@ -16,6 +16,7 @@ VERSION = "0.0.3" + class MaskGen: def __init__(self): # Masks collections with meta data @@ -24,10 +25,10 @@ def __init__(self): self.target_time = None self.output_file = None - self.minlength = None - self.maxlength = None - self.mintime = None - self.maxtime = None + self.minlength = None + self.maxlength = None + self.mintime = None + self.maxtime = None self.mincomplexity = None self.maxcomplexity = None self.minoccurrence = None @@ -44,48 +45,55 @@ def getcomplexity(self, mask): """ Return mask complexity. """ count = 1 for char in mask[1:].split("?"): - if char == "l": count *= 26 - elif char == "u": count *= 26 - elif char == "d": count *= 10 - elif char == "s": count *= 33 - elif char == "a": count *= 95 - else: print("[!] Error, unknown mask ?%s in a mask %s" % (char,mask)) + if char == "l": + count *= 26 + elif char == "u": + count *= 26 + elif char == "d": + count *= 10 + elif char == "s": + count *= 33 + elif char == "a": + count *= 95 + else: + print("[!] Error, unknown mask ?%s in a mask %s" % (char, mask)) return count def loadmasks(self, filename): """ Load masks and apply filters. """ - maskReader = csv.reader(open(args[0],'r'), delimiter=',', quotechar='"') + mask_reader = csv.reader(open(args[0], 'r'), delimiter=',', quotechar='"') - for (mask,occurrence) in maskReader: + for (mask, occurrence) in mask_reader: - if mask == "": continue + if not mask: + continue mask_occurrence = int(occurrence) - mask_length = len(mask)//2 + mask_length = len(mask) // 2 mask_complexity = self.getcomplexity(mask) - mask_time = mask_complexity//self.pps + mask_time = mask_complexity // self.pps self.total_occurrence += mask_occurrence # Apply filters based on occurrence, length, complexity and time - if (self.minoccurrence == None or mask_occurrence >= self.minoccurrence) and \ - (self.maxoccurrence == None or mask_occurrence <= self.maxoccurrence) and \ - (self.mincomplexity == None or mask_complexity <= self.mincomplexity) and \ - (self.maxcomplexity == None or mask_complexity <= self.maxcomplexity) and \ - (self.mintime == None or mask_time <= self.mintime) and \ - (self.maxtime == None or mask_time <= self.maxtime) and \ - (self.maxlength == None or mask_length <= self.maxlength) and \ - (self.minlength == None or mask_length >= self.minlength): + if (self.minoccurrence is None or mask_occurrence >= self.minoccurrence) and \ + (self.maxoccurrence is None or mask_occurrence <= self.maxoccurrence) and \ + (self.mincomplexity is None or mask_complexity <= self.mincomplexity) and \ + (self.maxcomplexity is None or mask_complexity <= self.maxcomplexity) and \ + (self.mintime is None or mask_time <= self.mintime) and \ + (self.maxtime is None or mask_time <= self.maxtime) and \ + (self.maxlength is None or mask_length <= self.maxlength) and \ + (self.minlength is None or mask_length >= self.minlength): self.masks[mask] = dict() self.masks[mask]['length'] = mask_length self.masks[mask]['occurrence'] = mask_occurrence self.masks[mask]['complexity'] = 1 - mask_complexity self.masks[mask]['time'] = mask_time - self.masks[mask]['optindex'] = 1 - (mask_complexity//mask_occurrence) + self.masks[mask]['optindex'] = 1 - (mask_complexity // mask_occurrence) - def generate_masks(self,sorting_mode): + def generate_masks(self, sorting_mode): """ Generate optimal password masks sorted by occurrence, complexity or optindex """ sample_count = 0 sample_time = 0 @@ -95,12 +103,16 @@ def generate_masks(self,sorting_mode): # Group by length 1,2,3,4,5,6,7,8,9,10.... # Group by occurrence 10%, 20%, 30%, 40%, 50%.... - if self.showmasks: print("[L:] Mask: [ Occ: ] [ Time: ]") - for mask in sorted(list(self.masks.keys()), key=lambda mask: self.masks[mask][sorting_mode], reverse=True): + if self.showmasks: + print("[L:] Mask: [ Occ: ] [ Time: ]") + + for mask in sorted(list(self.masks.keys()), key=lambda m: self.masks[m][sorting_mode], reverse=True): if self.showmasks: - time_human = ">1 year" if self.masks[mask]['time'] > 60*60*24*365 else str(datetime.timedelta(seconds=self.masks[mask]['time'])) - print("[{:>2}] {:<30} [{:<7}] [{:>8}] ".format(self.masks[mask]['length'], mask, self.masks[mask]['occurrence'], time_human)) + time_human = ">1 year" if self.masks[mask]['time'] > 60*60*24*365 \ + else str(datetime.timedelta(seconds=self.masks[mask]['time'])) + print("[{:>2}] {:<30} [{:<7}] [{:>8}] ".format(self.masks[mask]['length'], mask, + self.masks[mask]['occurrence'], time_human)) if self.output_file: self.output_file.write("%s\n" % mask) @@ -115,7 +127,8 @@ def generate_masks(self,sorting_mode): print("[*] Finished generating masks:") print(" Masks generated: %s" % sample_count) - print(" Masks coverage: %d%% (%d/%d)" % (sample_occurrence*100//self.total_occurrence,sample_occurrence,self.total_occurrence)) + print(" Masks coverage: %d%% (%d/%d)" % (sample_occurrence * 100 // self.total_occurrence, + sample_occurrence, self.total_occurrence)) time_human = ">1 year" if sample_time > 60*60*24*365 else str(datetime.timedelta(seconds=sample_time)) print(" Masks runtime: %s" % time_human) @@ -126,7 +139,9 @@ def getmaskscoverage(self, checkmasks): total_complexity = 0 - if self.showmasks: print("[L:] Mask: [ Occ: ] [ Time: ]") + if self.showmasks: + print("[L:] Mask: [ Occ: ] [ Time: ]") + for mask in checkmasks: mask = mask.strip() mask_complexity = self.getcomplexity(mask) @@ -136,8 +151,10 @@ def getmaskscoverage(self, checkmasks): if mask in self.masks: if self.showmasks: - time_human = ">1 year" if self.masks[mask]['time'] > 60*60*24*365 else str(datetime.timedelta(seconds=self.masks[mask]['time'])) - print("[{:>2}] {:<30} [{:<7}] [{:>8}] ".format(self.masks[mask]['length'], mask, self.masks[mask]['occurrence'], time_human)) + time_human = ">1 year" if self.masks[mask]['time'] > 60*60*24*365 \ + else str(datetime.timedelta(seconds=self.masks[mask]['time'])) + print("[{:>2}] {:<30} [{:<7}] [{:>8}] ".format(self.masks[mask]['length'], mask, + self.masks[mask]['occurrence'], time_human)) if self.output_file: self.output_file.write("%s\n" % mask) @@ -145,22 +162,23 @@ def getmaskscoverage(self, checkmasks): sample_occurrence += self.masks[mask]['occurrence'] sample_count += 1 - if self.target_time and total_complexity/self.pps > self.target_time: + if self.target_time and total_complexity / self.pps > self.target_time: print("[!] Target time exceeded.") break # TODO: Something wrong here, complexity and time doesn't match with estimated from policygen - total_time = total_complexity/self.pps + total_time = total_complexity / self.pps time_human = ">1 year" if total_time > 60*60*24*365 else str(datetime.timedelta(seconds=total_time)) print("[*] Finished matching masks:") print(" Masks matched: %s" % sample_count) - print(" Masks coverage: %d%% (%d/%d)" % (sample_occurrence*100/self.total_occurrence,sample_occurrence,self.total_occurrence)) + print(" Masks coverage: %d%% (%d/%d)" % (sample_occurrence * 100 / self.total_occurrence, + sample_occurrence, self.total_occurrence)) print(" Masks runtime: %s" % time_human) if __name__ == "__main__": - header = " _ \n" + header = " _ \n" header += " MaskGen %s | |\n" % VERSION header += " _ __ __ _ ___| | _\n" header += " | '_ \ / _` |/ __| |/ /\n" @@ -172,35 +190,42 @@ def getmaskscoverage(self, checkmasks): parser = OptionParser("%prog pass0.masks [pass1.masks ...] [options]", version="%prog "+VERSION) - parser.add_option("-t", "--targettime", dest="target_time", type="int", metavar="86400", help="Target time of all masks (seconds)") - parser.add_option("-o", "--outputmasks", dest="output_masks", metavar="masks.hcmask", help="Save masks to a file") + parser.add_option("-t", "--targettime", dest="target_time", type="int", metavar="86400", + help="Target time of all masks (seconds)") + parser.add_option("-o", "--outputmasks", dest="output_masks", metavar="masks.hcmask", + help="Save masks to a file") filters = OptionGroup(parser, "Individual Mask Filter Options") - filters.add_option("--minlength", dest="minlength", type="int", metavar="8", help="Minimum password length") - filters.add_option("--maxlength", dest="maxlength", type="int", metavar="8", help="Maximum password length") - filters.add_option("--mintime", dest="mintime", type="int", metavar="3600", help="Minimum mask runtime (seconds)") - filters.add_option("--maxtime", dest="maxtime", type="int", metavar="3600", help="Maximum mask runtime (seconds)") - filters.add_option("--mincomplexity", dest="mincomplexity", type="int", metavar="1", help="Minimum complexity") - filters.add_option("--maxcomplexity", dest="maxcomplexity", type="int", metavar="100", help="Maximum complexity") - filters.add_option("--minoccurrence", dest="minoccurrence", type="int", metavar="1", help="Minimum occurrence") - filters.add_option("--maxoccurrence", dest="maxoccurrence", type="int", metavar="100", help="Maximum occurrence") + filters.add_option("--minlength", dest="minlength", type="int", metavar="8", help="Minimum password length") + filters.add_option("--maxlength", dest="maxlength", type="int", metavar="8", help="Maximum password length") + filters.add_option("--mintime", dest="mintime", type="int", metavar="3600", help="Minimum mask runtime (seconds)") + filters.add_option("--maxtime", dest="maxtime", type="int", metavar="3600", help="Maximum mask runtime (seconds)") + filters.add_option("--mincomplexity", dest="mincomplexity", type="int", metavar="1", help="Minimum complexity") + filters.add_option("--maxcomplexity", dest="maxcomplexity", type="int", metavar="100", help="Maximum complexity") + filters.add_option("--minoccurrence", dest="minoccurrence", type="int", metavar="1", help="Minimum occurrence") + filters.add_option("--maxoccurrence", dest="maxoccurrence", type="int", metavar="100", help="Maximum occurrence") parser.add_option_group(filters) sorting = OptionGroup(parser, "Mask Sorting Options") - sorting.add_option("--optindex", action="store_true", dest="optindex", help="sort by mask optindex (default)", default=False) - sorting.add_option("--occurrence", action="store_true", dest="occurrence", help="sort by mask occurrence", default=False) - sorting.add_option("--complexity", action="store_true", dest="complexity", help="sort by mask complexity", default=False) + sorting.add_option("--optindex", action="store_true", dest="optindex", help="sort by mask optindex (default)", + default=False) + sorting.add_option("--occurrence", action="store_true", dest="occurrence", help="sort by mask occurrence", + default=False) + sorting.add_option("--complexity", action="store_true", dest="complexity", help="sort by mask complexity", + default=False) parser.add_option_group(sorting) coverage = OptionGroup(parser, "Check mask coverage") - coverage.add_option("--checkmasks", dest="checkmasks", help="check mask coverage", metavar="?u?l?l?l?l?l?d,?l?l?l?l?l?d?d") - coverage.add_option("--checkmasksfile", dest="checkmasks_file", help="check mask coverage in a file", metavar="masks.hcmask") + coverage.add_option("--checkmasks", dest="checkmasks", help="check mask coverage", + metavar="?u?l?l?l?l?l?d,?l?l?l?l?l?d?d") + coverage.add_option("--checkmasksfile", dest="checkmasks_file", help="check mask coverage in a file", + metavar="masks.hcmask") parser.add_option_group(coverage) - parser.add_option("--showmasks", dest="showmasks",help="Show matching masks", action="store_true", default=False) + parser.add_option("--showmasks", dest="showmasks", help="Show matching masks", action="store_true", default=False) misc = OptionGroup(parser, "Miscellaneous options") - misc.add_option("--pps", dest="pps",help="Passwords per Second", type="int", metavar="1000000000") + misc.add_option("--pps", dest="pps", help="Passwords per Second", type="int", metavar="1000000000") misc.add_option("-q", "--quiet", action="store_true", dest="quiet", default=False, help="Don't show headers.") parser.add_option_group(misc) @@ -219,24 +244,35 @@ def getmaskscoverage(self, checkmasks): maskgen = MaskGen() # Settings - if options.target_time: maskgen.target_time = options.target_time + if options.target_time: + maskgen.target_time = options.target_time if options.output_masks: print("[*] Saving generated masks to [%s]" % options.output_masks) maskgen.output_file = open(options.output_masks, 'w') # Filters - if options.minlength: maskgen.minlength = options.minlength - if options.maxlength: maskgen.maxlength = options.maxlength - if options.mintime: maskgen.mintime = options.mintime - if options.maxtime: maskgen.maxtime = options.maxtime - if options.mincomplexity: maskgen.mincomplexity = options.mincomplexity - if options.maxcomplexity: maskgen.maxcomplexity = options.maxcomplexity - if options.minoccurrence: maskgen.minoccurrence = options.minoccurrence - if options.maxoccurrence: maskgen.maxoccurrence = options.maxoccurrence + if options.minlength: + maskgen.minlength = options.minlength + if options.maxlength: + maskgen.maxlength = options.maxlength + if options.mintime: + maskgen.mintime = options.mintime + if options.maxtime: + maskgen.maxtime = options.maxtime + if options.mincomplexity: + maskgen.mincomplexity = options.mincomplexity + if options.maxcomplexity: + maskgen.maxcomplexity = options.maxcomplexity + if options.minoccurrence: + maskgen.minoccurrence = options.minoccurrence + if options.maxoccurrence: + maskgen.maxoccurrence = options.maxoccurrence # Misc - if options.pps: maskgen.pps = options.pps - if options.showmasks: maskgen.showmasks = options.showmasks + if options.pps: + maskgen.pps = options.pps + if options.showmasks: + maskgen.showmasks = options.showmasks print("[*] Using {:,d} keys/sec for calculations.".format(maskgen.pps)) @@ -260,11 +296,12 @@ def getmaskscoverage(self, checkmasks): else: # Process masks according to specified sorting algorithm if options.occurrence: - sorting_mode = "occurrence" + sort_mode = "occurrence" elif options.complexity: - sorting_mode = "complexity" + sort_mode = "complexity" else: - sorting_mode = "optindex" + sort_mode = "optindex" - print("[*] Sorting masks by their [%s]." % sorting_mode) - maskgen.generate_masks(sorting_mode) \ No newline at end of file + print("[*] Sorting masks by their [%s]." % sort_mode) + maskgen.generate_masks(sort_mode) + \ No newline at end of file From 20e2136e70587f75c64d61492bc1742f746b9ead Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Haidar-Bachminska Date: Tue, 21 Jun 2016 23:58:55 +0200 Subject: [PATCH 15/27] fix a typo in loadmasks where the opened file was arg[0] instead of the method's argument --- maskgen.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/maskgen.py b/maskgen.py index 9844d71..3f6a732 100755 --- a/maskgen.py +++ b/maskgen.py @@ -62,7 +62,7 @@ def getcomplexity(self, mask): def loadmasks(self, filename): """ Load masks and apply filters. """ - mask_reader = csv.reader(open(args[0], 'r'), delimiter=',', quotechar='"') + mask_reader = csv.reader(open(filename, 'r'), delimiter=',', quotechar='"') for (mask, occurrence) in mask_reader: @@ -304,4 +304,3 @@ def getmaskscoverage(self, checkmasks): print("[*] Sorting masks by their [%s]." % sort_mode) maskgen.generate_masks(sort_mode) - \ No newline at end of file From 901ac05a5ac4a442f3d84560d9051fbb6f3d9cc0 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Haidar-Bachminska Date: Wed, 22 Jun 2016 00:01:12 +0200 Subject: [PATCH 16/27] add a static decorator to getcomplexity --- maskgen.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/maskgen.py b/maskgen.py index 3f6a732..6a70624 100755 --- a/maskgen.py +++ b/maskgen.py @@ -41,7 +41,8 @@ def __init__(self): # Counter for total masks coverage self.total_occurrence = 0 - def getcomplexity(self, mask): + @staticmethod + def getcomplexity(mask): """ Return mask complexity. """ count = 1 for char in mask[1:].split("?"): From 17f27f1582ea4ee5aa5f2a336c9b58d6c0c348a0 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Haidar-Bachminska Date: Wed, 22 Jun 2016 00:15:38 +0200 Subject: [PATCH 17/27] improved PEP8 compliance for policygen.py and code cleanup --- policygen.py | 188 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 114 insertions(+), 74 deletions(-) diff --git a/policygen.py b/policygen.py index 36dff3b..77dee73 100755 --- a/policygen.py +++ b/policygen.py @@ -16,19 +16,20 @@ VERSION = "0.0.2" -class PolicyGen: + +class PolicyGen: def __init__(self): self.output_file = None - self.minlength = 8 - self.maxlength = 8 - self.mindigit = None - self.minlower = None - self.minupper = None + self.minlength = 8 + self.maxlength = 8 + self.mindigit = None + self.minlower = None + self.minupper = None self.minspecial = None - self.maxdigit = None - self.maxlower = None - self.maxupper = None + self.maxdigit = None + self.maxlower = None + self.maxupper = None self.maxspecial = None # PPS (Passwords per Second) Cracking Speed @@ -39,15 +40,21 @@ def getcomplexity(self, mask): """ Return mask complexity. """ count = 1 for char in mask[1:].split("?"): - if char == "l": count *= 26 - elif char == "u": count *= 26 - elif char == "d": count *= 10 - elif char == "s": count *= 33 - elif char == "a": count *= 95 - else: print("[!] Error, unknown mask ?%s in a mask %s" % (char,mask)) + if char == "l": + count *= 26 + elif char == "u": + count *= 26 + elif char == "d": + count *= 10 + elif char == "s": + count *= 33 + elif char == "a": + count *= 95 + else: + print("[!] Error, unknown mask ?%s in a mask %s" % (char, mask)) return count - + def generate_masks(self, noncompliant): """ Generate all possible password masks matching the policy """ @@ -60,16 +67,15 @@ def generate_masks(self, noncompliant): sample_complexity = 0 # TODO: Randomize or even statistically arrange matching masks - for length in range(self.minlength, self.maxlength+1): + for length in range(self.minlength, self.maxlength + 1): print("[*] Generating %d character password masks." % length) total_length_count = 0 sample_length_count = 0 - total_length_complexity = 0 sample_length_complexity = 0 - for masklist in itertools.product(['?d','?l','?u','?s'], repeat=length): + for masklist in itertools.product(['?d', '?l', '?u', '?s'], repeat=length): mask = ''.join(masklist) @@ -78,37 +84,47 @@ def generate_masks(self, noncompliant): digitcount = 0 specialcount = 0 - mask_complexity = self.getcomplexity(mask) - + mask_complexity = self.getcomplexity(mask) + total_length_count += 1 total_length_complexity += mask_complexity # Count character types in a mask for char in mask[1:].split("?"): - if char == "l": lowercount += 1 - elif char == "u": uppercount += 1 - elif char == "d": digitcount += 1 - elif char == "s": specialcount += 1 - + if char == "l": + lowercount += 1 + elif char == "u": + uppercount += 1 + elif char == "d": + digitcount += 1 + elif char == "s": + specialcount += 1 + # Filter according to password policy # NOTE: Perform exact opposite (XOR) operation if noncompliant # flag was set when calling the function. - if ((self.minlower is None or lowercount >= self.minlower) and - (self.maxlower is None or lowercount <= self.maxlower) and - (self.minupper is None or uppercount >= self.minupper) and - (self.maxupper is None or uppercount <= self.maxupper) and - (self.mindigit is None or digitcount >= self.mindigit) and - (self.maxdigit is None or digitcount <= self.maxdigit) and - (self.minspecial is None or specialcount >= self.minspecial) and - (self.maxspecial is None or specialcount <= self.maxspecial)) ^ noncompliant: + if ((self.minlower is None or lowercount >= self.minlower) and + (self.maxlower is None or lowercount <= self.maxlower) and + (self.minupper is None or uppercount >= self.minupper) and + (self.maxupper is None or uppercount <= self.maxupper) and + (self.mindigit is None or digitcount >= self.mindigit) and + (self.maxdigit is None or digitcount <= self.maxdigit) and + (self.minspecial is None or specialcount >= self.minspecial) and + (self.maxspecial is None or specialcount <= self.maxspecial)) ^ noncompliant: sample_length_count += 1 sample_length_complexity += mask_complexity if self.showmasks: - mask_time = mask_complexity//self.pps - time_human = ">1 year" if mask_time > 60*60*24*365 else str(datetime.timedelta(seconds=mask_time)) - print("[{:>2}] {:<30} [l:{:>2} u:{:>2} d:{:>2} s:{:>2}] [{:>8}] ".format(length, mask, lowercount,uppercount,digitcount,specialcount, time_human)) + mask_time = mask_complexity // self.pps + time_human = ">1 year" if mask_time > 60 * 60 * 24 * 365 \ + else str(datetime.timedelta(seconds=mask_time)) + print("[{:>2}] {:<30} [l:{:>2} u:{:>2} d:{:>2} s:{:>2}] [{:>8}] ".format(length, mask, + lowercount, + uppercount, + digitcount, + specialcount, + time_human)) if self.output_file: self.output_file.write("%s\n" % mask) @@ -119,19 +135,20 @@ def generate_masks(self, noncompliant): total_complexity += total_length_complexity sample_complexity += sample_length_complexity - total_time = total_complexity//self.pps - total_time_human = ">1 year" if total_time > 60*60*24*365 else str(datetime.timedelta(seconds=total_time)) + total_time = total_complexity // self.pps + total_time_human = ">1 year" if total_time > 60 * 60 * 24 * 365 else str(datetime.timedelta(seconds=total_time)) print("[*] Total Masks: %d Time: %s" % (total_count, total_time_human)) - sample_time = sample_complexity//self.pps - sample_time_human = ">1 year" if sample_time > 60*60*24*365 else str(datetime.timedelta(seconds=sample_time)) + sample_time = sample_complexity // self.pps + sample_time_human = ">1 year" if sample_time > 60 * 60 * 24 * 365 else str( + datetime.timedelta(seconds=sample_time)) print("[*] Policy Masks: %d Time: %s" % (sample_count, sample_time_human)) if __name__ == "__main__": - header = " _ \n" - header += " PolicyGen %s | |\n" % VERSION + header = " _ \n" + header += " PolicyGen %s | |\n" % VERSION header += " _ __ __ _ ___| | _\n" header += " | '_ \ / _` |/ __| |/ /\n" header += " | |_) | (_| | (__| < \n" @@ -141,23 +158,33 @@ def generate_masks(self, noncompliant): header += "\n" # parse command line arguments - parser = OptionParser("%prog [options]\n\nType --help for more options", version="%prog "+VERSION) - parser.add_option("-o", "--outputmasks", dest="output_masks",help="Save masks to a file", metavar="masks.hcmask") + parser = OptionParser("%prog [options]\n\nType --help for more options", version="%prog " + VERSION) + parser.add_option("-o", "--outputmasks", dest="output_masks", help="Save masks to a file", metavar="masks.hcmask") parser.add_option("--pps", dest="pps", help="Passwords per Second", type="int", metavar="1000000000") parser.add_option("--showmasks", dest="showmasks", help="Show matching masks", action="store_true", default=False) - parser.add_option("--noncompliant", dest="noncompliant", help="Generate masks for noncompliant passwords", action="store_true", default=False) - - group = OptionGroup(parser, "Password Policy", "Define the minimum (or maximum) password strength policy that you would like to test") - group.add_option("--minlength", dest="minlength", type="int", metavar="8", default=8, help="Minimum password length") - group.add_option("--maxlength", dest="maxlength", type="int", metavar="8", default=8, help="Maximum password length") - group.add_option("--mindigit", dest="mindigit", type="int", metavar="1", help="Minimum number of digits") - group.add_option("--minlower", dest="minlower", type="int", metavar="1", help="Minimum number of lower-case characters") - group.add_option("--minupper", dest="minupper", type="int", metavar="1", help="Minimum number of upper-case characters") - group.add_option("--minspecial",dest="minspecial",type="int", metavar="1", help="Minimum number of special characters") - group.add_option("--maxdigit", dest="maxdigit", type="int", metavar="3", help="Maximum number of digits") - group.add_option("--maxlower", dest="maxlower", type="int", metavar="3", help="Maximum number of lower-case characters") - group.add_option("--maxupper", dest="maxupper", type="int", metavar="3", help="Maximum number of upper-case characters") - group.add_option("--maxspecial",dest="maxspecial",type="int", metavar="3", help="Maximum number of special characters") + parser.add_option("--noncompliant", dest="noncompliant", help="Generate masks for noncompliant passwords", + action="store_true", default=False) + + group = OptionGroup(parser, "Password Policy", + "Define the minimum (or maximum) password strength policy that you would like to test") + group.add_option("--minlength", dest="minlength", type="int", metavar="8", default=8, + help="Minimum password length") + group.add_option("--maxlength", dest="maxlength", type="int", metavar="8", default=8, + help="Maximum password length") + group.add_option("--mindigit", dest="mindigit", type="int", metavar="1", help="Minimum number of digits") + group.add_option("--minlower", dest="minlower", type="int", metavar="1", + help="Minimum number of lower-case characters") + group.add_option("--minupper", dest="minupper", type="int", metavar="1", + help="Minimum number of upper-case characters") + group.add_option("--minspecial", dest="minspecial", type="int", metavar="1", + help="Minimum number of special characters") + group.add_option("--maxdigit", dest="maxdigit", type="int", metavar="3", help="Maximum number of digits") + group.add_option("--maxlower", dest="maxlower", type="int", metavar="3", + help="Maximum number of lower-case characters") + group.add_option("--maxupper", dest="maxupper", type="int", metavar="3", + help="Maximum number of upper-case characters") + group.add_option("--maxspecial", dest="maxspecial", type="int", metavar="3", + help="Maximum number of special characters") parser.add_option_group(group) parser.add_option("-q", "--quiet", action="store_true", dest="quiet", default=False, help="Don't show headers.") @@ -175,30 +202,43 @@ def generate_masks(self, noncompliant): print("[*] Saving generated masks to [%s]" % options.output_masks) policygen.output_file = open(options.output_masks, 'w') - # Password policy - if options.minlength != None: policygen.minlength = options.minlength - if options.maxlength != None: policygen.maxlength = options.maxlength - if options.mindigit != None: policygen.mindigit = options.mindigit - if options.minlower != None: policygen.minlower = options.minlower - if options.minupper != None: policygen.minupper = options.minupper - if options.minspecial != None: policygen.minspecial = options.minspecial - if options.maxdigit != None: policygen.maxdigits = options.maxdigit - if options.maxlower != None: policygen.maxlower = options.maxlower - if options.maxupper != None: policygen.maxupper = options.maxupper - if options.maxspecial != None: policygen.maxspecial = options.maxspecial + if options.minlength is not None: + policygen.minlength = options.minlength + if options.maxlength is not None: + policygen.maxlength = options.maxlength + if options.mindigit is not None: + policygen.mindigit = options.mindigit + if options.minlower is not None: + policygen.minlower = options.minlower + if options.minupper is not None: + policygen.minupper = options.minupper + if options.minspecial is not None: + policygen.minspecial = options.minspecial + if options.maxdigit is not None: + policygen.maxdigits = options.maxdigit + if options.maxlower is not None: + policygen.maxlower = options.maxlower + if options.maxupper is not None: + policygen.maxupper = options.maxupper + if options.maxspecial is not None: + policygen.maxspecial = options.maxspecial # Misc - if options.pps: policygen.pps = options.pps - if options.showmasks: policygen.showmasks = options.showmasks + if options.pps: + policygen.pps = options.pps + if options.showmasks: + policygen.showmasks = options.showmasks print("[*] Using {:,d} keys/sec for calculations.".format(policygen.pps)) # Print current password policy print("[*] Password policy:") print(" Pass Lengths: min:%d max:%d" % (policygen.minlength, policygen.maxlength)) - print(" Min strength: l:%s u:%s d:%s s:%s" % (policygen.minlower, policygen.minupper, policygen.mindigit, policygen.minspecial)) - print(" Max strength: l:%s u:%s d:%s s:%s" % (policygen.maxlower, policygen.maxupper, policygen.maxdigit, policygen.maxspecial)) + print(" Min strength: l:%s u:%s d:%s s:%s" % ( + policygen.minlower, policygen.minupper, policygen.mindigit, policygen.minspecial)) + print(" Max strength: l:%s u:%s d:%s s:%s" % ( + policygen.maxlower, policygen.maxupper, policygen.maxdigit, policygen.maxspecial)) print("[*] Generating [%s] masks." % ("compliant" if not options.noncompliant else "non-compliant")) - policygen.generate_masks(options.noncompliant) \ No newline at end of file + policygen.generate_masks(options.noncompliant) From 0b229173c15214af23f2154b4680360697c91743 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Haidar-Bachminska Date: Wed, 22 Jun 2016 00:16:17 +0200 Subject: [PATCH 18/27] add the staticmethod decorator for getcomplexity method --- policygen.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/policygen.py b/policygen.py index 77dee73..ac86de9 100755 --- a/policygen.py +++ b/policygen.py @@ -36,7 +36,8 @@ def __init__(self): self.pps = 1000000000 self.showmasks = False - def getcomplexity(self, mask): + @staticmethod + def getcomplexity(mask): """ Return mask complexity. """ count = 1 for char in mask[1:].split("?"): From 15e77efd6a075fbfabd4770586fd84f076ee0d6a Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Haidar-Bachminska Date: Wed, 22 Jun 2016 02:51:06 +0200 Subject: [PATCH 19/27] clean up and PEP8 compliance for rulegen.py --- rulegen.py | 735 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 437 insertions(+), 298 deletions(-) diff --git a/rulegen.py b/rulegen.py index aadb249..485ddef 100755 --- a/rulegen.py +++ b/rulegen.py @@ -15,31 +15,29 @@ import sys import re import time -import enchant +import multiprocessing +import subprocess +import enchant from optparse import OptionParser, OptionGroup - from collections import Counter -import subprocess - -import multiprocessing - VERSION = "0.0.4" # Testing rules with hashcat --stdout HASHCAT_PATH = "hashcat/" + # Rule Generator class responsible for the complete cycle of rule generation class RuleGen: - # Initialize Rule Generator class - def __init__(self,language="en",providers="aspell,myspell",basename='analysis',threads=multiprocessing.cpu_count()): + def __init__(self, language="en", providers="aspell,myspell", basename='analysis', + threads=multiprocessing.cpu_count()): self.threads = threads self.enchant_broker = enchant.Broker() - self.enchant_broker.set_ordering("*",providers) + self.enchant_broker.set_ordering("*", providers) self.enchant = enchant.Dict(language, self.enchant_broker) @@ -62,7 +60,7 @@ def __init__(self,language="en",providers="aspell,myspell",basename='analysis',t # Debugging options self.verbose = False self.debug = False - self.word = None # Custom word to use. + self.word = None # Custom word to use. self.quiet = False ######################################################################## @@ -77,66 +75,130 @@ def __init__(self,language="en",providers="aspell,myspell",basename='analysis',t self.password_pattern["insertion"] = re.compile('^[^a-z]*(?P.+?)[^a-z]*$', re.IGNORECASE) self.password_pattern["email"] = re.compile('^(?P.+?)@[A-Z0-9.-]+\.[A-Z]{2,4}', re.IGNORECASE) self.password_pattern["alldigits"] = re.compile('^(\d+)$', re.IGNORECASE) - self.password_pattern["allspecial"]= re.compile('^([^a-z0-9]+)$', re.IGNORECASE) + self.password_pattern["allspecial"] = re.compile('^([^a-z0-9]+)$', re.IGNORECASE) ######################################################################## # Hashcat Rules Engine self.hashcat_rule = dict() - # Dummy rule - self.hashcat_rule[':'] = lambda x: x # Do nothing - - # Case rules - self.hashcat_rule["l"] = lambda x: x.lower() # Lowercase all letters - self.hashcat_rule["u"] = lambda x: x.upper() # Capitalize all letters - self.hashcat_rule["c"] = lambda x: x.capitalize() # Capitalize the first letter - self.hashcat_rule["C"] = lambda x: x[0].lower() + x[1:].upper() # Lowercase the first found character, uppercase the rest - self.hashcat_rule["t"] = lambda x: x.swapcase() # Toggle the case of all characters in word - self.hashcat_rule["T"] = lambda x,y: x[:y] + x[y].swapcase() + x[y+1:] # Toggle the case of characters at position N - self.hashcat_rule["E"] = lambda x: " ".join([i[0].upper()+i[1:] for i in x.split(" ")]) # Upper case the first letter and every letter after a space - - # Rotation rules - self.hashcat_rule["r"] = lambda x: x[::-1] # Reverse the entire word - self.hashcat_rule["{"] = lambda x: x[1:]+x[0] # Rotate the word left - self.hashcat_rule["}"] = lambda x: x[-1]+x[:-1] # Rotate the word right - - # Duplication rules - self.hashcat_rule["d"] = lambda x: x+x # Duplicate entire word - self.hashcat_rule["p"] = lambda x,y: x*y # Duplicate entire word N times - self.hashcat_rule["f"] = lambda x: x+x[::-1] # Duplicate word reversed - self.hashcat_rule["z"] = lambda x,y: x[0]*y+x # Duplicate first character N times - self.hashcat_rule["Z"] = lambda x,y: x+x[-1]*y # Duplicate last character N times - self.hashcat_rule["q"] = lambda x: "".join([i+i for i in x]) # Duplicate every character - self.hashcat_rule["y"] = lambda x,y: x[:y]+x # Duplicate first N characters - self.hashcat_rule["Y"] = lambda x,y: x+x[-y:] # Duplicate last N characters - - # Cutting rules - self.hashcat_rule["["] = lambda x: x[1:] # Delete first character - self.hashcat_rule["]"] = lambda x: x[:-1] # Delete last character - self.hashcat_rule["D"] = lambda x,y: x[:y]+x[y+1:] # Deletes character at position N - self.hashcat_rule["'"] = lambda x,y: x[:y] # Truncate word at position N - self.hashcat_rule["x"] = lambda x,y,z: x[:y]+x[y+z:] # Delete M characters, starting at position N - self.hashcat_rule["@"] = lambda x,y: x.replace(y,'') # Purge all instances of X - - # Insertion rules - self.hashcat_rule["$"] = lambda x,y: x+y # Append character to end - self.hashcat_rule["^"] = lambda x,y: y+x # Prepend character to front - self.hashcat_rule["i"] = lambda x,y,z: x[:y]+z+x[y:] # Insert character X at position N - - # Replacement rules - self.hashcat_rule["o"] = lambda x,y,z: x[:y]+z+x[y+1:] # Overwrite character at position N with X - self.hashcat_rule["s"] = lambda x,y,z: x.replace(y,z) # Replace all instances of X with Y - self.hashcat_rule["L"] = lambda x,y: x[:y]+chr(ord(x[y])<<1)+x[y+1:] # Bitwise shift left character @ N - self.hashcat_rule["R"] = lambda x,y: x[:y]+chr(ord(x[y])>>1)+x[y+1:] # Bitwise shift right character @ N - self.hashcat_rule["+"] = lambda x,y: x[:y]+chr(ord(x[y])+1)+x[y+1:] # Increment character @ N by 1 ascii value - self.hashcat_rule["-"] = lambda x,y: x[:y]+chr(ord(x[y])-1)+x[y+1:] # Decrement character @ N by 1 ascii value - self.hashcat_rule["."] = lambda x,y: x[:y]+x[y+1]+x[y+1:] # Replace character @ N with value at @ N plus 1 - self.hashcat_rule[","] = lambda x,y: x[:y]+x[y-1]+x[y+1:] # Replace character @ N with value at @ N minus 1 - - # Swappping rules - self.hashcat_rule["k"] = lambda x: x[1]+x[0]+x[2:] # Swap first two characters - self.hashcat_rule["K"] = lambda x: x[:-2]+x[-1]+x[-2] # Swap last two characters - self.hashcat_rule["*"] = lambda x,y,z: x[:y]+x[z]+x[y+1:z]+x[y]+x[z+1:] if z > y else x[:z]+x[y]+x[z+1:y]+x[z]+x[y+1:] # Swap character X with Y + ###################### + # Dummy rule # + ###################### + + # Do nothing + self.hashcat_rule[':'] = lambda x: x + + ###################### + # Case rules # + ###################### + + # Lowercase all letters + self.hashcat_rule["l"] = lambda x: x.lower() + # Capitalize all letters + self.hashcat_rule["u"] = lambda x: x.upper() + # Capitalize the first letter + self.hashcat_rule["c"] = lambda x: x.capitalize() + # Lowercase the first found character, uppercase the rest + self.hashcat_rule["C"] = lambda x: x[0].lower() + x[1:].upper() + # Toggle the case of all characters in word + self.hashcat_rule["t"] = lambda x: x.swapcase() + # Toggle the case of characters at position N + self.hashcat_rule["T"] = lambda x, y: x[:y] + x[y].swapcase() + x[y + 1:] + # Upper case the first letter and every letter after a space + self.hashcat_rule["E"] = lambda x: " ".join([i[0].upper() + i[1:] for i in x.split(" ")]) + + ###################### + # Rotation rules # + ###################### + + # Reverse the entire word + self.hashcat_rule["r"] = lambda x: x[::-1] + # Rotate the word left + self.hashcat_rule["{"] = lambda x: x[1:] + x[0] + # Rotate the word right + self.hashcat_rule["}"] = lambda x: x[-1] + x[:-1] + + ###################### + # Duplication rules # + ###################### + + # Duplicate entire word + self.hashcat_rule["d"] = lambda x: x + x + # Duplicate entire word N times + self.hashcat_rule["p"] = lambda x, y: x * y + # Duplicate word reversed + self.hashcat_rule["f"] = lambda x: x + x[::-1] + # Duplicate first character N times + self.hashcat_rule["z"] = lambda x, y: x[0] * y + x + # Duplicate last character N times + self.hashcat_rule["Z"] = lambda x, y: x + x[-1] * y + # Duplicate every character + self.hashcat_rule["q"] = lambda x: "".join([i + i for i in x]) + # Duplicate first N characters + self.hashcat_rule["y"] = lambda x, y: x[:y] + x + # Duplicate last N characters + self.hashcat_rule["Y"] = lambda x, y: x + x[-y:] + + ###################### + # Cutting rules # + ###################### + + # Delete first character + self.hashcat_rule["["] = lambda x: x[1:] + # Delete last character + self.hashcat_rule["]"] = lambda x: x[:-1] + # Deletes character at position N + self.hashcat_rule["D"] = lambda x, y: x[:y] + x[y + 1:] + # Truncate word at position N + self.hashcat_rule["'"] = lambda x, y: x[:y] + # Delete M characters, starting at position N + self.hashcat_rule["x"] = lambda x, y, z: x[:y] + x[y + z:] + # Purge all instances of X + self.hashcat_rule["@"] = lambda x, y: x.replace(y, '') + + ###################### + # Insertion rules # + ###################### + + # Append character to end + self.hashcat_rule["$"] = lambda x, y: x + y + # Prepend character to front + self.hashcat_rule["^"] = lambda x, y: y + x + # Insert character X at position N + self.hashcat_rule["i"] = lambda x, y, z: x[:y] + z + x[y:] + + ###################### + # Replacement rules # + ###################### + + # Overwrite character at position N with X + self.hashcat_rule["o"] = lambda x, y, z: x[:y] + z + x[y + 1:] + # Replace all instances of X with Y + self.hashcat_rule["s"] = lambda x, y, z: x.replace(y, z) + # Bitwise shift left character @ N + self.hashcat_rule["L"] = lambda x, y: x[:y] + chr(ord(x[y]) << 1) + x[y + 1:] + # Bitwise shift right character @ N + self.hashcat_rule["R"] = lambda x, y: x[:y] + chr(ord(x[y]) >> 1) + x[y + 1:] + # Increment character @ N by 1 ascii value + self.hashcat_rule["+"] = lambda x, y: x[:y] + chr(ord(x[y]) + 1) + x[y + 1:] + # Decrement character @ N by 1 ascii value + self.hashcat_rule["-"] = lambda x, y: x[:y] + chr(ord(x[y]) - 1) + x[y + 1:] + # Replace character @ N with value at @ N plus 1 + self.hashcat_rule["."] = lambda x, y: x[:y] + x[y + 1] + x[y + 1:] + # Replace character @ N with value at @ N minus 1 + self.hashcat_rule[","] = lambda x, y: x[:y] + x[y - 1] + x[y + 1:] + + ###################### + # Swapping rules # + ###################### + + # Swap first two characters + self.hashcat_rule["k"] = lambda x: x[1] + x[0] + x[2:] + # Swap last two characters + self.hashcat_rule["K"] = lambda x: x[:-2] + x[-1] + x[-2] + # Swap character X with Y + self.hashcat_rule["*"] = lambda x, y, z: x[:y] + x[z] + x[y + 1:z] + x[y] + x[z + 1:] if z > y \ + else x[:z] + x[y] + x[z + 1:y] + x[z] + x[y + 1:] ######################################################################## # Common numeric and special character substitutions (1337 5p34k) @@ -160,14 +222,14 @@ def __init__(self,language="en",providers="aspell,myspell",basename='analysis',t ######################################################################## # Preanalysis rules to bruteforce for each word self.preanalysis_rules = [] - self.preanalysis_rules.append(([],self.hashcat_rule[':'])) # Blank rule - self.preanalysis_rules.append((['r'],self.hashcat_rule['r'])) # Reverse rule - #self.preanalysis_rules.append((['{'],self.hashcat_rule['}'])) # Rotate left - #self.preanalysis_rules.append((['}'],self.hashcat_rule['{'])) # Rotate right + self.preanalysis_rules.append(([], self.hashcat_rule[':'])) # Blank rule + self.preanalysis_rules.append((['r'], self.hashcat_rule['r'])) # Reverse rule + # self.preanalysis_rules.append((['{'],self.hashcat_rule['}'])) # Rotate left + # self.preanalysis_rules.append((['}'],self.hashcat_rule['{'])) # Rotate right ############################################################################ # Calculate Levenshtein edit path matrix - def levenshtein(self,word,password): + def levenshtein(self, word, password): matrix = [] # Generate and populate the initial matrix @@ -182,14 +244,14 @@ def levenshtein(self,word,password): matrix[i].append(0) # Calculate edit distance for each substring - for i in range(1,len(password) + 1): - for j in range(1,len(word) + 1): - if password[i-1] == word[j-1]: - matrix[i][j] = matrix[i-1][j-1] + for i in range(1, len(password) + 1): + for j in range(1, len(word) + 1): + if password[i - 1] == word[j - 1]: + matrix[i][j] = matrix[i - 1][j - 1] else: - insertion = matrix[i-1][j] + 1 - deletion = matrix[i][j-1] + 1 - substitution = matrix[i-1][j-1] + 1 + insertion = matrix[i - 1][j] + 1 + deletion = matrix[i][j - 1] + 1 + substitution = matrix[i - 1][j - 1] + 1 matrix[i][j] = min(insertion, deletion, substitution) return matrix @@ -203,7 +265,7 @@ def levenshtein_distance(self, s1, s2): return self.levenshtein_distance(s2, s1) if not s1: return len(s2) - + previous_row = range(len(s2) + 1) for i, c1 in enumerate(s1): current_row = [i + 1] @@ -213,15 +275,17 @@ def levenshtein_distance(self, s1, s2): substitutions = previous_row[j] + (c1 != c2) current_row.append(min(insertions, deletions, substitutions)) previous_row = current_row - + return previous_row[-1] - def levenshtein_print(self,matrix,word,password): + def levenshtein_print(self, matrix, word, password): """ Print word X password matrix """ print(" %s" % " ".join(list(word))) - for i,row in enumerate(matrix): - if i == 0: print(" ", end=' ') - else: print(password[i-1], end=' ') + for i, row in enumerate(matrix): + if i == 0: + print(" ", end=' ') + else: + print(password[i - 1], end=' ') print(" ".join("%2d" % col for col in row)) def generate_levenshtein_rules(self, word, password): @@ -231,12 +295,12 @@ def generate_levenshtein_rules(self, word, password): matrix = self.levenshtein(word, password) # 2) Trace reverse paths through the matrix. - paths = self.levenshtein_reverse_recursive(matrix,len(matrix)-1,len(matrix[0])-1,0) + paths = self.levenshtein_reverse_recursive(matrix, len(matrix) - 1, len(matrix[0]) - 1, 0) # 3) Return a collection of reverse paths. return [path for path in paths if len(path) <= matrix[-1][-1]] - def levenshtein_reverse_recursive(self,matrix,i,j,path_len): + def levenshtein_reverse_recursive(self, matrix, i, j, path_len): """ Calculate reverse Levenshtein paths. Recursive, Depth First, Short-circuited algorithm by Peter Kacherginsky Generates a list of edit operations necessary to transform a source word @@ -254,37 +318,45 @@ def levenshtein_reverse_recursive(self,matrix,i,j,path_len): # Calculate minimum cost of each operation cost_delete = cost_insert = cost_equal_or_replace = sys.maxsize - if i > 0: cost_insert = matrix[i-1][j] - if j > 0: cost_delete = matrix[i][j-1] - if i > 0 and j > 0: cost_equal_or_replace = matrix[i-1][j-1] + if i > 0: + cost_insert = matrix[i - 1][j] + if j > 0: + cost_delete = matrix[i][j - 1] + if i > 0 and j > 0: + cost_equal_or_replace = matrix[i - 1][j - 1] cost_min = min(cost_delete, cost_insert, cost_equal_or_replace) # Recurse through reverse path for each operation if cost_insert == cost_min: - insert_paths = self.levenshtein_reverse_recursive(matrix,i-1,j,path_len+1) - for insert_path in insert_paths: paths.append(insert_path + [('insert',i-1,j)]) + insert_paths = self.levenshtein_reverse_recursive(matrix, i - 1, j, path_len + 1) + for insert_path in insert_paths: + paths.append(insert_path + [('insert', i - 1, j)]) if cost_delete == cost_min: - delete_paths = self.levenshtein_reverse_recursive(matrix,i,j-1,path_len+1) - for delete_path in delete_paths: paths.append(delete_path + [('delete',i,j-1)]) + delete_paths = self.levenshtein_reverse_recursive(matrix, i, j - 1, path_len + 1) + for delete_path in delete_paths: + paths.append(delete_path + [('delete', i, j - 1)]) if cost_equal_or_replace == cost_min: if cost_equal_or_replace == cost: - equal_paths = self.levenshtein_reverse_recursive(matrix,i-1,j-1,path_len) - for equal_path in equal_paths: paths.append(equal_path) + equal_paths = self.levenshtein_reverse_recursive(matrix, i - 1, j - 1, path_len) + for equal_path in equal_paths: + paths.append(equal_path) else: - replace_paths = self.levenshtein_reverse_recursive(matrix,i-1,j-1,path_len+1) - for replace_path in replace_paths: paths.append(replace_path + [('replace',i-1,j-1)]) + replace_paths = self.levenshtein_reverse_recursive(matrix, i - 1, j - 1, path_len + 1) + for replace_path in replace_paths: + paths.append(replace_path + [('replace', i - 1, j - 1)]) return paths - def load_custom_wordlist(self,wordlist_file): + def load_custom_wordlist(self, wordlist_file): self.enchant = enchant.request_pwl_dict(wordlist_file) - def generate_words(self,password): + def generate_words(self, password): """ Generate source word candidates.""" - if self.debug: print("[*] Generating source words for %s" % password) + if self.debug: + print("[*] Generating source words for %s" % password) words = list() words_collection = list() @@ -304,40 +376,41 @@ def generate_words(self,password): pre_password = pre_rule_lambda(password) # Generate word suggestions - if self.word: suggestions = [self.word] - elif self.simple_words: suggestions = self.generate_simple_words(pre_password) - else: suggestions = self.generate_advanced_words(pre_password) + if self.word: + suggestions = [self.word] + elif self.simple_words: + suggestions = self.generate_simple_words(pre_password) + else: + suggestions = self.generate_advanced_words(pre_password) # HACK: Perform some additional expansion on multi-word suggestions # TODO: May be I should split these two and see if I can generate # rules for each of the suggestions for suggestion in suggestions[:self.max_words]: - suggestion = suggestion.replace(' ','') - suggestion = suggestion.replace('-','') - if not suggestion in suggestions: + suggestion = suggestion.replace(' ', '') + suggestion = suggestion.replace('-', '') + if suggestion not in suggestions: suggestions.append(suggestion) if len(suggestions) != len(set(suggestions)): print(sorted(suggestions)) print(sorted(set(suggestions))) - for suggestion in suggestions: - - distance = self.levenshtein_distance(suggestion,pre_password) + distance = self.levenshtein_distance(suggestion, pre_password) word = dict() word["suggestion"] = suggestion - word["distance"] = distance - word["password"] = pre_password - word["pre_rule"] = pre_rule + word["distance"] = distance + word["password"] = pre_password + word["pre_rule"] = pre_rule word["best_rule_length"] = 9999 words.append(word) ####################################################################### # Perform Optimization - for word in sorted(words, key=lambda word: word["distance"], reverse=False): + for word in sorted(words, key=lambda w: w["distance"], reverse=False): # Optimize for best distance if not self.more_words: @@ -345,35 +418,35 @@ def generate_words(self,password): best_found_distance = word["distance"] elif word["distance"] > best_found_distance: - if self.verbose: - print("[-] %s => {edit distance suboptimal: %d (%d)} => %s" % \ - (word["suggestion"], word["distance"], best_found_distance, word["password"])) - break + if self.verbose: + print("[-] %s => {edit distance suboptimal: %d (%d)} => %s" % + (word["suggestion"], word["distance"], best_found_distance, word["password"])) + break - # Filter words with too big edit distance + # Filter words with too big edit distance if word["distance"] <= self.max_word_dist: - if self.debug: - print("[+] %s => {edit distance: %d (%d)} = > %s" % \ - (word["suggestion"], word["distance"],best_found_distance, word["password"])) + if self.debug: + print("[+] %s => {edit distance: %d (%d)} = > %s" % + (word["suggestion"], word["distance"], best_found_distance, word["password"])) words_collection.append(word) else: - if self.verbose: - print("[-] %s => {max distance exceeded: %d (%d)} => %s" % \ - (word["suggestion"], word["distance"], self.max_word_dist, word["password"])) + if self.verbose: + print("[-] %s => {max distance exceeded: %d (%d)} => %s" % + (word["suggestion"], word["distance"], self.max_word_dist, word["password"])) - if self.max_words: + if self.max_words: words_collection = words_collection[:self.max_words] return words_collection - def generate_simple_words(self,password): + def generate_simple_words(self, password): """ Generate simple words. A simple spellcheck.""" return self.enchant.suggest(password) - def generate_advanced_words(self,password): + def generate_advanced_words(self, password): """ Generate advanced words. Perform some additional non-destructive cleaning to help spell-checkers: 1) Remove non-alpha prefixes and appendixes. @@ -394,23 +467,30 @@ def generate_advanced_words(self,password): # Replace common special character replacements (1337 5p34k) preanalysis_password = '' for c in password: - if c in self.leet: preanalysis_password += self.leet[c] - else: preanalysis_password += c + if c in self.leet: + preanalysis_password += self.leet[c] + else: + preanalysis_password += c password = preanalysis_password - if self.debug: "[*] Preanalysis Password: %s" % password + if self.debug: + "[*] Preanalysis Password: %s" % password return self.enchant.suggest(password) ############################################################################ # Hashcat specific offset definition 0-9,A-Z - def int_to_hashcat(self,N): - if N < 10: return N - else: return chr(65+N-10) + def int_to_hashcat(self, n): + if n < 10: + return n + else: + return chr(65 + n - 10) - def hashcat_to_int(self,N): - if N.isdigit(): return int(N) - else: return ord(N)-65+10 + def hashcat_to_int(self, n): + if n.isdigit(): + return int(n) + else: + return ord(n) - 65 + 10 def generate_hashcat_rules(self, suggestion, password): """ Generate hashcat rules. Returns a length sorted list of lists of hashcat rules.""" @@ -426,13 +506,13 @@ def generate_hashcat_rules(self, suggestion, password): # Generate hashcat rule for each levenshtein rule for lev_rule in lev_rules: - if self.simple_rules: + if self.simple_rules: hashcat_rule = self.generate_simple_hashcat_rules(suggestion, lev_rule, password) - else: + else: hashcat_rule = self.generate_advanced_hashcat_rules(suggestion, lev_rule, password) - if hashcat_rule == None: - print("[!] Processing FAILED: %s => ;( => %s" % (suggestion,password)) + if hashcat_rule is None: + print("[!] Processing FAILED: %s => ;( => %s" % (suggestion, password)) print(" Sorry about that, please report this failure to") print(" the developer: iphelix [at] thesprawl.org") @@ -443,7 +523,7 @@ def generate_hashcat_rules(self, suggestion, password): ####################################################################### # Perform Optimization - for hashcat_rule in sorted(hashcat_rules, key=lambda hashcat_rule: len(hashcat_rule)): + for hashcat_rule in sorted(hashcat_rules, key=lambda rule: len(rule)): rule_length = len(hashcat_rule) @@ -452,9 +532,9 @@ def generate_hashcat_rules(self, suggestion, password): best_found_rule_length = rule_length elif rule_length > best_found_rule_length: - if self.verbose: - print("[-] %s => {best rule length exceeded: %d (%d)} => %s" % \ - (suggestion, rule_length, best_found_rule_length, password)) + if self.verbose: + print("[-] %s => {best rule length exceeded: %d (%d)} => %s" % + (suggestion, rule_length, best_found_rule_length, password)) break if rule_length <= self.max_rule_len: @@ -462,46 +542,52 @@ def generate_hashcat_rules(self, suggestion, password): return hashcat_rules_collection - def generate_simple_hashcat_rules(self,word,rules,password): + def generate_simple_hashcat_rules(self, word, rules, password): """ Generate basic hashcat rules using only basic insert,delete,replace rules. """ hashcat_rules = [] - if self.debug: print("[*] Simple Processing %s => %s" % (word,password)) + if self.debug: + print("[*] Simple Processing %s => %s" % (word, password)) # Dynamically apply rules to the source word # NOTE: Special case were word == password this would work as well. word_rules = word - for (op,p,w) in rules: + for (op, p, w) in rules: - if self.debug: print("\t[*] Simple Processing Started: %s - %s" % (word_rules, " ".join(hashcat_rules))) + if self.debug: + print("\t[*] Simple Processing Started: %s - %s" % (word_rules, " ".join(hashcat_rules))) if op == 'insert': - hashcat_rules.append("i%s%s" % (self.int_to_hashcat(p),password[p])) - word_rules = self.hashcat_rule['i'](word_rules,p,password[p]) + hashcat_rules.append("i%s%s" % (self.int_to_hashcat(p), password[p])) + word_rules = self.hashcat_rule['i'](word_rules, p, password[p]) elif op == 'delete': hashcat_rules.append("D%s" % self.int_to_hashcat(p)) - word_rules = self.hashcat_rule['D'](word_rules,p) + word_rules = self.hashcat_rule['D'](word_rules, p) elif op == 'replace': - hashcat_rules.append("o%s%s" % (self.int_to_hashcat(p),password[p])) - word_rules = self.hashcat_rule['o'](word_rules,p,password[p]) + hashcat_rules.append("o%s%s" % (self.int_to_hashcat(p), password[p])) + word_rules = self.hashcat_rule['o'](word_rules, p, password[p]) - if self.debug: print("\t[*] Simple Processing Ended: %s => %s => %s" % (word_rules, " ".join(hashcat_rules),password)) + if self.debug: + print("\t[*] Simple Processing Ended: %s => %s => %s" % (word_rules, " ".join(hashcat_rules), password)) # Check if rules result in the correct password if word_rules == password: return hashcat_rules else: - if self.debug: print("[!] Simple Processing FAILED: %s => %s => %s (%s)" % (word," ".join(hashcat_rules),password,word_rules)) + if self.debug: + print("[!] Simple Processing FAILED: %s => %s => %s (%s)" % + (word, " ".join(hashcat_rules), password, word_rules)) return None - def generate_advanced_hashcat_rules(self,word,rules,password): + def generate_advanced_hashcat_rules(self, word, rules, password): """ Generate advanced hashcat rules using full range of available rules. """ hashcat_rules = [] - if self.debug: print("[*] Advanced Processing %s => %s" % (word,password)) + if self.debug: + print("[*] Advanced Processing %s => %s" % (word, password)) # Dynamically apply and store rules in word_rules variable. # NOTE: Special case where word == password this would work as well. @@ -511,17 +597,18 @@ def generate_advanced_hashcat_rules(self,word,rules,password): password_lower = len([c for c in password if c.islower()]) password_upper = len([c for c in password if c.isupper()]) - for i,(op,p,w) in enumerate(rules): + for i, (op, p, w) in enumerate(rules): - if self.debug: print("\t[*] Advanced Processing Started: %s - %s" % (word_rules, " ".join(hashcat_rules))) + if self.debug: + print("\t[*] Advanced Processing Started: %s - %s" % (word_rules, " ".join(hashcat_rules))) if op == 'insert': - hashcat_rules.append("i%s%s" % (self.int_to_hashcat(p),password[p])) - word_rules = self.hashcat_rule['i'](word_rules,p,password[p]) + hashcat_rules.append("i%s%s" % (self.int_to_hashcat(p), password[p])) + word_rules = self.hashcat_rule['i'](word_rules, p, password[p]) elif op == 'delete': hashcat_rules.append("D%s" % self.int_to_hashcat(p)) - word_rules = self.hashcat_rule['D'](word_rules,p) + word_rules = self.hashcat_rule['D'](word_rules, p) elif op == 'replace': @@ -537,118 +624,133 @@ def generate_advanced_hashcat_rules(self,word,rules,password): # This rule was made obsolete by a prior global replacement if word_rules[p] == password[p]: - if self.debug: print("\t[*] Advanced Processing Obsolete Rule: %s - %s" % (word_rules, " ".join(hashcat_rules))) + if self.debug: + print("\t[*] Advanced Processing Obsolete Rule: %s - %s" % + (word_rules, " ".join(hashcat_rules))) # Swapping rules - elif p < len(password)-1 and p < len(word_rules)-1 and word_rules[p] == password[p+1] and word_rules[p+1] == password[p]: + elif p < len(password) - 1 and p < len(word_rules) - 1 \ + and word_rules[p] == password[p + 1] and word_rules[p + 1] == password[p]: # Swap first two characters - if p == 0 and self.generate_simple_hashcat_rules( self.hashcat_rule['k'](word_rules), rules[i+1:],password): + if p == 0 and self.generate_simple_hashcat_rules(self.hashcat_rule['k'](word_rules), rules[i + 1:], + password): hashcat_rules.append("k") word_rules = self.hashcat_rule['k'](word_rules) # Swap last two characters - elif p == len(word_rules)-2 and self.generate_simple_hashcat_rules( self.hashcat_rule['K'](word_rules), rules[i+1:],password): + elif p == len(word_rules) - 2 and self.generate_simple_hashcat_rules( + self.hashcat_rule['K'](word_rules), rules[i + 1:], password): hashcat_rules.append("K") word_rules = self.hashcat_rule['K'](word_rules) # Swap any two characters (only adjacent swapping is supported) - elif self.generate_simple_hashcat_rules( self.hashcat_rule['*'](word_rules,p,p+1), rules[i+1:],password): - hashcat_rules.append("*%s%s" % (self.int_to_hashcat(p),self.int_to_hashcat(p+1))) - word_rules = self.hashcat_rule['*'](word_rules,p,p+1) + elif self.generate_simple_hashcat_rules(self.hashcat_rule['*'](word_rules, p, p + 1), rules[i + 1:], + password): + hashcat_rules.append("*%s%s" % (self.int_to_hashcat(p), self.int_to_hashcat(p + 1))) + word_rules = self.hashcat_rule['*'](word_rules, p, p + 1) else: - hashcat_rules.append("o%s%s" % (self.int_to_hashcat(p),password[p])) - word_rules = self.hashcat_rule['o'](word_rules,p,password[p]) - + hashcat_rules.append("o%s%s" % (self.int_to_hashcat(p), password[p])) + word_rules = self.hashcat_rule['o'](word_rules, p, password[p]) + # Case Toggle: Uppercased a letter elif word_rules[p].islower() and word_rules[p].upper() == password[p]: # Toggle the case of all characters in word (mixed cases) - if password_upper and password_lower and self.generate_simple_hashcat_rules( self.hashcat_rule['t'](word_rules), rules[i+1:],password): + if password_upper and password_lower and self.generate_simple_hashcat_rules( + self.hashcat_rule['t'](word_rules), rules[i + 1:], password): hashcat_rules.append("t") word_rules = self.hashcat_rule['t'](word_rules) # Capitalize all letters - elif self.generate_simple_hashcat_rules( self.hashcat_rule['u'](word_rules), rules[i+1:],password): + elif self.generate_simple_hashcat_rules(self.hashcat_rule['u'](word_rules), rules[i + 1:], + password): hashcat_rules.append("u") word_rules = self.hashcat_rule['u'](word_rules) # Capitalize the first letter - elif p == 0 and self.generate_simple_hashcat_rules( self.hashcat_rule['c'](word_rules), rules[i+1:],password): + elif p == 0 and self.generate_simple_hashcat_rules(self.hashcat_rule['c'](word_rules), + rules[i + 1:], password): hashcat_rules.append("c") word_rules = self.hashcat_rule['c'](word_rules) # Toggle the case of characters at position N else: hashcat_rules.append("T%s" % self.int_to_hashcat(p)) - word_rules = self.hashcat_rule['T'](word_rules,p) + word_rules = self.hashcat_rule['T'](word_rules, p) # Case Toggle: Lowercased a letter elif word_rules[p].isupper() and word_rules[p].lower() == password[p]: # Toggle the case of all characters in word (mixed cases) - if password_upper and password_lower and self.generate_simple_hashcat_rules( self.hashcat_rule['t'](word_rules), rules[i+1:],password): + if password_upper and password_lower and self.generate_simple_hashcat_rules( + self.hashcat_rule['t'](word_rules), rules[i + 1:], password): hashcat_rules.append("t") word_rules = self.hashcat_rule['t'](word_rules) # Lowercase all letters - elif self.generate_simple_hashcat_rules( self.hashcat_rule['l'](word_rules), rules[i+1:],password): + elif self.generate_simple_hashcat_rules(self.hashcat_rule['l'](word_rules), rules[i + 1:], + password): hashcat_rules.append("l") word_rules = self.hashcat_rule['l'](word_rules) # Lowercase the first found character, uppercase the rest - elif p == 0 and self.generate_simple_hashcat_rules( self.hashcat_rule['C'](word_rules), rules[i+1:],password): + elif p == 0 and self.generate_simple_hashcat_rules(self.hashcat_rule['C'](word_rules), + rules[i + 1:], password): hashcat_rules.append("C") word_rules = self.hashcat_rule['C'](word_rules) # Toggle the case of characters at position N else: hashcat_rules.append("T%s" % self.int_to_hashcat(p)) - word_rules = self.hashcat_rule['T'](word_rules,p) + word_rules = self.hashcat_rule['T'](word_rules, p) # Special case substitution of 'all' instances (1337 $p34k) - elif word_rules[p].isalpha() and not password[p].isalpha() and self.generate_simple_hashcat_rules( self.hashcat_rule['s'](word_rules,word_rules[p],password[p]), rules[i+1:],password): + elif word_rules[p].isalpha() and not password[p].isalpha() and self.generate_simple_hashcat_rules( + self.hashcat_rule['s'](word_rules, word_rules[p], password[p]), rules[i + 1:], password): # If we have already detected this rule, then skip it thus # reducing total rule count. # BUG: Elisabeth => sE3 sl1 u o3Z sE3 => 31IZAB3TH - #if not "s%s%s" % (word_rules[p],password[p]) in hashcat_rules: - hashcat_rules.append("s%s%s" % (word_rules[p],password[p])) - word_rules = self.hashcat_rule['s'](word_rules,word_rules[p],password[p]) - + # if not "s%s%s" % (word_rules[p],password[p]) in hashcat_rules: + hashcat_rules.append("s%s%s" % (word_rules[p], password[p])) + word_rules = self.hashcat_rule['s'](word_rules, word_rules[p], password[p]) + # Replace next character with current - elif p < len(password)-1 and p < len(word_rules)-1 and password[p] == password[p+1] and password[p] == word_rules[p+1]: + elif p < len(password) - 1 and p < len(word_rules) - 1 and password[p] == password[p + 1] \ + and password[p] == word_rules[p + 1]: hashcat_rules.append(".%s" % self.int_to_hashcat(p)) - word_rules = self.hashcat_rule['.'](word_rules,p) + word_rules = self.hashcat_rule['.'](word_rules, p) # Replace previous character with current - elif p > 0 and w > 0 and password[p] == password[p-1] and password[p] == word_rules[p-1]: + elif p > 0 and w > 0 and password[p] == password[p - 1] and password[p] == word_rules[p - 1]: hashcat_rules.append(",%s" % self.int_to_hashcat(p)) - word_rules = self.hashcat_rule[','](word_rules,p) + word_rules = self.hashcat_rule[','](word_rules, p) # ASCII increment elif ord(word_rules[p]) + 1 == ord(password[p]): hashcat_rules.append("+%s" % self.int_to_hashcat(p)) - word_rules = self.hashcat_rule['+'](word_rules,p) + word_rules = self.hashcat_rule['+'](word_rules, p) # ASCII decrement elif ord(word_rules[p]) - 1 == ord(password[p]): hashcat_rules.append("-%s" % self.int_to_hashcat(p)) - word_rules = self.hashcat_rule['-'](word_rules,p) + word_rules = self.hashcat_rule['-'](word_rules, p) # SHIFT left elif ord(word_rules[p]) << 1 == ord(password[p]): hashcat_rules.append("L%s" % self.int_to_hashcat(p)) - word_rules = self.hashcat_rule['L'](word_rules,p) + word_rules = self.hashcat_rule['L'](word_rules, p) # SHIFT right elif ord(word_rules[p]) >> 1 == ord(password[p]): hashcat_rules.append("R%s" % self.int_to_hashcat(p)) - word_rules = self.hashcat_rule['R'](word_rules,p) + word_rules = self.hashcat_rule['R'](word_rules, p) - # Position based replacements. + # Position based replacements. else: - hashcat_rules.append("o%s%s" % (self.int_to_hashcat(p),password[p])) - word_rules = self.hashcat_rule['o'](word_rules,p,password[p]) + hashcat_rules.append("o%s%s" % (self.int_to_hashcat(p), password[p])) + word_rules = self.hashcat_rule['o'](word_rules, p, password[p]) - if self.debug: print("\t[*] Advanced Processing Ended: %s %s" % (word_rules, " ".join(hashcat_rules))) + if self.debug: + print("\t[*] Advanced Processing Ended: %s %s" % (word_rules, " ".join(hashcat_rules))) ######################################################################## # Prefix rules @@ -659,12 +761,12 @@ def generate_advanced_hashcat_rules(self,word,rules,password): prefix_rules.append("^%s" % hashcat_rule[2]) last_prefix += 1 elif len(prefix_rules): - hashcat_rules = prefix_rules[::-1]+hashcat_rules[len(prefix_rules):] + hashcat_rules = prefix_rules[::-1] + hashcat_rules[len(prefix_rules):] break else: break - else: - hashcat_rules = prefix_rules[::-1]+hashcat_rules[len(prefix_rules):] + else: + hashcat_rules = prefix_rules[::-1] + hashcat_rules[len(prefix_rules):] #################################################################### # Appendix rules @@ -673,14 +775,14 @@ def generate_advanced_hashcat_rules(self,word,rules,password): for hashcat_rule in hashcat_rules[::-1]: if hashcat_rule[0] == "i" and self.hashcat_to_int(hashcat_rule[1]) == last_appendix: appendix_rules.append("$%s" % hashcat_rule[2]) - last_appendix-= 1 + last_appendix -= 1 elif len(appendix_rules): - hashcat_rules = hashcat_rules[:-len(appendix_rules)]+appendix_rules[::-1] + hashcat_rules = hashcat_rules[:-len(appendix_rules)] + appendix_rules[::-1] break else: break else: - hashcat_rules = hashcat_rules[:-len(appendix_rules)]+appendix_rules[::-1] + hashcat_rules = hashcat_rules[:-len(appendix_rules)] + appendix_rules[::-1] #################################################################### # Truncate left rules @@ -690,67 +792,72 @@ def generate_advanced_hashcat_rules(self,word,rules,password): if hashcat_rule[0] == "D" and self.hashcat_to_int(hashcat_rule[1]) == last_precut: precut_rules.append("[") elif len(precut_rules): - hashcat_rules = precut_rules[::-1]+hashcat_rules[len(precut_rules):] + hashcat_rules = precut_rules[::-1] + hashcat_rules[len(precut_rules):] break else: break - else: - hashcat_rules = precut_rules[::-1]+hashcat_rules[len(precut_rules):] + else: + hashcat_rules = precut_rules[::-1] + hashcat_rules[len(precut_rules):] #################################################################### # Truncate right rules last_postcut = len(password) postcut_rules = list() for hashcat_rule in hashcat_rules[::-1]: - + if hashcat_rule[0] == "D" and self.hashcat_to_int(hashcat_rule[1]) >= last_postcut: postcut_rules.append("]") elif len(postcut_rules): - hashcat_rules = hashcat_rules[:-len(postcut_rules)]+postcut_rules[::-1] + hashcat_rules = hashcat_rules[:-len(postcut_rules)] + postcut_rules[::-1] break else: break else: - hashcat_rules = hashcat_rules[:-len(postcut_rules)]+postcut_rules[::-1] + hashcat_rules = hashcat_rules[:-len(postcut_rules)] + postcut_rules[::-1] # Check if rules result in the correct password if word_rules == password: return hashcat_rules else: - if self.debug: print("[!] Advanced Processing FAILED: %s => %s => %s (%s)" % (word," ".join(hashcat_rules),password,word_rules)) + if self.debug: + print("[!] Advanced Processing FAILED: %s => %s => %s (%s)" % + (word, " ".join(hashcat_rules), password, word_rules)) return None - def check_reversible_password(self, password): """ Check whether the password is likely to be reversed successfuly. """ # Skip all numeric passwords - if password.isdigit(): - if self.verbose and not self.quiet: print("[!] %s => {skipping numeric} => %s" % (password,password)) + if password.isdigit(): + if self.verbose and not self.quiet: + print("[!] %s => {skipping numeric} => %s" % (password, password)) self.numeric_stats_total += 1 return False # Skip passwords with less than 25% of alpha character # TODO: Make random word detection more reliable based on word entropy. - elif len([c for c in password if c.isalpha()]) < len(password)//4: - if self.verbose and not self.quiet:print("[!] %s => {skipping alpha less than 25%%} => %s" % (password,password)) + elif len([c for c in password if c.isalpha()]) < len(password) // 4: + if self.verbose and not self.quiet: + print("[!] %s => {skipping alpha less than 25%%} => %s" % (password, password)) self.special_stats_total += 1 return False # Only check english ascii passwords for now # TODO: Add support for more languages. elif [c for c in password if ord(c) < 32 or ord(c) > 126]: - if self.verbose and not self.quiet: print("[!] %s => {skipping non ascii english} => %s" % (password,password)) + if self.verbose and not self.quiet: + print("[!] %s => {skipping non ascii english} => %s" % (password, password)) self.foreign_stats_total += 1 return False else: return True - def analyze_password(self,password, rules_queue=multiprocessing.Queue(), words_queue=multiprocessing.Queue()): + def analyze_password(self, password, rules_queue=multiprocessing.Queue(), words_queue=multiprocessing.Queue()): """ Analyze a single password. """ - if self.verbose: print("[*] Analyzing password: %s" % password) + if self.verbose: + print("[*] Analyzing password: %s" % password) words = [] @@ -760,7 +867,7 @@ def analyze_password(self,password, rules_queue=multiprocessing.Queue(), words_q word = dict() word["password"] = password word["suggestion"] = password - word["hashcat_rules"] = [[],] + word["hashcat_rules"] = [[], ] word["pre_rule"] = [] word["best_rule_length"] = 9999 @@ -774,9 +881,8 @@ def analyze_password(self,password, rules_queue=multiprocessing.Queue(), words_q # Generate levenshtein reverse paths for each suggestion for word in words: - # Generate a collection of hashcat_rules lists - word["hashcat_rules"] = self.generate_hashcat_rules(word["suggestion"],word["password"]) + word["hashcat_rules"] = self.generate_hashcat_rules(word["suggestion"], word["password"]) self.print_hashcat_rules(words, password, rules_queue, words_queue) @@ -785,7 +891,7 @@ def print_hashcat_rules(self, words, password, rules_queue, words_queue): best_found_rule_length = 9999 # Sorted list based on rule length - for word in sorted(words, key=lambda word: len(word["hashcat_rules"][0])): + for word in sorted(words, key=lambda w: len(w["hashcat_rules"][0])): words_queue.put(word["suggestion"]) @@ -798,80 +904,91 @@ def print_hashcat_rules(self, words, password, rules_queue, words_queue): best_found_rule_length = rule_length elif rule_length > best_found_rule_length: - if self.verbose: - print("[-] %s => {best rule length exceeded: %d (%d)} => %s" % \ - (word["suggestion"], rule_length, best_found_rule_length, password)) + if self.verbose: + print("[-] %s => {best rule length exceeded: %d (%d)} => %s" % + (word["suggestion"], rule_length, best_found_rule_length, password)) break if rule_length <= self.max_rule_len: - hashcat_rule_str = " ".join(hashcat_rule + word["pre_rule"] or [':']) - if self.verbose: print("[+] %s => %s => %s" % (word["suggestion"], hashcat_rule_str, password)) + if self.verbose: + print("[+] %s => %s => %s" % (word["suggestion"], hashcat_rule_str, password)) rules_queue.put(hashcat_rule_str) - - def password_worker(self,i, passwords_queue, rules_queue, words_queue): - if self.debug: print("[*] Password analysis worker [%d] started." % i) + def password_worker(self, i, passwords_queue, rules_queue, words_queue): + if self.debug: + print("[*] Password analysis worker [%d] started." % i) try: while True: password = passwords_queue.get() # Interrupted by a Death Pill - if password == None: break + if password is None: + break self.analyze_password(password, rules_queue, words_queue) except (KeyboardInterrupt, SystemExit): - if self.debug: print("[*] Password analysis worker [%d] terminated." % i) + if self.debug: + print("[*] Password analysis worker [%d] terminated." % i) - if self.debug: print("[*] Password analysis worker [%d] stopped." % i) + if self.debug: + print("[*] Password analysis worker [%d] stopped." % i) def rule_worker(self, rules_queue, output_rules_filename): """ Worker to store generated rules. """ print("[*] Saving rules to %s" % output_rules_filename) f = open(output_rules_filename, 'w') - if self.debug: print("[*] Rule worker started.") + if self.debug: + print("[*] Rule worker started.") try: while True: rule = rules_queue.get() # Interrupted by a Death Pill - if rule == None: break + if rule is None: + break f.write("%s\n" % rule) f.flush() except (KeyboardInterrupt, SystemExit): - if self.debug: print("[*] Rule worker terminated.") + if self.debug: + print("[*] Rule worker terminated.") f.close() - if self.debug: print("[*] Rule worker stopped.") + if self.debug: + print("[*] Rule worker stopped.") def word_worker(self, words_queue, output_words_filename): """ Worker to store generated rules. """ print("[*] Saving words to %s" % output_words_filename) f = open(output_words_filename, 'w') - if self.debug: print("[*] Word worker started.") + if self.debug: + print("[*] Word worker started.") try: while True: word = words_queue.get() # Interrupted by a Death Pill - if word == None: break + if word is None: + break f.write("%s\n" % word) f.flush() except (KeyboardInterrupt, SystemExit): - if self.debug: print("[*] Word worker terminated.") + if self.debug: + print("[*] Word worker terminated.") f.close() - if self.debug: print("[*] Word worker stopped.") + if self.debug: + print("[*] Word worker stopped.") # Analyze passwords file - def analyze_passwords_file(self,passwords_file): + def analyze_passwords_file(self, passwords_file): """ Analyze provided passwords file. """ print("[*] Analyzing passwords file: %s:" % passwords_file) @@ -884,7 +1001,8 @@ def analyze_passwords_file(self,passwords_file): # Start workers for i in range(self.threads): - multiprocessing.Process(target=self.password_worker, args=(i, passwords_queue, rules_queue, words_queue)).start() + multiprocessing.Process(target=self.password_worker, + args=(i, passwords_queue, rules_queue, words_queue)).start() multiprocessing.Process(target=self.rule_worker, args=(rules_queue, "%s.rule" % self.basename)).start() multiprocessing.Process(target=self.word_worker, args=(words_queue, "%s.word" % self.basename)).start() @@ -895,7 +1013,7 @@ def analyze_passwords_file(self,passwords_file): password_count = 0 analysis_start = time.time() segment_start = analysis_start - try: + try: for password in f: password = password.rstrip('\r\n') if len(password) > 0: @@ -903,8 +1021,8 @@ def analyze_passwords_file(self,passwords_file): # Provide analysis time feedback to the user if not self.quiet and password_count != 0 and password_count % 5000 == 0: segment_time = time.time() - segment_start - print("[*] Processed %d passwords in %.2f seconds at the rate of %.2f p/sec" % \ - (password_count, segment_start - analysis_start, 5000/segment_time )) + print("[*] Processed %d passwords in %.2f seconds at the rate of %.2f p/sec" % + (password_count, segment_start - analysis_start, 5000 / segment_time)) segment_start = time.time() password_count += 1 @@ -919,9 +1037,9 @@ def analyze_passwords_file(self,passwords_file): else: # Signal workers to stop. for i in range(multiprocessing.cpu_count()): - passwords_queue.put(None) + passwords_queue.put(None) - # Wait for all of the queued passwords to finish. + # Wait for all of the queued passwords to finish. while not passwords_queue.empty(): time.sleep(1) @@ -932,19 +1050,20 @@ def analyze_passwords_file(self,passwords_file): f.close() analysis_time = time.time() - analysis_start - print("[*] Finished processing %d passwords in %.2f seconds at the rate of %.2f p/sec" % (password_count, analysis_time, float(password_count)/analysis_time )) + print("[*] Finished processing %d passwords in %.2f seconds at the rate of %.2f p/sec" % + (password_count, analysis_time, float(password_count) / analysis_time)) print("[*] Generating statistics for [%s] rules and words." % self.basename) - print("[-] Skipped %d all numeric passwords (%0.2f%%)" % \ - (self.numeric_stats_total, float(self.numeric_stats_total)*100.0/float(password_count))) - print("[-] Skipped %d passwords with less than 25%% alpha characters (%0.2f%%)" % \ - (self.special_stats_total, float(self.special_stats_total)*100.0/float(password_count))) - print("[-] Skipped %d passwords with non ascii characters (%0.2f%%)" % \ - (self.foreign_stats_total, float(self.foreign_stats_total)*100.0/float(password_count))) + print("[-] Skipped %d all numeric passwords (%0.2f%%)" % + (self.numeric_stats_total, float(self.numeric_stats_total) * 100.0 / float(password_count))) + print("[-] Skipped %d passwords with less than 25%% alpha characters (%0.2f%%)" % + (self.special_stats_total, float(self.special_stats_total) * 100.0 / float(password_count))) + print("[-] Skipped %d passwords with non ascii characters (%0.2f%%)" % + (self.foreign_stats_total, float(self.foreign_stats_total) * 100.0 / float(password_count))) # TODO: Counter breaks on large files. uniq -c | sort -rn is still the most # optimal way. - rules_file = open("%s.rule" % self.basename,'r') + rules_file = open("%s.rule" % self.basename, 'r') rules_sorted_file = open("%s-sorted.rule" % self.basename, 'w') rules_counter = Counter(rules_file) rule_counter_total = sum(rules_counter.values()) @@ -953,15 +1072,15 @@ def analyze_passwords_file(self,passwords_file): rules_i = 0 for (rule, count) in rules_counter.most_common(): rules_sorted_file.write(rule) - if rules_i < 10: print("[+] %s - %d (%0.2f%%)" % (rule.rstrip('\r\n'), count, count*100/rule_counter_total)) + if rules_i < 10: + print("[+] %s - %d (%0.2f%%)" % (rule.rstrip('\r\n'), count, count * 100 / rule_counter_total)) rules_i += 1 rules_file.close() rules_sorted_file.close() - - words_file = open("%s.word" % self.basename,'r') - words_sorted_file = open("%s-sorted.word" % self.basename,'w') + words_file = open("%s.word" % self.basename, 'r') + words_sorted_file = open("%s-sorted.word" % self.basename, 'w') words_counter = Counter(words_file) word_counter_total = sum(rules_counter.values()) @@ -969,38 +1088,43 @@ def analyze_passwords_file(self,passwords_file): words_i = 0 for (word, count) in words_counter.most_common(): words_sorted_file.write(word) - if words_i < 10: print("[+] %s - %d (%0.2f%%)" % (word.rstrip('\r\n'), count, count*100/word_counter_total)) + if words_i < 10: + print("[+] %s - %d (%0.2f%%)" % (word.rstrip('\r\n'), count, count * 100 / word_counter_total)) words_i += 1 words_file.close() words_sorted_file.close() ############################################################################ - def verify_hashcat_rules(self,word, rules, password): + def verify_hashcat_rules(self, word, rules, password): - f = open("%s/test.rule" % HASHCAT_PATH,'w') + f = open("%s/test.rule" % HASHCAT_PATH, 'w') f.write(" ".join(rules)) f.close() - f = open("%s/test.word" % HASHCAT_PATH,'w') + f = open("%s/test.word" % HASHCAT_PATH, 'w') f.write(word) f.close() - p = subprocess.Popen(["%s/hashcat-cli64.bin" % HASHCAT_PATH,"-r","%s/test.rule" % HASHCAT_PATH,"--stdout","%s/test.word" % HASHCAT_PATH], stdout=subprocess.PIPE) + p = subprocess.Popen(["%s/hashcat-cli64.bin" % HASHCAT_PATH, "-r", "%s/test.rule" % HASHCAT_PATH, "--stdout", + "%s/test.word" % HASHCAT_PATH], stdout=subprocess.PIPE) out, err = p.communicate() out = out.strip() if out == password: hashcat_rules_str = " ".join(rules or [':']) - if self.verbose: print("[+] %s => %s => %s" % (word, hashcat_rules_str, password)) + if self.verbose: + print("[+] %s => %s => %s" % (word, hashcat_rules_str, password)) else: - print("[!] Hashcat Verification FAILED: %s => %s => %s (%s)" % (word," ".join(rules or [':']),password,out)) + print("[!] Hashcat Verification FAILED: %s => %s => %s (%s)" % + (word, " ".join(rules or [':']), password, out)) + if __name__ == "__main__": - header = " _ \n" - header += " RuleGen %s | |\n" % VERSION + header = " _ \n" + header += " RuleGen %s | |\n" % VERSION header += " _ __ __ _ ___| | _\n" header += " | '_ \ / _` |/ __| |/ /\n" header += " | |_) | (_| | (__| < \n" @@ -1009,37 +1133,49 @@ def verify_hashcat_rules(self,word, rules, password): header += " |_| iphelix@thesprawl.org\n" header += "\n" + parser = OptionParser("%prog [options] passwords.txt", version="%prog " + VERSION) - parser = OptionParser("%prog [options] passwords.txt", version="%prog "+VERSION) - - parser.add_option("-b","--basename", help="Output base name. The following files will be generated: basename.words, basename.rules and basename.stats", default="analysis",metavar="rockyou") - parser.add_option("-w","--wordlist", help="Use a custom wordlist for rule analysis.", metavar="wiki.dict") + parser.add_option("-b", "--basename", + help="Output base name. The following files will be generated: " + + "basename.words, basename.rules and basename.stats", + default="analysis", metavar="rockyou") + parser.add_option("-w", "--wordlist", help="Use a custom wordlist for rule analysis.", metavar="wiki.dict") parser.add_option("-q", "--quiet", action="store_true", dest="quiet", default=False, help="Don't show headers.") - parser.add_option("--threads", type="int", default=multiprocessing.cpu_count(), help="Parallel threads to use for processing.") + parser.add_option("--threads", type="int", default=multiprocessing.cpu_count(), + help="Parallel threads to use for processing.") wordtune = OptionGroup(parser, "Fine tune source word generation:") - wordtune.add_option("--maxworddist", help="Maximum word edit distance (Levenshtein)", type="int", default=10, metavar="10") - wordtune.add_option("--maxwords", help="Maximum number of source word candidates to consider", type="int", default=5, metavar="5") - wordtune.add_option("--morewords", help="Consider suboptimal source word candidates", action="store_true", default=False) - wordtune.add_option("--simplewords", help="Generate simple source words for given passwords", action="store_true", default=False) + wordtune.add_option("--maxworddist", help="Maximum word edit distance (Levenshtein)", type="int", default=10, + metavar="10") + wordtune.add_option("--maxwords", help="Maximum number of source word candidates to consider", type="int", + default=5, metavar="5") + wordtune.add_option("--morewords", help="Consider suboptimal source word candidates", action="store_true", + default=False) + wordtune.add_option("--simplewords", help="Generate simple source words for given passwords", action="store_true", + default=False) parser.add_option_group(wordtune) ruletune = OptionGroup(parser, "Fine tune rule generation:") - ruletune.add_option("--maxrulelen", help="Maximum number of operations in a single rule", type="int", default=10, metavar="10") + ruletune.add_option("--maxrulelen", help="Maximum number of operations in a single rule", type="int", default=10, + metavar="10") ruletune.add_option("--maxrules", help="Maximum number of rules to consider", type="int", default=5, metavar="5") ruletune.add_option("--morerules", help="Generate suboptimal rules", action="store_true", default=False) - ruletune.add_option("--simplerules", help="Generate simple rules insert,delete,replace",action="store_true", default=False) - ruletune.add_option("--bruterules", help="Bruteforce reversal and rotation rules (slow)",action="store_true", default=False) + ruletune.add_option("--simplerules", help="Generate simple rules insert,delete,replace", action="store_true", + default=False) + ruletune.add_option("--bruterules", help="Bruteforce reversal and rotation rules (slow)", action="store_true", + default=False) parser.add_option_group(ruletune) spelltune = OptionGroup(parser, "Fine tune spell checker engine:") - spelltune.add_option("--providers", help="Comma-separated list of provider engines", default="aspell,myspell", metavar="aspell,myspell") + spelltune.add_option("--providers", help="Comma-separated list of provider engines", default="aspell,myspell", + metavar="aspell,myspell") parser.add_option_group(spelltune) debug = OptionGroup(parser, "Debuggin options:") - debug.add_option("-v","--verbose", help="Show verbose information.", action="store_true", default=False) - debug.add_option("-d","--debug", help="Debug rules.", action="store_true", default=False) - debug.add_option("--password", help="Process the last argument as a password not a file.", action="store_true", default=False) + debug.add_option("-v", "--verbose", help="Show verbose information.", action="store_true", default=False) + debug.add_option("-d", "--debug", help="Debug rules.", action="store_true", default=False) + debug.add_option("--password", help="Process the last argument as a password not a file.", action="store_true", + default=False) debug.add_option("--word", help="Use a custom word for rule analysis", metavar="Password") debug.add_option("--hashcat", help="Test generated rules with hashcat-cli", action="store_true", default=False) parser.add_option_group(debug) @@ -1055,41 +1191,44 @@ def verify_hashcat_rules(self,word, rules, password): exit(1) try: - rulegen = RuleGen(language="en", providers=options.providers, basename=options.basename, threads=options.threads) + rulegen = RuleGen(language="en", providers=options.providers, basename=options.basename, + threads=options.threads) except enchant.errors.DictNotFoundError: print("[-] Cannot find a dictionary for specified language. Please install it and try again.") print("[*] Hint: Usually this dictionary resides within an aspell / myspell package.") exit(-1) # Finetuning word generation - rulegen.max_word_dist=options.maxworddist - rulegen.max_words=options.maxwords - rulegen.more_words=options.morewords - rulegen.simple_words=options.simplewords + rulegen.max_word_dist = options.maxworddist + rulegen.max_words = options.maxwords + rulegen.more_words = options.morewords + rulegen.simple_words = options.simplewords # Finetuning rule generation - rulegen.max_rule_len=options.maxrulelen - rulegen.max_rules=options.maxrules - rulegen.more_rules=options.morerules - rulegen.simple_rules=options.simplerules - rulegen.brute_rules=options.bruterules - if rulegen.brute_rules: print("[!] Bruteforcing reversal and rotation rules. (slower)") + rulegen.max_rule_len = options.maxrulelen + rulegen.max_rules = options.maxrules + rulegen.more_rules = options.morerules + rulegen.simple_rules = options.simplerules + rulegen.brute_rules = options.bruterules + if rulegen.brute_rules: + print("[!] Bruteforcing reversal and rotation rules. (slower)") # Debugging options rulegen.word = options.word - rulegen.verbose=options.verbose + rulegen.verbose = options.verbose rulegen.debug = options.debug rulegen.hashcat = options.hashcat rulegen.quiet = options.quiet # Custom wordlist if not options.word: - if options.wordlist: rulegen.load_custom_wordlist(options.wordlist) + if options.wordlist: + rulegen.load_custom_wordlist(options.wordlist) print("[*] Using Enchant '%s' module. For best results please install" % rulegen.enchant.provider.name) print(" '%s' module language dictionaries." % rulegen.enchant.provider.name) # Analyze a single password or several passwords in a file - if options.password: + if options.password: rulegen.analyze_password(args[0]) else: rulegen.analyze_passwords_file(args[0]) From 7492eb64100461faa2f6ec1ed3ddc44985893641 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Haidar-Bachminska Date: Wed, 22 Jun 2016 12:57:54 +0200 Subject: [PATCH 20/27] make some methods static in rulegen --- rulegen.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/rulegen.py b/rulegen.py index 485ddef..c9e3841 100755 --- a/rulegen.py +++ b/rulegen.py @@ -229,7 +229,8 @@ def __init__(self, language="en", providers="aspell,myspell", basename='analysis ############################################################################ # Calculate Levenshtein edit path matrix - def levenshtein(self, word, password): + @staticmethod + def levenshtein(word, password): matrix = [] # Generate and populate the initial matrix @@ -278,7 +279,8 @@ def levenshtein_distance(self, s1, s2): return previous_row[-1] - def levenshtein_print(self, matrix, word, password): + @staticmethod + def levenshtein_print(matrix, word, password): """ Print word X password matrix """ print(" %s" % " ".join(list(word))) for i, row in enumerate(matrix): @@ -480,13 +482,15 @@ def generate_advanced_words(self, password): ############################################################################ # Hashcat specific offset definition 0-9,A-Z - def int_to_hashcat(self, n): + @staticmethod + def int_to_hashcat(n): if n < 10: return n else: return chr(65 + n - 10) - def hashcat_to_int(self, n): + @staticmethod + def hashcat_to_int(n): if n.isdigit(): return int(n) else: From e3ed4a87598a1576db060802342c097c1c550675 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Haidar-Bachminska Date: Wed, 22 Jun 2016 14:54:46 +0200 Subject: [PATCH 21/27] small style in statsgen.py --- statsgen.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/statsgen.py b/statsgen.py index d1d4376..81139c0 100755 --- a/statsgen.py +++ b/statsgen.py @@ -132,7 +132,7 @@ def analyze_password(password): else: charset = 'all' - return (pass_length, charset, simplemask_string, advancedmask_string, policy) + return pass_length, charset, simplemask_string, advancedmask_string, policy def generate_stats(self, filename): """ Generate password statistics. """ @@ -287,7 +287,7 @@ def print_stats(self): if options.maxlength is not None: statsgen.maxlength = options.maxlength if options.charsets is not None: - statsgen.charsets = [x.strip() for x in options.charsets.split(',')] + statsgen.charsets = [x.strip() for x in options.charsets.split(',')] if options.simplemasks is not None: statsgen.simplemasks = [x.strip() for x in options.simplemasks.split(',')] From 5d3ace966d63535aaf5b69e70a959ff3f65c60fb Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Haidar-Bachminska Date: Mon, 8 Aug 2016 21:54:09 +0200 Subject: [PATCH 22/27] Delete an useless list() added by 2to3 Reported by wolfy1339 here: https://github.com/Hydraze/pack/commit/9c5bef9e50d90edffa4a17ee4e3bda315efcd691#commitcomment-18462108 --- maskgen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maskgen.py b/maskgen.py index 6a70624..a92c9ba 100755 --- a/maskgen.py +++ b/maskgen.py @@ -107,7 +107,7 @@ def generate_masks(self, sorting_mode): if self.showmasks: print("[L:] Mask: [ Occ: ] [ Time: ]") - for mask in sorted(list(self.masks.keys()), key=lambda m: self.masks[m][sorting_mode], reverse=True): + for mask in sorted(self.masks.keys(), key=lambda m: self.masks[m][sorting_mode], reverse=True): if self.showmasks: time_human = ">1 year" if self.masks[mask]['time'] > 60*60*24*365 \ From ef1c21048cb99bdcc9f592637f598b0d02cbf519 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Haidar-Bachminska Date: Mon, 8 Aug 2016 21:57:32 +0200 Subject: [PATCH 23/27] policygen.py now handles maxdigit directive correctly. Reported and fixed by philsmd here: https://github.com/iphelix/pack/issues/1 (I couldn't just pull his fix due to my modifications) --- policygen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/policygen.py b/policygen.py index ac86de9..b935b47 100755 --- a/policygen.py +++ b/policygen.py @@ -217,7 +217,7 @@ def generate_masks(self, noncompliant): if options.minspecial is not None: policygen.minspecial = options.minspecial if options.maxdigit is not None: - policygen.maxdigits = options.maxdigit + policygen.maxdigit = options.maxdigit if options.maxlower is not None: policygen.maxlower = options.maxlower if options.maxupper is not None: From 92f073a75b57fb46b17d4d489449ce23f2d00c0f Mon Sep 17 00:00:00 2001 From: Matlink Date: Fri, 17 Mar 2017 13:39:21 +0100 Subject: [PATCH 24/27] Fix bug when threads != cpu_count When using the --threads flag, the script doesn't end, because it is waiting for cpu_count() threads (which is never satisfied). --- rulegen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rulegen.py b/rulegen.py index c9e3841..a8832f7 100755 --- a/rulegen.py +++ b/rulegen.py @@ -1040,7 +1040,7 @@ def analyze_passwords_file(self, passwords_file): else: # Signal workers to stop. - for i in range(multiprocessing.cpu_count()): + for i in range(self.threads): passwords_queue.put(None) # Wait for all of the queued passwords to finish. From ba885da89031ef2053aea22f72dfdeaffe18347f Mon Sep 17 00:00:00 2001 From: Mathieu Valois Date: Fri, 18 Aug 2017 00:10:18 +0200 Subject: [PATCH 25/27] shabang python3 --- maskgen.py | 2 +- policygen.py | 2 +- rulegen.py | 2 +- statsgen.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/maskgen.py b/maskgen.py index a92c9ba..563f2e3 100755 --- a/maskgen.py +++ b/maskgen.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python3 # MaskGen - Generate Password Masks # # This tool is part of PACK (Password Analysis and Cracking Kit) diff --git a/policygen.py b/policygen.py index b935b47..6599a77 100755 --- a/policygen.py +++ b/policygen.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python3 # PolicyGen - Analyze and Generate password masks according to a password policy # # This tool is part of PACK (Password Analysis and Cracking Kit) diff --git a/rulegen.py b/rulegen.py index a8832f7..8ff0524 100755 --- a/rulegen.py +++ b/rulegen.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # Rulegen.py - Advanced automated password rule and wordlist generator for the # Hashcat password cracker using the Levenshtein Reverse Path # algorithm and Enchant spell checking library. diff --git a/statsgen.py b/statsgen.py index 81139c0..d6f174a 100755 --- a/statsgen.py +++ b/statsgen.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # StatsGen - Password Statistical Analysis tool # # This tool is part of PACK (Password Analysis and Cracking Kit) From 9f63259bc2505ee99f0d9f5d7cc7b513e1b65c39 Mon Sep 17 00:00:00 2001 From: Matlink Date: Fri, 18 Aug 2017 00:16:04 +0200 Subject: [PATCH 26/27] fix python3 env --- maskgen.py | 2 +- policygen.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/maskgen.py b/maskgen.py index 563f2e3..2147a87 100755 --- a/maskgen.py +++ b/maskgen.py @@ -1,4 +1,4 @@ -#!/usr/bin/python3 +#!/usr/bin/env python3 # MaskGen - Generate Password Masks # # This tool is part of PACK (Password Analysis and Cracking Kit) diff --git a/policygen.py b/policygen.py index 6599a77..108d759 100755 --- a/policygen.py +++ b/policygen.py @@ -1,4 +1,4 @@ -#!/usr/bin/python3 +#!/usr/bin/env python3 # PolicyGen - Analyze and Generate password masks according to a password policy # # This tool is part of PACK (Password Analysis and Cracking Kit) From 78c931f24d5ef817b1637e74a3aefe251419a4f3 Mon Sep 17 00:00:00 2001 From: Chick3nman Date: Mon, 5 Feb 2018 11:24:01 -0600 Subject: [PATCH 27/27] Fixed 'x' and 'O' rules 'x' has been replaced with 'O' and the new functionality of 'x' has been added. --- rulegen.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rulegen.py b/rulegen.py index a8832f7..4e51960 100755 --- a/rulegen.py +++ b/rulegen.py @@ -152,7 +152,9 @@ def __init__(self, language="en", providers="aspell,myspell", basename='analysis # Truncate word at position N self.hashcat_rule["'"] = lambda x, y: x[:y] # Delete M characters, starting at position N - self.hashcat_rule["x"] = lambda x, y, z: x[:y] + x[y + z:] + self.hashcat_rule["O"] = lambda x, y, z: x[:y] + x[y + z:] + # Extracts M characters, starting at position N + self.hashcat_rule["'"] = lambda x, y, z: x[y:y+z] # Purge all instances of X self.hashcat_rule["@"] = lambda x, y: x.replace(y, '')