216 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			216 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env python
 | |
| # StatsGen - Password Statistical Analysis tool
 | |
| #
 | |
| # This tool is part of PACK (Password Analysis and Cracking Kit)
 | |
| #
 | |
| # VERSION 0.0.2
 | |
| #
 | |
| # Copyright (C) 2013 Peter Kacherginsky
 | |
| # All rights reserved.
 | |
| #
 | |
| # Please see the attached LICENSE file for additiona licensing information.
 | |
| 
 | |
| import sys
 | |
| import re, operator, string
 | |
| from optparse import OptionParser, OptionGroup
 | |
| 
 | |
| VERSION = "0.0.2"
 | |
| 
 | |
| class StatsGen:
 | |
|     def __init__(self):
 | |
|         self.output_file = None
 | |
| 
 | |
|         # Filters
 | |
|         self.minlength   = None
 | |
|         self.maxlength   = None
 | |
|         self.simplemasks = None
 | |
|         self.charsets    = None
 | |
| 
 | |
|         # Constants
 | |
|         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
 | |
|         self.stats_length = dict()
 | |
|         self.stats_simplemasks = dict()
 | |
|         self.stats_advancedmasks = dict()
 | |
|         self.stats_charactersets = dict()
 | |
| 
 | |
|         self.hiderare = False
 | |
| 
 | |
|         self.filter_counter = 0
 | |
|         self.total_counter = 0
 | |
| 
 | |
|     def simplemasks_check(self, password):
 | |
|         for (name,regex) in self.masks_regex:
 | |
|             if regex.match(password):
 | |
|                 return name
 | |
|         else:
 | |
|             return "othermask"
 | |
| 
 | |
|     def characterset_check(self, password):
 | |
|         for (name,regex) in self.chars_regex:
 | |
|             if regex.match(password):
 | |
|                 return name
 | |
|         else:
 | |
|             return "otherchar"
 | |
| 
 | |
|     def advancedmask_check(self, password):
 | |
|         mask = list()
 | |
|         for letter in password:
 | |
|             if letter in string.digits: mask.append("?d")
 | |
|             elif letter in string.lowercase: mask.append("?l")
 | |
|             elif letter in string.uppercase: mask.append("?u")
 | |
|             else: mask.append("?s")
 | |
|         return "".join(mask)
 | |
| 
 | |
|     def generate_stats(self, filename):
 | |
|         """ Generate password statistics. """
 | |
| 
 | |
|         f = open(filename,'r')
 | |
| 
 | |
|         for password in f:
 | |
|             password = password.rstrip('\r\n')
 | |
|             self.total_counter += 1  
 | |
|         
 | |
|             pass_length = len(password)
 | |
|             characterset = self.characterset_check(password)
 | |
|             simplemask   = self.simplemasks_check(password)
 | |
|             advancedmask = self.advancedmask_check(password)
 | |
| 
 | |
|             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):
 | |
| 
 | |
|                 self.filter_counter += 1         
 | |
| 
 | |
|                 if pass_length in self.stats_length:
 | |
|                     self.stats_length[pass_length] += 1
 | |
|                 else:
 | |
|                     self.stats_length[pass_length] = 1
 | |
| 
 | |
|                 if characterset in self.stats_charactersets:
 | |
|                     self.stats_charactersets[characterset] += 1
 | |
|                 else:
 | |
|                     self.stats_charactersets[characterset] = 1
 | |
| 
 | |
|                 if simplemask in self.stats_simplemasks:
 | |
|                     self.stats_simplemasks[simplemask] += 1
 | |
|                 else:
 | |
|                     self.stats_simplemasks[simplemask] = 1
 | |
| 
 | |
|                 if advancedmask in self.stats_advancedmasks:
 | |
|                     self.stats_advancedmasks[advancedmask] += 1
 | |
|                 else:
 | |
|                     self.stats_advancedmasks[advancedmask] = 1
 | |
| 
 | |
|         f.close()
 | |
| 
 | |
|     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[*] Line Count Statistics..."
 | |
|         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
 | |
|             print "[+] %25d: %02d%% (%d)" % (length, count*100/self.filter_counter, count)
 | |
| 
 | |
|         print "\n[*] Charset statistics..."
 | |
|         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
 | |
|             print "[+] %25s: %02d%% (%d)" % (char, count*100/self.filter_counter, count)
 | |
| 
 | |
|         print "\n[*] Simple Mask statistics..."
 | |
|         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
 | |
|             print "[+] %25s: %02d%% (%d)" % (simplemask, count*100/self.filter_counter, count)
 | |
| 
 | |
|         print "\n[*] Advanced Mask statistics..."
 | |
|         for (advancedmask,count) in sorted(self.stats_advancedmasks.iteritems(), 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))
 | |
| 
 | |
| if __name__ == "__main__":
 | |
| 
 | |
|     header  = "                       _ \n"
 | |
|     header += "     StatsGen %s   | |\n"  % VERSION
 | |
|     header += "      _ __   __ _  ___| | _\n"
 | |
|     header += "     | '_ \ / _` |/ __| |/ /\n"
 | |
|     header += "     | |_) | (_| | (__|   < \n"
 | |
|     header += "     | .__/ \__,_|\___|_|\_\\\n"
 | |
|     header += "     | |                    \n"
 | |
|     header += "     |_| iphelix@thesprawl.org\n"
 | |
|     header += "\n"
 | |
| 
 | |
|     parser = OptionParser("%prog [options] passwords.txt\n\nType --help for more options", version="%prog "+VERSION)
 | |
| 
 | |
|     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")
 | |
|     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("-q", "--quiet", action="store_true", dest="quiet", default=False, help="Don't show headers.")
 | |
|     (options, args) = parser.parse_args()
 | |
| 
 | |
|     # Print program header
 | |
|     if not options.quiet:
 | |
|         print header
 | |
| 
 | |
|     if len(args) != 1:
 | |
|         parser.error("no passwords file specified")
 | |
|         exit(1)
 | |
| 
 | |
|     print "[*] Analyzing passwords in [%s]" % args[0]
 | |
| 
 | |
|     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.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])
 | |
|     statsgen.print_stats() |