The sample is a .NET packed remcos sample.

MD5: 5e9770c2b22b03e5726285900afab954

SHA256: ad3dc7a0c6ce33a7e45775b3452343eb748fab8823311df58d4599d6a203ff80

VT Link: https://www.virustotal.com/gui/file/ad3dc7a0c6ce33a7e45775b3452343eb748fab8823311df58d4599d6a203ff80/

MalwareBazaar Link: https://bazaar.abuse.ch/sample/ad3dc7a0c6ce33a7e45775b3452343eb748fab8823311df58d4599d6a203ff80/

Analysing the sample

First Stage

Upon examining the sample in dnspy, we observe the following:

image from dnspy
Fig 1: monoFlat_b being called
  • Initially, the sample commences execution from myObject(), which triggers monoFlat_b() and invokes the GetTypes() function on the returned value. The variable type holds the method defined in the object returned by monoFlat_b().
    image from dnspy
    Fig 2: monoFlat_b calls monoFlatxB
  • monoFlat_b() calls the function monoFlatxB(). The object returned by it is loaded into memory using AppDomain.CurrentDomain.Load() which is of type assembly. This object is then returned by the function monoFlat_b().
    Resource being extracted
    Fig 3: Resource extraction
    Resources in the sample
    Fig 4. Resource section of the sample
  • In monoFlatxB(), artifacts present in subresource are extracted. The variable MainWindow.nabexx is set to “XX”. So, 2 files are extracted from the sample: XX and XXXXXX. Then it calls the function DmonoFlatECXES()
    Fig 5. Decryption routine
  • In DmonoFlatECXES(), the resource named “XXXXXX” is decrypted using artifact “XX” as the key. The decryption process utilizes AES-ECB mode, which is not the most secure version of AES due to its susceptibility to cryptographic attacks.

After decryption, it is loaded into memory, initiating the next phase of execution.

Now we can write a Python script to extract subresources and decrypt it using AES-ECB. We will use the library dotnetfile in order to extract subresources from the sample and the library pycryptodome for implementing cryptographic functions.

Python code:

import dotnetfile 
from Crypto.Cipher import AES

def aes_ecb_decrypt(key,data):
    ''' decrypts data using AES-ECB mode '''
    cipher = AES.new(key,AES.MODE_ECB)
    decrypted_data = cipher.decrypt(data)
    return  decrypted_data

def subresource_extract(filename):
    ''' extracts subresources from dotnetfile ''' 
    ''' reference : https://github.com/pan-unit42/dotnetfile/blob/main/examples/dotnetfile_dump.py '''
    extracted_subresources = []
    dotnet_file =dotnetfile.DotNetPE(filename)
    resource_data = dotnet_file.get_resources()
    for data in resource_data:
        for resource_item in data.items():
            if resource_item[0] == 'SubResources':
                if resource_item[1]:
                    
                    for sub_resource in resource_item[1]:
                        
                        for sub_resource_item in sub_resource.items():
                            
                            if sub_resource_item[0] == "Data":
                                extracted_subresources.append(sub_resource_item[1])
                            
    return extracted_subresources

def main ():

    ''' Name of the file'''
    filename = input("Filename: ")

    ''' Extract subresources which contains the dll file in encrypted form'''
    data_from_pe = subresource_extract(filename)

    ''' Getting the key and data pair correct'''
    if len(data_from_pe[0]) >= len(data_from_pe[1]):
        key = data_from_pe[1]
        data = data_from_pe[0]
    else:
        key = data_from_pe[0]
        data = data_from_pe[1]

    ''' Decrypting the first payload'''
    Decrypted_payload = aes_ecb_decrypt(key,data)

    write_to_file(Decrypted_payload,"first_payload")

if __name__ == '__main__':
    main()

Second Stage

