Updated documentation.

Implemented policy detection in statsgen.
Statsgen is now free of regex to speed things up.
This commit is contained in:
iphelix 2013-08-08 09:27:40 -07:00
parent 32e92d10d3
commit 7ef9748553
4 changed files with 641 additions and 632 deletions

1065
README

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@
# #
# This tool is part of PACK (Password Analysis and Cracking Kit) # This tool is part of PACK (Password Analysis and Cracking Kit)
# #
# VERSION 0.0.2 # VERSION 0.0.3
# #
# Copyright (C) 2013 Peter Kacherginsky # Copyright (C) 2013 Peter Kacherginsky
# All rights reserved. # All rights reserved.

View File

@ -811,7 +811,7 @@ class RuleGen:
def password_worker(self,i, passwords_queue, rules_queue, words_queue): def password_worker(self,i, passwords_queue, rules_queue, words_queue):
print "[*] Password analysis worker [%d] started." % i if self.debug: print "[*] Password analysis worker [%d] started." % i
try: try:
while True: while True:
password = passwords_queue.get() password = passwords_queue.get()
@ -821,15 +821,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):
print "[*] Password analysis worker [%d] terminated." % i if self.debug: print "[*] Password analysis worker [%d] terminated." % i
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): 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
f = open(output_rules_filename, 'w') f = open(output_rules_filename, 'w')
print "[*] Rule worker started." if self.debug: print "[*] Rule worker started."
try: try:
while True: while True:
rule = rules_queue.get() rule = rules_queue.get()
@ -841,16 +842,17 @@ class RuleGen:
f.flush() f.flush()
except (KeyboardInterrupt, SystemExit): except (KeyboardInterrupt, SystemExit):
print "[*] Rule worker terminated." if self.debug: print "[*] Rule worker terminated."
f.close() f.close()
print "[*] Rule worker stopped." if self.debug: print "[*] 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
f = open(output_words_filename, 'w') f = open(output_words_filename, 'w')
print "[*] Word worker started." if self.debug: print "[*] Word worker started."
try: try:
while True: while True:
word = words_queue.get() word = words_queue.get()
@ -862,16 +864,17 @@ class RuleGen:
f.flush() f.flush()
except (KeyboardInterrupt, SystemExit): except (KeyboardInterrupt, SystemExit):
print "[*] Word worker terminated." if self.debug: print "[*] Word worker terminated."
f.close() f.close()
print "[*] Word worker stopped." if self.debug: print "[*] Word worker stopped."
# Analyze passwords file # Analyze passwords file
def analyze_passwords_file(self,passwords_file): def analyze_passwords_file(self,passwords_file):
""" Analyze provided passwords file. """ """ Analyze provided passwords file. """
print "[*] Analyzing passwords file: %s:" % passwords_file print "[*] Analyzing passwords file: %s:" % passwords_file
print "[*] Press Ctrl-C to end execution and generate statistical analysis."
# Setup queues # Setup queues
passwords_queue = multiprocessing.Queue(multiprocessing.cpu_count() * 100) passwords_queue = multiprocessing.Queue(multiprocessing.cpu_count() * 100)
@ -948,7 +951,7 @@ 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 rule statistics" print "\n[*] 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)
@ -964,7 +967,7 @@ 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 word statistics" print "\n[*] 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)
@ -1082,13 +1085,10 @@ if __name__ == "__main__":
print "[*] Using Enchant '%s' module. For best results please install" % 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 print " '%s' module language dictionaries." % rulegen.enchant.provider.name
if not options.quiet:
print "[*] Saving rules to %s.rule" % options.basename
print "[*] Saving words to %s.word" % options.basename
print "[*] Press Ctrl-C to end execution and generate statistical analysis."
# 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])

View File

