Dev | Disassemble | Debug

Priv8: Technical Analysis of Rage Stealer.

Table Of Contents

Background.

Five days ago, I stumbled upon a tweet/post by fellow security researcher Yogesh Londhe, who goes by the handle @suyog41. The post highlighted an updated sample of Rage Stealer, a re-branded version of Priv8 (pronounced as частный aka Private) stealer, currently circulating in the wild. Despite the stealer’s presence in the wild in 2023, I chose to focus on this recent sample. In this blog, we will delve into the technical capabilities of Rage Stealer and conduct an infrastructure analysis. Additionally, we will employ a tool we’ve been developing called TeleCommd to extract and analyze the harvested stolen credentials.

About Rage Stealer.

Blank diagram(4)

Rage Stealer initially surfaced on the wild back in August 2023, as reported by Yogesh. Initially, it was named Priv8 stealer, but after rebranding it had a new name and some code omissions were made and it was renamed RageStealer or xStealer. The base stack of this stealer is .NET and a Telegram Bot which it uses to forward the stolen logs, the developer of this stealer is a Vietnamese guy, who mimics a Russian individual, by adding Russian texts inside the sample.

Metadata.

SHA-256: bcbbd089fd08706e25137d0ec7727bd07ebba568876f69acc367e2a96fffdfbc.

Sample: Available here.

Basic Static Analysis.

image

The filename of this sample is MineCraft.exe, and upon checking the code, it is crystal clear that the stealer is not using any sort of obfuscator or packer like other .NET Stealers. Upon checking this sample on VT, the threat is labelled as a data stealer.

Interesting string/s :

Credential & Info Stealing: Part-I.

image

image

The Rage Stealer supports the stealing capabilities of various wallets & related information. Initially, it enumerates all the available processes and then starts stealing or logging data from various sources and stores it by creating a directory with the prefix \\44_23.

image

One of the sources being stolen is the cryptocurrency wallets. The functions in the code take the target directory as the parameter and copy the contents inside it. Let us dive into the code using dnSpy to figure out the supported wallets.

Armory Wallet.

image

The ArmoryStr function targets Armory wallets (an open-source Bitcoin wallet with Cold Storage and Multi-Signature support). This function uses a try..catch exception handling mechanism and a for each loop to enumerate potential files or profiles related to the Armory wallet. The enumeration takes place in the \Armory\ path, and for each file found, the function uses the CopyTo method to duplicate the file into the target directory formed by directorypath + Armory.ArmoryDir. The destination for these files is \44_23\Wallets\Armory, indicating to be exfiltrated by the stealer.

Atomic Wallet.

image

The AtomicStr function targets Atomic Wallet a cryptocurrency wallet. This function employs a try..catch exception handling mechanism and a for each loop to iterate through files located in the \atomic\Local Storage\leveldb\ path. For each file discovered, the function creates a corresponding directory in the specified target path (directorypath + AtomicWallet.AtomDir) and duplicates the file into this directory using the CopyTo method. The destination for these copied files is \44_23\Wallets\Atomic\Local Storage\leveldb\. The function increments counters (AtomicWallet.count and Counting.Wallets) to keep track of the number of processed files and wallets and makes sure all the profiles are exfiltrated by the stealer.

Bitcoin Core Wallet.

image

The BCStr function targets Bitcoin Core wallets. Utilizing a try..catch exception and accesses the Windows Registry to retrieve information about the Bitcoin Core wallet. Specifically, it looks for registry keys under “Software” -> “Bitcoin” -> “Bitcoin-Qt”. Upon successfully obtaining this information, the function creates a target directory (directorypath + “\Wallets\BitcoinCore\”) and copies the wallet.dat file from the specified location in the registry (registryKey.GetValue(“strDataDir”).ToString() + “\wallet.dat”) to the newly created directory. The function increments counters (BitcoinCore.count and Counting.Wallets) to track the number of processed files and wallets and makes sure all the wallet profiles are exfiltrated by the stealer.

ByteCoin Wallet.

image

The BCNcoinStr function targets ByteCoin cryptocurrency wallet it employs a try..catch exception and iterates through files in the \bytecoin directory under the AppData path. For each file found, the function creates a corresponding directory in the specified target path (directorypath + “\Wallets\Bytecoin\”). If the file has a “.wallet” extension, it is copied to the target directory using the CopyTo method. The function increments counters (Bytecoin.count and Counting.Wallets) to keep track of the number of processed files and wallets and makes sure the profiles of the wallet are exfiltrated by the stealer.

