Latrodectus was first discovered by researchers in October 2023 and has been in heavy development ever since. The malware works mainly as a loader/downloader. Latrodectus has strong ties with the former, infamous loader IcedID, which was taken down in May 2024, thanks to the efforts of an international operation led by Europol and EC3. Since Operation Endgame, IcedID went under and Latrodectus is seen slowly taking its place in the cybercriminal ecosystem. Interestingly, Latrodectus also includes a specific C2 command, which can download a sample of IcedID loader.
Recently, the developers of this malware family have started on an iteration rampage, where multiple new versions were released in a relatively short time, perhaps in an effort to get ahead of the evergreen “cat-and-mouse” game between defenders and threat actors. These new versions only consist of small changes, even removal of existing features. The previous pace of development would suggest that Latrodectus will keep on iterating with new versions. Due to the prevalence of the malware family, we felt that adding malware configuration extraction support for all the recent versions was the best move forward for producing high-quality IoCs for our customers.
Furthermore, in this short blogpost, we would like to go over some of the most important features of the malware.
You can read our analysis report here (see Figure 1 for an overview).
Latrodectus is distributed in a chain of JavaScript → MSI droppers, finally ending in the core DLL payload. The DLL payload usually has 4 unique-looking exports, utilizing the same export address, eventually running the same core logic when one decides to try all 4 exports.
The loader has gone through several iterations: at the time of writing this post, the most up-to-date version is v1.8. Early versions have started to surface at the end of September 2023, while samples of the most recent version were just compiled at the end of September 2024. Effectively, we are talking about an evolution spanning over a year now.
We have tracked each version’s earliest PE compiled time to give a rough estimation over its timeline of creation:
Versions |
Compiled time for first samples of respective versions |
---|---|
v1.1a | 29 Sep 2023 13:29:13 UTC |
v1.1b | 15 Feb 2024 10:10:37 UTC |
v1.2 | 21 Mar 2024 16:27:39 UTC |
v1.3 | 09 May 2024 11:08:17 UTC |
v1.4 | 29 Jul 2024 10:07:54 UTC |
v1.5 | 30 Jul 2024 17:16:02 UTC |
v1.7 | 16 Sep 2024 08:44:51 UTC |
v1.8 | 25 Sep 2024 11:20:43 UTC |
A few major changes worth highlighting across the versions:
Overall, Latrodectus utilized 4 different anti-debugging and sandbox evasion techniques, these are as follows:
This sanity process count check is most likely aimed at evading sandboxes as virtualized environments may not display the same number of installed and running applications as a real desktop environment would do.
Latrodectus simply enumerates the Windows OS version via the API call RtlGetVersion or via GetVersionExW, if the Rtl version does not return data. If the routine detects Windows 10 or Windows 11 as the host OS, Latrodectus needs at least 75 active processes to launch, otherwise it simply terminates. The other condition does the same check, just for Windows versions v6.3 or less (which would constitute Windows 8.1, Windows 8, Windows 7 and anything below). In this case, the loader needs at least 50 active processes to launch. This is to account for baseline levels for different versions of Windows OS.
The VMRay Platform allows customers to directly specify the amount of background processes during analysis time, successfully countering such sandbox evasion techniques.
The second evasion check enumerates the _IP_ADAPTER_INFO structure via the GetAdaptersInfo API function, then all hardware addresses of present network adapters are examined against the argument of 6. In the event, it does not equal to 6 bytes, the program will simply terminate. While MAC addresses have been standardized to 6 bytes for a long time now, some older networking technologies used different address lengths and certain specialized or proprietary systems might use non-standard MAC address formats. This same evasion check was present in the BumbleBee loader as well.
A third evasion check is simply walking the Process Environment Block (PEB) data structure to query the BeingDebugged flag to detect any debugging attempts: this is a smarter way without calling the actual Windows API IsDebuggerPresent(), which may trigger some AV/EDR systems.
The next check is a validation of the current process, whether it is running under WOW64 on Windows, which simply ascertains whether the malware process is running as a 32-bit process on the 64-bit OS. In this case, the malware will simply exit. Since all Latrodectus DLLs so far have been 64-bit DLLs, it is not fully clear what the intention of the threat actors was with this condition, since it will not return 32-bit in normal circumstances. This might be an attempt to detect certain emulation scenarios.
In order to make reverse engineering process harder, Latrodectus employs string encryption. The internal strings hold a significant amount of information on how the malware operates, what behavior it resembles. These internal strings often serve as the base for malware configuration extraction as well. In early versions of samples, the malware family utilized a unique pseudo random generator (PRNG) for seeding. Later, Latrodectus downgraded this functionality and simply opted to use an increment-based seed variable, which in essence turned the encryption process into a rolling XOR method. As of the most recent versions, the loader is now using AES-256 encryption with a hardcoded key inside the sample and with a variable IV for each of encrypted strings.
Although, the encryption algorithm went through several changes as described previously, the storage of the encrypted strings remained almost similar. The prototype for these structures are simple: the encrypted strings are stored in the .data section of the DLL. In early versions of the loader, the first 4 bytes noted the XOR key and the delimiter bytes as well, the length of each strings are stored in the 5. and 6. bytes, and the remaining bytes are the actual encrypted data.
The recent versions, due to introducing the AES algorithm, a hardcoded key is burnt-in into the .text section of the samples. The data length still resides in the .data section in the first two bytes for each chunk, which is followed by the IV, taking up 16 bytes. The remaining data of each chunk is again the actual encrypted data.
String encryption | ||||||
---|---|---|---|---|---|---|
Versions | Algorithm | Key | Data length | IV | Data | Seed |
v1.1a |
XOR | chunk[:4] | chunk[4:6] | Not applicable | chunk[6:6+data_length] | PRNG |
v1.1b |
Rolling XOR | chunk[:4] | chunk[4:6] | Not applicable | chunk[6:6+data_length] | Incrementer |
v1.2 | Rolling XOR | chunk[:4] | chunk[4:6] | Not applicable | chunk[6:6+data_length] | Incrementer |
v1.3 |
Rolling XOR |
chunk[:4] |
chunk[4:6] |
Not applicable |
chunk[6:6+data_length] |
Incrementer |
v1.4 | AES-256 (CTR mode) | hardcoded | chunk[:2] | chunk[2:18] | chunk[18:18+data_length] | Not applicable |
v1.5 | AES-256 (CTR mode) | hardcoded | chunk[:2] | chunk[2:18] | chunk[18:18+data_length] | Not applicable |
v1.7 | AES-256 (CTR mode) | hardcoded | chunk[:2] | chunk[2:18] | chunk[18:18+data_length] | Not applicable |
v1.8 | AES-256 (CTR mode) | hardcoded | chunk[:2] | chunk[2:18] | chunk[18:18+data_length] | Not applicable |
Figure 7: Encryption changes across versions |
The loader again utilizes the Process Environment Block (PEB) structure to find the base addresses of kernel32.dll and ntdll.dll. Then Latrodectus continues to resolve other libraries, like user32.dll, wininet.dll, iphlpapi.dll via finding the files inside the \Windows\System32 folder, calculating the CRC32 checksums of the filenames and then comparing them with the hardcoded hashed values in the sample. The last step is then to call the LoadLibraryW function to finally load the library.
Once Latrodectus loaded all DLLs necessary, it continues to resolve the APIs by comparing the CRC32 checksums of the exported functions with the target values. The open-source project HashDB can help and save work here, as its Lookup Service can reverse the hash values and recreate the API names within an analysis. Reference: https://github.com/OALabs/hashdb
Using a simple condition, the malware verifies if it is running from under the %APPDATA% folder: if that’s not the case, it will copy itself to the location of either:
The part of the filename noted with XXXXXXXX gets filled up with the hardware ID, generated from the system’s volume serial number and a hardcoded constant described in the Hardware ID section of the blogpost.
The creativity of developers is again revealed at the next stage of persistence: instead of calling conventional APIs or scheduler commands, that would simply create a scheduled task, Latrodectus uses the Component Object Model (COM) interface to achieve persistence. Our function log clearly describes the behavior and it is easy to follow the chain of events. In the past, we have also taken a deep-dive into how the use of COM objects can blind malware analysis.
First, the sample calls the CoCreateInstance API to create and initialize an object, then connects to the ITaskService object. A new task is created inside the root of the scheduler and the job is set to execute whenever the user logs on. The name of the scheduled task is changing between “Updater” or “anxiety” between different versions of Latrodectus samples.
The task will point to the file previously dropped inside the %APPDATA% folder.
Latrodectus also tracks previously successful infections by creating a mutex on the target system. The hardcoded string “runnung” has been consistent across all Latrodectus versions and it is checked before execution to prevent re-infecting already corrupted systems.
So far, we have seen that each new version of the loader also introduces a new group ID. We suspect this may change in the future and there will be unique group IDs per versions, if Latrodectus decides to switch to a “Malware-as-a-Service” model.
The group IDs are present in the initial C2 check-in traffic as &group= parameter and are represented as decimal numbers. They are also present in the malware sample as a string in an encrypted form. Since, we have already discovered that a Fowler–Noll–Vo (FNV1a) hash is created based off of the IDs, we can easily brute-force a reasonable amount of potential group names if we don’t have the decrypted campaign name string. Our approach was to create a word-list of all possible combinations of the English alphabet (26 letters) and try and simply brute-force it. With a high-computing machine, it is also reasonable to try mixed lowercase and uppercase variations, but for this short experiment, we stuck with just capitalizing the first letters.
Keep in mind that – since this is FNV-1a 32-bit space – there could be multiple strings appearing under the same hash due to hash collisions. So in rare cases, there might be a slight chance that the script cannot find the original campaign name.
def generate_words(length):
alphabet =
'abcdefghijklmnopqrstuvwxyz'
words = []
for
combination in itertools.product(alphabet, repeat=length -
1
):
word =
''
.join(combination).capitalize()
words.append(word)
return
words
def write_words(words, file_path):
with open(file_path,
'a'
) as f:
for
word in words:
f.write(word +
'\n'
)
Once we gave enough time to the script to generate a massive (~ 130MB) wordlist (we kept it up to 7 letters), we can simply call a FNV1a hash generator to iterate through the given words line by line:
fnv_prime_32 =
2
**
24
+
2
**
8
+
0x93
offset_basis_32 =
0x811c9dc5
def fnv1a_hash_32(bs):
r = offset_basis_32
for
b in bs:
r = r ^ b
r = (r * fnv_prime_32) &
0xffffffff
return
r
if
__name__ ==
'__main__'
:
with open(
'wordlist.txt'
,
'rb'
) as file:
wordlist = file.readlines()
for
words in wordlist:
print(
"Campaign: "
+ Fore.YELLOW, wordbytes.decode(
'ascii'
),
"| FNV1a: "
, hex(fnv1a_hash_32(wordbytes)),
"| Dec: "
,
int
(hex(fnv1a_hash_32(wordbytes)),
16
))
The loader also generates a unique hardware ID for each target host. This ID is based off of the victim’s Serial Volume ID and simply multiplied with a hardcoded constant. This constant is consistent so far in all observed Latrodectus versions: 0x19660D. The generated GUID is present in the initial C2 check-in request as &guid= parameter.
The loader uses a rather fascinating self-deletion technique: besides Latrodectus, we have previously observed this technique in both DarkSide, Dark Power, HelloXD and other malware families. Ultimately, this method can delete a locked, or a currently running executable from disk. It uses the SetFileInformationByHandle Windows API to rename the executable’s primary data stream and then facilitates the DeleteFile flag in the FileDispositionInfo class to trigger the disposition. There is a publicly available proof-of-concept code for this method on GitHub: https://github.com/LloydLabs/delete-self-poc
We have – uniquely in the industry – tried creating a future-proof detection coverage specifically for this technique, which is now observable as a VMRay Threat Identifier (VTI).
Upon successful infection, Latrodectus sends an initial check-in POST request with a hardcoded User-Agent string: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Tob 1.1). This User-Agent header is consistent across all Latrodectus versions so far.
The POST request data includes parameter values collected from the system and also consists of a few hardcoded values, stored in the sample that identifies the campaign and the sample version. These parameters are originally sent towards the C2 server RC4 encrypted and then base64 encoded, but the VMRay Platform easily captures the decrypted values in the function logs.
Each of these parameters serve a specific purpose:
Parameter |
Value description |
---|---|
counter | C2 request throttling for evasion, (default = 0) |
type | Type of the request (check-in = 1) |
guid | Hardware ID, seeded by the volume serial number, multiplied by the hardcoded value of 0x19660D |
os | Windows OS version |
arch | Windows architecture version |
username | Username of the infected host |
group | Campaign ID in decimal representation |
version | Sample version |
up | Always 1 |
direction | C2 server |
mac | Network card MAC address |
computername | Hostname of infected host |
domain | Host domain |
Once an infection took place, the malicious process can receive further commands from the C2 server, 4 different commands are available:
Directives |
Description |
---|---|
CLEARURL | Clears the C2 table |
URLS | Sends a new C2 URL to be stored in the C2 table |
COMMAND | The command handler to other functionalities |
ERROR | Sends an error message to the host |
The COMMAND handler is the most interesting one as it can receive the following further sub-commands from the C2:
Command ID |
Description |
---|---|
2 | Grabs filelist from the Desktop folder |
3 | Gets host process list |
4 | Collects sysinfo |
12 | Downloads and executes a next-stage PE |
13 | Downloads and executes a next-stage DLL |
14 | Downloads and executes a next-stage shellcode |
15 | Updates and restarts the bot |
17 | Terminates itself |
18 | Downloads IcedID loader and execute |
19 | Increases timeout |
20 | Resets the counter value |
21 | Executes a stealer module |
22 | Downloads and executes shellcode via base64 function |
25 | Downloads a file to %APPDATA% |
We have introduced several forward-looking YARA signatures to detect all versions of the family. We also provide version-based signatures to aid customers with up-to-date information on the exact version of Latrodectus in question.
The VMRay Platform currently extracts all important malware configuration information from the samples. These would include the C2 URLs, the exact version, mission ID, and any potential encryption keys that are used for the string encryption or C2 communication: namely the RC4 key and the AES key (from v1.4 up to v1.8).
The threat actors behind the malware family seem to iterate versions in a speedy fashion, perhaps to wear defenders out or potentially to prepare for a substantial major change. We suspect the prevalent loader will enter into version 2.0 soon, as the previous pace of development seems to indicate even more updates incoming. Interestingly, we have seen that subversions even removed certain features from the loader, perhaps in an attempt to refactor some of the internal structures. As this threat is still prevalent today, we will make sure to follow-up on future changes to aid customers with proper detection coverage and precise malware configuration extraction.
The VMRay Platform currently detects all Latrodectus versions up to v1.8 and can acquire malware configuration from all working samples.
C2 URLs | hxxps://antyparkov[.]site/live/ hxxps://aytobusesre[.]com/live/ hxxps://carflotyup[.]com/live/ hxxps://drifajizo[.]fun/live/ hxxps://coolarition[.]com/live/ hxxps://finjuiceer[.]com/live/ hxxps://grebiunti[.]top/live/ hxxps://grunzalom[.]fun/live/ hxxps://illoskanawer[.]com/live/ hxxps://jertacco[.]com/live/ hxxps://saicetyapy[.]space/live/ hxxps://scifimond[.]com/live/ hxxps://skinnyjeanso[.]com/live/ hxxps://stratimasesstr[.]com/live/ hxxps://stripplasst[.]com/live/ hxxps://titnovacrion[.]top/live/ hxxps://trymeakafr[.]com/live/ hxxps://winarkamaps[.]com/live/ hxxps://workspacin[.]cloud/live/ hxxps://worlpquano[.]com/live/ hxxps://zumkoshapsret[.]com/live/ hxxps://minrezviko[.]com/test/ hxxps://pomaspoteraka[.]com/test/ hxxps://finilamedima[.]com/test/ hxxps://restoreviner[.]com/test/ hxxps://peronikilinfer[.]com/test/ hxxps://rilomenifis[.]com/test/ hxxps://isomicrotich[.]com/test/ |
||
Mutex | runnung | ||
Scheduled task name | Updater anxiety |
||
Persistence location | %APPDATA%\falsify_steward\confrontation_XXXXXXXX.dll %APPDATA%\Custom_update\Update_XXXXXXXX.dll |
||
RC4 keys | 12345 2sDbsEUXvhgLOO4Irt8AF6el3jJ0M1MowXyao00Nn6ZUjtjXwb u9X7Ogp3IECwtHNBFGa0uMc0fDXhjVnV9SiAiVzqdkoleTZy16 eNIHaXC815vAqddR21qsuD35eJFL7CnSOLI9vUBdcb5RPcS0h6 EhAyPSHvva9CvL6OIddDJvDXHJjoMsqXyjraKyYmXFqDGdAYyO 9edoY7pK6eQfntcLBNU1WSkauwf1sHj4I8vTuAddXvPwYbJPeP v9JrWM4aDsviWsTfSCgX5Ed98pH6kMpQr1VWWj5LTMiC5C5Lna k2C0I3yY0ZDMCy4zFZDFnCD3mzc4fFdEMw5uF1n6u59eGG2NDN xkxp7pKhnkQxUokR2dl00qsRa6Hx0xvQ31jTD7EwUqj4RXWtHwELbZFbOoqCnXl8 |
||
Group/Campaign IDs | Alpha (v1.8) Alpha (v1.7) Ceres Compati Delta (v1.5) Electrol Facial Jupiter Liniska Littlehw Mars Mercury Neptun Novik Olimp Supted Trusted Venus Wiski (v1.4) |
||
Versions | v1.1a v1.1b v1.2 v1.3 v1.4 v1.5 v1.7 v1.8 |