Spread

Epsilon generally spreads via the game tester scam. The victim is prompted to try out a game from an itch.io link. The download generally contains an archive, usually RAR, which has a password. The password will be given to the victim via Discord, this is to try and evade automated scanning/execution by anyone other than the victim.

An example DM a victim would receive

Infection

Once the victim has downloaded the archive and extracted it, they will be faced with a Unity game. Other Discord malware attempts to masquerade as a Unity game by including files from unrelated legitimate games inside the archive. Epsilon takes this a step further by wrapping the malicious code inside a Unity game shell which drops the second stage payload. Scanning the executable with VirusTotal will also turn up clean and shares a hash with many legitimate Unity game launchers.

The archive contents

When the victim launches the executable, a Unity game will launch and ask for a key. The purpose of this key is unknown, but I would guess this is designed to be a confirmation that a user has actually executed the file.

Pretending to have run the game without asking for the key

An example of the key entry screen

With a correct key entry, a very basic game will launch. This game is usually ripped directly from elsewhere, and only serves to convince the victim that the attacker’s request was legitimate.

Payload

Upon launching the game, the Unity shell drops an application called GameManager into %LocalAppData%\Temp\[random]\[random] and then executes it.

GameManager is an Electron based application which executes the initial malicious payload and also displays the “game” that is shown to the user.

The GameManager program

Information Gathering

IP/Location Data

The malware retrieves the users external IP address and geolocation data from ipinfo.io.

Chromium Data

Epsilon looks for Chromium data in the following locations, corresponding to Discord, Chrome, Operate, Brave, Yandex, Edge and Vivaldi install locations. Paths containing {number} are iterated over 5 times, substituting {number} with a number 1-5.

const appData = process.env.APPDATA; // Default: C:\Users\[username]\AppData\Roaming
const localAppData = process.env.LOCALAPPDATA; // Default: C:\Users\[username]\AppData\Local
let chromiumPaths = [
    appData + "\\discord\\",
    appData + "\\discordcanary\\",
    appData + "\\discordptb\\",
    appData + "\\discorddevelopment\\",
    appData + "\\lightcord\\",
    localAppData + "\\Google\\Chrome\\User Data\\Default\\",
    localAppData + "\\Google\\Chrome\\User Data\\Guest Profile\\",
    localAppData + "\\Google\\Chrome\\User Data\\Default\\Network\\",
    localAppData + "\\Google\\Chrome\\User Data\\Guest Profile\\Network\\",
    appData + "\\Opera Software\\Opera Stable\\",
    appData + "\\Opera Software\\Opera GX Stable\\",
    appData + "\\Opera Software\\Opera Stable\\Network\\",
    appData + "\\Opera Software\\Opera GX Stable\\Network\\",
    localAppData + "\\BraveSoftware\\Brave-Browser\\User Data\\Default\\",
    localAppData + "\\BraveSoftware\\Brave-Browser\\User Data\\Guest Profile\\",
    localAppData + "\\BraveSoftware\\Brave-Browser\\User Data\\Default\\Network\\",
    localAppData + "\\BraveSoftware\\Brave-Browser\\User Data\\Guest Profile\\Network\\",
    localAppData + "\\Yandex\\YandexBrowser\\User Data\\Guest Profile\\",
    localAppData + "\\Yandex\\YandexBrowser\\User Data\\Guest Profile\\Network\\",
    localAppData + "\\Microsoft\\Edge\\User Data\\Default\\",
    localAppData + "\\Microsoft\\Edge\\User Data\\Guest Profile\\",
    localAppData + "\\Microsoft\\Edge\\User Data\\Default\\Network\\",
    localAppData + "\\Microsoft\\Edge\\User Data\\Guest Profile\\Network\\",
    localAppData + "\\Vivaldi\\User Data\\Default\\",
    localAppData + "\\Vivaldi\\User Data\\Guest Profile\\",
    localAppData + "\\Vivaldi\\User Data\\Default\\Network\\",
    localAppData + "\\Vivaldi\\User Data\\Guest Profile\\Network\\",
    localAppData + "\\Google\\Chrome\\User Data\\Profile {number}\\",
    localAppData + "\\BraveSoftware\\Brave-Browser\\User Data\\Profile {number}\\",
    localAppData + "\\Yandex\\YandexBrowser\\User Data\\Profile {number}\\",
    localAppData + "\\Microsoft\\Edge\\User Data\\Profile {number}\\Network\\",
    localAppData + "\\Vivaldi\\User Data\\Profile {number}\\Network\\",
    localAppData + "\\Google\\Chrome\\User Data\\Profile {number}\\Network\\",
    localAppData + "\\BraveSoftware\\Brave-Browser\\User Data\\Profile {number}\\Network\\",
    localAppData + "\\Yandex\\YandexBrowser\\User Data\\Profile {number}\\Network\\",
    localAppData + "\\Microsoft\\Edge\\User Data\\Profile {number}\\Network\\",
    localAppData + "\\Vivaldi\\User Data\\Profile {number}\\Network\\"
];

The malware iterates over the list multiple times to extract the following information, skipping over the paths which contain the phrase cord:

  • Cookies
  • Saved Passwords
  • Autofill data
  • Saved Cards

The results of these extractions are stored a temp directory created with os.createtempdir().