DashCore Wallet.

image

The DSHcoinStr function targets the DashCore cryptocurrency wallet and it uses a try..catch exception and attempts to access the Windows Registry to retrieve information about the DashCore wallet. Specifically, it looks for registry keys under “Software” -> “Dash” -> “Dash-Qt”. Upon successfully obtaining this information, the function creates a target directory (directorypath + “\Wallets\DashCore\”) and copies the wallet.dat file from the specified location in the registry (registryKey.GetValue(“strDataDir”).ToString() + “\wallet.dat”) to the newly created directory. The function increments counters (DashCore.count and Counting.Wallets) to keep track of the number of processed files and wallets and makes sure the profiles of the wallet are exfiltrated by the stealer.

Electrum Wallet.

image

The EleStr function targets Electrum Wallet and it utilizes a try..catch exception and iterates through files in the \Electrum\wallets directory under the AppData path. For each file found, the function creates a corresponding directory in the specified target path (directorypath + Electrum.ElectrumDir). The file is then copied to this target directory using the CopyTo method. The function increments counters (Electrum.count and Counting.Wallets) to keep track of the number of processed files and wallets and makes sure the profiles of the wallet are exfiltrated by the stealer.

Ethereum Wallet.

image

The EcoinStr function targets Ethereum Wallet and it uses a try..catch exception and iterates through files in the \Ethereum\keystore directory under the AppData path. For each file found, the function creates a corresponding directory in the specified target path (directorypath + Ethereum.EthereumDir). The file is then copied to this target directory using the CopyTo method. The function increments counters (Ethereum.count and Counting.Wallets) to keep track of the number of processed files and wallets and makes sure the profiles of the wallet are exfiltrated by the stealer.

Litecoin Core Wallet.

image

The LitecStr function targets Litecoin Wallet and it uses a try..catch exception and then it attempts to access the Windows Registry to retrieve information about the Litecoin Core wallet. Specifically, it looks for registry keys under "Software" -> "Litecoin" -> "Litecoin-Qt". Upon successfully obtaining this information, the function creates a target directory (directorypath + “\Wallets\LitecoinCore\”) and copies the wallet.dat file from the specified location in the registry (registryKey.GetValue(“strDataDir”).ToString() + “\wallet.dat”) to the newly created directory. The function increments counters (LitecoinCore.count and Counting.Wallets) to keep track of the number of processed files and wallets and makes sure the profiles of the wallet are exfiltrated by the stealer.

Monero Wallet.

image

The XMRcoinStr function targets Monero Wallet and it uses a try..catch exception and attempts to access the Windows Registry to retrieve information about the Monero wallet. Specifically, it looks for registry keys under "Software" -> "monero-project" -> "monero-core". Upon successfully obtaining this information, the function creates a target directory (directorypath + Monero.base64xmr) and copies the wallet file from the specified location in the registry. The function increments counters (Monero.count and Counting.Wallets) to keep track of the number of processed files and wallets and makes sure the profiles of the wallet are exfiltrated by the stealer.

Exodus Wallet.

image

The ExodusStr function targets the Exodus Wallet and it uses a try..catch exception and iterates through files in the \\Exodus\\exodus.wallet\\ directory under the AppData path. For each file found, the function creates a corresponding directory in the specified target path (directorypath + Exodus.ExodusDir). The file is then copied to this target directory using the CopyTo method. The function increments counters (Exodus.count and Counting.Wallets) to keep track of the number of processed files and wallets and makes sure the profiles of the wallet are exfiltrated by the stealer.

ZCash Wallet.

image

The ZecwalletStr function targets the ZCash Wallet and it uses a try..catch exception and iterates through files in the \\Zcash\\ directory under the AppData path. For each file found, the function creates a corresponding directory in the specified target path (directorypath + Zcash.ZcashDir). The file is then copied to this target directory using the CopyTo method. The function increments the counter (Counting.Wallets) to keep track of the number of processed wallets and makes sure the profiles of the wallet are exfiltrated by the stealer.

Jaxx Liberty Wallet.

image