With the file loaded into memory extracted, we can proceed with static analysis. The file is a .NET Framework DLL file, containing three resources as depicted in below Fig 6. All three resources are decrypted and loaded into memory. The resource named “TypeIdXC1XSJP” contains the remcos sample, while the remaining two resources are .NET dll files utilized to load the decrypted remcos into memory and initiate its execution.

Resources present in the file
Fig 6. Resources in the second sample

Examining the relevant code in the sample in dnspy, we observe the following:

Main function of the sample
Fig 7. Main function of the sample

  • The main function is invoked in the dll sample. The highlighted portion shows the sample sleeping for 1000ms. Initially, XDDEKFADNMADHA() extracts a resource named “TypeIdXC1XSJP”. Subsequently, it undergoes a gunzip operation in PaddingYBobhvB(). Finally, it is decrypted in PaddingYBobhvBDecryptBytes() using the hardcoded password and salt.
    Decryption Routine
    Fig 8. Decryption Routine PaddingYBobhvBDecryptBytes()
  • The PaddingYBobhvBDecryptBytes() function utilizes AES-CBC mode with a 32-byte key and a 16-byte IV. It uses PasswordDeriveBytes, a Microsoft implementation of an extended PBKDF1 version, to generate key and IV bytes for decryption.

Now, we have a couple of challenges in writing our own extractor:

Implementation of PasswordDeriveBytes in Python

There are implementation of PasswordDeriveBytes available online:

  1. .Net implementation
  2. Java implementation
    • This implementation is quite good however it doesn’t behave the same way as original implementation when getBytes() is called
  3. Python implementation
    • This implementation only works for scenario where getBytes() is called only once.

So, we need to rewrite .NET code in Python. While AI tools were considered for translation, they were full of errors. Thus, a manual approach was chosen to ensure accurate conversion and same behaviour with multiple getBytes() calls.

Disclaimer: This Python implementation may lack certain methods which are irrelevant to the current use case.

Python implementation of PasswordDeriveBytes :

class PasswordDeriveBytes:

    def __init__(self,password,salt):
        self.iterations = 100
        if isinstance(password,str):
            self.password = password.encode()
        if isinstance(salt,str):
            self.salt = salt.encode()
        self.lasthash = []
        self.outbytes = []
        self.extra = []
        self.extraCount = 0
        self.ctrl = 0

    def ComputeBaseValue(self):
        lasthash = hashlib.sha1(self.password+self.salt).digest()
        self.iterations -= 1
        for i in range(self.iterations-1):
            lasthash = hashlib.sha1(lasthash).digest()
        self.lasthash = lasthash
    
    def ComputeBytes(self,cb):
        num2 = 20
        num = 0 
        array = []

        if self.ctrl == 0:
            self.outbytes = hashlib.sha1(self.lasthash).digest()
            self.ctrl += 1
        else :
            self.outbytes = hashlib.sha1(str(self.ctrl).encode()+self.lasthash).digest()

        array[num:num2+num] = self.outbytes[0:num2]
        num = num + num2
        
        while (cb > num):
            
            self.outbytes = hashlib.sha1(str(self.ctrl).encode()+self.lasthash).digest()
            self.ctrl += 1
            array[num:num+num2] = self.outbytes[0:num2]
            num += num2

        return array
    
    def getBytes(self,cb):
        num = 0
        array = []
        array2 = []
        
        if len(self.lasthash) == 0:
            self.ComputeBaseValue()
        elif len(self.extra) != 0:
            num = len(self.extra) - self.extraCount
            
            if num >= cb :
                array[0:cb] = self.extra[self.extraCount:self.extraCount+cb]
                if num > cb :
                    self.extraCount += cb
                else :
                    self.extra = []
                return bytearray(array)
            array[0:num] = self.extra[num:num+num]
            self.extra =[]

        array2 = self.ComputeBytes(cb - num)
        array[num:cb-num+num] = array2[0:cb-num]
        
        if len(array2) + num > cb:
            self.extra = array2
            self.extraCount = cb - num
        return bytearray(array)

