""" Misc. helper functions used in Empire. Includes the PowerShell functions that generate the randomized stagers. """ from time import localtime, strftime from Crypto.Random import random import re import string import commands import base64 import binascii import sys import os import socket import sqlite3 import iptools ############################################################### # # Validation methods # ############################################################### def validate_hostname(hostname): """ Tries to validate a hostname. """ if len(hostname) > 255: return False if hostname[-1:] == ".": hostname = hostname[:-1] allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?', re.DOTALL), '', data) # strip blank lines and lines starting with # strippedCode = "\n".join([line for line in strippedCode.split('\n') if ((line.strip() != '') and (not line.strip().startswith("#")))]) return strippedCode ############################################################### # # Parsers # ############################################################### def parse_mimikatz(data): """ Parse the output from Invoke-Mimikatz to return credential sets. """ # cred format: # credType, domain, username, password, hostname, sid creds = [] # regexes for "sekurlsa::logonpasswords" Mimikatz output regexes = ["(?s)(?<=msv :).*?(?=tspkg :)", "(?s)(?<=tspkg :).*?(?=wdigest :)", "(?s)(?<=wdigest :).*?(?=kerberos :)", "(?s)(?<=kerberos :).*?(?=ssp :)", "(?s)(?<=ssp :).*?(?=credman :)", "(?s)(?<=credman :).*?(?=Authentication Id :)", "(?s)(?<=credman :).*?(?=mimikatz)"] hostDomain = "" domainSid = "" hostName = "" lines = data.split("\n") for line in lines[0:2]: if line.startswith("Hostname:"): try: domain = line.split(":")[1].strip() temp = domain.split("/")[0].strip() domainSid = domain.split("/")[1].strip() hostName = temp.split(".")[0] hostDomain = ".".join(temp.split(".")[1:]) except: pass for regex in regexes: p = re.compile(regex) for match in p.findall(data): lines2 = match.split("\n") username, domain, password = "", "", "" for line in lines2: try: if "Username" in line: username = line.split(":")[1].strip() elif "Domain" in line: domain = line.split(":")[1].strip() elif "NTLM" in line or "Password" in line: password = line.split(":")[1].strip() except: pass if username != "" and password != "" and password != "(null)": sid = "" # substitute the FQDN in if it matches if hostDomain.startswith(domain.lower()): domain = hostDomain sid = domainSid if validate_ntlm(password): credType = "hash" else: credType = "plaintext" # ignore machine account plaintexts if not (credType == "plaintext" and username.endswith("$")): creds.append((credType, domain, username, password, hostName, sid)) # check if we have lsadump output to check for krbtgt # happens on domain controller hashdumps for x in xrange(8,13): if lines[x].startswith("Domain :"): domain, sid, krbtgtHash = "", "", "" try: domainParts = lines[x].split(":")[1] domain = domainParts.split("/")[0].strip() sid = domainParts.split("/")[1].strip() # substitute the FQDN in if it matches if hostDomain.startswith(domain.lower()): domain = hostDomain sid = domainSid for x in xrange(0, len(lines)): if lines[x].startswith("User : krbtgt"): krbtgtHash = lines[x+2].split(":")[1].strip() break if krbtgtHash != "": creds.append(("hash", domain, "krbtgt", krbtgtHash, hostName, sid)) except Exception as e: pass return uniquify_tuples(creds) ############################################################### # # Miscellaneous methods (formatting, sorting, etc.) # ############################################################### def get_config(fields): """ Helper to pull common database config information outside of the normal menu execution. Fields should be comma separated. i.e. 'version,install_path' """ conn = sqlite3.connect('./data/empire.db', check_same_thread=False) conn.isolation_level = None cur = conn.cursor() cur.execute("SELECT "+fields+" FROM config") results = cur.fetchone() cur.close() conn.close() return results def get_datetime(): """ Return the current date/time """ return strftime("%Y-%m-%d %H:%M:%S", localtime()) def get_file_datetime(): """ Return the current date/time in a format workable for a file name. """ return strftime("%Y-%m-%d_%H-%M-%S", localtime()) def lhost(): """ Return the local IP. """ if os.name != "nt": import fcntl import struct def get_interface_ip(ifname): try: s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) return socket.inet_ntoa(fcntl.ioctl( s.fileno(), 0x8915, # SIOCGIFADDR struct.pack('256s', ifname[:15]) )[20:24]) except IOError as e: return "" try: ip = socket.gethostbyname(socket.gethostname()) if ip.startswith("127.") and os.name != "nt": interfaces = ["eth0","eth1","eth2","wlan0","wlan1","wifi0","ath0","ath1","ppp0"] for ifname in interfaces: try: ip = get_interface_ip(ifname) if ip != "": break except: print "Unexpected error:", sys.exc_info()[0] pass except: print "Unexpected error:", sys.exc_info()[0] pass return ip def color(string, color=None): """ Change text color for the Linux terminal. """ attr = [] # bold attr.append('1') if color: if color.lower() == "red": attr.append('31') elif color.lower() == "green": attr.append('32') elif color.lower() == "blue": attr.append('34') return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), string) else: if string.startswith("[!]"): attr.append('31') return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), string) elif string.startswith("[+]"): attr.append('32') return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), string) elif string.startswith("[*]"): attr.append('34') return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), string) else: return string def unique(seq, idfun=None): # uniquify a list, order preserving # from http://www.peterbe.com/plog/uniqifiers-benchmark if idfun is None: def idfun(x): return x seen = {} result = [] for item in seq: marker = idfun(item) # in old Python versions: # if seen.has_key(marker) # but in new ones: if marker in seen: continue seen[marker] = 1 result.append(item) return result def uniquify_tuples(tuples): # uniquify mimikatz tuples based on the password # cred format- (credType, domain, username, password, hostname, sid) seen = set() return [item for item in tuples if "%s%s%s%s"%(item[0],item[1],item[2],item[3]) not in seen and not seen.add("%s%s%s%s"%(item[0],item[1],item[2],item[3]))] def urldecode(url): """ URL decode a string. """ rex=re.compile('%([0-9a-hA-H][0-9a-hA-H])',re.M) return rex.sub(htc,url) def decode_base64(data): """ Try to decode a base64 string. From http://stackoverflow.com/questions/2941995/python-ignore-incorrect-padding-error-when-base64-decoding """ missing_padding = 4 - len(data) % 4 if missing_padding: data += b'='* missing_padding try: result = base64.decodestring(data) return result except binascii.Error: # if there's a decoding error, just return the data return data def encode_base64(data): """ Decode data as a base64 string. """ return base64.encodestring(data).strip() def complete_path(text, line, arg=False): """ Helper for tab-completion of file paths. """ # stolen from dataq at # http://stackoverflow.com/questions/16826172/filename-tab-completion-in-cmd-cmd-of-python if arg: # if we have "command something path" argData = line.split()[1:] else: # if we have "command path" argData = line.split()[0:] if not argData or len(argData) == 1: completions = os.listdir('./') else: dir, part, base = argData[-1].rpartition('/') if part == '': dir = './' elif dir == '': dir = '/' completions = [] for f in os.listdir(dir): if f.startswith(base): if os.path.isfile(os.path.join(dir,f)): completions.append(f) else: completions.append(f+'/') return completions