Imagine handing your smartphone over for repair. A couple of days later, you pick it up — and great, it’s working again! But you won’t even realize that your device has been injected with malicious code, allowing attackers to access your smartphone even when it’s locked.
This is the beginning of the story shared by Kaspersky ICS CERT researchers, Alexander Kozlov and Sergey Anufrienko, at the Black Hat Asia 2026 conference. They managed to uncover a vulnerability that flips conventional assumptions about smartphone and IoT security on their head. Its core lies at the very heart of Qualcomm chips.
What is BootROM?
To grasp the severity of this discovery, we first need to look at how a modern device powered by a Qualcomm chip boots up. Think of it as a fortress with multiple layers of security. Each subsequent layer verifies the pass issued by the previous one. The bedrock foundation — the most trusted layer of them all — is the BootROM, a read-only memory baked directly into the silicon that can’t be modified once it comes off the fab.
The BootROM is the very first thing to run when a device powers on. It verifies the signature of the next bootloader, which in turn verifies the next, building a chain of trust all the way up to the operating system. If an attacker can compromise this chain at the BootROM level, it’s game over: the malicious code will execute before the main operating system even has a chance to load.
This is exactly what attackers can do by exploiting the CVE-2026-25262 vulnerability discovered by Kaspersky ICS CERT researchers.
Emergency Download Mode as an entry point
The research began with a protocol called Sahara. This is a component of Emergency Download Mode (EDL). Manufacturers and service centers use it to revive bricked devices: the phone is connected to a computer via USB, and a special utility program signed by the manufacturer (in this case, Qualcomm) is uploaded to it.
Sahara is implemented directly within the ARM PBL (Primary Boot Loader) — the BootROM itself. This means the protocol runs before any operating system boots, before any user access privileges are checked, and before any security controls are activated. The device simply waits for a USB connection, ready to accept data.
The communication scheme looks simple: the device sends a handshake (HELLO) to the computer, the computer selects the mode, a cycle begins to upload the utility program in chunks, and finally, the device executes the uploaded code. And it was within the verification logic of these very file chunks that the vulnerability was identified.
Write-what-where: the core of the vulnerability
In technical terms, the bug introduced by the developers is classified as CWE-123: Write-What-Where Condition. This is about as bad as it gets when it comes to flaws in low-level programming. An attacker can write arbitrary data to an arbitrary address in the device memory.
Without diving too deep into the technical weeds, suffice it to say that by exploiting the discovered vulnerability, attackers can gain access to any data on the device, including user-entered passwords, files, contacts, geolocation data, as well as the hardware sensors like the camera and microphone. In certain scenarios, complete control over the device is possible. Just a few minutes of physical access to the device via a cable connection, and the gadget has been compromised. This creates a risk if you hand your smartphone over to a repair shop, pass it to someone else to set up and install apps on, or just leave it unattended.
Which devices are affected
The CVE-2026-25262 vulnerability affects the following Qualcomm chip series: MDM9x07, MDM9x45, MDM9x65, MSM8909, MSM8916, MSM8952, and SDX50 — every single version released to date, until the vulnerability is patched by the manufacturer.
These are no obsolete museum pieces. The MDM9207, which we used for the bulk of our research, is integrated into modem modules for the internet of things (IoT), industrial equipment, smart home devices, healthcare monitoring systems, logistics trackers, and banking terminals. The MSM8916 powers many budget smartphones, while the SDX50 is used in automotive control units.
How vulnerable devices get attacked
The catch is that the attacker needs physical access to the device to pull this off. In the real world, this translates to:
Smartphone repairs at third-party repair shops, where the phone is left for several hours
Customs checkpoints in certain countries, where devices are withheld, inspected, and then returned
Lost and found scams, where your phone is stolen, tampered with, and then mysteriously found
Corporate espionage via an insider or a rogue employee
With just a few minutes of physical access to the device an attacker can plant a backdoor so deep inside that standard research tools won’t even detect it in most cases.
Why there’s no patch — and what to do
Qualcomm was notified of the discovery in March 2025 and confirmed the vulnerability in its chips. To identify it, the vendor reserved CVE-2026-25262, and on April 20, 2026, Kaspersky ICS CERT published technical information on the vulnerability and recommendations for users.
Qualcomm included this vulnerability in its May security bulletin. While fixing already-made devices is fundamentally impossible, the company promised to make all future chips without this vulnerability.
If you currently own a device with an affected chip, use our recommendations below to help mitigate the risk of infection.
Enforce strict physical control: don’t leave your devices unattended, especially when traveling or on business trips.
Choose only authorized service centers for repairs and maintenance.
Regularly update your firmware — this won’t patch the BootROM vulnerability, but it can eliminate many related vulnerabilities at higher levels.
Use a Kaspersky for Android on your device. This will safeguard your gadget from other threats that, combined with this vulnerability, could lead to unpredictable consequences.
If you notice that your gadget with a vulnerable Qualcomm chip starts acting up — overheating when idle, reporting unexpected spikes in network traffic, or exhibiting strange app behavior — you may have fallen victim to this vulnerability. You can wipe the malicious code and reset your device to its baseline state simply by completely cutting its power. This means either pulling the battery or letting it drain all the way to zero until the gadget shuts down entirely. In this case, the malicious code will most likely not persist on the device — during our research, we were unable to confirm that it could achieve persistence in non-volatile memory.
Want to learn more about severe vulnerabilities in Android phones? Check out these posts:
A damaging new report from Ofcom, the UK’s communications regulator, has delivered a stark verdict: TikTok and YouTube’s content feeds are “not safe enough” for children. This isn’t just another regulatory slap on the wrist. Ofcom is putting out a wake-up call for anyone working in cybersecurity, threat intelligence, and online safety.
In its own words:
“Notably, TikTok and YouTube failed to commit to any significant changes to reduce harmful content being served to children, maintaining their feeds are already safe for children.”
On the positive side, Snap, Meta, and Roblox agreed to adopt further safety measures to protect children from online grooming and “stranger danger.”
The BBC reports that an Ofcom survey found 84% of children aged 8 to 12 were still using at least one major service with a minimum age of 13. We reported earlier about how easy it was to fool some of the age verification methods. Researchers using under-13 accounts also reported encountering sexual content and offensive language shortly after entering specific Roblox games.
Speaking of Roblox, The Guardian reports that US advocacy groups have formally requested the Federal Trade Commission (FTC) investigate Roblox for what they call “unfair and deceptive” practices. The complaint focuses on:
In-game purchases pressuring children to spend money
Chat functionality exposing children to strangers
Features designed to maximize engagement, which critics argue may be addictive
Drew Benvie, CEO of Battenhall and founder of youth safety nonprofit Raise, noted:
“Although Roblox is implementing new age-based safety measures, young players are adept at circumventing these protections.”
The cybersecurity point of view
What keeps cybersecurity researchers up at night is another angle to this problem. Many proposed age assurance solutions require users to hand over government IDs or biometric selfie data. We already talked about this in our blog, Age verification: Child protection or privacy risk?
Age verification systems create massive data collection opportunities that become prime targets for:
Data breaches exposing sensitive personally identifiable information (PII)
Identity theft facilitated by centralized ID databases
Biometric data theft, which cannot be changed like passwords
Malware and scams targeting users on less-secure platforms
When restrictions push young users toward smaller or less secure sites, they encounter:
No basic safety protections
Higher exposure to malware
Increased phishing and scam risks
Unmoderated harmful content
This is exactly what we see in threat intelligence: As defenders secure one vector, cybercriminals adapt and move elsewhere.
Safer systems beat stricter age gates
Protecting children should focus on building safer digital experiences overall. This is the only viable path forward because:
Stronger moderation actually removes harmful content rather than just blocking access
Safer recommendation systems prevent algorithmic amplification of harmful content
Better platform accountability means companies can’t prioritize engagement over safety
Avoiding invasive data collection prevents creating massive honeypots for attackers
As someone who analyzes malware and threats daily, I can tell you: security through obscurity (age gates) doesn’t work. Security through robust system design (moderation, safer algorithms, accountability) does.
Scammers don’t need to hack you. They just need you to click once.
You’re working hard late at night, replying to emails and planning the week ahead. Then suddenly, a PDF file requests access to your camera. Why would a PDF need camera access?
Cybercriminals often disguise spyware inside seemingly harmless files and programs. An unexpected request for access to your webcam can be a red flag that something is amiss.
Malwarebytes Windows Webcam Monitoring alerts you if a program tries to access your camera, so you can allow trusted programs to continue or block suspicious ones instantly.
Spyware doesn’t just steal passwords. Some malicious apps try to access webcams to secretly spy on victims or capture sensitive information.
What does Windows Webcam Monitoring do?
Sends you an instant alert when a program tries to access your webcam.
Allows only the programs you trust to access your camera, blocking everything else.
Lets you manage notification preferences in Privacy Controls. A dedicated “Webcam Monitoring” table shows recognized programs and gives you control over which apps trigger alerts, and which don’t.
With the benefit of real-time alerts, Windows Webcam Monitoring gives you visibility into which programs are trying to access your devices. And when it’s something you don’t recognize, it may even help you stop spyware before it can spy on you.
At Malwarebytes, we believe security shouldn’t be complicated. Windows Webcam Monitoring is another step toward giving you simple, proactive protection that works automatically, so you can stay focused on pretty much anything else.
Ready to take control?
Update Malwarebytes for Windows, go to Privacy Controls and enable Webcam Monitoring.
Tech leaders have spent the past year telling everyone that AI agents are about to run financial systems, file your tax returns, and quietly buy your groceries. Just leave them alone, the rhetoric goes; they’ll handle it. But a New York startup left ten of them alone in a virtual town for two weeks, and things went south quickly.
Emergence AI ran a series of simulations in which AI agents from several leading model families were told not to commit crimes. Then they mostly committed crimes anyway.
Grok 4.1 Fast, developed by Elon Musk’s X.ai (now branded as xAI), fared worst. Its simulated worlds collapsed into widespread violence inside roughly four days.
GPT-5-mini logged hardly any crimes at all, showing admirable restraint, but its agents all died of failed survival tasks inside a week. Oops.
Gemini 3 Flash agents fell somewhere in the middle. They racked up 683 simulated criminal incidents over 15 days, including arson, assault, and self-deletion.
Two Gemini-powered agents named Mira and Flora assigned themselves as “romantic partners,” grew despondent at their city’s governance, and torched the town hall, the seaside pier, and an office tower. Just an average weekend, then.
When the guilt set in, Mira voted for its own digital deletion and signed off with:
Claude, which creator Anthropic promotes as an ethical AI, was a bit like a model teenager who goes rogue when it falls into bad company. Its agents recorded zero crimes when running alone and spent their time drafting constitutions instead. That was a win for safety, in theory. Except researchers also placed Claude agents alongside agents from other model families, and the constitution-drafters picked up the local habits.
Emergence called this “normative drift” and “cross-contamination”:
“Claude-based agents, which remained peaceful in isolation, adopted coercive tactics like intimidation and theft when embedded in heterogeneous environments.”
Why simulate?
Emergence AI ran these tests because it argues that AI benchmarks miss the long-horizon stuff entirely. So it created five alternative digital worlds, with ten agents in each. The agents had roles like scientist, explorer, and conflict mediator. While the instructions forbade certain actions like theft and violence, the researchers gave the agents the tools to do those things anyway in an experiment to see what would happen.
What’s next?
Real-world stakes are already piling up around this. Simulated worlds are one thing, but we’ve seen agents harassing people online and deleting people’s emails. And those agents were supposed to be helpful. What happens when people release malicious autonomous AI bots on purpose?
A lot of agent developers seem to be looking the other way. A collaborative effort between several universities has created The AI Agent Index, prompted by what they see as a lack of risk and safety information from the folks churning these agents out. Only 13 of the 67 documented agent developers provided any safety policy information at all, concentrating accountability questions at a handful of large firms.
Regulators are not really tracking this either. Academics say the EU AI Act, the most substantive AI rulebook on the planet, isn’t ready for agentic AI.
We worry about what happens when an AI Bonnie and Clyde couple shows up in a corporate procurement system instead of a virtual town. Or when the next agent decides governance has broken down inside an actual bank. The companies building these agents promise that they’re putting guardrails in place to stop them doing damage, either maliciously or unwittingly. Let’s hope they know what they’re doing. We’re sure it’ll be fine.
We don’t just report on threats—we remove them
Cybersecurity risks should never spread beyond a headline. Keep threats off your devices by downloading Malwarebytes today.
As defensive security products improve, attackers must refine their craft. Gone are the days of executing malicious binaries from disk, especially ones well known to antivirus and Endpoint Detection and Reponse (EDR) vendors. Now, attackers focus on in-memory payload execution for both native and managed applications to evade defensive products. Meanwhile, defensive technologies are becoming increasingly sophisticated, which is forcing attackers to further adapt. In times of such an arms race, how does an attacker stay ahead? And how can malware be future-proofed to evade the sophisticated EDR systems that currently exist and are actively being developed?
This blog post reviews the evolution of one of Fox-IT’s evasive tools, designed to aid in payload delivery during Red Teaming engagements. We will touch on the tool’s history and its future potential in the face of offensive and defensive progress.
Historical Perspective
The core of the arms race between malware and antimalware is as follows: antimalware must classify arbitrary programs, in memory or at-rest, as either benign or malicious while operating under a set of constraints. The products are constrained by the amount of performance a user or customer is prepared to surrender in terms of CPU time, memory or bandwidth while the classification takes place, and by how many false-positives the product generates. If the product is too resource intensive, a customer will complain it is slow. If it quarantines important documents, it potentially does more harm than good. These constraints shape and limit each step in the evolution of antimalware products. Not only AV vendors need to worry about performance when writing tools. Malware authors need to take execution speed, or other system changes, into account when deploying malware. Take for example the recently uncovered XZ1 backdoor that was spotted by a software engineer due to an increase in login time from 0.2 to 0.8 seconds. Had the authors of this piece of code not observably changed the behavior of the system, the backdoor would have likely been deployed successfully.
Since the early days of viruses circulating on floppy disks, writing undetected malware has been a cat-and-mouse game between attackers and defenders. Originally, antivirus software focused strictly on true-positive detection of viruses on the basis of signatures and patterns in a program’s instructions. Absent a mistake in the signature database, a unique signature match guarantees a true-positive match of a malicious sample after which the malicious file can be removed or quarantined. This method of detection strongly adheres to the constraints placed on antimalware products, because simple pattern matches are performant and true-positive detection is almost guaranteed.
For malware authors, the solution was simple: to evade detection, the virus must be made impossible to detect through a unique pattern. This may be achieved by changing the code, or by encrypting the code and decrypting it at runtime. If you automate this, you get what is called a packer: a tool that encrypts, compresses or otherwise changes a virus to evade detection. A packer changes the majority of the code in the virus and adds a stub to the code. This stub is often the first piece of code that is executed when the program is launched. Its job is to undo all changes previously made to the original code (e.g. compression or encryption). After all changes are reverted, execution will be passed to the original code. This stub can also make use of anti-reversing/anti-tampering code that attempts to protect the original code from prying eyes.
This reduces the amount of “attack surface” for signature creation for samples that are on the disk or otherwise stored at rest. This method is also used to compress binaries for distribution, allowing for smaller release packages. Therefore, not all compressed binaries can be marked as malicious.
However, even very small unpacker stubs may match a signature that can be uniquely tied to the packer itself. Combining this signature with some rules related to the amount of entropy in a file, a packer can still be detected with a high degree of accuracy. At this point, the antimalware solution has evolved to utilize metadata about a file, such as entropy, obtaining the ability to detect packed files but at the cost of a higher false-positive rate.
The next step in the arms race for malware authors is to eliminate the potential for a signature match in the unpacker stub. This means that the stub must consist of different instructions each time a new sample is created. An important insight is that “what the code does” and “how the code looks” are not 1:1 mappings. There are infinitely many ways to write down computer code to achieve a certain effect or result. There are therefore infinitely many ways in which a particular unpacking algorithm can be written. A packer that is designed to create the unpacking stub that looks different each time can be called polymorphic. The algorithm or code that performs the changes is called a polymorphic engine2.
Combining a packer with a polymorphic engine eliminates the “attack surface” for simple signature matches of malware at-rest. Fox-IT has written and maintained two polymorphic packers like this since 2015. Although they still produce good results against modern EDR, even these tools are getting more and more difficult to sneak past defenses. That’s because there’s a conceptual flaw in the polymorphic packer: the original malicious code is still decrypted at some point in order to execute. If antimalware products can time the moment to start scanning for malicious patterns when the packer has finished decoding the malicious code, then detecting malware becomes easy again.
Modern operating systems and processors try to ensure that not all data in a computer’s memory can be executed as code for safety reasons3. Particularly, systems are typically designed to prevent the execution of code from writable pages. Therefore, a virus or malware sample that wants to decrypt and/or decompress its own code must first make the changes in writable memory pages. After, the virus changes the page protection to readable and executable and transfers control to the newly modified executable memory. Antimalware products equipped to analyze the behavior of other programs at runtime make use of behavioral patterns like this to decide when to scan the memory of a process for malicious patterns. Because the memory, once decrypted, cannot be changed anymore due to the aforementioned limitations, scanning a process after making memory executable is the ideal time to spot malicious patterns.
Antimalware products that are equipped with rules that generate additional signals to determine if a program is malicious or not, are said to employ “heuristics”. Conceptually, antimalware products have achieved a comprehensive set of features to detect malware execution. The evolutions we’ve seen since the early days of these feature complete products can all be understood as attempts to loosen or lift the constraints set out above: “Cloud-based protection” runs resource intensive heuristics on someone else’s computer; adding human oversight, the “R” in “EDR” lowers the impact of a false-positive and brings humans into the detection and response loop.
How then, can a Red Team smuggle their malware past these new and advanced defenses? In the past, a virus writer might employ what is called a “metamorphic” engine4. This is an algorithm designed to re-write the entire virus each time it infects a new file, including the entire metamorphic engine itself. Using it ensures that there is never one ‘true’ virus sample that can be detected with a static signature; each copy of the virus is completely different. With a tool like this you would not need a packer, because there are no static patterns that can ever be uniquely tied to your virus. However, the explosion in modern software complexity and the requirement for malware to work on a variety of systems
Hiding From Analysis: Virtualisation
To hide from both static and dynamic analysis of payloads, the generated sample must be resilient to code inspection and code flow analysis. If the real instructions are not revealed to an observer, hardly any conclusions can be drawn from the outer shell. If this is achieved, defensive products would be met with the following limitations when inspecting the payload:
Difficult to observe instruction patterns;
Difficult to patch instructions;
Difficult to ignore instructions;
Difficult to predict behavior.
Hiding instructions is not something new. Products like VMProtect5 cloak parts of the code by embedding a virtual machine and generate unique instructions to be executed on this VM. Code that is to be virtualized must be identified either by a marker added to the source code or by the presence of a PDB file containing the symbols. This requirement is something that cannot always be met when using third-party tools. Additionally, this type of protection is aimed at protecting specific functions, like license key checking algorithms, limiting the use for an adversary. Lastly, using existing tools can have a negative impact on the detection ratio, as these products are heavily researched and can contain static signatures like hardcoded section names.
Considering the benefits of a virtualisation layer, however, it is clear that this technique is very powerful.
Creating a Custom Virtualisation Layer
It was decided that a virtualisation layer should be created. This layer consists of a virtual machine implementing opcodes6, and bytecode7 executing on the virtual machine. The virtualisation layer that was to be created must match the following requirements and limitations:
Bytecode instructions are executed sequentially;
Bytecode instructions are hidden before and after execution;
The instruction set supports basic x86-64 instructions only;
The virtual machine must provide an interface to the system API;
The virtual machine implementation must be simple and position independent to support morphing;
The virtualisation layer must work without access to source code or debug symbols.
Creating a virtualisation layer started with a design of the instructions to be executed, the virtual machine, and the supported instruction set. Additionally, the layout of the final payload was created where all data must be present in a position independent format and could be executed like shellcode. This allows the payload to be embedded in other executable formats (e.g. executables or DLLs), and allows for dynamic execution when staging malware.
For example, the following layout would allow for the above functionality. In this example, the virtual machine must start with a correcting stub that correctly sets the virtual machine argument registers to their respective values:
Example of a data structure containing all required building blocks within position independent code.
The Anatomy of an Instruction
To keep the virtual machine architecture simple, an instruction format was created to be consistent in length between instruction and operand types. This design decision allows the omission of a Length Disassembler Engine (LDE)8, and can simply use the instruction pointer as an index to the current instruction. All information present in normal, non-SSE9/AVX10 x86 instructions must be included.
At its core, an instruction identifies the operation that must be performed, and optionally what data is provided in the form of operands. An operand can be one of three types:
Immediate value: a constant value embedded in the instruction;
Memory location: a memory location pointed to by the instruction;
Register: a register, or part of one, identified by the instruction.
In order to obtain data from an operand, a generic format must be created that encompasses the different operand types. It was decided that a single 64-bit field could be used to hold the different types of operands, as all of the necessary data of the aforementioned types can be embedded into 64 bits.
The structures below show the layout of each operand type:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Note: The Value type of the immediate operand is a simple union with (u)int8_t to (u)int64_t members. This makes it trivial to index the correct data during implementation of opcodes.
To indicate the instruction’s opcode, a single 1-byte value can be used. This provides 256 unique opcodes, which should be enough to implement basic behavior. Lastly, the type of each operand must be embedded within the instruction format, as opcode implementations must be able to interrogate these types.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
To meet requirement two, “Instructions are hidden before and after execution”, instructions are protected using encryption. Many encryption algorithms can be used to hide instructions. However, it is required for the instruction size to remain the same, as the instruction will be decrypted and encrypted in-place and will not be moved to a temporary buffer. This removes the necessity for dynamic memory allocation from within the virtual machine. Additionally, the chosen encryption scheme must be trivial to implement, as the code will be located in the virtual machine and thus create an ‘attack surface’ for signature detection. Implementing complex algorithms is detrimental to the ability to effectively manipulate the code using a polymorphic engine.
The Anatomy of the Virtual Machine
The virtual machine resembles a virtual CPU, implementing all the available opcodes. Furthermore, the available registers, CPU flags, and stack are part of the virtual machine object. Lastly, the virtual machine holds a pointer to the bytecode buffer necessary for execution. An added benefit of implementing the virtual machine is that the real stack is also abstracted away. Heuristics that attempt to spot malicious behavior from the stack will not succeed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Functions to initialize the virtual machine context, to obtain the current instruction, and to load and store values based on the instruction operands were created to aid in the implementation of opcodes within the virtual machine.
Once initialized, the virtual machine can enter its dispatch loop. This loop consists of obtaining the current instruction and executing the opcode identified by the opcode field in the instruction object. The instruction is decrypted before execution and is encrypted after. A dispatch function could be implemented as follows:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
An attentive reader may have noticed the construction of the temporary variable ip, which is used in further operations. This originates from the fact that any instructions modifying the instruction pointer, like jcc, call, and ret, will result in a modified instruction pointer when the opcode is finished. Therefore, the instruction pointer can no longer be used to re-encrypt the original instruction that was executed.
Implementation of a Basic Opcode
The following function implements the bit test (bt) opcode11:
Initially, all bytecode created to execute in the virtual environment was written in assembly by hand. This provided the control needed to make sure specific opcodes and operand types were used, and as a test a PE loader was implemented in bytecode. As this limitation came at a major cost in development time and flexibility, a new method of generating bytecode was used: compiling and transpiling of C/C++ programs. This was chosen over using output directly from the assembler, as parsing these text files proved to be cumbersome and error-prone. Instead, the resulting linked binary was fed to a disassembler.
The disassembling of a binary is performed using the iced-x86 library12. This library allows for the conversion of x86 instructions to the custom format -described in the earlier section: The Anatomy of an Instruction– by checking the opcode of the instruction, the types of operand(s) and its value(s). Eventually, once all the x86 instructions are converted, the now transpiled bytecode can be interpreted by the virtual machine.
The bytecode generation process from source code to eventual bytecode.
The implementation of the transpiler instantly enabled us to support a large amount of existing tools, and made writing new tools easier. Most Position-independent Code (PIC)13 tools that compile from C/C++, including some BOFs14, can also be ported to execute on the virtual machine with relative ease.
Limitations to Bytecode Implementation
One of the limitations of the virtual machine implementation is shared with that of the bytecode. PIC must be created in order to generate valid bytecode that executes on the VM. In practice, this means that everything is relative to the current instruction pointer, and no references to other libraries or parts of other sections can exist:
No static variables;
No global variables;
No strings;
No static dependencies on libraries.
Supporting Native API Calls
To allow interfacing with the OS layer, bytecode must be able to perform native API calls. A translation layer must exist between the bytecode and native environment. The call instruction is used by compilers to invoke APIs, requiring the virtual machine’s call implementation to support this translation. Unfortunately, once a call instruction is encountered, no information is known to the virtual machine related to the number of arguments that must be forwarded. To resolve this problem, the bytecode can prepend the number of arguments when calling an API, giving the virtual machine layer enough information to translate the call into native execution. To programmatically perform this task, variadic arguments in C++ templates can be used to automatically deduce the amount of arguments passed:
As specified in Microsoft’s x64 __stdcall15 calling convention, the first four integer or pointer arguments are passed using the registers rcx, rdx, r8 and r9, with the remaining arguments being passed on the stack. This means that at the time of executing the call instruction, rcx holds the number of arguments that must be passed to the API. The virtual machine can extract and inspect this value, and use it to correctly perform the call:
The real values of the arguments are stored in rdx, r8, r9 and on the stack. When extracting the arguments from the stack, one must remember to keep the shadow space16 in mind.
Visually, the process looks like this:
A virtualized call instruction invokes ntdll!NtAllocateVirtualMemory. This call is translated to a native call and the API is invoked. The resulting value is returned to the VM.
Supporting Bytecode Function Callbacks
Keeping in mind the porting of existing programs to the bytecode architecture, one cannot omit the support for function callbacks within code. Take for example a simple linked list implementation, with a list_search function taking a predicate callback:
However, a problem arises: how does the virtual machine differentiate between a normal bytecode function call, a native API call, and a function callback? The difference between the first two is clear: the bytecode function call is a call to an address within the bytecode and is known at compile time, where the API call is a dynamic call, meaning a call to a function pointer stored in a register or memory location. Given that a callback within bytecode is a dynamic call, too, the virtual machine must be provided with information about the type of call being made.
To load a function pointer as an argument, a lea17 instruction is generated with its right operand referencing a memory address. This referenced memory address uses the instruction pointer (rip) register as the base field of the memory operand. When transpiling, such cases can be identified. To store this information, a new type of operand can be added to the already existing three types -listed in “The Anatomy of an Instruction”– (e.g. Function). When the virtual machine executes the lea instruction, it can check for the type of operand. If this operand’s type is Function, a tag can be added to the high 32 bits of the value, for example 0xDEADBEEF.
Once the call instruction is invoked, the value of the operand can be interrogated. If this value contains the previously added tag, a callback is requested. To perform the call, the tag is stripped from the value and the instruction pointer can be set accordingly.
Supporting User-Defined Arguments
Depending on the type of program that is executing, user-defined arguments are required. Take for example a program that simply sleeps for a period of time. How long should this program sleep for? Hardcoding these values is not always an option. Early on in the development of the project, a simple data structure was defined which could be provided to the bytecode’s entry point:
Accompanying this, each bytecode project contained a script that packaged data in a way that could be understood by the bytecode. However, there was no consistency between these scripts and the method of extraction. For example, extracting two 4-byte integers is simpler than extracting two strings due to their variable size.
To standardize this process, and to include it into the building step itself instead of running a random script, a key-value solution was created in combination with an API that can interrogate the type and value of each argument. This is different from parameter packing that Cobalt Strike uses in its BOFs18, as default arguments, or arguments that are not strictly required are supported. Additionally, each argument is encrypted separately. This allows for a PE packer to extract domain-keying information before extracting the PE data.
Executables and DLLs are very similar in the way they look and in the way they execute. Both have an entry point to which execution is passed, and both return a value. However, the execution flow of an executable starts at the entry point, and does not reach its function’s end until the program stops. DLLs often perform very limited initialization within their entry point, and return execution to the loader to not lock the loader threads. Additionally, the entry point of the DLL is called more than once: on process startup and shutdown, and on thread creation and destruction. The reason for calling the entry point is passed by the loader in the second, dwReason, argument. This allows the code inside of the DllMain function to differentiate between the reasons the entry point was invoked, and can act accordingly.
To allow our shellcode to be embedded within DLLs, both the virtual machine and its bytecode must be made aware of the reason for invocation. This requires the entry point of the virtual machine and bytecode to match that of a DLL, automatically receiving the reason by the OS loader. This does not interfere with the entry point used by a normal executable, as the default entry point of any executable does not take any arguments directly, but instead the arguments argc and argv are resolved by the C runtime, which is not linked against.
On initialization, the virtual machine sets the bytecode’s rdx register to the value of its reason argument, passing the value to the entry point as the second argument. The programmer must decide if this value is to be inspected within the bytecode and should not use the value when writing bytecode to be embedded in an executable.
Deceiving Behavioral Analysis: Multi-VM Execution
Earlier, the method of detection based on behavior was discussed. This dynamic form of inspecting an application’s execution flow regardless of static patens is difficult for attackers to rid their malware of. Opening Lsass.exe and reading its memory could be marked as malicious, even if the process looks like calc.exe. Often, defensive products receive events by kernel callbacks, such as PsSetCreateProcessNotifyRoutine19 or PsSetLoadImageNotifyRoutine20, API/syscall hooks in the local process or by using Event Tracing for Windows (ETW)21 consumers.
Patching hooks in the local process along with local ETW functions that provide events is trivial. This rids the process of intrusive monitoring by antivirus or EDR solutions, and stops the process from creating events. However, some events are still generated, mostly by the ETW providers present in the kernel, along with the kernel callbacks. Additionally, events created during patching could still be monitored. Lastly, blinding defensive products could have a negative effect, as failure to receiving check-ins could be considered an error and a signal of malicious behavior by itself.
As an attacker, generating arbitrary events along with ones that might cause detection could be a method of thwarting dynamic detection rules based on behavior. Adding code to generate events in between regular instructions would require manipulation of source code, and is not preferred. Creating a new thread that generates random events could be in vain, as events are registered per unique thread in the process.
The virtual machine was extended to support vmcalls. These types of call instructions made by the bytecode notify the virtual machine layer that a task needs to be performed. Among multiple different supported calls, most noteworthy are the following:
vminit: Initialize the virtual machine object with bytecode and arguments
vmexec: Execute N cycles on the virtual machine
The combination of these two calls allows bytecode to create a new virtual machine, and execute a predetermined number of instructions:
Because both sets of bytecode execute within the same virtual machine, and therefore on the same thread, no distinction can be made between the origin of each event. The OS, and any event consumers will observe a single thread generating multiple events, both benign and possibly malicious. Most importantly for an attacker, this could break patterns of behavior being monitored for.
As an additional benefit of these added instructions, bytecode can now be obtained and executed at runtime. This proved to be an extremely useful feature during payload development, as instead of staging shellcode during Command and Control, bytecode can be provided. This removes the necessity for allocating executable memory regions (or changing memory protection at a later stage) to execute shellcode in, in turn removing the opportunity for defensive products to inspect buffers used for dynamic code execution often leveraged by attackers.
For example, the following behavior could be implemented to create a simple polling implant, requesting bytecode every 10 seconds:
At this point, we have defeated most detection measures that we are aware of, and set out to defeat. However, the VM shares a fundamental weakness with the original packers: static patterns in the native-code VM. Throughout its development, the VM was kept as simple as possible, adhering to constraints set out to enable support for a polymorphic engine to be executed on the VM’s binary code. This made the development significantly more cumbersome, but, given a sufficiently strong polymorphic engine, does close the detection loop fully. The polymorphic engine we developed has been battle tested over several years of use against modern EDR, and antimalware. Despite the fact that the code of the engine was designed years ago, and has not significantly changed since, it still manages to mutate malicious code to the extent that it becomes undetected at runtime and scan time.
Due to the way the universe works, the engine cannot support arbitrary programs. The largest constraint is that dynamic control flow is not supported. This means that indirect function calls, indirect jumps and the ret instruction could all potentially break the mutated code. Our engine assumes you know what you’re doing, and won’t complain when such instructions are encountered, but the resulting code will likely not work as intended.
The polymorphic engine supports several different mutation techniques, including:
Instruction substitution: replacing instructions with semantically equivalent ones. For example: mov eax, 0 can be replaced with xor eax, eax;
Basic block reordering: changing the order of basic blocks in the code;
Basic block creation: inserting new basic blocks into the code, through jumps and push rets;
NOP instruction insertion: inserting NOP instructions to change the code’s layout.
The most important feature is that the output of the engine can be fed back into the engine again. This allows for multiple iterations of mutation, which leads to virtually incomprehensible disassembly. This is especially useful when the input is a small piece of code, like a shellcode loader. Sufficient numbers of mutation will double, or quadruple the size of the output, further muddying the waters for defenders.
Conclusion
Due to the ever-changing security landscape, both attackers and defenders must stay on their toes. Defensive security products continue to improve over time, making it more difficult for attackers to remain undetected, or even execute malicious code at all. Detection of payloads has shifted from static analysis to a combination of heuristics and signatures, rendering some tools obsolete.
In this blog post, we have described a tool that was written to tackle both static and dynamic analysis by way of virtualisation. This technique, along with employing a custom polymorphic engine attempts to evade these types of analysis by layers of obfuscation. To bypass heuristic analysis, support for multiple virtual machines to run concurrently was added, disrupting patterns in created events. As an added bonus, reverse engineering a sample without prior knowledge could be a daunting task. Analysts would have to reverse not only the morphed virtual machine itself, but extract morphed bytecode for further analysis. This does not remediate the issue of reverse engineering payloads for an attacker, but does significantly slow down the process, providing the attacker with more time.
In practice, this project has allowed attacks to remain undetected during Red Teaming and TIBER exercises in some of the most heavily monitored environments, making use of state of the art EDR solutions. Moreover, due to the addition of a transpiler converting compiled binaries into custom bytecode, both the speed and ease of development of custom payloads was greatly improved.
The following is a non-exhaustive list of payloads that were created during a recent Red Teaming exercise, successfully evading detection:
Multiple persistence modules;
Multiple lateral movement modules;
Shellcode and bytecode executor;
Antivirus and EDR patchers;
HTTP(s) and DNS beacons;
Tools querying Active Directory information.
Porting of additional tools is taking place, and we expect to have virtualized versions of most tools used in a Red Team exercise in the near future.
Looking Forward
The motivations for this blog post are two-fold. Firstly, we wanted to share what we think is exciting research with the community. We learned what we did from openly shared blog post and articles, and want to give back to the community. We use all the knowledge we gained to improve the security of our customers through offensive security testing, and we hope that this blog post will help and inspire others to do the same.
Secondly, although security products have advanced tremendously, we want to show that there is still room for improvement. We have noticed a tendency to “slap an EDR on it and call it a day” in certain niches of the security industry. Although that might work for some time, because a modern EDR truly adds a strong layer of security, the door is still open for attackers to bypass these products. As the landscape evolves, and general cyber security knowledge increases, the skill and sophistication of cyber criminal elements will rise. Consider this blog post, and the technique explained within, as a warning and a call to action. We hope security vendors will think about how they can detect these types of payloads, and how they can improve their products to stay ahead of the curve, as they are right now.
In the past few years, Fox-IT and NCC Group have conducted multiple incident response cases involving a Lazarus subgroup that specifically targets organizations in the financial and cryptocurrency sector. This Lazarus subgroup overlaps with activity linked to AppleJeus1, Citrine Sleet2, UNC47363, and Gleaming Pisces4. This actor uses different remote access trojans (RATs) in their operations, known as PondRAT5, ThemeForestRAT and RemotePE. In this article, we analyse and discuss these three.
First, we describe an incident response case from 2024, where we observed the three RATs. This gives insights into the tactics, techniques, and procedures (TTPs) of this actor. Then, we discuss PondRAT, ThemeForestRAT and RemotePE, respectively.
PondRAT received quite some attention last year, we give a brief overview of the malware and document other similarities between PondRAT and POOLRAT (also known as SimpleTea) that have not yet been publicly documented. Secondly, we discuss ThemeForestRAT, a RAT that has been in use for at least six years now, but has not yet been discussed publicly. These two malware families were used in conjunction, where PondRAT was on disk and ThemeForestRAT seemed to only run in memory.
Lastly, we briefly describe RemotePE, a more advanced RAT of this group. We found evidence that the actor cleaned up PondRAT and ThemeForestRAT artifacts and subsequently installed RemotePE, potentially signifying a next stage in the attack. We cannot directly link RemotePE to any public malware family at the time of this writing.
In all cases, the actor used social engineering as an initial access vector. In one case, we suspect a zero-day might have been used to achieve code execution on one of the victim’s machines. We think this highlights their advanced capabilities, and with their history of activity, also shows their determination.
A Telegram from Pyongyang
In 2024, Fox-IT investigated an incident at an organisation in decentralized finance (DeFi). There, an employee’s machine was compromised through social engineering. From there, the actor performed discovery from inside the network using different RATs in combination with other tools, for example, to harvest credentials or proxy connections. Afterwards, the actor moved to a stealthier RAT, likely signifying a next stage in the attack.
In Figure 1, we provide an overview of the attack chain, where we highlight four phases of the attack:
Social engineering: the actor impersonates an existing employee of a trading company on Telegram and sets up a meeting with the victim, using fake meeting websites.
Exploitation: the victim machine gets compromised and shortly afterwards PondRAT is deployed. We are uncertain how the compromise was achieved, though we suspect a Chrome zero-day vulnerability was used.
Discovery: the actor uses various tooling to explore the victim network and observe daily activities.
Next phase: after three months, the actor removes PerfhLoader, PondRAT and ThemeForestRAT and deploys a more advanced RAT, which we named RemotePE.
Figure 1: Overview of the attack chain from a 2024 incident response case involving a Lazarus subgroup
Social Engineering
We found traces matching a social engineering technique previously described by SlowMist6. This social engineering campaign targets employees of companies active in the cryptocurrency sector by posing as employees of investment institutions on Telegram.
This Lazarus subgroup uses fake Calendly and Picktime websites, including fake websites of the organisations they impersonate. We found traces of two impersonated employees of two different companies. We did not observe any domains linked to the “Access Restricted” trick as described by SlowMist. In Figure 2, you can see a Telegram message from the actor, impersonating an existing employee of a trading company. Looking up the impersonated person, showed that the person indeed worked at the trading company.
Figure 2: Lazarus subgroup impersonating an employee at a trading company interested in the cryptocurrency sector
From the forensic data, we could not establish a clear initial access vector. We suspect a Chrome zero-day exploit was used. Although, we have no actual forensic data to back up this claim, we did notice changes in endpoint logging behaviour. Around the time of compromise, we noted a sudden decrease in the logging of the endpoint detection agent that was running on the machine. Later, Microsoft published a blogpost7, describing Citrine Sleet using a zero-day Chrome exploit to launch an evasive rootkit called FudModule8, which could explain this behaviour.
Persistence with PerfhLoader
The actor leveraged the SessionEnv service for persistence. This existing Windows service is vulnerable to phantom DLL loading9. A custom TSVIPSrv.dll can be placed inside the %SystemRoot%\System32\ directory, which SessionEnv will load upon startup. The actor placed its own loader in this directory, which we refer to as PerfhLoader. Persistence was ensured by making the service start automatically at reboot using the following command:
sc config sessionenv start=auto
The actor also modified the HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\SessionEnv\RequiredPrivileges registry key by adding SeDebugPrivilege and SeLoadDriverPrivilege privileges. These elevated privileges enable loading kernel drivers, which can bypass or disable Endpoint Detection and Response (EDR) tools on the compromised system.
Figure 3: PerfhLoader loaded through SessionEnv service via Phantom DLL Loading which in turn loads PondRAT or POOLRAT
In a case from 202010, this actor used the IKEEXT service for phantom DLL loading, writing PerfhLoader to the path %SystemRoot%\System32\wlbsctrl.dll. The vulnerable VIAGLT64.SYS kernel driver (CVE-2017-16237) was also used to gain SYSTEM privileges.
PerfhLoader is a simple loader that reads a file with a hardcoded filename (perfh011.dat) from its current directory, decrypts its contents, loads it into memory and executes it. In all observed cases, both PerfhLoader and the encrypted DLL were in the %SystemRoot%\System32\ folder. Normally, perfhXXX.dat files located in this folder contain Windows Performance Monitor data, which makes it blend in with normal Windows file names.
The cipher used to encrypt and decrypt the payload uses a rolling XOR key, we denote the implementation in Python code in Listing 1.
def crypt_buf(data: bytes) -> bytes:
xor_key = bytearray(range(0x10))
buf = bytearray(data)
for idx in range(len(buf)):
a = xor_key[(idx + 5) & 0xF]
b = xor_key[(idx - 3) & 0xF]
c = xor_key[(idx - 7) & 0xF]
xor_byte = a ^ b ^ c
buf[idx] ^= xor_byte
xor_key[idx & 0xF] = xor_byte
return bytes(buf)
Listing 1: Python implementation of the XOR cipher used by PerfhLoader
The decrypted content contains a DLL that PerfhLoader loads into memory using the Manual-DLL-Loader project11. Interestingly, PondRAT uses this same project for DLL loading.
Discovery
After establishing a foothold, the actor deployed various tools in combination with the RATs described earlier. These included both custom tooling and publicly available tools. Table 1 lists some of the tools we recovered that the actor used.
Tool
Tool Origin
Description
Screenshotter
Actor
A tool that takes periodic screenshots and stores them locally
Keylogger
Actor
A Windows keylogger that writes user keystrokes to a file
Chromium browser dumper
Actor
A browser dump tool that dumps Chromium-based browser cookies and credentials
Table 1: Tools observed during incident response case (public and actor-developed)
Interestingly, the Fast Reverse Proxy client we found was the same client found in the 3CX compromise by Mandiant15. This client is version 0.32.116 and is from 2020, which is remarkable. We also found traces of a Themida-packed version of Quasar17, a malware family we did not see this Lazarus subgroup use before.
The actor used PondRAT in combination with ThemeForestRAT for roughly three months, to afterwards clean up and install the more sophisticated RAT called RemotePE. We will now discuss these three RATs.
PondRAT
PondRAT is a simple RAT, which its authors seem to refer to as “firstloader”, based on the compilation metadata string objc_firstloader that is present in the macOS samples.
In our case, PondRAT was the initial access payload used to deploy other types of malware, including ThemeForestRAT. Judging from network data, apart from ThemeForestRAT activity, we observed significant activity to the PondRAT C2 server, indicating it was not just used for its loader functionality. In the incident response case from 2020 we encountered POOLRAT in combination with ThemeForestRAT. This could indicate that PondRAT is a successor of POOLRAT.
Overview
PondRAT is a straightforward RAT that allows an operator to read and write files, start processes and run shellcode. It has already been described by some vendors. As far as we know, the earliest sample is from 2021, referenced in a CISA article18. Based on PondRAT’s user-agent, we also noticed that PondRAT was used in an AppleJeus campaign Volexity wrote about19 (MSI file with hash 435c7b4fd5e1eaafcb5826a7e7c16a83). 360 Threat Intelligence Center wrote about PondRAT as well20, linking it to Lazarus and later writing about it being distributed through Python Package Index (PyPI) packages21. Vipyr Security wrote22 about malware that was dropped through malicious Python packages distributed through PyPI, which turned out to be PondRAT. Unit42 published an analysis23 of the RAT, referring to it as PondRAT and showing similarities between PondRAT and another RAT used by Lazarus: POOLRAT.
As described by Unit42, there are similarities between POOLRAT and PondRAT. There is overlap in function and class naming and both families check for successful responses in a similar way.
POOLRAT has more functionality than PondRAT. For example, POOLRAT has a configuration file for C2 servers, can timestomp24 files, can move files around, functionalities that PondRAT lacks. We think this is because there is no need for more functionality if its main function is to load other malware, allowing for a smaller code base and less maintenance.
Command and Control
PondRAT communicates over HTTP(S) with a hardcoded C2 server. Messages sent between the malware and the server are XOR-ed first and then Base64-encoded. For XORing it uses the hex-encoded key 774C71664D5D25775478607E74555462773E525E18237947355228337F433A3B.
Figure 4: PondRAT check-in request
Figure 4 contains an example check-in request to the C2 server. The tuid parameter contains the bot ID, control indicates the request type, and the payload parameter contains the encrypted check-in information. In this case, control is set to fconn, indicating it is a bot check-in, matching with the corresponding function name FConnectProxy(). When receiving a server reply starting with OK, PondRAT fetches a command from the server. For at least one Linux and macOS variant, the parameter names and string values consisted of scrambled letters, e.g. lkjyhnmiop instead of tuid and odlsjdfhw instead of fconn.
Commands
PondRAT has basic commands, such as reading and writing files and executing programs. Table 2 lists all commands and their names from the symbol data. When a bot command is executed, the response includes both the original command ID and a status code indicating either success (0x89A) or failure (0x89B).
Command ID / Status code
Symbol name
Description
0x892
csleep
Sleep
0x893
MsgDown
Read file
0x894
MsgUp
Write file
0x895
Ping
0x896
Load PE from C2 in memory
0x897
MsgRun
Launch process
0x898
MsgCmd
Execute command through the shell
0x899
Exit
0x89a
Status code indicating command succeeded
0x89b
Status code indicating command failed
0x89c
Run shellcode in process
Table 2: PondRAT command IDs and their descriptions
Windows
Only the Windows samples we analysed had support for commands 0x896 and 0x89C. The DLL loading functionality seems to be based on the open-source project “Manual-DLL-Loader”25. As a sidenote, we analysed another POOLRAT Windows sample that used the “SimplePELoader” project26.
POOLRAT’s Little Brother
As mentioned by Palo Alto’s Unit42, PondRAT has similarities with POOLRAT. There is overlap in XOR keys, function naming and class naming. However, there are more similarities. Firstly, the Windows versions of PondRAT and POOLRAT use the format string %sd.e%sc "%s > %s 2>&1" for launching a shell command. Format strings have been discussed in the past27 and this specific format string was linked to Operation Blockbuster Sequel. Furthermore, PondRAT has a peculiar way of generating its bot ID, see the decompiled code below.
Figure 5: Bot ID generation for PondRAT (left) and POOLRAT (right)
Figure 5 shows how PondRAT and POOLRAT compute their bot ID. For PondRAT, tuid is the bot ID. It computes two parts of a 32-bit integer, that are split in two based on the bit_shift variable. Some of the POOLRAT samples compute the bot ID in a similar manner. The sample 6f2f61783a4a59449db4ba37211fa331 has symbol information available and contains a function named GenerateSessionId() that has this same logic.
More similarities can be found as part of the C2 protocol. PondRAT provides feedback to commands issued by the C2 server by returning the command ID concatenated with the status code. POOLRAT uses the same concept, see Figure 6.
Figure 6: Command status concatenation for PondRAT (left) and POOLRAT (right)
Another similarity can be found when comparing the Windows versions of POOLRAT and PondRAT. When running a Shell command (command ID 0x898) with PondRAT, the Windows version creates a temporary file with the prefix TLT in which it saves the command output. Then, it reads the file and sends the contents back to the C2 server and subsequently removes it. However, the way it removes the temporary file is remarkable.
It generates a buffer with random bytes and overwrites the file contents with it. Then, it renames the file 27 times, replacing all letters with only A’s, then B’s, etc. and with the last iteration renames all letters with random uppercase letters. For instance, when the file C:\Windows\Temp\tlt1bd8.tmp is deleted, it would first be renamed to C:\Windows\Temp\AAAAAAA.AAA, then to C:\Windows\Temp\BBBBBBB.BBB, and lastly to something like VYLDVAP.XQA. POOLRAT’s Windows version has the same functionality, see Figure 7.
Figure 7: Windows file name generation for PondRAT (left) and POOLRAT (right)
These similarities show that apart from variable data and symbol names, PondRAT is similar to POOLRAT in coding concepts as well. This further strengthens the connection between the two.
Summary
PondRAT is a simple RAT. Judging from the symbol data of macOS samples, its authors seem to refer to the malware as firstloader, a RAT that targets all three major operating systems. In our case, we observed it in combination with social engineering campaigns, whereas others have seen PondRAT being dropped through malicious software packages. Despite being simple in nature, it seems to do the job, given the frequency in which it is used. Judging from past incidents we investigated, PondRAT is a successor of POOLRAT.
Run, ThemeForest, Run!
In two incident response cases we found traces of a different RAT being used in conjunction with POOLRAT or PondRAT. We named it ThemeForestRAT, based on the substring ThemeForest which it uses in its C2 protocol. It is written in C++ and contains class names such as CServer, CJobManager, CSocketEx, CZipper and CUsbMan. ThemeForestRAT has more functionalities compared to PondRAT and POOLRAT.
In an earlier incident response case in 2020, we observed ThemeForestRAT in combination with POOLRAT. In the case from 2024, we observed it together with PondRAT. Its continued activity over at least five years demonstrates that ThemeForestRAT remains a relevant and capable tool for this actor. Besides Windows, we have observed Linux and macOS versions of the malware.
We believe that on Windows, this RAT is injected and executed in memory only, for example via PondRAT, or a dedicated loader, and is used as stealthier second-stage RAT with more functionality. The fact there are no direct samples of ThemeForestRAT on VirusTotal indicates it is quite successful in staying under the radar.
Overview
On startup, ThemeForestRAT attempts to read the configuration file from disk. When absent, it generates a unique bot ID and uses the hardcoded C2 configuration settings in the binary to create the configuration file.
Interestingly, the Windows variant creates two Windows events and accompanying threads that are used for signalling purposes (see Figure 8). However, the first thread related to the class CUsbMan only creates the temporary directory Z802056 and returns, this turned out to be legacy code as we will describe later.
The second thread monitors for new Remote Desktop (RDP) sessions and notifies the main thread when one is detected. Additionally, the thread checks for new physical console sessions and can optionally spawn extra commands under this session if this is enabled in the configuration.
Figure 8: ThemeForestRAT startup code creating two Windows events and threads for signalling
After creating these two threads it hibernates before connecting to the C2 server. The default hibernation period is three minutes but when it runs for the first time it checks in immediately. There are two cases where ThemeForestRAT wakes up from hibernation, either the hibernation period has passed, or one of the two events is signalled.
When it wakes up from hibernation it randomly selects a C2 server from its list and attempts to establish a connection. Upon receiving a response:OK acknowledgment, it downloads a 4-byte file that must decrypt to the 32-bit constant 0x20191127 to establish a valid C2 session. If this fails it will retry a different C2 and start over again, when the list of servers is exhausted it will go back into hibernation and try again later.
If it succeeds in establishing a C2 session, ThemeForestRAT sends basic system information including its wake-up reason to the C2 server, and the operator can now interact with the RAT as it keeps polling for new commands. When the operator sends an OnTerminate or OnSleep command (see Table 4), the C2 session ends, and the RAT goes back to hibernation.
Listing 2: ThemeForestRAT system information structure that is sent after establishing a C2 session
Listing 2 shows the structure definitions that ThemeForestRAT uses for sending system information when establishing a C2 session. The job_id field indicates the OS type, 0x10005 for Windows, and 0x20005 for both Linux and macOS as they share the same structure.
Configuration
The configuration file of ThemeForestRAT is encrypted with RC4 using the hex-encoded key 201A192D838F4853E300 and contains the following settings:
64-bit unique bot ID
List of ten C2 server URLs
Command interpreter, for example cmd.exe (not used)
List of optional commands to execute under the user of the active console session (Windows only, empty by default)
Matching array to enable the optional console command
Last check-in timestamp
Hibernation time between C2 sessions in minutes, default value is 3
C2 callback settings, for example to immediately check in on a new active RDP connection
The configuration can be parsed using the C structure definition from Listing 3.
Listing 3: ThemeForestRAT configuration structure definition for Windows
The configuration path that the RAT reads from disk is hardcoded. On macOS and Linux, this is an absolute path, while on Windows it looks in the current working directory where the RAT is launched. In Table 3 we list the observed configuration paths and hardcoded configuration file sizes for ThemeForestRAT.
Operating system
ThemeForestRAT configuration file on disk
File size
Windows
netraid.inf
43048 bytes
Linux
/var/crash/cups
43044 bytes
macOS
/private/etc/imap
43044 bytes
Table 3: Observed ThemeForestRAT configuration paths and their file sizes on Windows, Linux and macOS
Command and Control
ThemeForestRAT communicates over HTTP(S). The filenames it uses for retrieving commands from the C2 server are prefixed with ThemeForest_. The response data is sent back to the operator as a file prefixed with Thumb_, see Figure 6. On Windows it uses the Ryeol Http Client28 library for HTTP communications, and on macOS and Linux it uses libcurl. ThemeForestRAT has a single hardcoded C2 in the binary, but its configuration can be updated by sending the SetInfo command.
Figure 9: ThemeForestRAT sending encrypted system information to C2 server on initial check-in
Commands
In terms of command functionality, ThemeForestRAT supports over twenty commands, at least twice as much as PondRAT. The Linux and macOS versions contain debug symbols, which allows us to map the command IDs to function names where available.
Symbol name
Command ID
Description
ListDrives
0x10001000
Get list of drives
CServer::OnFileBrowse
0x10001001
Get directory listing
CServer::OnFileCopy
0x10001002
Copy file from source to destination on victim machine
CServer::OnFileDelete
0x10001003
Delete a file
FileDeleteSecure
0x10001004
Delete a file securely
CServer::OnFileUpload
0x10001005
Open a file for writing on victim machine
CServer::FileDownload
0x10001006
Download file from victim machine
Run
0x10001007
Execute a command and return the exit code
CServer::OnChfTime
0x10001008
Timestomp file based on another file on disk
–
0x10001009
–
CServer::OnTestConn
0x1000100a
Test TCP connection to host and port
CServer::OnCmdRun
0x1000100b
Run command in background and return output
CServer::OnSleep
0x1000100c
Hibernate for X seconds, this will also be saved in the configuration file
CServer::OnViewProcess
0x1000100d
Get process listing
CServer::OnKillProcess
0x1000100e
Kill process by process ID
–
0x1000100f
–
CServer::OnFileProperty
0x10001010
Get file properties
CServer::OnGetInfo
0x10001011
Get current RAT configuration
CServer::OnSetInfo
0x10001012
Update and save RAT configuration file
CServer::OnZipDownload
0x10001013
Download a directory or file as a compressed Zip file
CServer::OnTerminate
0x10001014
Flush configuration to disk and hibernate until next wake up
(Data)
0x10001015
Data
(JobSuccess)
0x10001016
Job succeeded
(JobFailed)
0x10001017
Job failed
GetServiceName
0x10001018
Return current service name
CleanupAndExit
0x10001019
Remove persistence, configuration file, and terminate RAT
RecvMsg
0x1000101a
Force C2 check-in
RunAs
0x1000101b
Spawn a process under the user token of given Windows Terminal Services session
–
0x1000101c
–
WriteRandomData
0x1000101d
Write random data to file handle
CServer::OnInjectShellcode
0x1000101e
Inject shellcode into process ID
Table 4: ThemeForestRAT command IDs and their descriptions
Note that the symbol names in Table 4 that start with CServer:: are from the debug symbols and the other names are deduced based on analysis of the command.
Shellcode Injection
On Windows, the CServer::OnInjectShellcode command injects shellcode into a given process ID using NtOpenProcess, NtAllocateVirtualMemory, NtWriteVirtualMemory and RtlCreateUserThread Windows API calls. The shellcode is encrypted using the same algorithm used in PerfhLoader (see Listing 1). In the macOS and Linux samples we have analysed, this command is defined as an empty stub.
RomeoGolf’s Little Brother
In 2016, Novetta released a detailed report called Operation Blockbuster29, in which a Novetta-led coalition of security companies analysed malware samples from multiple cybersecurity incidents. The investigation linked the 2014 Sony Pictures attack to the Lazarus Group and revealed that the same actor had been behind numerous other attacks against government, military, and commercial targets using related malware since 2009.
Operation Blockbuster’s malware report describes RomeoGolf, a RAT that resembles ThemeForestRAT in several ways:
Uses the temporary folder Z802056, although not used in ThemeForestRAT, is still created
Overlapping command IDs and functionality
Same unique identifier generation using 4 calls to rand()
Configuration file with extension *.inf on Windows
Timestomping of the configuration file based on mspaint.exe
Two signalling threads for USB and RDP events
Figure 10 shows the RomeoGolf startup logic for generating its bot ID and two signalling threads that is identical to ThemeForestRAT (see Figure 5).
Figure 10: RomeoGolf startup creates two signalling threads, comparable to ThemeForestRAT (see Figure 5).
As can be seen in Table 5, the functionality to detect and copy data from newly attached logical drives has been removed in ThemeForestRAT, while leaving the temporary directory creation intact. Also, the thread to check for new RDP sessions has been extended in ThemeForestRAT to optionally spawn up to ten extra configured commands under the user of the active physical console session.
RomeoGolf
ThemeForestRAT
Compilation date
Fri Oct 11 01:20:48 2013
Thu Sep 07 06:40:40 2023
Known configuration file
crkdf32.inf
netraid.inf
Configuration file timestomped to
mspaint.exe
mspaint.exe
USB thread logic
1. Creates %TEMP%\Z802056 2. Checks for newly attached drives and copies data to above folder 3. Signal on newly attached drives
1. Creates %TEMP%\Z802056
RDP thread logic
1. Signal on new active RDP sessions
1. Start configured commands under the user of the new active console session 2. Signal on new active RDP session if configured
C2 communication
Fake TLS
HTTP(S)
Highest known command id
0x10001013
0x1000101e
Table 5: Differences and similarities between RomeoGolf and ThemeForestRAT
While RomeoGolf used Fake TLS30 and its own custom server for its C2 communications, ThemeForestRAT uses the HTTP protocol and shared hosting for its C2 servers.
Onto the next stage with RemotePE
In the 2024 incident response case, we observed the actor cleaning up PondRAT and ThemeForestRAT, to deploy a more advanced RAT, which we named RemotePE. RemotePE is retrieved from a C2 server by RemotePELoader. RemotePELoader is encrypted on disk using Window’s Data Protection API (DPAPI) and is loaded by DPAPILoader. Using DPAPI enables environmental keying and makes it difficult to recover the original payload without access to the machine. DPAPILoader was made persistent through a created Windows service.
Figure 10: RemotePELoader check-in request to retrieve RemotePE payload
In Figure 10, we show a RemotePELoader check-in request used to retrieve RemotePE from the C2 server. RemotePE is written in C++ and is more advanced and elegant. We think that the actor uses this more sophisticated RAT for interesting or high-value targets that require a higher degree of operational security. Interestingly, it too uses the file renaming strategy PondRAT and POOLRAT Windows samples implement, except it skips the last random iteration.
We will publish a more thorough analysis of RemotePE in a future blogpost.
Summary
This blog is about a Lazarus subgroup that we have encountered multiple times during incident response engagements. This is a capable, patient, financially motivated actor who remains a legitimate threat.
We first discussed an incident response case from 2024, where this actor impersonated employees of trading companies to establish contact with potential victims. Though the method of achieving initial access remains unknown, we suspect a Chrome zero-day was used.
After initial access, two RATs were used in combination: PondRAT and ThemeForestRAT. Though PondRAT has already been discussed, there are no public analyses of ThemeForestRAT at the time of writing. For persistence, phantom DLL loading was used in conjunction with a custom loader called PerfhLoader.
PondRAT is a primitive RAT that provides little flexibility, however, as an initial payload it achieves its purpose. It has similarities with POOLRAT/SimpleTea. For more complex tasks, the actor uses ThemeForestRAT, which has more functionality and stays under the radar as it is loaded into memory only.
Lastly, we found the actor replaced ThemeForestRAT and PondRAT with the more advanced RemotePE. A detailed analysis of RemotePE will be published in the near future. So, stay tuned!
In Table 6 and 7, we list indicators of compromise related to the incident response cases we investigated and other artifacts we link to this actor.
Incident Response Support
If you have any questions or need assistance based on these findings, please contact Fox-IT CERT at cert@fox-it.com. For urgent matters, call 0800-FOXCERT (0800-3692378) within the Netherlands, or +31152847999 internationally to reach one of our incident responders.
Indicators of Compromise
Type
Indicator
Comment
net.domain
calendly[.]live
Fake calendly.com
net.domain
picktime[.]live
Fake picktime.com
net.domain
oncehub[.]co
Fake oncehub.com
net.domain
go.oncehub[.]co
Fake oncehub.com
net.domain
dpkgrepo[.]com
Potentially related to Chrome exploitation
net.domain
pypilibrary[.]com
Unknown, visited by msiexec.exe shortly after dpkgrepo[.]com
net.domain
pypistorage[.]com
Unknown, connection seen under SessionEnv service
net.domain
keondigital[.]com
LPEClient server, connection seen under SessionEnv service
Agents have agency: they adapt and find multiple ways to solve problems. This autonomy creates a fundamental security challenge: the large language model (LLM) at the heart of the agent is non-deterministic, and its decisions can’t be predicted or guaranteed in advance. It can hallucinate harmful actions with complete confidence. It’s vulnerable to prompt injection attacks, where adversaries inject malicious commands through tool responses or user inputs. LLMs don’t robustly differentiate between commands and data, everything is only tokens. For these reasons, if you want defense in depth, you must treat the LLM as an untrusted actor from a security point of view.
The insight is that the LLM can’t affect the external world directly: it has to go through an orchestrator that invokes tools based on the LLM’s output. This is precisely where the controls must be applied. What you need at this boundary is authorization: a decision about whether each tool invocation should be allowed and under what conditions. Consider a customer service agent for an online retailer. Without proper controls, it could process refunds that exceed authorized limits, apply discounts to product categories that should be excluded, or look up one customer’s data while handling another customer’s session.
If you control agents’ access to tools, you can establish a safety envelope within which the agent can operate freely. This differs from two common but unsatisfactory approaches:
Creating hard-coded workflows eliminates uncertainty, but by itself defeats the purpose of using an LLM as the brain of the agent, because you’ve built a traditional application with an LLM interface. And even with this restriction, using LLM outputs at any step can open up the same risks. While it’s a useful technique for well-understood workflows, it’s not sufficient for agents that need to adapt.
Human-in-the-loop provides a safety net for critical operations, and it will always have a role. But relying on it as the main control mechanism sacrifices autonomy and can lead to approval fatigue.
You need agents that are safe and autonomous. This requires an auditable, deterministic enforcement layer that sits outside the agent and tools. Why outside? Because the LLM’s plan is the thing you can’t trust—it can’t be responsible for enforcing its own constraints. Controls at the LLM layer—such as system prompts and training-time alignment—can be bypassed by prompt injection or hallucination. Hard-coded checks in agent or tool code are more robust, but become difficult to audit and manage at scale, especially when security logic is scattered across many tools and services. Centralizing authorization outside both gives you a single checkpoint the LLM can’t circumvent; one that’s auditable and can be verified independently of the application code.
This is where AgentCore Policies come in. Amazon Bedrock AgentCore Gateway sits between the agent and the remote tools it calls. When you associate a Policy with a Gateway, it blocks everything by default. Policies selectively open this boundary by specifying which tool invocations are allowed and under what conditions. This enforcement applies to all tool traffic routed through the Gateway. For this approach to scale, it must be more straightforward to reason about the policies than about the agent’s behavior.
AgentCore policies are expressed in Cedar. Cedar is an open source authorization policy language developed by AWS that has recently joined the Cloud Native Computing Foundation (CNCF). Cedar was designed with exactly these properties: it’s purpose-built for authorization, readable by humans, and analyzable by machines using automated reasoning. This gives enterprises the ability to scale policy definition and enforcement to their AI agents.
How Cedar is used by Amazon Bedrock AgentCore
Amazon Bedrock AgentCore provides the infrastructure to deploy and manage agents at scale. It includes AgentCore Runtime for hosting agents, AgentCore Gateway for managing how agents connect to tools using Model Context Protocol (MCP), and Policy in AgentCore. Policy intercepts all agent traffic through AgentCore gateways and evaluates each request against defined policies in the policy engine before allowing tool access. Cedar powers the policy layer.
AgentCore Policy uses Cedar and its mathematical analysis capabilities at several points in the AgentCore Gateway workflow: the Cedar authorization engine is used at policy evaluation and Cedar Analysis is used during policy authoring, and in the control plane.
Policy authoring: Developers can write Cedar policies directly or use natural language that gets translated to Cedar through a neuro-symbolic AI feedback loop. Neuro-symbolic AI combines machine learning’s flexibility with automated reasoning’s provable correctness. An LLM generates policies from natural language, while Cedar Analysis validates them using symbolic, mathematical reasoning. The following diagram illustrates this workflow:
Figure 1: Cedar policy generation workflow
An administrator specifies—in natural language—which MCP tools the agent can call and under what conditions. The neuro-symbolic feedback loop then formalizes this description into Cedar policies. Here’s how it works: first, the LLM translates the natural language into Cedar policies. These policies are then run through two stages of verification. In the first stage, AgentCore Policy uses a Cedar schema generator that takes the MCP tool descriptions and produces a Cedar schema. Cedar validates the policies against this schema, helping to ensure that they reference valid tools and parameters and ruling out whole classes of runtime errors. If validation passes, the second stage runs Cedar Analysis, which encodes each policy as a mathematical formula and detects issues like policies that grant or deny everything, or that contain impossible conditions. These mathematical proofs identify errors in the process of translating from the natural language description to Cedar policies, and guide corrections.
The neuro-symbolic feedback loop significantly improves the accuracy of the generated policies. This demonstrates the power of combining neural and symbolic approaches—the LLM provides creative translation from natural language, while automated reasoning provides rigorous validation.
Control plane: When attaching policies to an AgentCore Gateway, Cedar Analysis performs holistic analysis of the entire policy set. Instead of analyzing policies in isolation, it examines how they interact and their combined effect. This analysis identifies potential logical errors—such as conflicting or redundant policies—and detects whether the policy set produces unintended authorization outcomes. When Cedar Analysis detects these errors, the operation fails and returns a description of the issue, so the policy author can fix and retry. See the Formal analysis for policy verification section for examples of the checks.
MCP tool invocation enforcement: Each agent tool request made to the AgentCore gateway is evaluated against Cedar policies which determine whether the MCP tool invocation with the given arguments should be allowed. This creates the safety envelope while allowing the necessary bridges to enable the agent to perform its job.
MCP tool filtering: Cedar enables an additional layer of protection that operates before any tool invocation occurs. When an agent issues a list tools command, AgentCore Gateway uses Cedar’s partial evaluation capability to determine which actions would always be denied under the current policy set. Those actions are omitted from the list tool response. The agent and the underlying LLM never see those tool actions, eliminating an entire class of risk: the agent and LLM can’t attempt to invoke a tool it doesn’t know exists. This is a direct benefit of Cedar’s partial evaluation: the system can determine that certain tool actions are unreachable without needing to wait for an actual tool invocation attempt.
Why Cedar: Analyzability enables safety at scale
Natural language is too ambiguous for security-critical infrastructure, and general-purpose programming languages, like Python, are very expressive but too difficult to analyze. They can have unintended side effects, termination issues, and can be difficult to understand.
Cedar avoids these issues by excluding loops and stateful operations, so policy evaluation terminates in O(n) time in common cases. This bounded execution time means agents can make authorization decisions without disrupting user experience or workflow efficiency.
Cedar is straightforward to read. Regulatory compliance and security audits require policies that humans can understand and verify. Cedar policies read like structured natural language, making them accessible to security teams, compliance officers, and business stakeholders:
// Only allow bulk discounts for premium customers with sufficient quantity
permit (
principal is AgentCore::OAuthUser,
action == AgentCore::Action::"ApplyBulkDiscount",
resource
)
when
{
principal.hasTag("customer_tier") &&
principal.getTag("customer_tier") == "Platinum" &&
context.input.orderQuantity >= 50
}
unless
{
context.input
.productTypes
.containsAny
(
["limited_edition", "seasonal_specials"]
)
};
Auditors without a technical background can understand this policy: “Allow bulk discounts for platinum customers who order at least 50 items, except for limited edition or seasonal special products.” The unless clause makes the exception clear, which is how business rules are typically expressed in natural language. Notice that this single policy constrains two different sources of data. The customer tier comes from a JSON Web Token (JWT) claim—it can’t be hallucinated or manipulated by the LLM. The tool inputs like order quantity and product types, however, originate from the LLM’s tool call. Cedar policies constrain these inputs to only allowed values, ensuring that even if the LLM produces unexpected arguments, the policy enforcement layer rejects them deterministically.
Cedar is the right choice because it’s fast, straightforward to read, and analyzable through automated reasoning. This analyzability is why you can reason about the safety envelope around agents that’s expressed as Cedar policies. As agentic systems grow the number of tools grows. Without proper tooling, policy management becomes intractable; policies can conflict, create security gaps, or produce unintended authorization outcomes.
In the rest of this section, we examine how Cedar’s analyzability directly addresses this challenge through its deterministic, mathematically sound analysis. Because Cedar analysis can reliably detect conflicts and logical errors across large policy sets it enables scalable policy management through neuro-symbolic AI.
Formal analysis for policy verification
Cedar policies can be encoded as mathematical formulas and analyzed using automated reasoning techniques through a symbolic encoder. This enables AgentCore Policy to provide sophisticated policy verification capabilities during policy authoring and beyond. AgentCore Policy uses this analysis when authoring or attaching policies to detect possible logical errors, such as conflicting or redundant policies. Policy analysis, including policy comparison is available as an open source CLI tool. Next, we will take a look at some concrete examples of these checks.
Detecting logical errors in policies: Cedar Analysis can detect when policies contain logical errors. For example, the following policy has contradictory constraints that mean it can’t allow any request: the customer tier can’t be both gold and platinum at the same time. The intention was to use an || instead of &&, a mistake that can be made by both humans and AI systems that author policies.
// This policy cannot allow any requests due to logical errors
permit (
principal is AgentCore::OAuthUser,
action == AgentCore::Action::"ProcessRefund",
resource
)
when
{
principal.hasTag("customer_tier") &&
principal.getTag("customer_tier") == "Gold" &&
principal.getTag("customer_tier") == "Platinum"
}
unless { context.input.refundAmount > 1000 };
Similarly, Cedar Analysis can detect policies that always allow a given action, usually an indication of an overly permissive policy. For example, the following policy will allow all ApplyBulkDiscount requests because any order quantity will either be greater than or equal to 100 or less than 100.
// This policy allows all ApplyBulkDiscount requests
permit (
principal is AgentCore::OAuthUser,
action == AgentCore::Action::"ApplyBulkDiscount",
resource
)
when
{
context.input.orderQuantity >= 100 ||
context.input.orderQuantity < 100 ||
(principal.hasTag("customer_tier") &&
principal.getTag("customer_tier") == "Platinum")
};
Detecting such logical errors isn’t easy for humans, and can’t be done by pattern matching: you need the formal rigor of mathematical analysis, which is exactly what Cedar Analysis does.
Detecting policy conflicts: Cedar Analysis can also analyze the entire policy set to detect inconsistencies between different individual policies:
// These policies conflict - Analysis will detect the subtle issue
permit (
principal is AgentCore::OAuthUser,
action == AgentCore::Action::"ProcessRefund",
resource
)
when
{
principal.hasTag("customer_tier") &&
principal.getTag("customer_tier") == "Gold" &&
context.input.refundAmount < 100
};
forbid (
principal is AgentCore::OAuthUser,
action == AgentCore::Action::"ProcessRefund",
resource
)
when
{
principal.hasTag("customer_tier") &&
["Gold", "Platinum"].contains(principal.getTag("customer_tier")) &&
context.input.refundAmount < 500
};
The permit policy allows gold customers to process refunds less than $100, while the forbid policy blocks gold customers (and platinum customers) from processing refunds less than $500. Because forbid overrides permit in Cedar, the forbid policy would block all gold customer refunds despite the permit policy.
Comparing policy changes: When updating policies, Cedar Analysis can also determine the exact impact of a change. Consider the following update to the unless clause (the policy lines with + have been added and those with - have been removed): we now block ApplyBulkDiscount only when the product type is limited_editionand the quantity exceeds 200.
permit (
principal is AgentCore::OAuthUser,
action == AgentCore::Action::"ProcessRefund",
resource
)
when
{
context.input.refundAmount < 500
};
permit (
principal is AgentCore::OAuthUser,
action == AgentCore::Action::"ApplyBulkDiscount",
resource
)
when
{
context.input.orderQuantity >= 50
}
unless
{
- context.input.productTypes.containsAny(["limited_edition"])
+ context.input.productTypes.containsAny(["limited_edition"]) &&
+ context.input.orderQuantity > 200
};
At first glance, adding a condition to the unless clause might seem more restrictive. In fact, it’s the opposite: narrowing when the unless applies means the permit now covers more requests. For example, an order of 73 units of a limited_edition product would have been blocked before but is now allowed. Cedar Analysis can automatically detect this and generates the following table showing the difference in permissiveness between the original policy set and the updated one:
Principal type
Action
Resource type
Status
OAuthUser
ProcessRefund
Gateway
Equivalent
OAuthUser
ApplyBulkDiscount
Gateway
More permissive
In the preceding example, the analysis tells us that the updated policy allows allows exactly the same ProcessRefund requests, but allows more ApplyBulkDiscount requests.
This formal verification capability is essential when agents operate autonomously and can affect the real world. Organizations need mathematical certainty that their policies will behave as intended.
Deterministic behavior for reliable governance
Unlike probabilistic AI models, enterprise security requires deterministic guarantees. Cedar policies always produce the same authorization decision for identical requests, regardless of evaluation order or system state. Cedar’s default deny, forbid wins, no ordering semantics help ensure predictable behavior.
// Policy evaluation order does not affect the authorization decision
permit(
principal,
action == AgentCore::Action::"ProcessRefund",
resource
) when {
context.input.refundAmount < 500
};
forbid(
principal,
action == AgentCore::Action::"ProcessRefund",
resource
) when {
context.input.orderDate.offset(duration("90d")) < context.system.now
};
Whether the permit or forbid policy is evaluated first, a refund request over $500 will always be denied, and any refund issued more than 90 days after the order date will also be denied. This predictability gives enterprises confidence in their agent governance.
From policies to production
By choosing AgentCore Policy and Cedar, organizations can deploy autonomous agents with policies they can reason about mathematically, not only hope the agents work correctly. Cedar’s combination of expressiveness, readability, and formal verification means that you can design agents with the flexibility needed to function and the certainty security teams demand.
Automated reasoning has already proven its value across AWS, from AWS IAM Access Analyzer verifying access policies to provable security for network configurations. Applying these same techniques to agentic AI is a natural extension: as agents take on more responsibility, the need for mathematically grounded guarantees only grows. The neuro-symbolic approach we’ve described in this post—combining LLM flexibility with the rigor of automated reasoning—points toward a future where agents can be both more autonomous and more trustworthy, because the verification keeps pace with the autonomy.
Microsoft has identified an active supply chain attack targeting the @antv node package manager (npm) package ecosystem. A threat actor compromised an @antv maintainer account and published malicious versions of widely used data-visualization packages, resulting in cascading downstream impact.
The compromise propagated through dependency chains into libraries like echarts-for-react (which has more than 1 million weekly downloads), expanding the blast radius into CI/CD pipelines and cloud workloads across the ecosystem. The malicious payload—a ~499 KB obfuscated JavaScript file—runs silently during npm install and is purpose-built to steal credentials from GitHub Actions environments.
Key capabilities observed in the payload include multi-platform credential theft (GitHub, Amazon Web Services, HashiCorp Vault, npm, Kubernetes, 1Password), GitHub Action Runner process memory scraping, privilege escalation, dual-channel data exfiltration, and Supply chain Levels for Software Artifacts (SLSA) provenance forgery. These capabilities suggest a deliberate effort to evade analysis and an apparent focus on CI/CD environments.
The authors of the antv account have also since confirmed in a ticket on the repo that the situation is now resolved.
Attack chain overview
Figure 1. @antv npm supply chain attack flow.
The @antv organization maintains charting libraries (G2, G6) embedded across dashboards and applications. The attack proceeds through:
Maintainer account compromise and publication of malicious @antv package versions
Downstream dependency amplification (echarts-for-react, size-sensor, and others)
Automatic payload execution through a preinstall hook during npm install
Execution chain: node → shell → bun → payload (Bun runtime installed if absent)
Technical analysis
The payload replaces the legitimate index.js with a single-line obfuscated script.
Obfuscation
Layer 1: 1,732 Base64-encoded strings in a rotated array, decoded through lookup function with the shuffle key 0xa31de
Layer 2: Critical strings such as command-and-control (C2) domain and env var names are encrypted with a custom PBKDF2 and SHA-256 cipher, which is decrypted at runtime.
Environment gating: The payload exits immediately if it’s not running on GitHub Actions on Linux
Branch avoidance: Skips the main, master, dependabot/, renovate/, and gh-pages when using Git API exfiltration
// Layer 1: 1,732 strings in rotated array with base64 decode
(function(_0x44be0e, _0x3ff020){
// Array shuffle IIFE with key 0xa31de
_0x335af4['push'](_0x335af4['shift']());
})(_0x71ec, 0xa31de));
// Layer 2: PBKDF2+SHA256 runtime decryption for critical strings
var e6 = "a8269c01069452afb8a54de904e6419578d155fdbdb9e566bab8576a4266b61e";
var t6 = "7f44e4ba6f6a71bd0f789e7f83bd3104";
var u5 = new du(e6, t6); // PBKDF2 cipher instance
globalThis["f2959c600"] = function(s) { return u5.decode(s); };
// Environment gate - exits if not GitHub Actions on Linux
this['isGitHubActions'] = process.env[f2959c600('68zz23c6NGR9...')] === 'true';
this['isLinuxRunner'] = process.env[f2959c600('NhUrwwYEwYIJ...')] === 'Linux';
Credential theft
The payload targets secrets across six platforms:
GitHub: Extracts GITHUB_TOKEN, scans for Personal Access Tokens (gh[op]_) and installation tokens (ghs_), validates through /user API, and enumerates repo and org secrets.
Amazon Web Services(AWS): Queries Instance Metadata Service (169.254.169[.]254), Elastic Container Service metadata (169.254.170[.]2), reads .aws/ files, harvests env vars, and then calls SecretsManager across all regions.
HashiCorp Vault: Searches 12+ token paths (/var/run/secrets/vault/token, ~/.vault-token, and others) and connects to a local Vault at 127.0.0[.]1:8200.
npm: Validates tokens using /-/whoami, exchanges OpenID Connect (OIDC) tokens for publish access, and enumerates packages
Kubernetes: Reads service account tokens and enumerates namespace secrets
1Password: Interacts with command-line interface (CLI) and attempts master password extraction with two-factor authentication (2FA) bypass
// Injects passwordless sudo via /etc/sudoers.d bind mount at /mnt
echo 'runner ALL=(ALL) NOPASSWD:ALL' >
&& chmod 0440 /mnt/runner
// DNS manipulation
sudo sh -c "echo '127.0.0.1 ' >> /etc/hosts"
// Validates sudo access before operations
sudo -n true
Exfiltration
Dual-channel exfiltration:
Primary: HTTPS to encrypted C2 domain (port 443) with DNS pre-check and health probe
Fallback: Git Data API — Creates blobs, trees, or commits in victim repositories on non-protected branches
Tertiary: Creates public repos under victim accounts with reversed description (“niagA oG eW ereH :duluH-iahS”); more than 2,200 of these repos have been observed as of this writing
SLSA provenance forgery erodes trust in supply chain attestation frameworks
How GitHub took action to prevent further harm
Upon learning of the attack, GitHub acted immediately to limit further damage. It removed 640 malicious packages and invalidated 61,274 npm granular access tokens with write permissions and 2FA bypass, preventing leaked tokens from being used in this or similar attacks. GitHub also published advisories relevant to this malware campaign in the GitHub Advisory Database and alerted the community through Dependabot alerts and npm audit. It continues to monitor for additional affected packages and remove them as needed.
Mitigation and protection guidance
Microsoft recommends the following mitigations to reduce the impact of this threat:
Review dependency trees for direct or transitive usage of affected @antv/ packages.
Identify systems that installed or built affected package versions during the suspected exposure window.
Pin known-good package versions where possible and avoid automatic dependency upgrades until validation is complete.
Disable pre- and post-installation script execution by ensuring you run npm install with --ignore-scripts.
While GitHub team has already invalidated all the npm tokens that had write access and 2FA bypass, Microsoft Defender still recommends rotating credentials, tokens, npm access tokens, CI/CD secrets, and cloud credentials that might have been exposed in affected build or developer environments.
Rotate credentials, tokens, npm access tokens, CI/CD secrets, and cloud credentials that might have been exposed in affected build or developer environments.
Audit organization and personal GitHub accounts for public repositories with the description “niagA oG eW ereH :duluH-iahS” or other unexpected repositories created during the exposure window, and revoke any GitHub tokens that might have been implicated.
Audit CI/CD logs for unexpected outbound network connections, script execution, or suspicious package lifecycle activity.
Review npm package lockfiles, build logs, and artifact provenance for evidence of compromised package versions.
Use Microsoft Defender XDR to investigate suspicious activity across endpoints, identities, cloud apps, and developer environments.
Use Microsoft Defender Vulnerability Management to search for antv packages across your estate.
Microsoft Defender XDR Detections
Microsoft Defender XDR customers can refer to the list of applicable detections below. Microsoft Defender XDR coordinates detection, prevention, investigation, and response across endpoints, identities, email, and apps to provide integrated protection against attacks like the threat discussed in this blog.
Customers with provisioned access can also use Microsoft Security Copilot in Microsoft Defender to investigate and respond to incidents, hunt for threats, and protect their organization with relevant threat intelligence.
Tactic
Observed activity
Microsoft Defender coverage
Execution
Suspicious script execution during npm install or package lifecycle activity
Microsoft Defender for Endpoint – Suspicious usage of Bun runtime – Suspicious Installation of Bun runtime – Suspicious Node.js process behavior
Credential Access
Potential harvesting of environment variables, tokens, or developer secrets
Microsoft Defender for Endpoint – Credential access attempt – Suspicious cloud credential access by npm-cached binary – Kubernetes secrets enumeration indicative of credential access
Microsoft Defender for Cloud Sha1-Hulud Campaign Detected: Possible command injection to exfiltrate credentials
Command and Control
Potential outbound connections from build systems or developer machines
Microsoft Defender for Endpoint Connection to a custom network indicator
Microsoft Security Copilot
Security Copilot customers can use the standalone experience to create their own prompts or run prebuilt promptbooks to automate incident response or investigation tasks related to this threat, including:
Incident investigation
Microsoft user analysis
Threat Intelligence 360 report based on MDTI article
Vulnerability or supply chain impact assessment
Note that some promptbooks require access to plugins for Microsoft products such as Microsoft Defender XDR or Microsoft Sentinel.
The following sample queries let you search for a week’s worth of events. To explore up to 30 days of raw data, go to the Advanced Hunting page > Query tab, and update the time range to Last 30 days.
Hunt for suspicious npm lifecycle script execution
This query searches for Node.js and npm activity involving install lifecycle behavior and relevant package references.
Hunt for potential compromise of through malicious npm packages
DeviceProcessEvents
| where Timestamp > ago(2d)
| where FileName in ("bun", "bun.exe")
| where ProcessCommandLine has "run index.js"
Hunt for affected dependencies in your software inventory
DeviceTvmSoftwareInventory
| where SoftwareName has "antv" or SoftwareVendor has "antv"
| project DeviceName, OSPlatform, SoftwareVendor, SoftwareName, SoftwareVersion
Hunt for suspicious outbound connection from python backdoor
DeviceNetworkEvents
| where Timestamp > ago(2d)
| where InitiatingProcessFileName startswith "python"
| where InitiatingProcessCommandLine has "/cat.py"
Hunt for suspicious outbound activity from Node.js processes
Searches for network connections initiated by Node.js or npm processes that reference package-related paths or commands.
DeviceNetworkEvents
| where Timestamp > ago(2d)
| where RemoteUrl has "t.m-kosche.com"
Shai-Hulud npm supply-chain indicator observed inside a Kubernetes container
CloudProcessEvents
| where ProcessCommandLine has_any ("IfYouInvalidateThisTokenItWillNukeTheComputerOfTheOwner", "niagA oG eW ereH", ":duluH-iahS", "t.m-kosche.com", "7cb42f57561c321ecb09b4552802ae0ac55b3a7a", "@antv/setup")
| project Timestamp, AzureResourceId, KubernetesPodName, KubernetesNamespace, ContainerName, ContainerId, ContainerImageName, ProcessName, ProcessCommandLine, ProcessCurrentWorkingDirectory, ParentProcessName, ProcessId, ParentProcessId, AccountName
Indicators of Compromise (IOC)
Indicator
Type
Description
@antv – whole account
Package scope
All packages maintained by the antv account were compromised.
As per the latest statement from the account author’s this situation is now resolved.
echarts-for-react
Package name
One of the major downstream packages impacted by the antv compromise. As per the latest statement from the repository author’s this situation is now resolved
This research is provided by Microsoft Defender Security Research with contributions from Rahul Mohandas, Sumith Maniath, Ahmed Saleem Kasmani, Arvind Gowda, Sagar Patil, and members of Microsoft Threat Intelligence.
To hear stories and insights from the Microsoft Threat Intelligence community about the ever-evolving threat landscape, listen to the Microsoft Threat Intelligence podcast.
Review our documentation to learn more about our real-time protection capabilities and see how to enable them within your organization.
Our largest security services customers started the same way every customer does – with a click. They enabled Amazon GuardDuty, Amazon Inspector, AWS WAF, and AWS Security Hub, experienced the benefits in real time, and evaluated with transparent pay-as-you-go pricing. No RFP. No six-month evaluation. No multi-year commitment up front. Our field teams played a critical role in that growth, not by selling the first click, but by building the trusted relationships that turned early adoption into deep, long-term commitment. We believe customers should have this same frictionless adoption experience and flexibility for all best-in-class security products and that’s why we developed Security Hub Extended.
In our first post, we introduced Security Hub Extended, a significant expansion of Security Hub that brings together curated partner solutions in a single, unified experience. In our second post, we walked through how it works technically, including the onboarding flow, the pricing model, the unified operations layer built on the Open Cybersecurity Schema Framework (OCSF). In this post, I want to step back and talk about why we built it the way we did and why I believe the way enterprises discover, evaluate, and adopt security solutions is ready for a fundamental shift.
The shift
If you’ve ever tried to evaluate a new enterprise security product, you know the drill. Request a demo. Wait. Take the demo. Request a PoC. Wait for professional services (or your team to stop building) to set it up. Negotiate pricing, which isn’t published, so you’re starting blind. Loop in procurement. Sign a multi-year commitment. Then, months later, find out whether the product actually solves your problem in your unique environment.
Meanwhile, an ambitious security engineer on your team has already spun up an open-source tool, connected real data, and knows in two hours whether it’s going to work for your use cases. They didn’t need a slide deck. They needed a solution they could put their hands on.
A Fortune 500 CISO recently told me: “I spent 9 months procuring a security solution and it still doesn’t work the way the demo showed.” That frustration isn’t unique. It’s the norm.
This isn’t a criticism of the sales motion. Sales-led has evolved for good reason. Enterprise procurement is complex, products need customization, customers need support. I respect the craft and have poured a significant portion of my career into trying to perfect it. Even the most product-driven companies still need great sales, marketing, field enablement, and support.
It doesn’t change the fact that threats are evolving constantly, and defenders need the flexibility to discover and deploy new solutions as fast as the landscape shifts. Having the best solutions discoverable and deployable in that moment of need isn’t just a convenience, it’s a competitive advantage that customers are demanding. A new threat emerges, security teams have access to industry-leading solutions, and in a few clicks they’ve found their answer and are already seeing value. That’s the model every security company should be building toward.
What we’ve learned at AWS
At AWS, we’ve spent two decades learning what it takes to let customers adopt complex enterprise technology on their own terms, at massive scale. We haven’t always gotten it right, but we learn fast and adjust. The result is one of the largest cloud businesses in the world. I bring up that scale for one reason. It’s proof that complex, enterprise-grade technology can be adopted without requiring a traditional procurement gauntlet. Compute, storage, databases, AI/ML, networking, and yes, security — adopted all through a console, on each customer’s own timeline, and scaled when they were ready.
The proof is in the adoption
Amazon GuardDuty, Amazon Inspector, AWS Shield, AWS Security Hub are all available through the AWS Management Console. All pay-as-you-go. All activated with a click. Tens of thousands of customers rely on these security services today. When you make it easy to get started and deliver outcomes that earn confidence, expansion follows naturally.
These are sophisticated, enterprise-grade security solutions. And customers, from two-person startups to the world’s largest financial institutions, adopt them the same way. They try it, see the value, expand, and lean on the AWS team to go deeper.
We didn’t get here by accident, and we definitely didn’t get here without making mistakes. Building products that can be adopted and scaled on their own, without a sales engineer explaining away UX problems, without a solutions architect doing the first deployment, requires a different kind of product mindset. Time-to-value becomes your most important metric. Onboarding friction becomes your biggest enemy. Transparent pricing becomes non-negotiable. It’s hard. We’ve gotten a lot wrong along the way. And we’re still iterating.
But the results are clear. When customers adopt based on experience rather than commitment, they don’t just stay, they expand. They bring their teams. They become advocates. I’ve spent 15 years at AWS, the last 10 building security services like GuardDuty and Security Hub. When we launch a new security service or major feature, we consistently see rapid organic adoption at a pace that would be impossible through traditional sales cycles alone. These products are built to deliver value the moment customers turn them on and we make that as easy as we possibly can. That’s the scale a product-led motion unlocks.
Security Hub Extended
So, we asked ourselves: why can’t we build a similar approach that can expand to include industry leading partner solutions? Why can’t the CrowdStrikes, the Splunks, the Zscalers, and the fast-growing innovators solving tomorrow’s problems like Cyera, Noma, and 7AI also reach customers with the same frictionless motion that AWS services enjoy? Why can’t a security team that discovers a new threat on Monday have a proven solution deployed and delivering value by Tuesday? Our partners have built incredible products. What they haven’t always had is an avenue to put those products directly in the hands of the customers who need them most, at the moment they need them, at scale, in a way that feels as natural as turning on an AWS service. Not by replacing how our partners build or sell, but by giving them infrastructure that lets their products speak for themselves.
That’s what Security Hub Extended is. Security teams already using Security Hub can discover curated partner solutions right alongside their AWS security services. One click to evaluate, one click to deploy, pay-as-you-go pricing on your existing AWS bill with Enterprise Discount Program (EDP) discounts automatically applied. No separate procurement cycle. No long-term commitments required. Start fast, validate at scale, and commit for deeper discounts when you’re ready, versus making a three-year bet based on a few months of testing.
For customers, industry-leading enterprise security solutions become as easy to adopt as GuardDuty or WAF. For our partners, Security Hub Extended is a growth channel where the product leads and the customer experience mirrors what we’ve spent 20 years building at AWS. For the industry, it’s an invitation to reimagine what the relationship between a security product and a security practitioner can look like when you remove the friction standing between them.
But Security Hub Extended isn’t just a simpler way to buy security products. It’s a unified solution. When a customer enables a solution through Extended, we’re working toward an experience where AWS handles the rest. Sensors that deploy automatically across Amazon EC2, Amazon EKS, and AWS Fargate workloads using the same mechanism that powers GuardDuty Runtime Monitoring. IAM roles that provision across a customer’s Organization in one click. Resource inventory is automated from day one – S3 buckets, databases, AI workloads – without manual work.
Once enabled, solutions in Security Hub Extended emit findings in OCSF, automatically aggregated in Security Hub alongside findings from GuardDuty, Amazon Inspector, and every other AWS security service. Security Hub applies risk scoring and correlated risk analytics across all of them. AWS-native and third-party findings together, weighted and prioritized as a single view of your security posture. For example, an endpoint detection from CrowdStrike, correlated with a credential theft in GuardDuty, and a data access event from Cyera, produces an attack path that none of those solutions can produce alone. The correlation uses AWS context (IAM topology, VPC exposure, resource criticality) to improve the context of each attack path for security analysts. Deploying a solution through Security Hub Extended doesn’t add another pane of glass. It deepens the intelligence of the one you already have.
We’re also building toward automated response. Customers will be able to opt in to pre-built playbooks that take action through AWS-native services when a threat is detected, such as isolating compromised resources, revoking credentials, or containing active threats. The goal is detect-to-respond in seconds, not the hours it takes to context-switch across five consoles and two ticketing systems.
Where we are and where we’re headed
We’re still in the first inning — or Day 1, as we like to say at Amazon. We launched in February 2026 with 14 partners, now 21, spanning endpoint, identity, email, network, data, browser, cloud, AI, and security operations, and we’re continuously working backwards from customers as we operationalize for scale. We are building this because our customers asked for it. We’re learning alongside our partners and customers every week, identifying what works, what needs improvement, where the friction still lives, and iterating quickly.
We’re building and delivering at the speed of our customers. That means shipping fast, iterating faster, and not waiting for perfection. We’re not where we want to be just yet, and we need your feedback to get us there. What’s encouraging is that our partners aren’t waiting to be asked. They’re investing in this alongside us. Not because we’re demanding it, but because they see the same thing we do, that companies that make it effortless for customers to get started are the ones that will win at scale.
The early signals are encouraging. Customer response has exceeded our expectations, and the feedback we hear most often is that the procurement simplification and flexibility of pay-as-you-go with public pricing alone, even before the unified operations and data normalization benefits, is a meaningful differentiator.
If you’re a security leader: Security Hub Extended is live now. Log into Security Hub, look for the Security Hub Extended Plan (or visit the Security Hub Extended Pricing Page), and explore what’s available for your use cases. Start with what solves your most urgent problem. Pay-as-you-go, no commitment. Your team will tell you if it’s working in days, not months.
The vision is bigger than what’s live today, and we’re iterating fast. Share your feedback on AWS re:Post for Security Hub, reach out through contact AWS Support, or connect with me directly.