RecordBreaker is the successor of Raccoon Stealer and is often referred to as Raccoon Stealer 2.0. The malware has been completely rewritten in C. Sold as a Maas (Malware As A Service) for a few hundred dollars, it’s an information stealer, with a capability for downloading a second stage payload.
It can extract credentials, cookies and credit card information from Chrome based browsers (including Edge) and Firefox. It can also be configured to extract many files from the infected machine, depending on filters. Those functionalities seem to be used by the threat actors only for crypto wallets related files and browser extensions.
We will detail the complete list of the functionalities of this malware, as well as its complete network protocol. In the end, we’ll provide Suricata rules to detect its traffic, to complete the ones provided by ET Pro.
2 Sample analysis
We analyzed a first sample obtained on 09/14/2022: with following fingerprint
Timestamp: 09/10/2018 20:41:02
The payload is packed and protected (at least with anti-debugging) and a process hollowing is used to inject the final payload. The payload we extracted had the following hashes:
Timestamp: 08/15/2022 05:59:17
Note: we can see there is almost 2 months between the payload compilation, and the packer one.
2.1 General view
The following schema illustrates the different steps and communications of the malware:
2.2 Initial steps
The payload has no imports, except LoadLibrary and GetProcAddress:
Figure 2: Import table of the payload
The first action taken is to use those 2 functions to load a large number of others. The DLL names as well as the function names are in cleartext, no obfuscation is involved in this process.
All obfuscated strings are then decoded, using a simple xor based loop (xor each byte with a multibyte key), and they are all converted to widestring.
A mutex (named HJSIDHG#WOEJGSDGOHWEGHSDJG in our sample) is created, avoiding 2 simultaneous executions of the payload. The malware then checks if it is executed as a member of the local administrator group, and if so il runs through the currently executed processes but does nothing of this information.
2.3 First request
There is an array of 5 configured C2, encrypted using a different function but which works the same way. The 5 C2 URL in our sample are:
- 3 others not used (empty string)
A request is sent to each one, expecting at least 64 bytes in the response. 2 elements are sent in the POST data:
- machineId: HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid
- configId: hardcoded string
Figure 3 : User-Agent used for the first request
The configuration info sent as response to the first request is a newline (0x0A char) separated list of commands. Each line is generally the command name, followed by an underscore character, a first parameter, then a double dot character, and then a pipe separated list of further parameters (with an exception).
Each configuration option will be described as well as its effect below.
2.4 Malware actions and configuration
The following paragraphs describes each action taken by the malware, in their order of execution. Some of them originate from a configuration line, some not.
All requests are made to the responding C2, with the configuration token (see below) appended to the URL, and only contains one or more uploaded files in multipart data.
2.4.1 libs (configuration)
This function downloads DLL from arbitrary URL and saves them in the LocalLow directory. This command is used to download libraries necessary for information extraction from Edge (sqlite3) and Firefox (nss3, and dependencies). It is optional, the extraction will work if the libraries are already present on the system (in a library search path, like System32), which is most probably not the case.
Configuration line format: libs_name:url
- name: name of the DLL saved in LocalLow The .dll extension is hardcoded and always added to the final filename.
- url: full URL to download the file.
Figure 4 : User-Agent used in the function downloading DLLs
2.4.2 token (configuration, mandatory)
This parameter is extracted after the libraries are downloaded. All further requests will be made of the first working command and control URL found, appended with \tokenvalue.
- value: 32 characters string.
Number: single, needs to be placed after libs in the configuration
The size and placement (after libs) conditions are due to the libs command condition to end the parsing: it checks if the last line is exactly 38 bytes long before stopping (so 6 bytes for “token:”, and the 32 bytes value), and the token command is the only one with a size that could be considered as fixed.
2.4.3 sstmnfo (configuration)
This function provokes the extraction of hardware and software information.
configuration line format: ssmtnfo_filename:titlehard|titlesoft|unused
- filename: name of the uploaded file.
- titlehard: string copied in the extraction file, as a title for the hardware information.
- titlesoft: string copied in the extraction file, as a title for the software information.
- 1 more parameter: doesn’t seem to be used.
The content of the sent file, named “filename” would be:
Figure 5 : User-Agent used in all file uploads
2.4.4 Edge/Chrome extraction (if sqlite3 can be loaded) / ews (configuration)
If sqlite3 can be loaded (through LoadLibraryW, with a full path in the LocalLow folder), Microsoft Edge and Chrome browser data are extracted. A User-Data directory is searched recursively in %AppData%. Once found multiple sqlite request are made on the different file to extract different information, each sent in a separate file in the multipart request:
- The stored credentials (file \passwords.txt).
- The cookies (file \cookies.txt).
- The form autofill values (\autofill.txt).
- The stored credit cards (\CC.txt).
On each edge profile (User-Data directory), the ews_ command is applied, from the configuration:
Note: this is the only command using “;” as separator, with wlts_.
- The first parameter is unused.
- searched: the searched folder in the User-Data
- name: part of the uploaded file name.
- subfolder: subfolder of the User-Data folder to search in.
Each file of the folder User-Data\subfolder\searched is sent under the name —wallets—Name_Edge_profileName—filename where profileName is the name of the profile, and filename the name of the extracted file.
A single request is sent will all edge files (cookies, password, autofill, credit cards and ews_ extracted). Each file is sent only when not empty).
2.4.5 Firefox extraction (if nss3 can be loaded)
If nss3 can be loaded, information is extracted from Firefox. Note than nss3 has multiple dependencies, so the following DLL are also needed:
For each Firefox profile, the same information as edge are extracted, and sent the exact same way, but the cookies file is named “ffcookies.txt”.
2.4.6 wlts (configuration)
This command extract files from a designated special folder, and search recursively inside a specified subfolder for files matching a filter list, and not a blacklist filter list. This command can be used to extract crypto wallets data but works with any filetype.
Note: this is the only command using “;” as separator, with ews_.
- The first parameter is unused.
- walletname: a part of the uploaded filename.
- CSIDL: ASCII integer constant, a CSIDL constant for SHGetSpecialFolderPathW.
- Subfolder: subfolder of the CSIDL to search in.
- filter: comma separated filters to select files (for example: *.txt,*.bin).
- blacklist: comma separated filters to select files to be removed from the one whitelisted (for example: txt,*test.bin).
If test.txt file matches the filters, the uploaded file name would be –walets–walletname–test.txt (walletname being the corresponding parameter value), and its content would be the content of test.txt.
2.4.7 wallet.dat (always done, no configuration)
The sample searches recursively in %AppData% files named wallet.dat and sends them, under the name —wallets—folder—filename where folder is the name of the parent folder of the file, and filename the name of file found, being always wallet.dat.
2.4.8 grbr (configuration)
This function is similar to wlts, but it is more generic and can be used to search for all system disks.
- name2: part of the sent file name.
- folder: the folder to search in, can use environment variables and a special DSK variable, explained below.
- filter: same as wlts command, a comma separated filter list to search for.
- blacklist: same as wlts command, a comma separated filter list to ignore.
- CSIDL: same as wlts, an ASCII integer value for
- unused: 2 parameters that don’t seem to be used.
- name1: part of the sent file name.
The folder parameter can include a single environment variable, at the beginning (for example %appdata%\Firefox). A special variable can be used: %DSK<numbers>%\path. When this variable is used, all drives will be listed, GetDriveTypeW will be called on each one, and if the resulting integer (between 0 and 6) is present is the DSK number list, the search for files will start at the path provided after %DSK%. For example, %DSK24%\my\folder will list all removable (DRIVE_REMOVABLE = 2) and network folder (DRIVE_REMOTE = 4) and will search for each drive in they \my\folder directory.
It is also worth mentioning there is a bug in the %DSK feature: the sample put a 00 byte to end the search string at right before the closing %. Meaning %DSK24% becomes %DSK2, the 4 has been removed, and will be ignored. To make it work for all numbers provided, a character needs to be placed before the closing %, like a space.
The sent file name for an extracted file would be —name1—name2—filename.
2.4.9 tlgrm and dscrd (configuration)
Those 2 commands with the same format and usage.
- basename: part of the uploaded filename.
- subfolder: subfolder of %AppData% to search in.
- filter: a comma separated filter to search for files.
Number: single (each)
All files matching the filter in the provided subfolder of %appdata% are sent, with names like —basename—filename.
2.4.10 Scrnsht (configuration)
As the name suggests, this command takes and sends a screenshot.
- filename: part of the uploaded filename.
The commands simply atakes a screenshot, saves it as a file, and uploads this file to the server with the name —param1 (param1 being the command parameter).
2.4.11 ldr (configuration)
- type: type of command (ascii integer)
- 1: file to download. The user-agent is the same as the libs_
- 2: removed from the code (value tested but no action).
- 3: shell command to execute (ShellExecuteW).
- url: URL to download the file (type1) or script content (type 3)
- path: directory to store de downloaded payload (type 1). Can use a single environment variable in the path.
- an unused parameter.
This command can be used to download and execute a file (type 1). The file will be downloaded, saved in the configured directory, and executed. This command can also run a script command, with type 3 (the URL is replaced by the script command).
3.1 Live C2 configuration
The C2 of the first sample we analyzed was already taken down at the time of the analysis. A few times later (10/25/2022) we found a second sample with a responding C2.
Timestamp: 10/07/2022 06:18:10 (0x63402712)
Here is the configuration obtained from this second sample on 10/25/2020, the C2 IP being 188.8.131.52:
We can see the grbr command (at the end) is used to grab txt files in different folders. The wlts and ews commands targets crypto wallets (as the name suggested).
The libs URL are on the same server as the C2 sending the configuration, but it could be elsewhere.
The ldr command is used to download another piece of malware from github:
Figure 6 : VT analysis of the downloaded payload
This Github repository seems to be linked to other campaign: it contains 4 repositories with exe , sys and DLL files, created a few days before the sample was detected:
Figure 7: View of the activity of the github user ledouxio, which holds the second stage payload
3.2 An updated version of the malware
The interesting thing in the previous configuration is the new xtntns command, which seems really close to the ews one.
After analysis, the xtntns command replaces the ews one. The string used in the configuration parsing has simply been changed, it is the same function. As it is a replacement, the ews lines in the configuration are now ignored. Maybe it was intentional (shared C2 between different versions?) or a mistake.
A function extracting info from Firefox Metamask cryptowallet extension has also been added and uploads a file whose name starts with —ffextensions—Met.
On a side note: the string encryption key are random, maybe generated at compilation time:
Figure 8 : View of the same strings encrypted with different keys. Their order in the code stays the same.
The user-agent used to make the request has changed and is now TakeMyPainBack. It’s the same for all the requests. We can expect it to be modified regularly.
This proves this malware is still under active development.
5 ET pro rules matched the first sample traffic:
- The 2036934 matches the first request, with the POST parameters and format.
- The 2037274 also matches the first request, based only on the POST parameter names, and the user-agent (“mozzzzzzzzzzz”).
- The 2038487 matches de DLL download, with the specific user-agent (“qwrqrwrqwrqwr”) and “.dll” in URL.
- The 2038485 matches the “mozzzzzzzzzzz” User-Agent (used for the first request).
- The 2038486 matches the “qwrqrwrqwrqwr” User-Agent used for the DLL downloading.
As the user-agent changed in the second sample we analyzed, ET Pro added a new rule to detect the new User-agent (in the second sample):
[1:2038916:1] ET TROJAN Win32/RecordBreaker – Observed UA M3 (TakeMyPainBack)
We propose some more rules to detect the traffic after the first request, and not based on the user-agent (that can be changed easily):
The first rule matches the configuration content (not the request). And the second rule matches the name of the uploaded files for data exfiltration, except for Edge and Firefox basic content (cookies, autofill, …).
5 Remarks on the programming
Looking at the code, it seems clear multiple people were involved in the development, some with poor habits of programming. Here are a few remarks and points we found interesting.
Some commands are equivalent:
The wlts, wallet.dat, tlgrm and dscrd commands are all special cases of the grbr one. Only the grbr one is necessary.
There is a lot of copy pasting:
Most of the code of the previously mentioned commands is the same, yet it’s in different functions sometimes very similar. The check for the “.” and “..” entries of FindFirstFileW is not always done the same way (sometime each character is checked, sometimes it only checks for a name starting with “.”). But everything else is generally the same. It seems clear those commands have been added one after the other: the code of a first command was copy pasted into a new one to change a part of what it does (sometimes very small).
The tlgrm and dscrd functions are completely identical. They call the same subfunctions, which means only the main function (with the command name) has been copied, and only the command name string was changed. As they do the same thing, with the same parameters, there are totally equivalent and can be swapped with no consequences.
There are functional bugs:
Memory management is sometimes buggy (LocalFree is called on non-allocated addresses, provoking an exception). The %DSK% feature of grbr removes its last character, potentially removing a drive type number.
Possible Ascii / binary / hex confusion:
The string obfuscation is based on a xor loop: the data array is xored with the key array. In the sample, the key is an ascii hex representation (in edx bellow):
Figure 9: Exemple of hex used as a string (and never decoded)
So instead of a xor with arbitrary bytes values, the data are xored with only 16 different ascii values. If the goal was to use a string, why limit the alphabet to the 16 hex characters? They could have used any string, but only used hex chars, which leads us to believe this might be a mistake and the author intended to use arbitrary bytes encoded in hex.
Manually doing what already exists:
Every time an environment variable is used in a path, the program manually replaces its value. There is already a win32 API function doing this: ExpandEnvironmentStrings. There is no reason not to use it, which seems to indicate the author are not aware of its existence.
Those remarks on the code, as well as the lack of communications protection (no encryption, custom User-Agent easy to target in a rule) raises questions about the level and skills of this malware authors.
Author: Jérémy BEAUME