Extraction of hardcoded password and salt

In order to extract password and salt from .NET file, we utilize dnlib library. A comprehensive tutorial by Embee Research provides detailed instructions on how to utlize dnlib: https://www.embeeresearch.io/introduction-to-dotnet-configuration-extraction-revengerat/

By following the steps outlined in the tutorial and modifying the provided code, we can successfully extract the hardcoded password and salt.

 IL view
Fig 9. IL view of the function shown in Fig 7.
We need to modify the signature and code a bit to suit our specific use case. We must update the signature array with highlighted IL opcode shown in Fig 9. above and adjust the code to locate the signature within the opcodes extracted by dnlib.

Code Snippet with comments:

#Initializing dnlib
import clr

clr.AddReference("dnlib")

import dnlib

from dnlib.DotNet import *
from dnlib.DotNet.Emit import OpCodes


def has_config_pattern(method,signature):
    if method.HasBody:
        if len(method.Body.Instructions) >= len(signature):
            ins = [x.OpCode.Name for x in method.Body.Instructions] # create a list 
            if any(signature == list(x) for x in zip(*[ins[i:] for i in range(len(signature))])): # creates various list and matches it against signature 
                return True
    return False

def extract_password_salt(filename,signature):
    strings_from_file = []
    module = dnlib.DotNet.ModuleDefMD.Load(filename) # loads the file
    for type in module.GetTypes():
        for method in type.Methods:
            if has_config_pattern(method,signature) and method.HasBody:
                for instr in method.Body.Instructions:
                    if instr.OpCode == OpCodes.Ldstr: # opcode ldstr loads the hardcoded strings. So we are checking for this opcode.
                        strings_from_file.append(instr.Operand)
    return strings_from_file

..............................................................................................................................................
..............................................................................................................................................

    # used to find the functions where password and salt is used
    signature =  ["ldc.i4", "call", "ldarg.0", "ldarg.0", "ldarg.0", "ldstr", "callvirt", "callvirt", "ldstr", "ldstr", "callvirt", "stloc.0"]
    strings_from_file = extract_password_salt("first_payload",signature)

    # discarding the first string which refers to name of the executable
    # name of the resource containing malware
    resource_name = strings_from_file[1]
    # password used for decrypting
    password = strings_from_file[2]
    # salt used for decrypting
    salt = strings_from_file[3] 

Now we can combine the above code with our previously written code and successfully extract second stage

Python Code

import dotnetfile 
from Crypto.Cipher import AES
import gzip 
import hashlib
import clr

clr.AddReference("dnlib")

import dnlib

from dnlib.DotNet import *
from dnlib.DotNet.Emit import OpCodes

class PasswordDeriveBytes:

    def __init__(self,password,salt):
        self.iterations = 100
        if isinstance(password,str):
            self.password = password.encode()
        if isinstance(salt,str):
            self.salt = salt.encode()
        self.lasthash = []
        self.outbytes = []
        self.extra = []
        self.extraCount = 0
        self.ctrl = 0

    def ComputeBaseValue(self):
        lasthash = hashlib.sha1(self.password+self.salt).digest()
        self.iterations -= 1
        for i in range(self.iterations-1):
            lasthash = hashlib.sha1(lasthash).digest()
        self.lasthash = lasthash
    
    def ComputeBytes(self,cb):
        num2 = 20
        num = 0 
        array = []

        if self.ctrl == 0:
            self.outbytes = hashlib.sha1(self.lasthash).digest()
            self.ctrl += 1
        else :
            self.outbytes = hashlib.sha1(str(self.ctrl).encode()+self.lasthash).digest()

        array[num:num2+num] = self.outbytes[0:num2]
        num = num + num2
        
        while (cb > num):
            
            self.outbytes = hashlib.sha1(str(self.ctrl).encode()+self.lasthash).digest()
            self.ctrl += 1
            array[num:num+num2] = self.outbytes[0:num2]
            num += num2

        return array
    
    def getBytes(self,cb):
        num = 0
        array = []
        array2 = []
        
        if len(self.lasthash) == 0:
            self.ComputeBaseValue()
        elif len(self.extra) != 0:
            num = len(self.extra) - self.extraCount
            
            if num >= cb :
                array[0:cb] = self.extra[self.extraCount:self.extraCount+cb]
                if num > cb :
                    self.extraCount += cb
                else :
                    self.extra = []
                return bytearray(array)
            array[0:num] = self.extra[num:num+num]
            self.extra =[]

        array2 = self.ComputeBytes(cb - num)
        array[num:cb-num+num] = array2[0:cb-num]
        
        if len(array2) + num > cb:
            self.extra = array2
            self.extraCount = cb - num
        return bytearray(array)


