Analyzing a Facebook Profile Stealer Written in Node.js
We analyze an information stealer written in Node.js, packaged into an executable, exfiltrated stolen data via both Telegram bot API and a C&C server, and employed GraphQL as a channel for C&C communication.
Save to Folio
During our previous analysis of a campaign involving a Facebook stealer, we discovered another interesting stealer. It was written in Node.js, packaged into an executable, exfiltrated stolen data via both Telegram bot API and a command-and-control (C&C) server, and employed GraphQL as a channel for C&C communication. This blog entry investigates this new stealer and provides an in-depth analysis of its routines and capabilities.
Like the earlier campaign, we noticed that this stealer was distributed via malicious Large Language Model (LLM)-themed Facebook ads. These malicious ads contain a link to a page hosted on Google Sites, which then contains a link to an archive hosted on Trello (an online project and task management tool).
Searching further back, we managed to find older campaigns that abused Trello to host archives containing stealers. The keywords used in the file names include “marketing specialist,” “marketing recruitment,” “CV sample,” “advertising campaign,” “marketing project,” and the names of services like Google, Facebook, and TikTok.
Another example showed a fake website impersonating the CapCut video editor that was used to trick victims into downloading the package.
The offsets and lengths of this source map can be found at the end of the packaged executable file. Researchers can then use the provided information to extract the desired components from the package. As shown in figure 5, app.js (the main file) is the starting point of the application.
When the stealer is executed, it runs its main function that steals cookies and credentials from several Chromium-based web browsers, then exfiltrates the data to the C&C server and to the Telegram bot. It also subscribes the client to the C&C server running GraphQL. When the C&C server sends a message to the client, the stealing function will run again.
- The main function prevents the stealer from running too often. The stealer saves the timestamp of its last run to a text file in the home directory (c:\Users\<username>). If the time elapsed from the last run is less than approximately 30 minutes (or 0x1cd6d0 ms = 1890000 ms = 1890 s = 31.5 mins, to be exact), the stealing routine is skipped for this run.
- It checks for the backup C&C server address, which is encoded using Base64, by querying <C&C server>/bk/map.txt. This backup response is also saved in another text file in the user home directory for future use.
- It retrieves the external IP address by querying the whoer.net website.
- It initializes graphql-ws, ws, and other necessary libraries to enable GraphQL over websockets communication with the C&C server. It then initializes the Apollo Client to access a self-hosted supergraph.
- It subscribes to the C&C server to enable it to push messages to the victim’s machine. It subscribes to observables with callbacks using the zen-observable library. The subscribed operation contains an external IP address as a parameter, with the response (the content of the push message) returning the string value _id.
- It runs the information theft process.
- It then copies itself to the home directory and sets autorun persistence.
If a threat actor pushes a message to the infected client, the received message has the JSON format seen in Figure 6. The purpose of the a message is to assign an _id to the infected client.
After the client receives an _id, it then sends another message back to the C&C server to modify some back-end data (called a mutation in GraphQL terminology). The mutation operation contains a previously assigned _id, followed by a status message concatenated with the current date and time. The theft of credentials from all browsers in the targeted machine then follows.
After the stealing process is completed, the client sends another status message to the server stating that the stealing process has been completed. In the case of a server pushing a message during the stealing process, the client responds with a “wait for the completion” message.
The reason for the implementation of handling such messages is the activation of the stealing process. The threat actor maintains an IP address list of infected clients that are just waiting for activation (by receiving a message). After the threat actor pushes a message to the clients, the stealing process restarts.
The stealer focuses on the following web browsers:
- Microsoft Edge
- Google Chrome
- Opera / OperaGX
For each browser, the stealer searches for available profiles in the User Data folder. It then collects the user home path, the profile path, the User Data path, and version information from the \User Data\Last Version file. Next, it extracts the encrypted key (used to decrypt Chrome cookies and passwords) from \User Data\Local State and decrypts it. Finally, it will kill the browser process.
For each available profile, the stealer gets all saved cookies database from <profile>\Network\Cookies. It then checks if a Facebook cookie named xs is present. This cookie is a session ID that indicates whether a user is logged in to Facebook for that profile. If this cookie is not found, it skips the profile.
It extracts all cookies belonging to Facebook, Google, and Outlook (live.com) and decrypts all of them with a key previously obtained from \User Data\Local State. It also retrieves the database of all saved login credentials from <profile path>\Login Data.
Furthermore, the stealer decrypts all Facebook, Google, and Outlook logins (usernames, emails and passwords) also using a key obtained from \User Data\Local State.
It then checks if the MetaMask extension exists in <profile path>\Local Extension Settings\, after which it packs the whole MetaMask extension directory into a zip archive and exfiltrates it to the Telegram bot.
It gets Facebook’s c_user cookie and additional browser information such as operating system, version, and architecture. The stealer will also attempt to steal Facebook’s access token. If this is unsuccessful, it will exfiltrate Facebook cookies, browser names, executable paths, saved logins, IP addresses, and country codes. The targeted profile is then skipped, with the stealer proceeding to another profile.
It then exfiltrates the following in order via GET requests to C&C server:
- Facebook identity numbers, full usernames, email addresses, birthdays, access tokens, Facebook cookies, browser names, executable paths, saved logins, IP addresses, and country codes
- Gmail credentials and cookies
- Outlook credentials and cookies
- Additional Facebook information, such as email addresses and location information
- Business account information, such as usernames and identifiers. For each business account, it will steal the name, ad account limit, creation time, business ID, permitted roles, verification status, and number of business users associated with the business.
- Page information, including usernames and page access tokens
- Ad account information, which includes usernames. For each ad accounts, the stealer extracts ad account IDs, ad account agencies, spending limits, extended credits (invoice and how often it is billed), currency ratio to USD, time zones, next billing dates, the creation time, billing thresholds, balances, payment cards, payment card expiration dates, payment card verification status, ad account insights, and account status
Most likely for backup purposes, previously extracted information is also saved into a text file and sent to the Telegram bot.
Exfiltration to the C&C server
The exfiltration of stolen data to the C&C server is done via GET request to a randomly generated path (<server>/image/<random 26-character ID>.png). The exfiltrated content is passed inside the authorization header.
The payload in the authorization header is encrypted and hex-encoded. The author likely intended to use a simple string for the encryption, as suggested by the following snippet.
However, in the code, the encryption and decryption is implemented using the improper/illogical use of map/reduce. This means that all characters in passphrase are XORed, which in turn makes the encryption a single-byte XOR encryption.
To make analysis by security researchers more difficult, the application hides its console window by calling the hideConsole command from the node-hide-console-window package.
Threat actor background
Within the code, we noticed several comments, status messages and variable names in Vietnamese language, suggesting that the author(s) of this malware may have knowledge of this language.
The sheer amount of information pilfered by stealers such as the one we just analyzed in this blog entry means that users must be extra careful since these kinds of malware often results in significant compromise and could lead to being targeted for follow-up attacks. As always, vigilance and proper cybersecurity hygiene go a long way in ensuring that a person’s online activities are safe and secure.
Here are some steps users can take to minimize the risks associated with malware that use malicious ads for distribution:
- Avoid clicking on ads, especially if they seem suspicious or offer something that seems too good to be true.
- Be cautious when dealing with pop-up windows or ads that ask for personal information. Never provide sensitive information unless absolutely sure of the legitimacy of the app or website being visited.
- Invest in reputable antivirus and anti-malware technologies. These tools can help detect and mitigate the impact of malicious software that might be delivered through ads.
- Learn how to recognize the most common signs of malicious ads, such as typographical errors, poor grammar, and offers that seem too good to be true.
- When downloading or clicking on ads that promote software, ensure they come from reputable sources. Stick to official app stores and trusted websites when downloading tools and apps.
Indicators of Compromise
The indicators of compromise for this entry can be found here.