The JaxxStr function targets the Jaxx Liberty wallet and it uses a try..catch exception & iterates through files in the \\com.liberty.jaxx\\IndexedDB\\file__0.indexeddb.leveldb\\ directory under the AppData path. For each file found, the function creates a corresponding directory in the specified target path (directorypath + Jaxx.JaxxDir). The file is then copied to this target directory using the CopyTo method. The function increments counters (Jaxx.count and Counting.Wallets) to keep track of the number of processed files and wallets and makes sure the profiles of the wallet are exfiltrated by the stealer.

Overview.

image

Once the stealing is performed the credentials of the wallets are loaded into their specific directories and in case this fails, the message Старт грабера с кошелями дал сбой" is printed by this sample indicating a failed task of wallet information stealing.

Credential & Info Stealing: Part II.

image

The Rage Stealer, post stealing wallet profiles and information, also exfiltrates various system information like a list of processes, geolocation, and various other system-based information. Let us dive into that.

GeoIP.

image

image

The Ethernet function uses a try..catch exception to and inside the try block of code it uses https://ip-api.com/ geolocation API to query the current geolocation of the system and retrieves it in an XML format.

Screen Grabbing.

image

The GetScreen function utilizes the BitMap object to create dimensions of the size of the screen and then uses the Graphics.CopyFromScreen method to copy and then saves the image in a PNG format using the Save method from the bitmap object with a filename Screen.png for exfiltration.

Process Enumeration.

image

The WriteProcess function uses a for-each loop inside which it employs the GetProcess method to enumerate all processes and saves the name of the processes inside a file which is created for exfiltration known as \\Process.txt . The other function ProcessExecutablePath enumerates the executable path of the process’s binaries with a format Process ID: Executable Path .

System Enumeration.

image

The GetSystem function uses various other methods from the SystemInfo Object like GetSystemVersion which returns the OS version, then it queries the clipboard data using another method GetBuffer, then it uses method ScreenMetrics to query the screen resolution, and then uses GetProcessorID to enumerate the HWID and then it queries the CPU information using the GetCPUName which returns the processor information, and then it uses GetRAM method to query the physical memory available in the target computer, then it queries GPU information using the GetGpuName method and finally it uses IP * Country which returns the IP address and the two-letter country code and finally the BSSID using the GetBSSID method. After querying all this information, it saves it in a file known as \\Information.txt for exfiltration.

Credential & Info Stealing: Part III

image

After stealing the system information, the stealer then moves ahead to stealing VPN applications & Gaming Profiles. Let us dive into the code and check it out.

ProtonVPN

image

The function Save from the class ProtonVPN enumerates directories in the system, it then checks for the existence of this directory and, if present, proceeds to iterate through its subdirectories. Within each subdirectory, the method checks for the presence of ProtonVPN.exe in the path. For directories containing the executable, it further enumerates subdirectories and, for each, identifies the user.config file. The method then creates a corresponding directory structure in the exploitation directory AKA the directory to be exfiltrated, named after the parent directory of the user.config file. If the destination directory doesn’t exist, it is created, and the user.config file is copied to this location. The process increments the Counting.ProtonVPN counter for each successfully saved ProtonVPN configuration, The method aids the stealer in the extraction and organization of ProtonVPN configurations for exfiltration.

OpenVPN

image

The function Save from the OpenVPN class enumerates directories in the system. It does it by first defining the target exploitation directory (exploitDir AKA the directory to be exfiltrated) using the Help class. Then, it constructs the path to the OpenVPN Connect profiles directory within the user’s application data folder. If this directory doesn’t exist, the method terminates. Upon confirming the existence of the profiles directory, the method creates a corresponding directory structure in the exploitation directory (exploitDir + “\VPN\OpenVPN”). It proceeds to iterate through the files in the OpenVPN profiles directory, identifying those with the ovpn extension. For each matching file, it copies it to the designated exploitation directory with the same filename. The method increments the Counting.OpenVPN counter to track the number of OpenVPN configurations successfully captured, the method aids the stealer in the extraction and organization of OpenVPN profiles in a certain specific directory for exfiltration.

NordVPN

image

image

The Save function within the NordVPN class enumerates directories by utilizing the GetDirectories method and searches for NordVPN. Once the relevant directory is identified, it further explores subdirectories to locate the executable named NordVPN.exe. The function checks for the existence of the user.config files within these subdirectories. Upon discovering such a file, it creates a corresponding directory structure within the exploitDir (the directory designated for exfiltration). After loading the content of the XML file, it proceeds to decode the encoded credentials using the Decode method. The decoded credentials are then appended to a new file named accounts.txt in the exploitDir directory. This process systematically extracts and decodes NordVPN credentials, organizing them in a proper manner for exfiltration.