def gzip_decrompress(data):
    data_decompress = gzip.decompress(data)
    return data_decompress

def write_to_file(data,filename):
        with open(filename, "wb") as binary_file:
            binary_file.write(data)
        return None

def aes_ecb_decrypt(key,data):

    cipher = AES.new(key,AES.MODE_ECB)
    decrypted_data = cipher.decrypt(data)
    return  decrypted_data

def aes_cbc_decrypt(key,data,salt):
        
    derivedBytes = PasswordDeriveBytes(key,salt)
    
    derivedkey = derivedBytes.getBytes(32)
    
    iv = derivedBytes.getBytes(16)
    
    cipher = AES.new(derivedkey,AES.MODE_CBC,iv)
    decrypted_data = cipher.decrypt(data)
    return decrypted_data

def resource_extract(filename):
    ''' extracts subresources from dotnetfile ''' 
    ''' reference : https://github.com/pan-unit42/dotnetfile/blob/main/examples/dotnetfile_dump.py '''
    extracted_resources = {}
    dotnet_file = dotnetfile.DotNetPE(filename)
    resource_data = dotnet_file.get_resources()
    for data in resource_data:
        for resource_item in data.items():
            if resource_item[0] == "Name":
                key = resource_item[1]        
            if resource_item[0] == "Data":
                extracted_resources[key] = resource_item[1]
    
    return extracted_resources

def subresource_extract(filename):
    ''' extracts subresources from dotnetfile ''' 
    ''' reference : https://github.com/pan-unit42/dotnetfile/blob/main/examples/dotnetfile_dump.py '''
    extracted_subresources = []
    dotnet_file =dotnetfile.DotNetPE(filename)
    resource_data = dotnet_file.get_resources()
    for data in resource_data:
        for resource_item in data.items():
            if resource_item[0] == 'SubResources':
                if resource_item[1]:
                    
                    for sub_resource in resource_item[1]:
                        
                        for sub_resource_item in sub_resource.items():
                            
                            if sub_resource_item[0] == "Data":
                                extracted_subresources.append(sub_resource_item[1])
                            
    return extracted_subresources

def has_config_pattern(method,signature):
    if method.HasBody:
        if len(method.Body.Instructions) >= len(signature):
            ins = [x.OpCode.Name for x in method.Body.Instructions] # create a list
            if any(signature == list(x) for x in zip(*[ins[i:] for i in range(len(signature))])): # creates various list and matches it against signature
                return True
    return False

def extract_password_salt(filename,signature):
    strings_from_file = []
    module = dnlib.DotNet.ModuleDefMD.Load(filename)
    for type in module.GetTypes():
        for method in type.Methods:
            if has_config_pattern(method,signature) and method.HasBody:
                for instr in method.Body.Instructions:
                    if instr.OpCode == OpCodes.Ldstr: # opcode ldstr loads the hardcoded strings. So we are checking for this opcode.
                        strings_from_file.append(instr.Operand)
    return strings_from_file

