Raccoon Stealer was one of the most mentioned malware in 2019. Cybercriminals sold this simple but versatile info stealer as a MaaS just for $75 per week and $200 per month. And it successfully attacked numerous systems. But in March 2022, threat authors shut down their operations.
In July 2022, a new variant of this malware was released. And now Raccoon Stealer 2.0 has gone viral and got a new name in the wild – RecordBreaker. In this article, we will analyze several samples of the info stealer to find out its techniques and what data it collects.
What is Raccoon Stealer?
Raccoon Stealer is a kind of malware that steals various data from an infected computer. It’s quite a basic malware, but hackers who provide excellent service and simple navigation have made Raccoon popular.
The malware’s owners are interested in the following data:
- Login/password pairs from various services saved in browsers
- Cookies from different browsers
- Bank data
- Cryptocurrency wallets
- Credit card information
- Arbitrary files, which can be of interest to intruders
Raccoon – a sample overview
In the process of malware analysis, we worked with the following samples:
sha-256 |
9ee50e94a731872a74f47780317850ae2b9fae9d6c53a957ed7187173feb4f42 |
0142baf3e69fe93e0151a1b5719c90df8e2adca4301c3aa255dd19e778d84edf |
022432f770bf0e7c5260100fcde2ec7c49f68716751fd7d8b9e113bf06167e03 |
048c0113233ddc1250c269c74c9c9b8e9ad3e4dae3533ff0412d02b06bdf4059 |
263c18c86071d085c69f2096460c6b418ae414d3ea92c0c2e75ef7cb47bbe693 |
27e02b973771d43531c97eb5d3fb662f9247e85c4135fe4c030587a8dea72577 |
494ab44bb96537fc8a3e832e3cf032b0599501f96a682205bc46d9b7744d52ab |
f26f5331588cb62a97d44ce55303eb81ef21cf563e2c604fe06b06d97760f544 |
fcdc29b4d9cb808c178954d08c53c0519970fe585b850204655e44c1a901c4de |
MITRE ATT&CK Matrix produced by ANY.RUN Sandbox:
Challenges during the malware analysis of Raccoon stealer v2
Raccoon stealer v.2 got extremely famous, and, of course, we decided to look into it closely. And here, we have faced several challenges:
When we first started our malware analysis, we immediately got a sample 9ee50e94a731872a74f4778037850ae2b9fae9d6c53a957ed7187173feb4f4, which we were unable to run in our sandbox. This example was packed and immediately finished execution when we tried to run it in a virtual environment. So, our team decided to investigate the sandbox evasion mechanisms.
During the sample’s reverse-engineering, we encountered another issue: the packer detects the presence of Anti-Anti-Debugger and terminates before checking the execution’s environment. In our case, we used TitanHide.
When running the program under a debugger, the NtQueryInformationProcess call causes the ProcessInformation variable to be overwritten. The packer compares the random value written to this variable earlier with the value after the call. If they are different, it stops execution.
The challenge was solved with the following script for x64dbg:
bphc
run
findallmem 0, #e91727f5ff#
bph ref.addr(0)+5
run
$p = [esp+0x10]
$val = [p]
log "secret:{0}",$val
bphc
sti
sti
mov [$p], $val
ret
It turned out that the bug was known but had not been fixed at the moment of our research. After the report, it was fixed. Therefore, this Anti-debugger detection method no longer works.
But this script didn’t solve the problem of running in the virtual environment without a debugger. So we continued our malware analysis and came across an interesting piece of code:
As it turned out, this piece of code is executed differently in virtual and real environments. An exception occurs after the IF flag is set in the flag register with the popfd command. If we run in a virtual environment, the exception handler pre-installed by the malware considers that the exception occurred on the “call” instruction.
However, when running on a real machine, the exception occurs on the “nop” instruction. Thus, by comparing the addresses of the exceptions that occurred, the malware determines the presence of a virtual environment.
Bypassing this check is enough to decrease the EIP register value by one when entering the exception handler. After that, the malware is successfully launched.
After making the necessary corrections on our end, this detection method no longer works in ANY.RUN sandbox.
Execution process of RecordBreaker malware
Loading WinAPI libraries, getting addresses of used functions
First, Raccoon dynamically loads WinAPI libraries using kernel32.dll!LoadLibraryW and gets addresses of WinAPI functions using kernel32.dll!GetProcAddress
Decryption of strings
Depending on the sample, the algorithm for encrypting strings can be:
- encrypted with RC4 algorithm, then encoded into the Base64 format
- XOR encrypted with a random key, e.g.:
- encryption may not be applied at all
Examples of decrypted strings:
logins.json |
\autofill.txt |
\cookies.txt |
\passwords.txt |
formhistory.sqlite |
… |
C2 servers decryption
The next malware step is to decrypt C&C servers. There can be several up to five ones. As in the case of strings, the encryption algorithm of C&C servers may vary depending on a sample.
From all the samples we have reviewed, at least two methods have been identified:
- Encryption using the RC4 algorithm with further recoding to Base64:
- Encryption with XOR:
Raccoon termination triggers
At this stage the malware has not executed any malicious code yet. There are certain triggers that may cause the program to terminate without executing any other actions.
The user’s locale is checked (in some samples, certain locales corresponding to the locales of CIS countries cause Raccoon to terminate)
A check is made to see if the malware has been rerun, in parallel with another sample running on this machine. RecordBreaker tries to open a particular mutex (the value of the mutex varies in different samples). If it succeeds, it terminates immediately. If not, it creates the mutex itself.
We can see the result in ANY.RUN: the mutex was created.
Privilege Level Check
After creating a mutex, the malware performs a System/LocalSystem level privilege check using Advapi32.dll!GetTokenInformation and Advapi32.dll!ConvertSidToStringSidW comparing StringSid with L “S-1-5-18”:
Process enumeration
If the check shows that RecordBreaker has the privilege level it needs, it starts enumerating processes using the TlHelp32 API (kernel32.dll!CreateToolhelp32Snapshot to capture processes and kernel32.dll!Process32First / kernel32.dll!Process32Next). In our samples this information isn’t collected or processed in any way.
Connecting to C2 servers
The next important step is to attempt to connect to one of the C&C servers. To do this, Raccoon stealer generates a string like:
machineId={machineguid}|{username}&configId={c2_key}
Then the program tries to send a POST request with the string to every possible server.
An example of a connection request that was intercepted by the HTTP MITM proxy feature in ANY.RUN sandbox:
It is important to note that if there are multiple C&C servers, the malware will only accept commands from the one it was able to connect to first. In response to the above request, the server will send the malware a configuration. If RecordBreaker fails to connect to any of the C&C servers, it will stop its work.
Description of the malware configuration structure
Configuration lines are divided into prefixes, each tells the malware how to interpret a particular line. Here is a table describing these prefixes and what they do:
Prefix | Example | Prefix’s function |
---|---|---|
libs_ | libs_nss3:http://{HOSTADDR}/{RANDOM_STRING}/nss3.dlllibs_msvcp140:http://{HOSTADDR}/{RANDOM_STRING}/msvcp140.dll libs_vcruntime140:http://{HOSTADDR}/{RANDOM_STRING}/vcruntime140.dll | Legitimate libraries necessary for malware work |
grbr_ | grbr_dekstop:%USERPROFILE%\Desktop\|*.txt, *.doc, *pdf*|-|5|1|0|files grbr_documents:%USERPROFILE%\Documents\|*.txt, *.doc, *pdf*|-|5|1|0|files grbr_downloads:%USERPROFILE%\Downloads\|*.txt, *.doc, *pdf*|-|5|1|0|files | Targeted arbitrary files from custom directories |
wlts_ | wlts_exodus:Exodus;26;exodus;*;*partitio*,*cache*,*dictionar* wlts_atomic:Atomic;26;atomic;*;*cache*,*IndexedDB* wlts_jaxxl:JaxxLiberty;26;com.liberty.jaxx;*;*cache* | Targeted crypto-wallets and the files associated with them |
ews_ | ews_meta_e:ejbalbakoplchlghecdalmeeeajnimhm;MetaMask;Local Extension Settings ews_tronl:ibnejdfjmmkpcnlpebklmnkoeoihofec;TronLink;Local Extension Settingsews_bsc:fhbohimaelbohpjbbldcngcnapndodjp;BinanceChain;Local Extension Settings | Targeted cryptowallet related extensions for Google Chrome |
ldr_ | [missing in the configuration of the sample] | Additional commands that should be executed by malware |
tlgrm_ | tlgrm_Telegram:Telegram Desktop\tdata|*|*emoji*,*user_data*,*tdummy*,*dumps* | Targeted files related to the Telegram messenger |
scrnsht_ | scrnsht_Screenshot.jpeg:1 | The name of the screenshot(s) that the malware takes in the process |
token | 101f4cb19fcd8b9713dcbf6a5816dc74 | Part of the URL path for further queries to C2 |
sstmnfo_ | sstmnfo_System Info.txt:System Information: |Installed applications: | | The file description with some system data and a list of installed applications that the malware will generate later |
Once the info stealer receives information concerning what kind of data to collect from C2, it proceeds to do so.
System data collection
The stealer collects various information about the infected system, including the OS bitness, information about RAM, CPU, and user data like the applications installed in the system.
Raccoon’s mechanisms for data collection:
- gets the size of the main monitor using user32.dll!GetSystemMetrics
- finds a list of GPU devices, using user32.dll!EnumDisplayDevicesW
- determines the architecture (bitness) of the system by calling the x64-specific function kernel32.dll!GetSystemWow64DirectoryW and comparing the last error code with ERROR_CALL_NOT_IMPLEMENTED
- collects RAM information via kernel32.dll!GlobalMemoryStatusEx
- gets information about the user’s timezone by kernel32!GetTimeZoneInformation:
- grabs the OS version from the registry, using advapi32.dll!RegOpenKeyExW and advapi32.dll!RegQueryValueExW to read the value of the key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProductName
- obtains Information about the vendor of the CPU using asm-instruction __cpuid:
- gets CPU cores number with kernel32.dll!GetSystemInfo
- collects the user’s default locale info requesting kernel32.dll!GetUserDefaultLCID and kernel32.dll!GetLocaleInfoW
- grabs data about installed apps from the registry using advapi32.dll!RegOpenKeyExW, advapi32.dll!RegEnumKeyExW, and advapi32.dll!RegQueryValueExW.
The “DisplayName” and “DisplayVersion” values of all \HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall key sub-keys:
After obtaining the system information, RecordBreaker gets ready to steal user data. The malware loads the previously downloaded legitimate libraries to reach this goal.
This way, the program has the functions needed for operations:
Once the libraries have been loaded, Raccoon starts to collect user data.
User data collection
Cookies
First of all, the stealer collects cookies. It creates a copy of the cookies file and tries to open it. If it fails to do so, the current subroutine is terminated.
If the sample manages to open the database, it retrieves cookies from it by executing the SQL query
SELECT host, path, isSecure, expiry, name, value FROM moz_cookies
.
Autofill data
The next step in Raccoon’s “plan” is to retrieve the autofill data. The program tries to open the database logins.json:
Then the stealer tries to decrypt the data from that database, using the Зnss3.dll!PK11SDR_Decrypt method.
After that, the malware formats collected data like so:
“URL:%s\nUSR:%s\nPASS:%s”
Autofill form data
After these manipulations, the stealer collects the autofill form data. It attempts to open the formhistory.sqlite database:
If the connection to the database is successful, the program retrieves form data values from it with an SQL query like:
SELECT name, value FROM autofill
RecordBreaker concatenates all data together and sends POST requests to C2. ANY.RUN sandbox’s HTTP MITM proxy feature intercepts all the data that the malware has managed to collect.
- SystemInfo POST request
- UserInfo POST request
When the C2 server gets each chunk of data, it responds “received”:
Crypto-wallets, Custom, and Telegram file data collection
Crypto-wallets data
RecordBreaker is looking for users’ crypto-wallets data using filters and templates retrieved from the configuration.
Custom files
Then, the wallet.dat file is searched (it contains local information about the bitcoin wallet). After that, the stealer looks for arbitrary files from custom directories specified in the configuration.
Telegram messenger files
The sample is looking for files related to Telegram messenger using data from the configuration.
After the malware has sent all the files, it takes a screenshot(s).
An example of a screenshot captured by ANY.RUN:
If any additional commands are provided in configuration, the sample will execute them before finishing its work. For example, Raccoon executes other commands with the help of WinAPI (shell32.dll!ShellExecuteW) if C2 has sent them in the prefix ldr_:
Then, the malware releases the remaining allocated resources, unloads the libraries, and finishes its work.
Raccoon configuration extraction
You can use our Python script to extract C2 servers from the unpacked Raccoon sample, or get malware configuration right in our service, which will unpack the sample from memory dumps and extract C2s for you:
import os, sys, re, string
from enum import IntEnum
from base64 import b64decode, b64encode
from malduck import xor, rc4, base64
# c2 buffer len & invalid c2 placeholder
RACCOON_C2_PLACEHOLDER = b" " * 64
RACCOON_C2_BUFF_LEN = len(RACCOON_C2_PLACEHOLDER)
# c2s array size & key size
RACCOON_C2S_LEN = 5
RACCOON_KEY_LEN = 32
class ERaccoonBuild(IntEnum):
UNKNOWN_BUILD = -1,
OLD_BUILD = 0,
NEW_BUILD = 1
# extracts ascii and unicode strings from binary file
class RaccoonStringExtractor:
ASCII_BYTE = string.printable.encode()
c2_list = []
rc4_key = str()
xor_key = str()
raccoon_build = ERaccoonBuild.UNKNOWN_BUILD
def __init__(self, binary_path) -> None:
with open(binary_path, 'rb') as bin:
self.buffer = bin.read()
self.__process_strings()
def __is_base64_encoded(self, data) -> bool:
try:
data = data.rstrip()
return b64encode(b64decode(data)) == data
except Exception:
return False
def __is_valid_key(self, key) -> bool:
key_re = re.compile(rb"^[a-z0-9]{%d,}" % RACCOON_KEY_LEN)
return re.match(key_re, key)
def __process_strings(self) -> None:
ascii_re = re.compile(rb"([%s]{%d,})" % (self.ASCII_BYTE, 4))
self.c2_list = []
ascii_strings = []
for i, match in enumerate(ascii_re.finditer(self.buffer)):
a_string = match[0]
offset = match.start()
string_entry = (a_string, offset)
ascii_strings.append(string_entry)
if len(a_string) == RACCOON_C2_BUFF_LEN and \
a_string != RACCOON_C2_PLACEHOLDER and \
self.__is_base64_encoded(a_string) == True:
self.raccoon_build = ERaccoonBuild.OLD_BUILD
print(f"[+] found possible encrypted c2 {a_string.rstrip()} at {hex(offset)}")
self.c2_list.append(string_entry)
if len(self.c2_list) == 1: # first c2 found
rc4_key, offset = ascii_strings[i-1]
# rc4 key should be 32-bytes long and contain only a-z 0-9 chars
if self.__is_valid_key(rc4_key):
self.rc4_key = rc4_key
print(f"[+] found possible rc4 key {self.rc4_key} at {hex(offset)}")
else:
continue
# have we found any c2s yet?
if len(self.c2_list) == 0:
for a_string, offset in ascii_strings:
if len(a_string) == RACCOON_KEY_LEN and self.__is_valid_key(a_string):
self.raccoon_build = ERaccoonBuild.NEW_BUILD
self.xor_key = a_string
print(f"[+] found possible xor key {self.xor_key} at {hex(offset)}")
# extract c2s for new builds
curr_offset = offset + 36
for _ in range(0, RACCOON_C2S_LEN):
enc_c2 = self.buffer[curr_offset : curr_offset + RACCOON_C2_BUFF_LEN]
if enc_c2.find(0x20) != 0 and enc_c2 != RACCOON_C2_PLACEHOLDER: # check if c2 is empty
print(f"[+] found possible encrypted c2 {enc_c2.rstrip()} at {hex(curr_offset)}")
self.c2_list.append((enc_c2, curr_offset))
curr_offset += RACCOON_C2_BUFF_LEN + 8 # each c2 is padded by 8 bytes
return # don't process strings any further
else:
return
print(f"[!] C2Cs not found, could be a new build of raccoon sample")
class RaccoonC2Decryptor:
def __init__(self, sample_path: str) -> None:
self.extractor = RaccoonStringExtractor(sample_path)
def __is_valid_c2(self, c2):
return re.match(
rb"((https?):((//)|(\\\\))+([\w\d:#@%/;$()~_?\+-=\\\.&](#!)?)*)", c2
)
def decrypt(self) -> bool:
raccoon_build = self.extractor.raccoon_build
if raccoon_build == ERaccoonBuild.OLD_BUILD:
return self.decrypt_method_1()
elif raccoon_build == ERaccoonBuild.NEW_BUILD:
return self.decrypt_method_2()
else:
return False # unknown raccoon build
def decrypt_method_1(self) -> None:
for enc_c2, _ in self.extractor.c2_list:
decrypted_c2 = rc4(
self.extractor.rc4_key,
base64(enc_c2.rstrip())
)
if self.__is_valid_c2:
print(f"[>] decrypted c2: {decrypted_c2}")
else:
print(f"[!] invalid c2: {decrypted_c2}")
def decrypt_method_2(self) -> None:
for enc_c2, _ in self.extractor.c2_list:
decrypted_c2 = xor(
self.extractor.xor_key,
enc_c2.rstrip()
)
if self.__is_valid_c2:
print(f"[>] decrypted c2: {decrypted_c2}")
else:
print(f"[!] invalid c2: {decrypted_c2}")
def main():
# parse arguments
if len(sys.argv) == 2:
sample_path = os.path.abspath(sys.argv[1])
else:
print(f"[!] usage: {os.path.basename(__file__)} <sample path>")
return False
try:
RaccoonC2Decryptor(sample_path).decrypt()
except Exception as ex:
print(f"[!] exception: {ex}")
if __name__ == '__main__':
main()
IOCs
Filename | SHA-256 |
---|---|
\AppData\LocalLow\nss3.dll | c65b7afb05ee2b2687e6280594019068c3d3829182dfe8604ce4adf2116cc46e |
\AppData\LocalLow\msvcp140.dll | 2db7fd3c9c3c4b67f2d50a5a50e8c69154dc859780dd487c28a4e6ed1af90d01 |
\AppData\LocalLow\vcruntime140.dll | 9d02e952396bdff3abfe5654e07b7a713c84268a225e11ed9a3bf338ed1e424c |
\AppData\LocalLow\mozglue.dll | 4191faf7e5eb105a0f4c5c6ed3e9e9c71014e8aa39bbee313bc92d1411e9e862 |
\AppData\LocalLow\freebl3.dll | b2ae93d30c8beb0b26f03d4a8325ac89b92a299e8f853e5caa51bb32575b06c6 |
\AppData\LocalLow\softokn3.dll | 44be3153c15c2d18f49674a092c135d3482fb89b77a1b2063d01d02985555fe0 |
\AppData\LocalLow\sqlite3.dll | 1b4640e3d5c872f4b8d199f3cff2970319345c766e697a37de65d10a1cffa102 |
HTTP/HTTPS Requests:
http://[C2 address]/ |
http://[C2 address] /aN7jD0qO6kT5bK5bQ4eR8fE1xP7hL2vK/nss3.dll |
http://[C2 address]/aN7jD0qO6kT5bK5bQ4eR8fE1xP7hL2vK/msvcp140.dll |
http://[C2 address]/aN7jD0qO6kT5bK5bQ4eR8fE1xP7hL2vK/vcruntime140.dll |
http://[C2 address]/aN7jD0qO6kT5bK5bQ4eR8fE1xP7hL2vK/mozglue.dll |
http://[C2 address]/aN7jD0qO6kT5bK5bQ4eR8fE1xP7hL2vK/freebl3.dll |
http://[C2 address]/aN7jD0qO6kT5bK5bQ4eR8fE1xP7hL2vK/sqlite3.dll |
http://[C2 address]/[config token] |
http://[C2 address]/aN7jD0qO6kT5bK5bQ4eR8fE1xP7hL2vK/softokn3.dll |
Conclusion
We have done malware analysis of the Raccoon stealer 2.0 performance using a v2 sample in ANY.RUN sandbox. The examined sample has used various techniques to evade detection: legitimate libraries for data collection, dynamic library loading, string encryption, and C&C server encryption. Some examples are additionally protected by packers or being a part of other malware.
Copy the script of Raccoon stealer and try to extract C2 servers by yourselves and let us know about your results.
And write in the comments below what other malware analysis you are interested in. We will be glad to add it to the series!
A few words about ANY.RUN
ANY.RUN is a cloud malware sandbox that handles the heavy lifting of malware analysis for SOC and DFIR teams. Every day, 300,000 professionals use our platform to investigate incidents and streamline threat analysis.
Request a demo today and enjoy 14 days of free access to our Enterprise plan.
14 comments
Wow! Must read. Thank you for your work, guys!
Thank you for the feedback?
So hot and interesting, thank you so much!
Great article! Thanks for the informative text and fantastic visualisation!!!
Good work!
Good job my friend
Nice article! What other malware analysis you are interested in – latest version of XLOADER with C2 extractor will be fantastic!
Thank you for the idea!
206.188.196.200
add drive.exe to the list 0de9a4355f0632687294467468be4f04ef90808e9b6d110602181a56697fd72d
Thanks for the comment! It’s an old sample and its C2 server isn’t available anymore. We can advise you to turn off the Internet while running the task, and it will work out just fine. For example, you can try the following sample: https://app.any.run/tasks/ad60685f-a55d-4d94-bb9f-fd9e4cbeedee
You can also turn on FakeNet or set network to disconnected to extract configurations. Good luck!
Thanks for another informative web site. I have a project that I’m just now working on, and I’ve been on the look out for such info.
You have observed very interesting details! ps nice web site.
As a Newbie, I am constantly searching online for articles that can be of assistance to me. Thank you