The list is iterated a final time to retrieve Discord login data specifically.

  • For paths including the phrase discord:
    • The Local State file is read to extract the login decryption key.
    • The login decryption key is decrypted using DPAPI unprotectData
    • The folder Local Storage\leveldb is iterated over, reading each file which ends in .log or .ldb
    • Each file is opened as text, and it attempts to match the regex for an encrypted Discord token dQw4w9WgXcQ:[^.*\['(.*)'\].*$][^\"]*
    • For each match, the encrypted element of the token is extracted and decrypted using the key retrieved earlier
    • The decrypted token is pushed to the results array
  • For all other paths:
    • The .log and .ldb files are retrieved from Local Storage\leveldb as before
    • The files are opened as text, and the regex for an unencrypted Discord token is used [\w-]{24}.[\w-]{6}.[\w-]{25,110}
    • The token is pushed to the results array

Firefox Data

Similar to Chromium data extraction, the following data is extracted from %APPDATA%\Mozilla\Firefox\Profiles:

  • Saved Passwords
  • Cookies
  • Autofill data

The results of these extractions are stored in the same temp directory as the Chromium data, under a sub-folder called epsilon-[username]. Interestingly, no attempt is made to extract Discord tokens from Firefox.

Discord

The tokens retrieved from Chromium data extraction are used to extract the following info:

  • Account badges
  • Nitro subscription (if any)
  • Email address
  • Payment sources
  • Phone Number
  • HQ Friends list

Epsilon downloads and injects a payload into the Discord client which is used for further data extraction. The following paths are checked for a Discord installation:

discordPaths = [
    localAppData + "\\discord\\",
    localAppData + "\\discordcanary\\",
    localAppData + "\\discordptb\\",
    localAppData + "\\discorddevelopment\\",
    localAppData + "\\lightcord\\"
];

If a Discord installation is found, it then attempts to glob match a file with the path \app-*\modules\discord_desktop_core-*\discord_desktop_core\index.js. This path corresponds to a plaintext JS file which is executed before the Discord client is initialised, and is usually contains a single line. Epsilon downloads a payload from a paste service and overwrites index.js with the contents of the paste. A directory is also made called Epsilon-Stealer as a crude way of signalling that the injection is successful. This is a common practise in stealers, and in most the presence of this directory is checked for in order to decide whether to continue with the infection, however it doesn’t appear to be used here.

The presence of BetterDiscord is also checked for and, if found, BetterDiscord’s ‘webhook protection’ feature is disabled by overwriting the string api/webhooks with vulsa1111 in \BetterDiscord\data\betterdiscord.asar

The following paths are also checked for a file which contains the phrase discord_backup_codes, presumably hoping to find a file containing 2FA backup codes.

let userProfile = process.env.USERPROFILE;
userPaths = [
    userProfile + "\\Desktop\\",
    userProfile + "\\Downloads\\",
    userProfile + "\\Documents\\"
];

Cryptocurrency

Each non-discord chromium path is checked for the following extensions, and the data is extracted from each:

cryptoExtensions["Local Extension Settings\\nkbihfbeogaeaoehlefnkodbefgpgknn"] = "Metamask";
cryptoExtensions["Local Extension Settings\\bfnaelmomeimhlpmgjnjophhpkkoljpa"] = "Phantom";
cryptoExtensions["Local Extension Settings\\fhbohimaelbohpjbbldcngcnapndodjp"] = "Binance";
cryptoExtensions["Local Extension Settings\\hnfanknocfeofbddgcijnmhnfnkdnaad"] = "Coinbase";
cryptoExtensions["Local Extension Settings\\aeachknmefphepccionboohckonoeemg"] = "Coin98";
cryptoExtensions["Local Extension Settings\\hifafgmccdpekplomjjkcfgodnhcellj"] = "Crypto_com";

%APPDATA%\Exodus\exodus.wallet is also checked for and extracted.

The data for Crypto wallets is stored in [temp dir]\epsilon-[username]\Wallets.

Telegram

Epsilon checks for the existence of %APPDATA%\Telegram Desktop\tdata, lists all sub-dirs and files and:

  • If a sub-directory name is exactly 16 letters long, the contents of the directory is extracted
  • If a filename ends in s and is exactly 17 letters long, the file is extracted

The malware also attempts to extract the usertag, settingss and key_datas files from the tdata directory, due to poor programming it copies each of these files once for every file inside the tdata directory.

The extracted data is copied to [temp dir]\epsilon-[username]\Messengers\Telegram

MobaXterm

Interestingly, Epsilon targets MobaXTerm, extracting %APPDATA%\MobaXterm\MobaXterm.ini and storing it in [temp dir]\epsilon-[username]\Ftps. This is the only SSH client that is targeted.

FileZilla

FileZilla is also targeted, extracting %APPDATA%\FileZilla\filezilla.xml and storing it in [temp dir]\epsilon-[username]\Ftps

Exfiltration

Data is sent back to the attacker via a Discord webhook. This webhook is retrieved from a paste service, although in one of the samples analysed the paste was retrieved but its result was discarded in favour of a hardcoded webhook.

Once all the above data has been extracted, the entire epsilon-[username] folder is compressed into a zip file. The zip file is then uploaded to the webhook, along with two embeds. One which summarises the number of each type of data that has been extracted, along with the computer name, username and IP address. The other which shows the Discord specific information.

The emojis used in the embeds

The Discord specific embed uses emojis which are prefixed with EM_, and were created between 2022-02-02 and 2022-07-05, with one emoji being created a year earlier on 2021-02-26.

The summary embed refers to messengers as “Applications de communications”, suggesting that the creator is French-speaking.

Evasion

Epsilon does not appear to try and evade detection or analysis in any way. The name “Epsilon” is written in several places inside the malware, with each embed and file being marked with the name.

Persistence

After the initial data extraction is complete, a second version of the malware titled WindowsBootManager.exe is downloaded from the Discord CDN and moved into %APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup. This file appears to be functionally identical to the original malware, but without the game. In the latest sample, this file was uploaded on 2022-11-11.

Interestingly, if the package electron-squirrel-startup is detected, the app quits immediately before running its payload.