def main ():

    ''' Name of the file'''
    filename = input("Filename: ")

    ''' Extract subresources which contains the dll file in encrypted form'''
    data_from_pe = subresource_extract(filename)

    ''' Getting the key and data pair correct by ensuring size of key is less than size of data'''
    if len(data_from_pe[0]) >= len(data_from_pe[1]):
        key = data_from_pe[1]
        data = data_from_pe[0]
    else:
        key = data_from_pe[0]
        data = data_from_pe[1]

    ''' Decrypting the first payload'''
    Decrypted_payload = aes_ecb_decrypt(key,data)

    write_to_file(Decrypted_payload,"first_payload") #saving it to the file

    ''' Extracting resources from first payload'''
    data_from_first_playload = resource_extract("first_payload") # extracting the resources 


    ''' Extracting the hardcoded password and salt from the file '''
    
    # used to find the functions where password and salt is used
    signature =  ["ldc.i4", "call", "ldarg.0", "ldarg.0", "ldarg.0", "ldstr", "callvirt", "callvirt", "ldstr", "ldstr", "callvirt", "stloc.0"]
    strings_from_file = extract_password_salt("first_payload",signature)

    # discarding the first string which refers to name of the executable
    # name of the resource containing malware
    resource_name = strings_from_file[1]
    # password used for decrypting
    password = strings_from_file[2]
    # salt used for decrypting
    salt = strings_from_file[3]    

    ''' Processing the first extracted resource'''
    decompress_data = gzip_decrompress(data_from_first_playload[resource_name]) # gunzipping the required resource.
    
    Decrypted_final_payload = aes_cbc_decrypt(password,decompress_data,salt) # decrypting it using extracted hardcoded password and salt

    write_to_file(Decrypted_final_payload,"second_payload") # Saving it to the file


if __name__ == '__main__':
    main()

Final Stage

At this point, we have successfully extracted the remcos malware. After analyzing the remcos sample for a period of time, we can identify where the configuration is stored and decrypted. Tools like capa can be used in Ghidra to reduce analysis time.

Upon examining the functions using Ghidra, we observe the following:

Decompiler view
Fig 10. Decompiled view at FUN_004098e8 in Ghidra
  • FUN_004098e8 calls 3 functions: FUN_00409d73, FUN_00402f8b and FUN_0040308e. It also allocates various buffer.
    Decompiler view
    Fig 11. Decompiled view at FUN_00409d73 in Ghidra
  • FUN_00409d73 extracts resource named: “SETTINGS” from the sample.
    Decompiler view
    Fig 12. Decompiled view at FUN_00402fa6
  • FUN_00402fa6 implements the first phase : Key Scheduling Algorithm of RC4
    Decompiler view
    Fig 13. Decompiled view at FUN_0040308e
  • FUN_0040308e implements the second phase : Pseudo-Random Generation Algorithm of RC4 and XORring with encrypted config.

Upon analyzing the FUN_004098e8 function, it becomes clear that the first byte of the data read by FUN_00409d73 represents the length of the key, followed by the key itself, and then the encrypted configuration.

Hex view
Fig 14. Hex view of the resource 'SETTINGS'

All that is left for us to do is to write the code for extracting resource name Settings, separate the key and encrypted config and finally decrypt it using RC4.

Python Code :

import pefile
import dotnetfile 
from Crypto.Cipher import AES,ARC4
import gzip 
import hashlib
import clr

clr.AddReference("dnlib")

import dnlib

from dnlib.DotNet import *
from dnlib.DotNet.Emit import OpCodes