@ -3,18 +3,19 @@
# #
# This tool is part of PACK (Password Analysis and Cracking Kit) # This tool is part of PACK (Password Analysis and Cracking Kit)
# #
# VERSION 0.0.2 # VERSION 0.0.3
# #
# Copyright (C) 2013 Peter Kacherginsky # Copyright (C) 2013 Peter Kacherginsky
# All rights reserved. # All rights reserved.
# #
# Please see the attached LICENSE file for additiona licensing information. # Please see the attached LICENSE file for additional licensing information.
import sys import sys
import re, operator, string import re, operator, string
from optparse import OptionParser, OptionGroup from optparse import OptionParser, OptionGroup
import time
VERSION = "0.0.2" VERSION = "0.0.3"
class StatsGen: class StatsGen:
def __init__(self): def __init__(self):
@ -25,37 +26,8 @@ class StatsGen:
self.maxlength = None self.maxlength = None
self.simplemasks = None self.simplemasks = None
self.charsets = None self.charsets = None
self.quiet = False
# Constants self.debug = True
self.chars_regex = list()
self.chars_regex.append(('numeric',re.compile('^[0-9]+$')))
self.chars_regex.append(('loweralpha',re.compile('^[a-z]+$')))
self.chars_regex.append(('upperalpha',re.compile('^[A-Z]+$')))
self.chars_regex.append(('mixedalpha',re.compile('^[a-zA-Z]+$')))
self.chars_regex.append(('loweralphanum',re.compile('^[a-z0-9]+$')))
self.chars_regex.append(('upperalphanum',re.compile('^[A-Z0-9]+$')))
self.chars_regex.append(('mixedalphanum',re.compile('^[a-zA-Z0-9]+$')))
self.chars_regex.append(('special',re.compile('^[^a-zA-Z0-9]+$')))
self.chars_regex.append(('loweralphaspecial',re.compile('^[^A-Z0-9]+$')))
self.chars_regex.append(('upperalphaspecial',re.compile('^[^a-z0-9]+$')))
self.chars_regex.append(('mixedalphaspecial',re.compile('^[^0-9]+$')))
self.chars_regex.append(('loweralphaspecialnum',re.compile('^[^A-Z]+$')))
self.chars_regex.append(('upperalphaspecialnum',re.compile('^[^a-z]+$')))
self.chars_regex.append(('mixedalphaspecialnum',re.compile('.*')))
self.masks_regex = list()
self.masks_regex.append(('alldigit',re.compile('^\d+$', re.IGNORECASE)))
self.masks_regex.append(('allstring',re.compile('^[a-z]+$', re.IGNORECASE)))
self.masks_regex.append(('stringdigit',re.compile('^[a-z]+\d+$', re.IGNORECASE)))
self.masks_regex.append(('digitstring',re.compile('^\d+[a-z]+$', re.IGNORECASE)))
self.masks_regex.append(('digitstringdigit',re.compile('^\d+[a-z]+\d+$', re.IGNORECASE)))
self.masks_regex.append(('stringdigitstring',re.compile('^[a-z]+\d+[a-z]+$', re.IGNORECASE)))
self.masks_regex.append(('allspecial',re.compile('^[^a-z0-9]+$', re.IGNORECASE)))
self.masks_regex.append(('stringspecial',re.compile('^[a-z]+[^a-z0-9]+$', re.IGNORECASE)))
self.masks_regex.append(('specialstring',re.compile('^[^a-z0-9]+[a-z]+$', re.IGNORECASE)))
self.masks_regex.append(('stringspecialstring',re.compile('^[a-z]+[^a-z0-9]+[a-z]+$', re.IGNORECASE)))
self.masks_regex.append(('stringspecialdigit',re.compile('^[a-z]+[^a-z0-9]+\d+$', re.IGNORECASE)))
self.masks_regex.append(('specialstringspecial',re.compile('^[^a-z0-9]+[a-z]+[^a-z0-9]+$', re.IGNORECASE)))
# Stats dictionaries # Stats dictionaries
self.stats_length = dict() self.stats_length = dict()
@ -63,33 +35,88 @@ class StatsGen:
self.stats_advancedmasks = dict() self.stats_advancedmasks = dict()
self.stats_charactersets = dict() self.stats_charactersets = dict()
# Ignore stats with less than 1% coverage
self.hiderare = False self.hiderare = False
self.filter_counter = 0 self.filter_counter = 0
self.total_counter = 0 self.total_counter = 0
def simplemasks_check(self, password): # Minimum password complexity counters
for (name,regex) in self.masks_regex: self.mindigit = None
if regex.match(password): self.minupper = None
return name self.minlower = None
else: self.minspecial = None
return "othermask"
def characterset_check(self, password): self.maxdigit = None
for (name,regex) in self.chars_regex: self.maxupper = None
if regex.match(password): self.maxlower = None
return name self.maxspecial = None
else:
return "otherchar"
def advancedmask_check(self, password): def analyze_password(self, password):
mask = list()
# Password length
pass_length = len(password)
# Character-set and policy counters
digit = 0
lower = 0
upper = 0
special = 0
simplemask = list()
advancedmask_string = ""
# Detect simple and advanced masks
for letter in password: for letter in password:
if letter in string.digits: mask.append("?d")
elif letter in string.lowercase: mask.append("?l") if letter in string.digits:
elif letter in string.uppercase: mask.append("?u") digit += 1
else: mask.append("?s") advancedmask_string += "?d"
return "".join(mask) if not simplemask or not simplemask[-1] == 'digit': simplemask.append('digit')
elif letter in string.lowercase:
lower += 1
advancedmask_string += "?l"
if not simplemask or not simplemask[-1] == 'string': simplemask.append('string')
elif letter in string.uppercase:
upper += 1
advancedmask_string += "?u"
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')
# String representation of masks
simplemask_string = ''.join(simplemask) if len(simplemask) <= 3 else 'othermask'
# Policy
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'
return (pass_length, charset, simplemask_string, advancedmask_string, policy)
def generate_stats(self, filename): def generate_stats(self, filename):
""" Generate password statistics. """ """ Generate password statistics. """
@ -98,19 +125,32 @@ class StatsGen:
for password in f: for password in f:
password = password.rstrip('\r\n') password = password.rstrip('\r\n')
if len(password) == 0: continue
self.total_counter += 1 self.total_counter += 1
pass_length = len(password) (pass_length,characterset,simplemask,advancedmask, policy) = self.analyze_password(password)
characterset = self.characterset_check(password) (digit,lower,upper,special) = policy
simplemask = self.simplemasks_check(password)
advancedmask = self.advancedmask_check(password)
if (self.charsets == None or characterset in self.charsets) and \ if (self.charsets == None or characterset in self.charsets) and \
(self.simplemasks == None or simplemask in self.simplemasks) and \ (self.simplemasks == None or simplemask in self.simplemasks) and \
(self.maxlength == None or pass_length <= self.maxlength) and \ (self.maxlength == None or pass_length <= self.maxlength) and \
(self.minlength == None or pass_length >= self.minlength): (self.minlength == None or pass_length >= self.minlength):
self.filter_counter += 1 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.minupper == None or upper < self.minupper: self.minupper = upper
if self.maxupper == 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.minspecial == None or special < self.minspecial: self.minspecial = special
if self.maxspecial == None or special > self.maxspecial: self.maxspecial = special
if pass_length in self.stats_length: if pass_length in self.stats_length:
self.stats_length[pass_length] += 1 self.stats_length[pass_length] += 1
@ -139,22 +179,28 @@ class StatsGen:
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 " NOTE: Statistics below is relative to the number of analyzed passwords, not total number of passwords"
print "\n[*] Line Count Statistics..." print "\n[*] Length:"
for (length,count) in sorted(self.stats_length.iteritems(), key=operator.itemgetter(1), reverse=True): for (length,count) in sorted(self.stats_length.iteritems(), 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 "[+] %25d: %02d%% (%d)" % (length, count*100/self.filter_counter, count)
print "\n[*] Charset statistics..." print "\n[*] Character-set:"
for (char,count) in sorted(self.stats_charactersets.iteritems(), key=operator.itemgetter(1), reverse=True): for (char,count) in sorted(self.stats_charactersets.iteritems(), 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 "[+] %25s: %02d%% (%d)" % (char, count*100/self.filter_counter, count)
print "\n[*] Simple Mask statistics..." 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): for (simplemask,count) in sorted(self.stats_simplemasks.iteritems(), 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)" % (simplemask, count*100/self.filter_counter, count) print "[+] %25s: %02d%% (%d)" % (simplemask, count*100/self.filter_counter, count)
print "\n[*] Advanced Mask statistics..." print "\n[*] Advanced Masks:"
for (advancedmask,count) in sorted(self.stats_advancedmasks.iteritems(), key=operator.itemgetter(1), reverse=True): for (advancedmask,count) in sorted(self.stats_advancedmasks.iteritems(), key=operator.itemgetter(1), reverse=True):
if count*100/self.filter_counter > 0: 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)
@ -213,4 +259,4 @@ if __name__ == "__main__":
statsgen.output_file = open(options.output_file, 'w') statsgen.output_file = open(options.output_file, 'w')
statsgen.generate_stats(args[0]) statsgen.generate_stats(args[0])
statsgen.print_stats() statsgen.print_stats()