Steam.

image

The SteamGet function within the Steam class, enumerates the Steam directory, then goes ahead and copies all the credentials in a file known as AccountsList.txt , the exfiltrated information contains information like names of Games & user config.

Credential & Info Stealing: Part IV

image

Once, it is done stealing the VPN and gaming profiles and the passwords, it then moves ahead to steal the sensitive information related to social media services like Telegram, VimeWorld, and Discord. Let us go ahead and check it out.

Discord.

image

image

The GetTokens method of the Discord class, enumerates the following directories, and once it is found, it then looks for files with extensions .log & .ldb . Once the GetFiles returns that the file has been found, it uses the constant TokenRegex to match and look for Discord Tokens, and this process keeps up happening recursively as the method employs foreach until there is no file left in the directory to be checked against the following conditions.

image

Once, the tokens are harvested from the files, then the WriteDiscord method in the Discord class uses AppendAllText to save the contents inside a file called Tokens.txt which will be later used for exfiltration.

FileZilla.

image

image

The GetDataFileZilla method from the FileZilla Class reads the specified FileZilla configuration file (recentservers.xml), extracts server details (host, port, user, and password), and appends the information to a StringBuilder (FileZilla.SB), then it appends the details to a log file (FileZilla.log) in the exploit directory which is to be exfiltrated.

Telegram.

image

The GetTelegramSessions method in the Telegram class retrieves Telegram session data. It searches for Telegram data directories, copies specific files and directories to a new location, and counts the number of Telegram sessions found. The copied files include those related to user tags, settings, and key data, and the function excludes files larger than 5 KB. The copied data is stored in a directory within the specified exploitdirectory which is to be exfiltrated.

VimeWorld.

image

image

The Get function is in the “Vime” class which is responsible for retrieving VimeWorld player information. It reads data from a configuration file, checks for the presence of a “password” keyword, and if found, proceeds to create a directory for VimeWorld data within the specified exploit directory. It then downloads player information from a VimeWorld API, encrypts and saves relevant data, including player nickname, rank, level, and unique identifier (OSSUID). The saved data is organized into a file named after the player’s nickname. The associated functions Level(), Donate(), OSSUID(), and NickName() handle specific aspects of extracting player information and then the data is saved for exfiltration.

Credential & Info Stealing: Part V

image

Once the stealer is done stealing the social media information, it then moves ahead to stealing Chrome-based sensitive data that is cookies and passwords. Let us explore the code.

Chrome.

image

Out of the two functions, looking into the first method the GetPasswords method is responsible for asynchronously retrieving stored passwords from the Google Chrome browser. It first checks for the existence of the Chrome browser directory, and if it is found, it then attempts to obtain the encryption key asynchronously. Subsequently, the code iterates through files named Login Data within the Chrome directory and its subdirectories. For each file, it copies the data to a temporary location, processes the SQLite database containing login information, and decrypts passwords using the obtained encryption key. The decrypted information, including URLs, usernames, and passwords, is stored in instances of a PasswordFormat class. The method handles exceptions and, upon completion, returns an array containing the collected password information. The implementation involves asynchronous tasks, encryption key retrieval, SQLite database handling, and password decryption to achieve the extraction of Chrome-saved passwords.

image

Now, the second one the GetCookies method is responsible for retrieving stored cookies from the Google Chrome browser. Initially, it checks for the existence of the Chrome browser directory. If the directory is found, it asynchronously attempts to obtain the encryption key used for decrypting sensitive data. The code then iterates through files named Cookies within the Chrome directory and its subdirectories. For each file, it copies the data to a temporary location, processes the SQLite database containing cookie information, and decrypts cookie values using the obtained encryption key. The decrypted details, including host, name, path, value, and expiry timestamp, are stored in instances of a CookieFormat class. Similar to the password extraction process, the method handles exceptions and, upon completion, returns an array containing the collected cookie information. The implementation involves asynchronous tasks, encryption key retrieval, SQLite database handling, and cookie value decryption to achieve the extraction of Chrome-saved cookies. The Chrome browser path and encryption key are declared as constants within the class.

