From 49ea2833f4a1a94da56ae6974872aa0215a7116c Mon Sep 17 00:00:00 2001 From: kmahyyg <16604643+kmahyyg@users.noreply.github.com> Date: Mon, 1 Aug 2022 04:15:25 +0800 Subject: [PATCH] update legacy support --- README.md | 14 ++-- mremoteng_decrypt.py | 132 +++++++++++++++++++++++++----------- mremoteng_decrypt_legacy.py | 49 ------------- 3 files changed, 103 insertions(+), 92 deletions(-) delete mode 100644 mremoteng_decrypt_legacy.py diff --git a/README.md b/README.md index c63a4b5..cda0b42 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,14 @@ # mRemoteNG Decrypt -Use for Decrypt mRemoteNG v1.75+ Config. For v1.74 and lower, use Python version with `_legacy` suffix. +# Python Version + +Usage: `python3 mremoteng_decrypt.py [-f FILE | -s STRING | -rf XML_FILE] [-p CUSTOM_PASSWORD] [-L IS_LEGACY]` + +If version < v1.75, make sure you specify `-L true`. + +# Java Version + +Use for Decrypt mRemoteNG v1.75+ Config. Usage: `java -jar [Custom Password]` @@ -12,10 +20,6 @@ org.apache.commons.codec org.bouncycastle -## Python Script - -Usage: `python3 mremoteng_decrypt.py [-f FILE | -s STRING] [-p CUSTOM_PASSWORD]` - ## Where's the file? Carefully check the page. diff --git a/mremoteng_decrypt.py b/mremoteng_decrypt.py index 42305cc..07b8b96 100644 --- a/mremoteng_decrypt.py +++ b/mremoteng_decrypt.py @@ -3,47 +3,103 @@ import hashlib import base64 from Cryptodome.Cipher import AES +from Cryptodome.Util.Padding import unpad import argparse import sys +import xml.etree.ElementTree as ET + + +def decrypt_legacy(encrypted_data, password): + try: + encrypted_data = encrypted_data.strip() + encrypted_data = base64.b64decode(encrypted_data) + initial_vector = encrypted_data[:16] + ciphertext = encrypted_data[16:] + key = hashlib.md5(password.encode()).digest() + + cipher = AES.new(key, AES.MODE_CBC, initial_vector) + plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size) + return plaintext + except Exception as e: + print("Failed to decrypt the password with the following error: {}".format(e)) + return b'' + +def decrypt(encrypted_data, password): + try: + encrypted_data = encrypted_data.strip() + encrypted_data = base64.b64decode(encrypted_data) + salt = encrypted_data[:16] + associated_data = encrypted_data[:16] + nonce = encrypted_data[16:32] + ciphertext = encrypted_data[32:-16] + tag = encrypted_data[-16:] + key = hashlib.pbkdf2_hmac( + "sha1", password.encode(), salt, 1000, dklen=32) + + cipher = AES.new(key, AES.MODE_GCM, nonce=nonce) + cipher.update(associated_data) + plaintext = cipher.decrypt_and_verify(ciphertext, tag) + return plaintext + except Exception as e: + print("Failed to decrypt the password with the following error: {}".format(e)) + return b'' + def main(): - parser = argparse.ArgumentParser(description="Decrypt mRemoteNG passwords.") - group = parser.add_mutually_exclusive_group() - group.add_argument("-f", "--file", help="name of file containing mRemoteNG password") - group.add_argument("-s", "--string", help="base64 string of mRemoteNG password") - parser.add_argument("-p", "--password", help="Custom password", default="mR3m") - - if len(sys.argv) < 2: - parser.print_help(sys.stderr) - sys.exit(1) - - args = parser.parse_args() - encrypted_data = "" - if args.file != None: - with open(args.file) as f: - encrypted_data = f.read() - encrypted_data = encrypted_data.strip() - encrypted_data = base64.b64decode(encrypted_data) - - elif args.string != None: - encrypted_data = args.string - encrypted_data = base64.b64decode(encrypted_data) - - else: - print("Please use either the file (-f, --file) or string (-s, --string) flag") - sys.exit(1) - - salt = encrypted_data[:16] - associated_data = encrypted_data[:16] - nonce = encrypted_data[16:32] - ciphertext = encrypted_data[32:-16] - tag = encrypted_data[-16:] - key = hashlib.pbkdf2_hmac("sha1", args.password.encode(), salt, 1000, dklen=32) - - cipher = AES.new(key, AES.MODE_GCM, nonce=nonce) - cipher.update(associated_data) - plaintext = cipher.decrypt_and_verify(ciphertext, tag) - print("Password: {}".format(plaintext.decode("utf-8"))) + parser = argparse.ArgumentParser( + description="Decrypt mRemoteNG passwords.") + if len(sys.argv) < 2: + parser.print_help(sys.stderr) + sys.exit(1) + + group = parser.add_mutually_exclusive_group() + group.add_argument( + "-f", "--file", help="Name of file containing mRemoteNG password") + # Thanks idea from @KingKorSin + group.add_argument( + "-rf", "--realFile", help="Name of the Real mRemoteNG connections file containing the passwords") + group.add_argument( + "-s", "--string", help="base64 string of mRemoteNG password") + parser.add_argument("-p", "--password", + help="Custom password", default="mR3m") + parser.add_argument("-L", "--legacy", help="version <= 1.74", type=bool, default=False) + args = parser.parse_args() + + decrypt_func = decrypt + if args.legacy: + decrypt_func = decrypt_legacy + + if args.realFile != None: + tree = ET.parse(args.realFile) + root = tree.getroot() + for node in root.iter('Node'): + if node.attrib['Password']: + decPass = decrypt_func(node.attrib['Password'], args.password) + if node.attrib['Username']: + print("Username: {}".format(node.attrib['Username'])) + if node.attrib['Hostname']: + print("Hostname: {}".format(node.attrib['Hostname'])) + print("Password: {} \n".format(decPass.decode("utf-8"))) + sys.exit(1) + + elif args.file != None: + with open(args.file) as f: + encrypted_data = f.read() + decPass = decrypt(encrypted_data, args.password) + + elif args.string != None: + encrypted_data = args.string + decPass = decrypt(encrypted_data, args.password) + + else: + print("Please use either the file (-f, --file) or string (-s, --string) flag") + sys.exit(1) + + try: + print("Password: {}".format(decPass.decode("utf-8"))) + except Exception as e: + print("Failed to find the password property with the following error: {}".format(e)) + if __name__ == "__main__": - main() + main() diff --git a/mremoteng_decrypt_legacy.py b/mremoteng_decrypt_legacy.py deleted file mode 100644 index 0cb24a0..0000000 --- a/mremoteng_decrypt_legacy.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python3 -# -# Version < 1.74 -# https://www.errno.fr/mRemoteNG.html - -import hashlib -import base64 -from Cryptodome.Cipher import AES -from Cryptodome.Util.Padding import unpad -import argparse -import sys - -def main(): - parser = argparse.ArgumentParser(description="Decrypt mRemoteNG passwords.") - group = parser.add_mutually_exclusive_group() - group.add_argument("-f", "--file", help="name of file containing mRemoteNG password") - group.add_argument("-s", "--string", help="base64 string of mRemoteNG password") - parser.add_argument("-p", "--password", help="Custom password", default="mR3m") - - if len(sys.argv) < 2: - parser.print_help(sys.stderr) - sys.exit(1) - - args = parser.parse_args() - encrypted_data = "" - if args.file != None: - with open(args.file) as f: - encrypted_data = f.read() - encrypted_data = encrypted_data.strip() - encrypted_data = base64.b64decode(encrypted_data) - - elif args.string != None: - encrypted_data = args.string - encrypted_data = base64.b64decode(encrypted_data) - - else: - print("Please use either the file (-f, --file) or string (-s, --string) flag") - sys.exit(1) - - initial_vector = encrypted_data[:16] - ciphertext = encrypted_data[16:] - key = hashlib.md5(args.password.encode()).digest() - - cipher = AES.new(key, AES.MODE_CBC, initial_vector) - plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size) - print("Password: {}".format(plaintext.decode("utf-8"))) - -if __name__ == "__main__": - main()