class PasswordDeriveBytes:

    def __init__(self,password,salt):
        self.iterations = 100
        if isinstance(password,str):
            self.password = password.encode()
        if isinstance(salt,str):
            self.salt = salt.encode()
        self.lasthash = []
        self.outbytes = []
        self.extra = []
        self.extraCount = 0
        self.ctrl = 0

    def ComputeBaseValue(self):
        lasthash = hashlib.sha1(self.password+self.salt).digest()
        self.iterations -= 1
        for i in range(self.iterations-1):
            lasthash = hashlib.sha1(lasthash).digest()
        self.lasthash = lasthash
    
    def ComputeBytes(self,cb):
        num2 = 20
        num = 0 
        array = []

        if self.ctrl == 0:
            self.outbytes = hashlib.sha1(self.lasthash).digest()
            self.ctrl += 1
        else :
            self.outbytes = hashlib.sha1(str(self.ctrl).encode()+self.lasthash).digest()

        array[num:num2+num] = self.outbytes[0:num2]
        num = num + num2
        
        while (cb > num):
            
            self.outbytes = hashlib.sha1(str(self.ctrl).encode()+self.lasthash).digest()
            self.ctrl += 1
            array[num:num+num2] = self.outbytes[0:num2]
            num += num2

        return array
    
    def getBytes(self,cb):
        num = 0
        array = []
        array2 = []
        
        if len(self.lasthash) == 0:
            self.ComputeBaseValue()
        elif len(self.extra) != 0:
            num = len(self.extra) - self.extraCount
            
            if num >= cb :
                array[0:cb] = self.extra[self.extraCount:self.extraCount+cb]
                if num > cb :
                    self.extraCount += cb
                else :
                    self.extra = []
                return bytearray(array)
            array[0:num] = self.extra[num:num+num]
            self.extra =[]

        array2 = self.ComputeBytes(cb - num)
        array[num:cb-num+num] = array2[0:cb-num]
        
        if len(array2) + num > cb:
            self.extra = array2
            self.extraCount = cb - num
        return bytearray(array)


def gzip_decrompress(data):
    data_decompress = gzip.decompress(data)
    return data_decompress

def write_to_file(data,filename):
        with open(filename, "wb") as binary_file:
            binary_file.write(data)
        return None

def aes_ecb_decrypt(key,data):

    cipher = AES.new(key,AES.MODE_ECB)
    decrypted_data = cipher.decrypt(data)
    return  decrypted_data

def aes_cbc_decrypt(key,data,salt):
        
    derivedBytes = PasswordDeriveBytes(key,salt)
    
    derivedkey = derivedBytes.getBytes(32)
    
    iv = derivedBytes.getBytes(16)
    
    cipher = AES.new(derivedkey,AES.MODE_CBC,iv)
    decrypted_data = cipher.decrypt(data)
    return decrypted_data

def rc4_decrypt(data,key):

    cipher = ARC4.new(key)
    decrypted_data = cipher.decrypt(data)

    return decrypted_data

def resource_extract(filename):
    ''' extracts subresources from dotnetfile ''' 
    ''' reference : https://github.com/pan-unit42/dotnetfile/blob/main/examples/dotnetfile_dump.py '''
    extracted_resources = {}
    dotnet_file = dotnetfile.DotNetPE(filename)
    resource_data = dotnet_file.get_resources()
    for data in resource_data:
        for resource_item in data.items():
            if resource_item[0] == "Name":
                key = resource_item[1]        
            if resource_item[0] == "Data":
                extracted_resources[key] = resource_item[1]
    
    return extracted_resources

def subresource_extract(filename):
    ''' extracts subresources from dotnetfile ''' 
    ''' reference : https://github.com/pan-unit42/dotnetfile/blob/main/examples/dotnetfile_dump.py '''
    extracted_subresources = []
    dotnet_file =dotnetfile.DotNetPE(filename)
    resource_data = dotnet_file.get_resources()
    for data in resource_data:
        for resource_item in data.items():
            if resource_item[0] == 'SubResources':
                if resource_item[1]:
                    
                    for sub_resource in resource_item[1]:
                        
                        for sub_resource_item in sub_resource.items():
                            
                            if sub_resource_item[0] == "Data":
                                extracted_subresources.append(sub_resource_item[1])
                            
    return extracted_subresources