Infrastructure Analysis.

Now, after looking into the stealer capabilities and features, in this section, we will focus on the analysis of the code responsible for exfiltration. Then we will do a little bit of analysis on the Telegram Channel. Let us dive inside the code without any delay.

Exfiltration using Telegram

image

image

image

The exfiltrated data is concatenated which contains the IP and other stolen artefacts as a caption which is then uploaded as a Zipped version and then using a certain telegram bot API token is exfiltrated to a certain channel where the malicious threat actor is waiting for the logs.

Exploring Telegram C2.

I have been using a tool developed by me & Kumar[https://github.com/DoubleAtoString] known as TeleCommd[https://github.com/RixedLabs/TeleCommd] to enumerate information about telegram bots and channels, it supports various capabilities like forwarding stolen logs in NodeJS & Python, and we do plan to open-source it in few days.

Let us find the name of the bot using TeleCommd.

image

We can see that the name of the bot is logs_nsper_bot.

image

image

Now, upon looking into the description of the bot, we can see the link to the official channel & author of the market. Let us look into the official channel.

image

And, we can see that this .NET stealer is for sale per build worth 15 US Dollars.

Recovering Stolen credentials using TeleCommd.

Now, that we have the API Token, and the channel ID let us recover the stolen credentials using Telecommd. Initially, we will see the number of victims.

xStealer(1)

As, we can see there are only 72 victims to date, as the script stops dumping logs for the 73rd user. But there might be cases where similar logs have been dumped multiple times. Therefore, we will sort that out in the victim landscape section.

nsper12

Finally, we were able to retain the stolen credential using TeleCommd. Now let us do some vetting focused on the collected dump.

image

image

image

This clarifies that the dumped logs by our script are legitimate, with the most recent victim being from Germany. Due to its low price, it’s easily being purchased by individuals and is currently active ITW(In the Wild).

Victim Landscape.

ChartGo

After carefully sorting the data, it became crystal clear that there have been only 41 victims across the globe infected by Rage Stealer with the United States having 13 individuals affected to Switzerland having only a single victim.

Code Attribution.

The code from this stealer, resembles the same codebase as BlackGuard .NET Stealer, just the only difference is the use of .NET protector and some variable renaming. Another similarity from version one of Priv8 or Rage Stealer resembles a python stealer which is also known as RageStealer, uploaded by the developer of this stealer, you can find the repository here.

Developer Portfolio.

The Rage Stealer is developed by a Vietnamese guy AKA Trương Ngọc Khánh . Trương frequently maintains decent activity at his Original GitHub account where he mentions his general whereabouts that is his social accounts & his website which now just returns a JSON of his Facebook profile.

image

image

He loves programming in Python, so he initially programmed Rage Stealer in Python, created an alias and uploaded his work to an alternate Github account.

F1zWMjvWYAAQnu5

Later, the same stealer was re-written by him and was detected by Yogesh’s hunting rules.

F1zWRu7XsAA4OOm

This log, from Yogesh’s tweet, clearly shows that the channel name goes by Tnk_K07VN.

image

image

Now, as we know pretty well Trương is an avid Python programmer, he then decided to release two projects one of obfuscation and the other generally a cryptography-based project. One of them had his name, his alias mentioned.

Now, after a little bit of re-branding and removing the Roblox part of the stealer and the credit card stealing part, Trương has dumped his old alias and is now known as nsper and Rage Stealer has been re-branded to xStealer.

YARA Rule

Wrote a very simple YARA Rule, if you feel something is wrong with it, please reach out! Thanks!

import "pe"

rule RageStealer 
{
    meta:
        author = "ElementalX"
        date = "2024-01-24"
        description = "Detects Rage-Stealer"
        hash = "0623dbe28b6054553016d7d43bf876a1"
        source = "https://xelemental.github.io/Priv8-Technical-Analysis-of-Rage-Stealer/"
    strings:
        $str0 = "RageStealer"
        $str1 = "Chrome"
        $str2 = "xStealer"
        $str3 = "ProtonVPN"
        $str4 = "zip"
        $str5 = "Grabbed Files"
        $str6 = "Armory"
        $str7 = "Exodus"
        $str8 = "Information.txt"
        $str9 = "Discord"
    condition:
        uint16(0) == 0x5a4d and
        filesize < 1MB and
        7 of them
}