diff --git a/CHANGELOG b/CHANGELOG index 80e85c04..0cf28de9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,10 @@ +LaZagne 0.8 (11/06/2015) +- Only Linux + * /etc/shadow modules (dictionary attack on hash) + +- For Windows / Linux + * management of the following options "-path" (for dictionary attack) and "-b" (for bruteforce attack) in a different way. Used as general options and not implemented by module. Using the same option, the file will be used by different modules; example: to find the mozilla masterpassword, the unix system password (from the hash), used by skype (for windows), etc. + LaZagne 0.71 (04/06/2015) - Only Linux * Wifi password module from WPA Supplicant implemented (by rpesche) diff --git a/Linux/src/LaZagne.py b/Linux/src/LaZagne.py index 2040bb0f..cbc7c1ab 100644 --- a/Linux/src/LaZagne.py +++ b/Linux/src/LaZagne.py @@ -75,22 +75,29 @@ def launch_module(b): b[i].run() def manage_advanced_options(): - if 'manually' in args: - constant.manually = args['manually'] + + # file used for dictionary attacks if 'path' in args: constant.path = args['path'] if 'bruteforce' in args: constant.bruteforce = args['bruteforce'] - if 'defaultpass' in args: - constant.defaultpass = args['defaultpass'] + + # mozilla advanced options + if 'manually' in args: + constant.manually = args['manually'] if 'specific_path' in args: constant.specific_path = args['specific_path'] + if 'mails' in args['auditType']: constant.mozilla_software = 'Thunderbird' elif 'browsers' in args['auditType']: constant.mozilla_software = 'Firefox' + + # jitsi advanced options if 'master_pwd' in args: constant.jitsi_masterpass = args['master_pwd'] + + # i.e advanced options if 'historic' in args: constant.ie_historic = args['historic'] @@ -124,6 +131,8 @@ def error(self, message): PPoptional._optionals.title = 'optional arguments' PPoptional.add_argument('-v', dest='verbose', action='count', default=0, help='increase verbosity level') PPoptional.add_argument('--version', action='version', version='Version ' + str(constant.CURRENT_VERSION), help='laZagne version') +PPoptional.add_argument('-path', dest='path', action= 'store', help = 'path of a file used for dictionnary file') +PPoptional.add_argument('-b', dest='bruteforce', action= 'store', help = 'number of character to brute force') # Output PWrite = argparse.ArgumentParser(add_help=False,formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=constant.MAX_HELP_POSITION)) diff --git a/Linux/src/config/constant.py b/Linux/src/config/constant.py index 3f745bd5..dca92664 100644 --- a/Linux/src/config/constant.py +++ b/Linux/src/config/constant.py @@ -2,7 +2,7 @@ class constant(): folder_name = 'results' MAX_HELP_POSITION = 27 - CURRENT_VERSION = 0.71 + CURRENT_VERSION = 0.8 output = None file_logger = None verbose = False @@ -14,7 +14,6 @@ class constant(): manually = None path = None bruteforce = None - defaultpass = None specific_path = None mozilla_software = '' diff --git a/Linux/src/config/manageModules.py b/Linux/src/config/manageModules.py index ea6f45eb..8818ff0a 100644 --- a/Linux/src/config/manageModules.py +++ b/Linux/src/config/manageModules.py @@ -7,6 +7,7 @@ # sysadmin from softwares.sysadmin.filezilla import Filezilla from softwares.sysadmin.env_variable import Env_variable +from softwares.sysadmin.shadow import Shadow # chats from softwares.chats.pidgin import Pidgin from softwares.chats.jitsi import Jitsi @@ -40,6 +41,7 @@ def get_modules(): Mozilla(), Opera(), Pidgin(), + Shadow(), SQLDeveloper(), Squirrel(), Wifi(), diff --git a/Linux/src/config/write_output.py b/Linux/src/config/write_output.py index b5fb2fc0..320a68a2 100644 --- a/Linux/src/config/write_output.py +++ b/Linux/src/config/write_output.py @@ -65,24 +65,33 @@ def print_output(software_name, pwdFound): Header().title(software_name) toWrite = [] + password_category = False for pwd in pwdFound: + # detect which kinds of password has been found lower_list = [s.lower() for s in pwd.keys()] password = [s for s in lower_list if "password" in s] - key = [s for s in lower_list if "key" in s] # for the wifi + if password: + password_category = password + else: + key = [s for s in lower_list if "key" in s] # for the wifi + if key: + password_category = key + else: + hash = [s for s in lower_list if "hash" in s] + if hash: + password_category = hash # No password found - if not password and not key: + if not password_category: print_debug("FAILED", "Password not found !!!") else: - print_debug("OK", "Password found !!!") + print_debug("OK", '%s found !!!' % password_category[0].title()) toWrite.append(pwd) + # Store all passwords found on a table => for dictionary attack if master password set constant.nbPasswordFound += 1 try: - if password: - constant.passwordFound.append(pwd['Password'].strip()) - elif key: - constant.passwordFound.append(pwd['key']) + constant.passwordFound.append(pwd[password_category[0]]) except: pass diff --git a/Linux/src/softwares/browsers/mozilla.py b/Linux/src/softwares/browsers/mozilla.py index e36bd78a..c3956622 100644 --- a/Linux/src/softwares/browsers/mozilla.py +++ b/Linux/src/softwares/browsers/mozilla.py @@ -10,7 +10,7 @@ import json import shutil from config.dico import get_dico -import itertools +from itertools import product #https://pypi.python.org/pypi/pyasn1/ from pyasn1.codec.der import decoder from struct import unpack @@ -91,16 +91,13 @@ def __init__(self, isThunderbird = False): self.toCheck = [] self.manually_pass = None self.dictionnary_path = None - self.number_toStop = None + self.number_toStop = 0 self.key3 = '' # Manage options suboptions = [ {'command': '-m', 'action': 'store', 'dest': 'manually', 'help': 'enter the master password manually', 'title': 'Advanced Mozilla master password options'}, - {'command': '-p', 'action': 'store', 'dest': 'path', 'help': 'path of a dictionnary file', 'title': 'Advanced Mozilla master password options'}, - {'command': '-b', 'type':int, 'action': 'store', 'dest': 'bruteforce', 'help': 'number of caracter to brute force', 'title': 'Advanced Mozilla master password options'}, - {'command': '-d', 'action': 'store_true', 'dest': 'defaultpass', 'help': 'try 500 most common passwords', 'title': 'Advanced Mozilla master password options'}, {'command': '-s', 'action': 'store', 'dest': 'specific_path', 'help': 'enter the specific path to a profile you want to crack', 'title': 'Advanced Mozilla master password options'} ] @@ -129,12 +126,9 @@ def manage_advanced_options(self): self.toCheck.append('a') if constant.bruteforce: - self.number_toStop = constant.bruteforce + self.number_toStop = int(constant.bruteforce) + 1 self.toCheck.append('b') - if constant.defaultpass: - self.toCheck.append('d') - # default attack if self.toCheck == []: self.toCheck = ['b', 'd'] @@ -395,21 +389,20 @@ def found_masterpassword(self): print_debug('WARNING', 'No password has been found using the default list') # brute force attack - if 'b' in self.toCheck: + if 'b' in self.toCheck or constant.bruteforce: charset_list = 'abcdefghijklmnopqrstuvwxyz1234567890!?' - tab = [i for i in charset_list] - - print_debug('ATTACK', 'Brute force attack !!! (%s characters)' % str(self.number_toStop)) - current = 0 - pass_found = False + print_debug('ATTACK', 'Brute force attack !!! (%s characters)' % str(constant.bruteforce)) + print_debug('DEBUG', 'charset: %s' % charset_list) + try: - while current <= self.number_toStop and pass_found == False: - for i in itertools.product(tab, repeat=current): - word = ''.join(map(str,i)) - if self.is_masterpassword_correct(word)[0]: - print_debug('FIND', 'Master password found: %s' % word.strip()) - return word.strip() - current+= 1 + for length in range(1, int(self.number_toStop)): + words = product(charset_list, repeat=length) + for word in words: + print_debug('DEBUG', '%s' % ''.join(word)) + if self.is_masterpassword_correct(''.join(word))[0]: + w = ''.join(word) + print_debug('FIND', 'Master password found: %s' % w.strip()) + return w.strip() except (KeyboardInterrupt, SystemExit): print 'INTERRUPTED!' print_debug('INFO', 'Dictionnary attack interrupted') @@ -417,7 +410,8 @@ def found_masterpassword(self): print_debug('DEBUG', '{0}'.format(e)) print_debug('WARNING', 'No password has been found using the brute force attack') - return False + + return False # ------------------------------ End of Master Password Functions ------------------------------ @@ -529,4 +523,3 @@ def run(self): # print the results print_output(software_name, pwdFound) - diff --git a/Linux/src/softwares/sysadmin/shadow.py b/Linux/src/softwares/sysadmin/shadow.py new file mode 100644 index 00000000..37930d05 --- /dev/null +++ b/Linux/src/softwares/sysadmin/shadow.py @@ -0,0 +1,135 @@ +import os,sys +import crypt +from config.header import Header +from config.write_output import print_debug, print_output +from config.moduleInfo import ModuleInfo +from config.dico import get_dico +from config.constant import * +from itertools import product + +class Shadow(ModuleInfo): + + def __init__(self): + # Manage options + options = {'command': '-s', 'action': 'store_true', 'dest': 'shadow', 'help': '/etc/shadow - Need root Privileges'} + ModuleInfo.__init__(self, 'shadow', 'sysadmin', options) + + self.filestr = '/etc/shadow' + self.hash = '\n' + self.pwdFound = [] + + # used for dictionary attack, if user specify a specific file + def get_dic(self, dictionnary_path): + words = [] + if dictionnary_path: + try: + dicFile = open (dictionnary_path,'r') + except Exception,e: + print_debug('DEBUG', '{0}'.format(e)) + print_debug('ERROR', 'Unable to open passwords file: %s' % str(self.dictionnary_path)) + return [] + + for word in dicFile.readlines(): + words.append(word.strip('\n')) + dicFile.close() + return words + + def attack(self, user, cryptPwd): + # By default 500 most famous passwords are used for the dictionary attack + dic = get_dico() + # add the user on the list to found weak password (login equal password) + dic.insert(0, user) + + # file for dictionnary attack entered + if constant.path: + if os.path.exists(constant.path): + dic = self.get_dic(constant.path) + else: + print_debug('WARNING', 'The file does not exist: %s' % str(constant.path)) + + # Different possible hash type + # ID | Method + # -------------------------------------------------------------------------- + # 1 | MD5 + # 2 | Blowfish (not in mainline glibc; added in some Linux distributions) + # 5 | SHA-256 (since glibc 2.7) + # 6 | SHA-512 (since glibc 2.7) + + hashType = cryptPwd.split("$")[1] + values = {'Category': 'System Account'} + + if hashType == '1': # MD5 + print_debug('INFO', '[+] Hash type MD5 detected ...') + elif hashType == '2': + print_debug('INFO', '[+] Hash type Blowfish detected ...') + elif hashType == '5': + print_debug('INFO', '[+] Hash type SHA-256 detected ...') + elif hashType == '6': # ShA-512 => used by all modern computers + print_debug('INFO', '[+] Hash type SHA-512 detected ...') + + salt = cryptPwd.split("$")[2] + realSalt = "$" + hashType + "$" + salt + "$" + + # -------------------------- Dictionary attack -------------------------- + print_debug('INFO', 'Dictionnary Attack on the hash !!! ') + try: + for word in dic: + try: + cryptWord = crypt.crypt(word, realSalt) + except Exception,e: + print_debug('DEBUG', '{0}'.format(e)) + cryptWord = '' + + if cryptWord == cryptPwd: + values['User'] = user + values['password'] = word + self.pwdFound.append(values) + return + except (KeyboardInterrupt, SystemExit): + print 'INTERRUPTED!' + print_debug('DEBUG', 'Dictionnary attack interrupted') + except Exception,e: + print_debug('DEBUG', '{0}'.format(e)) + + print_debug('INFO', 'No password found using this attack !!! ') + + def root_access(self): + if os.getuid() != 0: + print_debug('INFO', 'You need more privileges (run it with sudo)\n') + return False + return True + + def check_file_access(self): + if not os.path.exists(self.filestr): + print_debug('WARNING', 'The path "%s" does not exist' % s(self.filestr)) + return False + return True + + def run(self): + Header().title_info('System account (from /etc/shadow)') + + # check root access + if self.root_access(): + if self.check_file_access(): + shadowFile = open (self.filestr,'r') + for line in shadowFile.readlines(): + _hash = line.replace('\n', '') + + line = _hash.split(':') + + # check if a password is defined + if not line[1] in [ 'x', '*','!' ]: + user = line[0] + cryptPwd = line[1] + + # save each hash non empty + self.hash += _hash + '\n' + + # try dictionary and bruteforce attack + self.attack(user, cryptPwd) + + values = {'Category' : 'Hash', 'Hash' : self.hash } + self.pwdFound.append(values) + + # print the results + print_output('System account (from /etc/shadow)', self.pwdFound) diff --git a/Linux/standalone/32bits/LaZagne-32bits b/Linux/standalone/32bits/LaZagne-32bits index fbcdfbd7..e0f04fc2 100755 Binary files a/Linux/standalone/32bits/LaZagne-32bits and b/Linux/standalone/32bits/LaZagne-32bits differ diff --git a/Linux/standalone/64bits/LaZagne-64bits b/Linux/standalone/64bits/LaZagne-64bits index 8a1058be..cd51611a 100755 Binary files a/Linux/standalone/64bits/LaZagne-64bits and b/Linux/standalone/64bits/LaZagne-64bits differ diff --git a/Windows/src/LaZagne/config/constant.py b/Windows/src/LaZagne/config/constant.py index 10207b6d..e276942c 100644 --- a/Windows/src/LaZagne/config/constant.py +++ b/Windows/src/LaZagne/config/constant.py @@ -2,7 +2,7 @@ class constant(): folder_name = 'results' MAX_HELP_POSITION = 27 - CURRENT_VERSION = 0.7 + CURRENT_VERSION = 0.8 output = None file_logger = None @@ -10,11 +10,9 @@ class constant(): jitsi_masterpass = None # mozilla options - isInteractive = False manually = None path = None bruteforce = None - defaultpass = None specific_path = None mozilla_software = '' diff --git a/Windows/src/LaZagne/laZagne.py b/Windows/src/LaZagne/laZagne.py index fa01b530..eeeccd04 100644 --- a/Windows/src/LaZagne/laZagne.py +++ b/Windows/src/LaZagne/laZagne.py @@ -75,22 +75,28 @@ def launch_module(b): b[i].run() def manage_advanced_options(): - if 'manually' in args: - constant.manually = args['manually'] + # file used for dictionary attacks if 'path' in args: constant.path = args['path'] if 'bruteforce' in args: constant.bruteforce = args['bruteforce'] - if 'defaultpass' in args: - constant.defaultpass = args['defaultpass'] + + # mozilla advanced options + if 'manually' in args: + constant.manually = args['manually'] if 'specific_path' in args: constant.specific_path = args['specific_path'] + if 'mails' in args['auditType']: constant.mozilla_software = 'Thunderbird' elif 'browsers' in args['auditType']: constant.mozilla_software = 'Firefox' + + # jitsi advanced options if 'master_pwd' in args: constant.jitsi_masterpass = args['master_pwd'] + + # i.e advanced options if 'historic' in args: constant.ie_historic = args['historic'] @@ -124,6 +130,8 @@ def error(self, message): PPoptional._optionals.title = 'optional arguments' PPoptional.add_argument('-v', dest='verbose', action='count', default=0, help='increase verbosity level') PPoptional.add_argument('--version', action='version', version='Version ' + str(constant.CURRENT_VERSION), help='laZagne version') +PPoptional.add_argument('-path', dest='path', action= 'store', help = 'path of a file used for dictionnary file') +PPoptional.add_argument('-b', dest='bruteforce', action= 'store', help = 'number of character to brute force') # Output PWrite = argparse.ArgumentParser(add_help=False,formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=constant.MAX_HELP_POSITION)) diff --git a/Windows/src/LaZagne/softwares/browsers/mozilla.py b/Windows/src/LaZagne/softwares/browsers/mozilla.py index 9953b249..0dfa60e5 100644 --- a/Windows/src/LaZagne/softwares/browsers/mozilla.py +++ b/Windows/src/LaZagne/softwares/browsers/mozilla.py @@ -10,7 +10,7 @@ import json import shutil from config.dico import get_dico -import itertools +from itertools import product #https://pypi.python.org/pypi/pyasn1/ from pyasn1.codec.der import decoder from struct import unpack @@ -98,9 +98,6 @@ def __init__(self, isThunderbird = False): # Manage options suboptions = [ {'command': '-m', 'action': 'store', 'dest': 'manually', 'help': 'enter the master password manually', 'title': 'Advanced Mozilla master password options'}, - {'command': '-p', 'action': 'store', 'dest': 'path', 'help': 'path of a dictionnary file', 'title': 'Advanced Mozilla master password options'}, - {'command': '-b', 'type':int, 'action': 'store', 'dest': 'bruteforce', 'help': 'number of caracter to brute force', 'title': 'Advanced Mozilla master password options'}, - {'command': '-d', 'action': 'store_true', 'dest': 'defaultpass', 'help': 'try 500 most common passwords', 'title': 'Advanced Mozilla master password options'}, {'command': '-s', 'action': 'store', 'dest': 'specific_path', 'help': 'enter the specific path to a profile you want to crack', 'title': 'Advanced Mozilla master password options'} ] @@ -133,12 +130,9 @@ def manage_advanced_options(self): self.toCheck.append('a') if constant.bruteforce: - self.number_toStop = constant.bruteforce + self.number_toStop = int(constant.bruteforce) + 1 self.toCheck.append('b') - if constant.defaultpass: - self.toCheck.append('d') - # default attack if self.toCheck == []: self.toCheck = ['b', 'd'] @@ -399,21 +393,20 @@ def found_masterpassword(self): print_debug('WARNING', 'No password has been found using the default list') # brute force attack - if 'b' in self.toCheck: + if 'b' in self.toCheck or constant.bruteforce: charset_list = 'abcdefghijklmnopqrstuvwxyz1234567890!?' - tab = [i for i in charset_list] - - print_debug('ATTACK', 'Brute force attack !!! (%s characters)' % str(self.number_toStop)) - current = 0 - pass_found = False + print_debug('ATTACK', 'Brute force attack !!! (%s characters)' % str(constant.bruteforce)) + print_debug('DEBUG', 'charset: %s' % charset_list) + try: - while current <= self.number_toStop and pass_found == False: - for i in itertools.product(tab, repeat=current): - word = ''.join(map(str,i)) - if self.is_masterpassword_correct(word)[0]: - print_debug('FIND', 'Master password found: %s' % word.strip()) - return word.strip() - current+= 1 + for length in range(1, int(self.number_toStop)): + words = product(charset_list, repeat=length) + for word in words: + print_debug('DEBUG', '%s' % ''.join(word)) + if self.is_masterpassword_correct(''.join(word))[0]: + w = ''.join(word) + print_debug('FIND', 'Master password found: %s' % w.strip()) + return w.strip() except (KeyboardInterrupt, SystemExit): print 'INTERRUPTED!' print_debug('INFO', 'Dictionnary attack interrupted') @@ -421,7 +414,7 @@ def found_masterpassword(self): print_debug('DEBUG', '{0}'.format(e)) print_debug('WARNING', 'No password has been found using the brute force attack') - return False + return False # ------------------------------ End of Master Password Functions ------------------------------ diff --git a/Windows/src/LaZagne/softwares/chats/skype.py b/Windows/src/LaZagne/softwares/chats/skype.py index 5a5e4ed3..66f64a2b 100644 --- a/Windows/src/LaZagne/softwares/chats/skype.py +++ b/Windows/src/LaZagne/softwares/chats/skype.py @@ -75,8 +75,29 @@ def get_md5_hash(self, enc_hex, key): # byte to hex return binascii.hexlify(tmp) + # used for dictionary attack, if user specify a specific file + def get_dic_file(self, dictionnary_path): + words = [] + if dictionnary_path: + try: + dicFile = open (dictionnary_path,'r') + except Exception,e: + print_debug('DEBUG', '{0}'.format(e)) + print_debug('ERROR', 'Unable to open passwords file: %s' % str(dictionnary_path)) + return [] + + for word in dicFile.readlines(): + words.append(word.strip('\n')) + dicFile.close() + return words + def dictionary_attack(self, login, md5): wordlist = get_dico() + + # if the user specify the file path + if constant.path: + wordlist += self.get_dic_file(constant.path) + for word in wordlist: hash = hashlib.md5('%s\nskyper\n%s' % (login, word)).hexdigest() if hash == md5: diff --git a/Windows/src/LaZagne/softwares/windows/secretsdump.py b/Windows/src/LaZagne/softwares/windows/secretsdump.py index 7ab06a8c..88d6525c 100644 --- a/Windows/src/LaZagne/softwares/windows/secretsdump.py +++ b/Windows/src/LaZagne/softwares/windows/secretsdump.py @@ -63,6 +63,7 @@ import ntpath import time import string +from itertools import product try: from Crypto.Cipher import DES, ARC4, AES @@ -1039,14 +1040,54 @@ def create_nthash(self, word): generated_hash = hashlib.new('md4', word.encode('utf-16le')).digest() return binascii.hexlify(generated_hash) - def bruteForce_Hash(self, hash): - # check with a basic dictionary list and with all passwords already found + def dictionaryAttack_Hash(self, hash): + # check using a basic dictionary list and all passwords already found for word in self.wordlist: generated_hash = self.create_nthash(word) if generated_hash == hash: return word return False + def bruteFortce_hash(self, hash): + # brute force attack + charset_list = 'abcdefghijklmnopqrstuvwxyz1234567890!?' + print_debug('ATTACK', 'Brute force attack !!! (%s characters)' % str(constant.bruteforce)) + print_debug('DEBUG', 'charset: %s' % charset_list) + + try: + for length in range(1, int(constant.bruteforce)+1): + words = product(charset_list, repeat=length) + for word in words: + print_debug('DEBUG', '%s' % ''.join(word)) + generated_hash = self.create_nthash(''.join(word).strip()) + if generated_hash == hash: + return ''.join(word) + + except (KeyboardInterrupt, SystemExit): + print 'INTERRUPTED!' + print_debug('INFO', 'Dictionnary attack interrupted') + except Exception,e: + print_debug('DEBUG', '{0}'.format(e)) + + print_debug('WARNING', 'No password has been found using the brute force attack') + return False + + # used for dictionary attack, if user specify a specific file + def get_dic(self, dictionnary_path): + words = [] + if dictionnary_path: + try: + dicFile = open (dictionnary_path,'r') + except Exception,e: + print_debug('DEBUG', '{0}'.format(e)) + print_debug('ERROR', 'Unable to open passwords file: %s' % str(dictionnary_path)) + return [] + + for word in dicFile.readlines(): + words.append(word.strip('\n')) + dicFile.close() + return words + def hashes_to_dic(self, title, format, content): Header().title1(title) print_debug('INFO', 'Format: (%s)' % format) @@ -1054,14 +1095,19 @@ def hashes_to_dic(self, title, format, content): items = sorted(content) pwdFound = [] values = {} - + self.wordlist += self.get_dic(constant.path) all_hash = '\r\n' for item in items: hash = content[item] (uid, rid, lmhash, nthash) = hash.split(':')[:4] + + # add the user on the list to found weak password (login equal password) self.wordlist.append(uid.encode("utf8")) all_hash = '%s\r\n%s' % (all_hash, hash) - password = self.bruteForce_Hash(nthash) + password = self.dictionaryAttack_Hash(nthash) + + if not password and constant.bruteforce: + password = self.bruteFortce_hash(nthash) # if a password has been found from the dictionary attack if password: diff --git a/Windows/standalone/laZagne.exe b/Windows/standalone/laZagne.exe index e8801d91..38133618 100644 Binary files a/Windows/standalone/laZagne.exe and b/Windows/standalone/laZagne.exe differ