def extract_encrypted_resource(filename):
    pe = pefile.PE(filename)

    for resource in pe.DIRECTORY_ENTRY_RESOURCE.entries:
        for entry in resource.directory.entries:
            if entry.name is not None:
                if str(entry.name) == "SETTINGS":
                    offset = entry.directory.entries[0].data.struct.OffsetToData
                    size = entry.directory.entries[0].data.struct.Size
    
    encrypted_resource = pe.get_memory_mapped_image()[offset:offset+size]
    
    return encrypted_resource

def has_config_pattern(method,signature):
    if method.HasBody:
        if len(method.Body.Instructions) >= len(signature):
            ins = [x.OpCode.Name for x in method.Body.Instructions] # create a list
            if any(signature == list(x) for x in zip(*[ins[i:] for i in range(len(signature))])): # creates various list and matches it against signature
                return True
    return False

def extract_password_salt(filename,signature):
    strings_from_file = []
    module = dnlib.DotNet.ModuleDefMD.Load(filename)
    for type in module.GetTypes():
        for method in type.Methods:
            if has_config_pattern(method,signature) and method.HasBody:
                for instr in method.Body.Instructions:
                    if instr.OpCode == OpCodes.Ldstr: # opcode ldstr loads the hardcoded strings. So we are checking for this opcode.
                        strings_from_file.append(instr.Operand)
    return strings_from_file

def main ():

    ''' Name of the file'''
    filename = input("Filename: ")

    ''' Extract subresources which contains the dll file in encrypted form'''
    data_from_pe = subresource_extract(filename)

    ''' Getting the key and data pair correct by ensuring size of key is less than size of data'''
    if len(data_from_pe[0]) >= len(data_from_pe[1]):
        key = data_from_pe[1]
        data = data_from_pe[0]
    else:
        key = data_from_pe[0]
        data = data_from_pe[1]

    ''' Decrypting the first payload'''
    Decrypted_payload = aes_ecb_decrypt(key,data)

    write_to_file(Decrypted_payload,"first_payload") #saving it to the file

    ''' Extracting resources from first payload'''
    data_from_first_playload = resource_extract("first_payload") # extracting the resources 


    ''' Extracting the hardcoded password and salt from the file '''
    
    # used to find the functions where password and salt is used
    signature =  ["ldc.i4", "call", "ldarg.0", "ldarg.0", "ldarg.0", "ldstr", "callvirt", "callvirt", "ldstr", "ldstr", "callvirt", "stloc.0"]
    strings_from_file = extract_password_salt("first_payload",signature)

    # discarding the first string which refers to name of the executable
    # name of the resource containing malware
    resource_name = strings_from_file[1]
    # password used for decrypting
    password = strings_from_file[2]
    # salt used for decrypting
    salt = strings_from_file[3]    

    ''' Processing the first extracted resource'''
    decompress_data = gzip_decrompress(data_from_first_playload[resource_name]) # gunzipping the required resource.
    
    Decrypted_final_payload = aes_cbc_decrypt(password,decompress_data,salt) # decrypting it using extracted hardcoded password and salt

    write_to_file(Decrypted_final_payload,"second_payload") # Saving it to the file
    
    '''final config extraction from remcos malware'''

    # extract resource named SETTINGS

    encrypted_data = extract_encrypted_resource("second_payload")

    keylen = encrypted_data[0] # size of the key
    key = encrypted_data[1:keylen+1] # key 
    encrypted_config = encrypted_data[keylen+1:] # encrypted config

    decrypted_config = rc4_decrypt(encrypted_config,key)

    print(decrypted_config.decode("utf-8"))

if __name__ == '__main__':
    main()

Running the extractor

When we run the final code against our malware sample, we can see that it prints decrypted config:

Output of code
Fig 15. Remcos Decrypted Config

Further reading regarding .NET internals: