Port rulegen.py to py3 and logging
This commit is contained in:
parent
622dd925bf
commit
57aa4f8a00
210
rulegen.py
210
rulegen.py
@ -12,19 +12,23 @@
|
|||||||
#
|
#
|
||||||
# Please see the attached LICENSE file for additional licensing information.
|
# Please see the attached LICENSE file for additional licensing information.
|
||||||
|
|
||||||
import sys
|
|
||||||
import re
|
|
||||||
import time
|
|
||||||
import operator
|
|
||||||
import enchant
|
|
||||||
|
|
||||||
from optparse import OptionParser, OptionGroup
|
|
||||||
|
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
|
from optparse import OptionParser, OptionGroup
|
||||||
import subprocess
|
import enchant
|
||||||
|
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
|
import operator
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logging.VERBOSE = 15
|
||||||
|
logging.addLevelName(logging.VERBOSE, "VERBOSE")
|
||||||
|
logging.Logger.verbose = lambda self, msg, *args, **kwargs: \
|
||||||
|
self.log(logging.VERBOSE, msg, *args, **kwargs)
|
||||||
|
|
||||||
|
LOGGER = logging.getLogger("rulegen")
|
||||||
|
|
||||||
VERSION = "0.0.4"
|
VERSION = "0.0.4"
|
||||||
|
|
||||||
@ -61,8 +65,6 @@ class RuleGen:
|
|||||||
self.brute_rules = False
|
self.brute_rules = False
|
||||||
|
|
||||||
# Debugging options
|
# 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
|
self.quiet = False
|
||||||
|
|
||||||
@ -75,10 +77,10 @@ class RuleGen:
|
|||||||
########################################################################
|
########################################################################
|
||||||
# Preanalysis Password Patterns
|
# Preanalysis Password Patterns
|
||||||
self.password_pattern = dict()
|
self.password_pattern = dict()
|
||||||
self.password_pattern["insertion"] = re.compile('^[^a-z]*(?P<password>.+?)[^a-z]*$', re.IGNORECASE)
|
self.password_pattern["insertion"] = re.compile(r'^[^a-z]*(?P<password>.+?)[^a-z]*$', re.IGNORECASE)
|
||||||
self.password_pattern["email"] = re.compile('^(?P<password>.+?)@[A-Z0-9.-]+\.[A-Z]{2,4}', re.IGNORECASE)
|
self.password_pattern["email"] = re.compile(r'^(?P<password>.+?)@[A-Z0-9.-]+\.[A-Z]{2,4}', re.IGNORECASE)
|
||||||
self.password_pattern["alldigits"] = re.compile('^(\d+)$', re.IGNORECASE)
|
self.password_pattern["alldigits"] = re.compile(r'^(\d+)$', re.IGNORECASE)
|
||||||
self.password_pattern["allspecial"]= re.compile('^([^a-z0-9]+)$', re.IGNORECASE)
|
self.password_pattern["allspecial"]= re.compile(r'^([^a-z0-9]+)$', re.IGNORECASE)
|
||||||
|
|
||||||
########################################################################
|
########################################################################
|
||||||
# Hashcat Rules Engine
|
# Hashcat Rules Engine
|
||||||
@ -172,9 +174,9 @@ class RuleGen:
|
|||||||
matrix = []
|
matrix = []
|
||||||
|
|
||||||
# Generate and populate the initial matrix
|
# Generate and populate the initial matrix
|
||||||
for i in xrange(len(password) + 1):
|
for i in range(len(password) + 1):
|
||||||
matrix.append([])
|
matrix.append([])
|
||||||
for j in xrange(len(word) + 1):
|
for j in range(len(word) + 1):
|
||||||
if i == 0:
|
if i == 0:
|
||||||
matrix[i].append(j)
|
matrix[i].append(j)
|
||||||
elif j == 0:
|
elif j == 0:
|
||||||
@ -183,8 +185,8 @@ class RuleGen:
|
|||||||
matrix[i].append(0)
|
matrix[i].append(0)
|
||||||
|
|
||||||
# Calculate edit distance for each substring
|
# Calculate edit distance for each substring
|
||||||
for i in xrange(1,len(password) + 1):
|
for i in range(1,len(password) + 1):
|
||||||
for j in xrange(1,len(word) + 1):
|
for j in range(1,len(word) + 1):
|
||||||
if password[i-1] == word[j-1]:
|
if password[i-1] == word[j-1]:
|
||||||
matrix[i][j] = matrix[i-1][j-1]
|
matrix[i][j] = matrix[i-1][j-1]
|
||||||
else:
|
else:
|
||||||
@ -205,7 +207,7 @@ class RuleGen:
|
|||||||
if not s1:
|
if not s1:
|
||||||
return len(s2)
|
return len(s2)
|
||||||
|
|
||||||
previous_row = xrange(len(s2) + 1)
|
previous_row = range(len(s2) + 1)
|
||||||
for i, c1 in enumerate(s1):
|
for i, c1 in enumerate(s1):
|
||||||
current_row = [i + 1]
|
current_row = [i + 1]
|
||||||
for j, c2 in enumerate(s2):
|
for j, c2 in enumerate(s2):
|
||||||
@ -219,11 +221,11 @@ class RuleGen:
|
|||||||
|
|
||||||
def levenshtein_print(self,matrix,word,password):
|
def levenshtein_print(self,matrix,word,password):
|
||||||
""" Print word X password matrix """
|
""" Print word X password matrix """
|
||||||
print " %s" % " ".join(list(word))
|
print(" %s" % " ".join(list(word)))
|
||||||
for i,row in enumerate(matrix):
|
for i,row in enumerate(matrix):
|
||||||
if i == 0: print " ",
|
if i == 0: print(" "),
|
||||||
else: print password[i-1],
|
else: print(password[i-1]),
|
||||||
print " ".join("%2d" % col for col in row)
|
print(" ".join("%2d" % col for col in row))
|
||||||
|
|
||||||
def generate_levenshtein_rules(self, word, password):
|
def generate_levenshtein_rules(self, word, password):
|
||||||
""" Generates levenshtein rules. Returns a list of lists of levenshtein rules. """
|
""" Generates levenshtein rules. Returns a list of lists of levenshtein rules. """
|
||||||
@ -254,7 +256,7 @@ class RuleGen:
|
|||||||
cost = matrix[i][j]
|
cost = matrix[i][j]
|
||||||
|
|
||||||
# Calculate minimum cost of each operation
|
# 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 i > 0: cost_insert = matrix[i-1][j]
|
||||||
if j > 0: cost_delete = matrix[i][j-1]
|
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 and j > 0: cost_equal_or_replace = matrix[i-1][j-1]
|
||||||
@ -285,7 +287,7 @@ class RuleGen:
|
|||||||
def generate_words(self,password):
|
def generate_words(self,password):
|
||||||
""" Generate source word candidates."""
|
""" Generate source word candidates."""
|
||||||
|
|
||||||
if self.debug: print "[*] Generating source words for %s" % password
|
LOGGER.debug("[*] Generating source words for %s" % password)
|
||||||
|
|
||||||
words = list()
|
words = list()
|
||||||
words_collection = list()
|
words_collection = list()
|
||||||
@ -319,8 +321,8 @@ class RuleGen:
|
|||||||
suggestions.append(suggestion)
|
suggestions.append(suggestion)
|
||||||
|
|
||||||
if len(suggestions) != len(set(suggestions)):
|
if len(suggestions) != len(set(suggestions)):
|
||||||
print sorted(suggestions)
|
print(sorted(suggestions))
|
||||||
print sorted(set(suggestions))
|
print(sorted(set(suggestions)))
|
||||||
|
|
||||||
|
|
||||||
for suggestion in suggestions:
|
for suggestion in suggestions:
|
||||||
@ -346,23 +348,20 @@ class RuleGen:
|
|||||||
best_found_distance = word["distance"]
|
best_found_distance = word["distance"]
|
||||||
|
|
||||||
elif word["distance"] > best_found_distance:
|
elif word["distance"] > best_found_distance:
|
||||||
if self.verbose:
|
LOGGER.verbose("[-] %s => {edit distance suboptimal: %d (%d)} => %s" % \
|
||||||
print "[-] %s => {edit distance suboptimal: %d (%d)} => %s" % \
|
(word["suggestion"], word["distance"], best_found_distance, word["password"]))
|
||||||
(word["suggestion"], word["distance"], best_found_distance, word["password"])
|
|
||||||
break
|
break
|
||||||
|
|
||||||
# Filter words with too big edit distance
|
# Filter words with too big edit distance
|
||||||
if word["distance"] <= self.max_word_dist:
|
if word["distance"] <= self.max_word_dist:
|
||||||
if self.debug:
|
LOGGER.debug("[+] %s => {edit distance: %d (%d)} = > %s" % \
|
||||||
print "[+] %s => {edit distance: %d (%d)} = > %s" % \
|
(word["suggestion"], word["distance"],best_found_distance, word["password"]))
|
||||||
(word["suggestion"], word["distance"],best_found_distance, word["password"])
|
|
||||||
|
|
||||||
words_collection.append(word)
|
words_collection.append(word)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if self.verbose:
|
LOGGER.verbose("[-] %s => {max distance exceeded: %d (%d)} => %s" % \
|
||||||
print "[-] %s => {max distance exceeded: %d (%d)} => %s" % \
|
(word["suggestion"], word["distance"], self.max_word_dist, word["password"]))
|
||||||
(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]
|
words_collection = words_collection[:self.max_words]
|
||||||
@ -399,7 +398,7 @@ class RuleGen:
|
|||||||
else: preanalysis_password += c
|
else: preanalysis_password += c
|
||||||
password = preanalysis_password
|
password = preanalysis_password
|
||||||
|
|
||||||
if self.debug: "[*] Preanalysis Password: %s" % password
|
LOGGER.debug("[*] Preanalysis Password: %s" % password)
|
||||||
|
|
||||||
return self.enchant.suggest(password)
|
return self.enchant.suggest(password)
|
||||||
|
|
||||||
@ -433,9 +432,9 @@ class RuleGen:
|
|||||||
hashcat_rule = self.generate_advanced_hashcat_rules(suggestion, lev_rule, password)
|
hashcat_rule = self.generate_advanced_hashcat_rules(suggestion, lev_rule, password)
|
||||||
|
|
||||||
if hashcat_rule == None:
|
if hashcat_rule == None:
|
||||||
print "[!] Processing FAILED: %s => ;( => %s" % (suggestion,password)
|
LOGGER.warning("[!] Processing FAILED: %s => ;( => %s" % (suggestion,password))
|
||||||
print " Sorry about that, please report this failure to"
|
LOGGER.warning(" Sorry about that, please report this failure to")
|
||||||
print " the developer: iphelix [at] thesprawl.org"
|
LOGGER.warning(" the developer: iphelix [at] thesprawl.org")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
hashcat_rules.append(hashcat_rule)
|
hashcat_rules.append(hashcat_rule)
|
||||||
@ -453,9 +452,8 @@ class RuleGen:
|
|||||||
best_found_rule_length = rule_length
|
best_found_rule_length = rule_length
|
||||||
|
|
||||||
elif rule_length > best_found_rule_length:
|
elif rule_length > best_found_rule_length:
|
||||||
if self.verbose:
|
LOGGER.verbose("[-] %s => {best rule length exceeded: %d (%d)} => %s" % \
|
||||||
print "[-] %s => {best rule length exceeded: %d (%d)} => %s" % \
|
(suggestion, rule_length, best_found_rule_length, password))
|
||||||
(suggestion, rule_length, best_found_rule_length, password)
|
|
||||||
break
|
break
|
||||||
|
|
||||||
if rule_length <= self.max_rule_len:
|
if rule_length <= self.max_rule_len:
|
||||||
@ -467,7 +465,7 @@ class RuleGen:
|
|||||||
""" Generate basic hashcat rules using only basic insert,delete,replace rules. """
|
""" Generate basic hashcat rules using only basic insert,delete,replace rules. """
|
||||||
hashcat_rules = []
|
hashcat_rules = []
|
||||||
|
|
||||||
if self.debug: print "[*] Simple Processing %s => %s" % (word,password)
|
LOGGER.debug("[*] Simple Processing %s => %s" % (word,password))
|
||||||
|
|
||||||
# Dynamically apply rules to the source word
|
# Dynamically apply rules to the source word
|
||||||
# NOTE: Special case were word == password this would work as well.
|
# NOTE: Special case were word == password this would work as well.
|
||||||
@ -475,7 +473,7 @@ class RuleGen:
|
|||||||
|
|
||||||
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))
|
LOGGER.debug("\t[*] Simple Processing Started: %s - %s" % (word_rules, " ".join(hashcat_rules)))
|
||||||
|
|
||||||
if op == 'insert':
|
if op == 'insert':
|
||||||
hashcat_rules.append("i%s%s" % (self.int_to_hashcat(p),password[p]))
|
hashcat_rules.append("i%s%s" % (self.int_to_hashcat(p),password[p]))
|
||||||
@ -489,20 +487,20 @@ class RuleGen:
|
|||||||
hashcat_rules.append("o%s%s" % (self.int_to_hashcat(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])
|
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)
|
LOGGER.debug("\t[*] Simple Processing Ended: %s => %s => %s" % (word_rules, " ".join(hashcat_rules),password))
|
||||||
|
|
||||||
# Check if rules result in the correct password
|
# Check if rules result in the correct password
|
||||||
if word_rules == password:
|
if word_rules == password:
|
||||||
return hashcat_rules
|
return hashcat_rules
|
||||||
else:
|
else:
|
||||||
if self.debug: print "[!] Simple Processing FAILED: %s => %s => %s (%s)" % (word," ".join(hashcat_rules),password,word_rules)
|
LOGGER.debug("[!] Simple Processing FAILED: %s => %s => %s (%s)" % (word," ".join(hashcat_rules),password,word_rules))
|
||||||
return None
|
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. """
|
""" Generate advanced hashcat rules using full range of available rules. """
|
||||||
hashcat_rules = []
|
hashcat_rules = []
|
||||||
|
|
||||||
if self.debug: print "[*] Advanced Processing %s => %s" % (word,password)
|
LOGGER.debug("[*] Advanced Processing %s => %s" % (word,password))
|
||||||
|
|
||||||
# Dynamically apply and store rules in word_rules variable.
|
# Dynamically apply and store rules in word_rules variable.
|
||||||
# NOTE: Special case where word == password this would work as well.
|
# NOTE: Special case where word == password this would work as well.
|
||||||
@ -514,7 +512,7 @@ class RuleGen:
|
|||||||
|
|
||||||
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))
|
LOGGER.debug("\t[*] Advanced Processing Started: %s - %s" % (word_rules, " ".join(hashcat_rules)))
|
||||||
|
|
||||||
if op == 'insert':
|
if op == 'insert':
|
||||||
hashcat_rules.append("i%s%s" % (self.int_to_hashcat(p),password[p]))
|
hashcat_rules.append("i%s%s" % (self.int_to_hashcat(p),password[p]))
|
||||||
@ -538,7 +536,7 @@ class RuleGen:
|
|||||||
|
|
||||||
# This rule was made obsolete by a prior global replacement
|
# This rule was made obsolete by a prior global replacement
|
||||||
if word_rules[p] == password[p]:
|
if word_rules[p] == password[p]:
|
||||||
if self.debug: print "\t[*] Advanced Processing Obsolete Rule: %s - %s" % (word_rules, " ".join(hashcat_rules))
|
LOGGER.debug("\t[*] Advanced Processing Obsolete Rule: %s - %s" % (word_rules, " ".join(hashcat_rules)))
|
||||||
|
|
||||||
# Swapping 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]:
|
||||||
@ -649,7 +647,7 @@ class RuleGen:
|
|||||||
hashcat_rules.append("o%s%s" % (self.int_to_hashcat(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])
|
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))
|
LOGGER.debug("\t[*] Advanced Processing Ended: %s %s" % (word_rules, " ".join(hashcat_rules)))
|
||||||
|
|
||||||
########################################################################
|
########################################################################
|
||||||
# Prefix rules
|
# Prefix rules
|
||||||
@ -718,7 +716,7 @@ class RuleGen:
|
|||||||
if word_rules == password:
|
if word_rules == password:
|
||||||
return hashcat_rules
|
return hashcat_rules
|
||||||
else:
|
else:
|
||||||
if self.debug: print "[!] Advanced Processing FAILED: %s => %s => %s (%s)" % (word," ".join(hashcat_rules),password,word_rules)
|
LOGGER.debug("[!] Advanced Processing FAILED: %s => %s => %s (%s)" % (word," ".join(hashcat_rules),password,word_rules))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@ -727,21 +725,21 @@ class RuleGen:
|
|||||||
|
|
||||||
# Skip all numeric passwords
|
# Skip all numeric passwords
|
||||||
if password.isdigit():
|
if password.isdigit():
|
||||||
if self.verbose and not self.quiet: print "[!] %s => {skipping numeric} => %s" % (password,password)
|
if not self.quiet: LOGGER.verbose("[!] %s => {skipping numeric} => %s" % (password,password))
|
||||||
self.numeric_stats_total += 1
|
self.numeric_stats_total += 1
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Skip passwords with less than 25% of alpha character
|
# Skip passwords with less than 25% of alpha character
|
||||||
# TODO: Make random word detection more reliable based on word entropy.
|
# 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)
|
if not self.quiet: LOGGER.verbose("[!] %s => {skipping alpha less than 25%%} => %s" % (password,password))
|
||||||
self.special_stats_total += 1
|
self.special_stats_total += 1
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Only check english ascii passwords for now
|
# Only check english ascii passwords for now
|
||||||
# TODO: Add support for more languages.
|
# TODO: Add support for more languages.
|
||||||
elif [c for c in password if ord(c) < 32 or ord(c) > 126]:
|
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 not self.quiet: LOGGER.verbose("[!] %s => {skipping non ascii english} => %s" % (password,password))
|
||||||
self.foreign_stats_total += 1
|
self.foreign_stats_total += 1
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -751,7 +749,7 @@ class RuleGen:
|
|||||||
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. """
|
""" Analyze a single password. """
|
||||||
|
|
||||||
if self.verbose: print "[*] Analyzing password: %s" % password
|
LOGGER.verbose("[*] Analyzing password: %s" % password)
|
||||||
|
|
||||||
words = []
|
words = []
|
||||||
|
|
||||||
@ -799,21 +797,20 @@ class RuleGen:
|
|||||||
best_found_rule_length = rule_length
|
best_found_rule_length = rule_length
|
||||||
|
|
||||||
elif rule_length > best_found_rule_length:
|
elif rule_length > best_found_rule_length:
|
||||||
if self.verbose:
|
LOGGER.verbose("[-] %s => {best rule length exceeded: %d (%d)} => %s" % \
|
||||||
print "[-] %s => {best rule length exceeded: %d (%d)} => %s" % \
|
(word["suggestion"], rule_length, best_found_rule_length, password))
|
||||||
(word["suggestion"], rule_length, best_found_rule_length, password)
|
|
||||||
break
|
break
|
||||||
|
|
||||||
if rule_length <= self.max_rule_len:
|
if rule_length <= self.max_rule_len:
|
||||||
|
|
||||||
hashcat_rule_str = " ".join(hashcat_rule + word["pre_rule"] or [':'])
|
hashcat_rule_str = " ".join(hashcat_rule + word["pre_rule"] or [':'])
|
||||||
if self.verbose: print "[+] %s => %s => %s" % (word["suggestion"], hashcat_rule_str, password)
|
LOGGER.verbose("[+] %s => %s => %s" % (word["suggestion"], hashcat_rule_str, password))
|
||||||
|
|
||||||
rules_queue.put(hashcat_rule_str)
|
rules_queue.put(hashcat_rule_str)
|
||||||
|
|
||||||
|
|
||||||
def password_worker(self,i, passwords_queue, rules_queue, words_queue):
|
def password_worker(self,i, passwords_queue, rules_queue, words_queue):
|
||||||
if self.debug: print "[*] Password analysis worker [%d] started." % i
|
LOGGER.debug("[*] Password analysis worker [%d] started." % i)
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
password = passwords_queue.get()
|
password = passwords_queue.get()
|
||||||
@ -823,16 +820,16 @@ class RuleGen:
|
|||||||
|
|
||||||
self.analyze_password(password, rules_queue, words_queue)
|
self.analyze_password(password, rules_queue, words_queue)
|
||||||
except (KeyboardInterrupt, SystemExit):
|
except (KeyboardInterrupt, SystemExit):
|
||||||
if self.debug: print "[*] Password analysis worker [%d] terminated." % i
|
LOGGER.debug("[*] Password analysis worker [%d] terminated." % i)
|
||||||
|
|
||||||
if self.debug: print "[*] Password analysis worker [%d] stopped." % i
|
LOGGER.debug("[*] Password analysis worker [%d] stopped." % i)
|
||||||
|
|
||||||
def rule_worker(self, rules_queue, output_rules_filename):
|
def rule_worker(self, rules_queue, output_rules_filename):
|
||||||
""" Worker to store generated rules. """
|
""" Worker to store generated rules. """
|
||||||
print "[*] Saving rules to %s" % output_rules_filename
|
LOGGER.info("[*] Saving rules to %s" % output_rules_filename)
|
||||||
|
|
||||||
f = open(output_rules_filename, 'w')
|
f = open(output_rules_filename, 'w')
|
||||||
if self.debug: print "[*] Rule worker started."
|
LOGGER.debug("[*] Rule worker started.")
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
rule = rules_queue.get()
|
rule = rules_queue.get()
|
||||||
@ -844,17 +841,17 @@ class RuleGen:
|
|||||||
f.flush()
|
f.flush()
|
||||||
|
|
||||||
except (KeyboardInterrupt, SystemExit):
|
except (KeyboardInterrupt, SystemExit):
|
||||||
if self.debug: print "[*] Rule worker terminated."
|
LOGGER.debug("[*] Rule worker terminated.")
|
||||||
|
|
||||||
f.close()
|
f.close()
|
||||||
if self.debug: print "[*] Rule worker stopped."
|
LOGGER.debug("[*] Rule worker stopped.")
|
||||||
|
|
||||||
def word_worker(self, words_queue, output_words_filename):
|
def word_worker(self, words_queue, output_words_filename):
|
||||||
""" Worker to store generated rules. """
|
""" Worker to store generated rules. """
|
||||||
print "[*] Saving words to %s" % output_words_filename
|
LOGGER.info("[*] Saving words to %s" % output_words_filename)
|
||||||
|
|
||||||
f = open(output_words_filename, 'w')
|
f = open(output_words_filename, 'w')
|
||||||
if self.debug: print "[*] Word worker started."
|
LOGGER.debug("[*] Word worker started.")
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
word = words_queue.get()
|
word = words_queue.get()
|
||||||
@ -866,17 +863,17 @@ class RuleGen:
|
|||||||
f.flush()
|
f.flush()
|
||||||
|
|
||||||
except (KeyboardInterrupt, SystemExit):
|
except (KeyboardInterrupt, SystemExit):
|
||||||
if self.debug: print "[*] Word worker terminated."
|
LOGGER.debug("[*] Word worker terminated.")
|
||||||
|
|
||||||
f.close()
|
f.close()
|
||||||
if self.debug: print "[*] Word worker stopped."
|
LOGGER.debug("[*] Word worker stopped.")
|
||||||
|
|
||||||
# Analyze passwords file
|
# Analyze passwords file
|
||||||
def analyze_passwords_file(self,passwords_file):
|
def analyze_passwords_file(self,passwords_file, encoding):
|
||||||
""" Analyze provided passwords file. """
|
""" Analyze provided passwords file. """
|
||||||
|
|
||||||
print "[*] Analyzing passwords file: %s:" % passwords_file
|
LOGGER.info("[*] Analyzing passwords file: %s:" % passwords_file)
|
||||||
print "[*] Press Ctrl-C to end execution and generate statistical analysis."
|
LOGGER.info("[*] Press Ctrl-C to end execution and generate statistical analysis.")
|
||||||
|
|
||||||
# Setup queues
|
# Setup queues
|
||||||
passwords_queue = multiprocessing.Queue(self.threads)
|
passwords_queue = multiprocessing.Queue(self.threads)
|
||||||
@ -891,7 +888,7 @@ class RuleGen:
|
|||||||
|
|
||||||
# Continue with the main thread
|
# Continue with the main thread
|
||||||
|
|
||||||
f = open(passwords_file,'r')
|
f = open(passwords_file,'r', encoding=encoding)
|
||||||
|
|
||||||
password_count = 0
|
password_count = 0
|
||||||
analysis_start = time.time()
|
analysis_start = time.time()
|
||||||
@ -904,8 +901,8 @@ class RuleGen:
|
|||||||
# Provide analysis time feedback to the user
|
# Provide analysis time feedback to the user
|
||||||
if not self.quiet and password_count != 0 and password_count % 5000 == 0:
|
if not self.quiet and password_count != 0 and password_count % 5000 == 0:
|
||||||
segment_time = time.time() - segment_start
|
segment_time = time.time() - segment_start
|
||||||
print "[*] Processed %d passwords in %.2f seconds at the rate of %.2f p/sec" % \
|
LOGGER.info("[*] Processed %d passwords in %.2f seconds at the rate of %.2f p/sec" % \
|
||||||
(password_count, segment_start - analysis_start, 5000/segment_time )
|
(password_count, segment_start - analysis_start, 5000/segment_time ))
|
||||||
segment_start = time.time()
|
segment_start = time.time()
|
||||||
|
|
||||||
password_count += 1
|
password_count += 1
|
||||||
@ -915,7 +912,7 @@ class RuleGen:
|
|||||||
passwords_queue.put(password)
|
passwords_queue.put(password)
|
||||||
|
|
||||||
except (KeyboardInterrupt, SystemExit):
|
except (KeyboardInterrupt, SystemExit):
|
||||||
print "\n[!] Rulegen was interrupted."
|
LOGGER.warning("\n[!] Rulegen was interrupted.")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Signal workers to stop.
|
# Signal workers to stop.
|
||||||
@ -933,15 +930,15 @@ class RuleGen:
|
|||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
analysis_time = time.time() - analysis_start
|
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 )
|
LOGGER.info("[*] 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
|
LOGGER.info("[*] Generating statistics for [%s] rules and words." % self.basename)
|
||||||
print "[-] Skipped %d all numeric passwords (%0.2f%%)" % \
|
LOGGER.info("[-] Skipped %d all numeric passwords (%0.2f%%)" % \
|
||||||
(self.numeric_stats_total, float(self.numeric_stats_total)*100.0/float(password_count))
|
(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%%)" % \
|
LOGGER.info("[-] 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))
|
(self.special_stats_total, float(self.special_stats_total)*100.0/float(password_count)))
|
||||||
print "[-] Skipped %d passwords with non ascii characters (%0.2f%%)" % \
|
LOGGER.info("[-] Skipped %d passwords with non ascii characters (%0.2f%%)" % \
|
||||||
(self.foreign_stats_total, float(self.foreign_stats_total)*100.0/float(password_count))
|
(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
|
# TODO: Counter breaks on large files. uniq -c | sort -rn is still the most
|
||||||
# optimal way.
|
# optimal way.
|
||||||
@ -950,11 +947,11 @@ class RuleGen:
|
|||||||
rules_counter = Counter(rules_file)
|
rules_counter = Counter(rules_file)
|
||||||
rule_counter_total = sum(rules_counter.values())
|
rule_counter_total = sum(rules_counter.values())
|
||||||
|
|
||||||
print "\n[*] Top 10 rules"
|
LOGGER.info("[*] Top 10 rules")
|
||||||
rules_i = 0
|
rules_i = 0
|
||||||
for (rule, count) in rules_counter.most_common():
|
for (rule, count) in rules_counter.most_common():
|
||||||
rules_sorted_file.write(rule)
|
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: LOGGER.info("[+] %s - %d (%0.2f%%)" % (rule.rstrip('\r\n'), count, count*100/rule_counter_total))
|
||||||
rules_i += 1
|
rules_i += 1
|
||||||
|
|
||||||
rules_file.close()
|
rules_file.close()
|
||||||
@ -966,11 +963,11 @@ class RuleGen:
|
|||||||
words_counter = Counter(words_file)
|
words_counter = Counter(words_file)
|
||||||
word_counter_total = sum(rules_counter.values())
|
word_counter_total = sum(rules_counter.values())
|
||||||
|
|
||||||
print "\n[*] Top 10 words"
|
LOGGER.info("[*] Top 10 words")
|
||||||
words_i = 0
|
words_i = 0
|
||||||
for (word, count) in words_counter.most_common():
|
for (word, count) in words_counter.most_common():
|
||||||
words_sorted_file.write(word)
|
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: LOGGER.info("[+] %s - %d (%0.2f%%)" % (word.rstrip('\r\n'), count, count*100/word_counter_total))
|
||||||
words_i += 1
|
words_i += 1
|
||||||
|
|
||||||
words_file.close()
|
words_file.close()
|
||||||
@ -993,19 +990,19 @@ class RuleGen:
|
|||||||
|
|
||||||
if out == password:
|
if out == password:
|
||||||
hashcat_rules_str = " ".join(rules or [':'])
|
hashcat_rules_str = " ".join(rules or [':'])
|
||||||
if self.verbose: print "[+] %s => %s => %s" % (word, hashcat_rules_str, password)
|
LOGGER.verbose("[+] %s => %s => %s" % (word, hashcat_rules_str, password))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print "[!] Hashcat Verification FAILED: %s => %s => %s (%s)" % (word," ".join(rules or [':']),password,out)
|
LOGGER.error("[!] Hashcat Verification FAILED: %s => %s => %s (%s)" % (word," ".join(rules or [':']),password,out))
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
||||||
header = " _ \n"
|
header = " _ \n"
|
||||||
header += " RuleGen %s | |\n" % VERSION
|
header += " RuleGen %s | |\n" % VERSION
|
||||||
header += " _ __ __ _ ___| | _\n"
|
header += " _ __ __ _ ___| | _\n"
|
||||||
header += " | '_ \ / _` |/ __| |/ /\n"
|
header += " | '_ \\ / _` |/ __| |/ /\n"
|
||||||
header += " | |_) | (_| | (__| < \n"
|
header += " | |_) | (_| | (__| < \n"
|
||||||
header += " | .__/ \__,_|\___|_|\_\\\n"
|
header += " | .__/ \\__,_|\\___|_|\\_\\\n"
|
||||||
header += " | | \n"
|
header += " | | \n"
|
||||||
header += " |_| iphelix@thesprawl.org\n"
|
header += " |_| iphelix@thesprawl.org\n"
|
||||||
header += "\n"
|
header += "\n"
|
||||||
@ -1017,6 +1014,7 @@ if __name__ == "__main__":
|
|||||||
parser.add_option("-w","--wordlist", help="Use a custom wordlist for rule analysis.", metavar="wiki.dict")
|
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("-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.")
|
||||||
|
parser.add_option("-e", "--encoding", help="Input file encoding.", default="utf-8")
|
||||||
|
|
||||||
wordtune = OptionGroup(parser, "Fine tune source word generation:")
|
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("--maxworddist", help="Maximum word edit distance (Levenshtein)", type="int", default=10, metavar="10")
|
||||||
@ -1037,7 +1035,7 @@ if __name__ == "__main__":
|
|||||||
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)
|
parser.add_option_group(spelltune)
|
||||||
|
|
||||||
debug = OptionGroup(parser, "Debuggin options:")
|
debug = OptionGroup(parser, "Debugging options:")
|
||||||
debug.add_option("-v","--verbose", help="Show verbose information.", 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("-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("--password", help="Process the last argument as a password not a file.", action="store_true", default=False)
|
||||||
@ -1049,7 +1047,7 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
# Print program header
|
# Print program header
|
||||||
if not options.quiet:
|
if not options.quiet:
|
||||||
print header
|
print (header)
|
||||||
|
|
||||||
if len(args) < 1:
|
if len(args) < 1:
|
||||||
parser.error("no passwords file specified")
|
parser.error("no passwords file specified")
|
||||||
@ -1069,23 +1067,25 @@ if __name__ == "__main__":
|
|||||||
rulegen.more_rules=options.morerules
|
rulegen.more_rules=options.morerules
|
||||||
rulegen.simple_rules=options.simplerules
|
rulegen.simple_rules=options.simplerules
|
||||||
rulegen.brute_rules=options.bruterules
|
rulegen.brute_rules=options.bruterules
|
||||||
if rulegen.brute_rules: print "[!] Bruteforcing reversal and rotation rules. (slower)"
|
if rulegen.brute_rules: LOGGER.info("[!] Bruteforcing reversal and rotation rules. (slower)")
|
||||||
|
|
||||||
# Debugging options
|
# Debugging options
|
||||||
rulegen.word = options.word
|
rulegen.word = options.word
|
||||||
rulegen.verbose=options.verbose
|
|
||||||
rulegen.debug = options.debug
|
|
||||||
rulegen.hashcat = options.hashcat
|
rulegen.hashcat = options.hashcat
|
||||||
rulegen.quiet = options.quiet
|
rulegen.quiet = options.quiet
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.DEBUG if options.debug
|
||||||
|
else logging.VERBOSE if options.verbose
|
||||||
|
else logging.INFO)
|
||||||
|
|
||||||
# Custom wordlist
|
# Custom wordlist
|
||||||
if not options.word:
|
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
|
LOGGER.info("[*] Using Enchant '%s' module. For best results please install" % rulegen.enchant.provider.name)
|
||||||
print " '%s' module language dictionaries." % rulegen.enchant.provider.name
|
LOGGER.info(" '%s' module language dictionaries." % rulegen.enchant.provider.name)
|
||||||
|
|
||||||
# Analyze a single password or several passwords in a file
|
# Analyze a single password or several passwords in a file
|
||||||
if options.password:
|
if options.password:
|
||||||
rulegen.analyze_password(args[0])
|
rulegen.analyze_password(args[0])
|
||||||
else:
|
else:
|
||||||
rulegen.analyze_passwords_file(args[0])
|
rulegen.analyze_passwords_file(args[0], options.encoding)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user