FlowerStorm unleashes the KrakVM: PhaaS operators turn to VM-based obfuscation
May 14, 2026
Sublime Threat Intelligence and Research (STIR) identified a phishing campaign using a recently published open-source Javascript virtual machine and obfuscation tool called KrakVM.
Authors
Sublime Threat Intelligence & Research
STIR
FlowerStorm is a widely known Phishing-As-A-Service (PhaaS) attack kit that has been active since at least mid-2024, increasingly in large scale campaigns. FlowerStorm performs targeted, complex collection of a victim’s credentials, including the management of multi-factor authentication (MFA).
In April 2026, Sublime Threat Intelligence and Research (STIR) identified a phishing campaign using a recently published open-source Javascript virtual machine and obfuscation tool called KrakVM. The initial KrakVM-encoded HTML file arrives as an attachment in phishing emails. These emails are short, often not even including a body, with subjects and HTML attachment filenames suggesting the target has a new voicemail, a vendor credit, or unpaid invoices as shown in Table 1.
Table 1. Example attachment filename and phishing email subject
If a victim opens this attachment in a web browser (the default application for .html files), embedded JavaScript will immediately start a complex series of events leading to a credential harvesting web page tailored to the target's environment. The threat actor targeted several verticals during this April 2026 phishing campaign to include: local government, logistics, retail, communications, and real estate.
This particular activity, tracing back to mid-March 2026, shares the commonality of German domain names assembled from English words in combinations that mimic authentic-sounding businesses that has been highlighted in previous public FlowerStorm reporting. This campaign’s attack chain layered two distinct malicious tools to deliver one of the most capable phishing kits currently in the wild.
What makes this campaign notable is the adoption of KrakVM as a delivery wrapper within a month of the project's public release. Both it and FlowerStorm appear to have been deployed close to their default configurations. Based on evidence from this campaign, we assess with moderate confidence that the operators required minimal technical sophistication. This campaign also likely represents only the earliest use of KrakVM’s obfuscation capabilities, and we anticipate more complex implementations as its adoption grows.
While FlowerStorm has been active since at least mid-2024, recent samples include the use of KrakVM applied to HTML phishing attachments. Scripted virtual machines offer a level of complex obscurity to code as actual JavaScript is compiled into unreadable bytes. These virtual machines run in memory to execute its own input code, running additional scripts that cannot be easily accessed by static analysis tools.
While at least some FlowerStorm operators or developers adopted KrakVM quickly by deploying it roughly a month after KrakVM was published on Github, STIR has not identified any indications that the author of KrakVM is a FlowerStorm developer or operator.
KrakVM technical analysis
KrakVM presents analysts with highly obfuscated code containing a large Base64 encoded section. Figure 1 shows a beautified example which is still challenging to understand even with cleaner formatting.
Figure 1. Beautified malicious JavaScript that uses KrakVM
Cleartext strings show a code exception handler named __krak_throw, in place to run the code and collect any errors. There were also signifiers like the function runVM() and the bytecode variable. After analysis, we determined all these indicators were seen in this exact same structure within the KrakVM source code, showing there was minimal effort to customize the attack after it was compiled.
While the bytecode is a large block of Base64 data, its contents are binary and not readable by analysts. This is compiled virtual machine code that is run by the remainder of the JavaScript. Seeking other patterns, we noted the continuous calls to function _0x52cb() with varying Base64 values, such as:
The _0x52cb() function, shown in the screenshot, contains a large amount of bitwise math, suggesting byte-by-byte encryption, each keyed off a unique number assigned to each string. There are multiple layers to each decryption, beginning with a Base64 decode. However, while Base64 is a well-known encoding routine, it does have certain encryption characteristics if the standard alphabet is customized. In this instance, the standard alphabet of “ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/” is rearranged to “XK362qWsj+kTd8OLeHn9ARmovVuxCF/DZyYwJ45fzBIt7GabrhSNicp1EgMQl0UP” for each attack, requiring that any decoder know the exact same sequence. Finally, there is a simple linear congruential generator (LCG) to create the bytes to decrypt the string against, with the various seed values varying between each attack. In the screenshot above, these are shown as _0x11bac3 = [531236379, 9744819, 5575441].
After decrypting each string, most were standard function names used for further execution, and one additional code identification string. For example:
Within this decryption routine was an odd array of bytes, _0x8b657f. This array is simple four string values separated by their individual characters, each represented as an integer value. They signify a random set of error messages that are thrown when the code crashes. While STIR was able to threat hunt based on these values, they are not always the same. In the attack STIR analyzed, the strings were completely different and had unique values appended to each.
Figure 2. Code showing the custom Base64 alphabet and byte-encoded messages
The virtual machine
A virtual machine is a code interpreter that works between the human readable script and the underlying web browser. It takes a series of bytes and performs certain math or function calls based on the bytes it sees. Instead of a structured set of readable code, the result is a long series of individual, very granular operations. Virtual machines have been used by malware families over the decades, many of which use commercially available solutions, like Themida/WinLicense and ASProtect. Instead of just simply reading code and interpreting, each adds extra layers of obfuscation to prevent analysis.
KrakVM implements an interesting and effective approach where each byte of the bytecode is encrypted individually. The initial key, 95 (0x5F), is stored within the initial VM state configuration. Whenever a byte of code is read, that key is used to XOR decrypt the byte. Once complete, a new key is created for the next byte. By knowing this approach, STIR wrote a simple decryptor for the code to produce an estimation of the disassembled content:
The functionality of this code is actually very simple and can be inferred just from basic review. For each instruction, also known as an opcode, there is a set location and an operand, the value to operate against. At location 0x0000 we see a simple jump (JMP) to location 0x000C. By looking downward for this address we see an EVAL statement, used to execute a line of passed code. That code reads a byte and stores it within a table of opcodes. It then jumps to a long series of similar code, populating an opcode table with varying bytes. This is the virtual machine actually preparing itself to execute code by establishing how to interpret each expected instruction.
The final line of code jumps to near the beginning at 0x0006. It then begins the actual malicious code portion. This begins with reading a large block of Base64 data into memory (READ_STR at 0x0065) and then reading various function names like “atob” (JavaScript for decoding Base64), “number”, “document”, and “write”. In effect, this code simply loads an encoded block of Base64 data, decodes it, and uses document.write() to force the victim’s system to immediately load and execute it.
That's it. Six thousand bytes of bytecode, a custom cipher, an opcode installer, and a stateful instruction machine just to call two JavaScript functions to view standard Base64 data. This block of Base64 decodes to the actual attack payload, which was just simply obfuscated by the virtual machine layer. This code is simply the below:
Beyond the random words strewn throughout, a tactic used for static code anti-analysis, there is one line that stands out from this entire segment. There is a final call to a file bootstrap.min.js hosted on a legitimate Tencent provider, myqcloud[.]com, using a Tencent Cloud COS (Cloud Object Storage) bucket. Many modern websites use Bootstrap code for developing mobile-friendly websites, so the filename is well known. In fact, the same filename was imported legitimately in an earlier line of code.
The inline script serves one purpose: passing the victim's email address from the KrakVM wrapper into the FlowerStorm kit. The KrakVM attachment has the victim's email pre-baked into it as a variable named therapeutic. FlowerStorm never references that variable by name. Instead, it declares const a = 'therapeutic' and reads globalThis[a], so the two layers share the value without either one hardcoding a direct reference to the other. The sidedness() function provides an override: if the phishing link contains a ?e=email parameter or a Bases64 encoded address in the URL, that value replaces the embedded default. Whichever address wins, it arrives pre-filled in the email field when the login page renders.
The malicious bootstrap.min.js is the credential harvesting kit, a 1,092,847 byte JavaScript that contains highly obfuscated code able to steal credentials for Microsoft 365 and other providers, while also supporting adversary-in-the-middle (AITM) MFA interception. Notable though is the Base64 value at the very beginning, decoding to a next stage C2 URL.
Figure 3. Second stage JavaScript code from bootstrap.min.js
There are many components to this script but one that stands out is a large array of string values at the end of the script, shown in the screenshot below. These values alternate between varying data structures including Base64, HTML, and CSS. This array of over 26,000 elements is rotated and shifted numerous times and a lookup table formed to find particular data blocks.
Figure 4. Elements showing embedded data within bootstrap.min.js
After automatically deobfuscating this file, STIR found standard FlowerStorm code and functionality in place. This included embedded HTML and graphics to mimic login sites for Microsoft 365, Hotmail, and GoDaddy. The selection process follows the same pattern in prior FlowerStorm attacks where a C2 guides the individual victim.
The attack flow begins with an HTTP POST transmission to a C2 server stored as a URL Base64 encoded within the script, here seen as the file variable. It first sends a simple do=user-check to determine if the server is active and accepting connections. If so, another HTTP POST sends the initial information as do=check&email=<victim_email>. The C2 will respond with which provider credentials should be targeted. It can also optionally provide a custom banner and background image to the fake login page if targeting an organization with branded logins.
From this point, the malware communication can vary based upon how that particular account is to be targeted, starting with a simple do=login that automatically sends the user a password error, requiring them to type it in a second time. This often encourages the user to carefully ensure they are typing the password in correctly.
A widely known unique feature of FlowerStorm is its capability for advanced AITM and MFA interception. This includes sending fake requests for one-time codes for a variety of services. When the fake login appears to the victim it will be pre-populated with an email address Base64 encoded within the initial email attachment. The service can then attempt to login as the user, detect the need for MFA, and prepare a fake page for the user to type in their code. The malware will simply resubmit this on the victim’s behalf and gain access to their account. In reviewing the code we saw multiple MFA methods that could be used in attacks, shown in the code below.
let methods = JSON['parse'](atob(resp['method']));
token = resp['token'];
// Iterate over every MFA method the victim has registered on their account
methods['forEach'](function(entry) {
// Method: Microsoft Authenticator mobile push notification
((entry['authMethodId'] == 'PhoneAppNotification') ||
(entry['authMethodId'] == 'CompanionAppsNotification')) &&
($('#phoneAppNotif')['show'](), // Show a "Approve on your phone" button
$('#phoneAppNotif')['attr']('onclick', // Button to send victim choice to attacker
'OfficeSendVerify(\'' + entry['authMethodId'] + '\')')),
// Method: Microsoft Authenticator TOTP (6-digit code from the app)
(entry['authMethodId'] == 'PhoneAppOTP') &&
$('#PhoneAppOTP')['show'](), // Show TOTP code entry field
// Method: SMS one-time code — entry['display'] for phone number
(entry['authMethodId'] == 'OneWaySMS') &&
($('#VerifSms')['show'](), // Show SMS code entry field
$('.numberSms')['text'](entry['display'])), // Populate it with the victim's masked number
// Method: Automated voice call to mobile or office phone
((entry['authMethodId'] == 'TwoWayVoiceMobile') ||
(entry['authMethodId'] == 'TwoWayVoiceOffice')) &&
($('#verifTelp')['show'](), // Show a "Receive a call" button
$('#verifTelp')['attr']('onclick', // Button to send victim choice to attacker
'OfficeSendVerify(\'' + entry['authMethodId'] + '\')'),
$('#numberTelp')['text'](entry['display'])); // Show victim's phone number
});
Attack summary
In this recent activity, STIR noted activity showing campaigns using multiple layers of obfuscation. The first layer abuses KrakVM, an open-source JavaScript virtual machine that compiles its payload into encrypted bytecode, making the malicious content invisible to tools that inspect the attachment without executing it. The second layer, FlowerStorm, is a mature PhaaS kit with built-in support for targeted credential harvesting across Microsoft 365, Hotmail, and GoDaddy, including real-time AITM MFA interception.
To reiterate what was said at the start of this blog, what makes this campaign notable is the adoption of KrakVM as a delivery wrapper within a month of the project's public release. Both it and FlowerStorm appear to have been deployed close to their default configurations. Based on evidence from this campaign, we assess with moderate confidence that the operators required minimal technical sophistication. This campaign also likely represents only the earliest use of KrakVM’s obfuscation capabilities, and we anticipate more complex implementations as its adoption grows.
As a note, Sublime’s Autonomous Security Analyst (ASA), used the obfuscation and VM bytecode within the payload as malicious signals when analyzing the attack email:
The HTML attachment contains heavily obfuscated JavaScript with custom virtual machine bytecode designed to execute malicious code when opened, likely rendering a fake voicemail interface that harvests credentials.
Sublime releases, detections, blogs, events, and more directly to your inbox.
Thank you!
Thank you for reaching out. A team member will get back to you shortly.
Oops! Something went wrong while submitting the form.
What is email security?
Email security refers to protective measures that prevent unauthorized access to email accounts and protect against threats like phishing, malware, and data breaches. Modern email security like Sublime use AI-powered technology to detect and block sophisticated attacks while providing visibility and control over your email environment.
Related articles
Machine learning
Introducing a new framework for evaluating autonomy in security AI
May 13, 2026
Sublime news
Quarantine Digests are now generally available: visibility for users, control for the SOC
May 11, 2026
Attack spotlight
Prompt injection attacks don't look like what you’re seeing in social media and headlines