LevelBlue + SentinelOne Partner to Deliver AI-Powered Managed Security Operations and Incident Response. Learn More

Operation FlutterBridge: The FlutterShell macOS Backdoor

Identified through macOS endpoint monitoring, the CL-CRI-1089 cluster, delivered under the publicly reported Operation FlutterBridge campaign, demonstrates a deliberate misuse of the Flutter framework for macOS malware delivery. Rather than re-documenting the campaign itself, this report treats the recovered FlutterShell artifacts as a technical detection case study.

The analysis focuses on what can be learned directly from ten Mach-O samples across three generations between December 2025 and March 2026: cross-generation binary fingerprints, Dart AOT evolution, Gen 3 symbol obfuscation, sandbox timeout behavior, and code-level evidence such as the FlutterInvoke JavaScript (JS) bridge.

These findings highlight durable detection surfaces that remain useful even as domains, certificates, command names, and visible strings change. At its core, FlutterShell separates the static binary from the command payload: a WebView loads attacker-controlled content at runtime, which can issue commands to a native Dart handler through a JavaScript message channel. Without a live, responding C2 server, no bridge-command activity was observed in the reviewed sandbox telemetry, consistent with a C2-conditional execution model.

  • Cluster: CL-CRI-1089

  • Campaign: Operation FlutterBridge

  • Malware Family: FlutterShell (OSX/FlutterShell.A/B)

  • Platform: macOS · x86_64 · arm64

  • Motivation: Financial, browser search hijack

  • Activity Window: December 2025 to March 2026

  • Report Date: June 2026

 

Methodology

All primary findings derive from independent static analysis of ten Mach-O binaries recovered through macOS-targeted monitoring. This report does not reproduce or summarize any prior published account; where findings align with published research, this is noted explicitly. No controlled live execution was performed by the research team; automated detonation telemetry was reviewed.

Method

What it reveals

Coverage

Signature metadata review

File type, CPU architecture, code signing status, Team ID, bundle ID, revocation tags

All 10 artifacts

Mach-O load command parsing

Segment layout, linked libraries, build version, export structure

All 10 artifacts

Fuzzy hash comparison

Binary similarity and obfuscation boundary detection

All 6 payload dylibs

Exported symbol fingerprinting

Symbol table identity across generations

All 6 payload dylibs

 

 

Key Behavioral Finding: Sandbox Timeouts

A "timeout" in behavioral environments indicates the binary successfully launched but produced zero behavioral output (no process activity, network traffic, or file writes).

  • Gen 1: Five independent environments timed out.

  • Gen 2: One environment timed out.

This provides strong supporting evidence that the binary can launch successfully but remains behaviorally dormant without a live C2 response. [Observed]

The static detection signatures that fired on Gen 1 and Gen 2 were all heuristic or pattern-based, keyed on Dart internal symbol strings, file structure, or known-bad certificate metadata, not on observed runtime behavior. Gen 3's low initial static visibility reflects successful rotation of the certificate and Dart symbol table: the static patterns no longer matched, and no behavioral signals were produced to trigger heuristics.

 

Why No Behavioral Output

The malware implements a C2-conditional execution model. The binary initializes a WKWebView and loads the attacker-controlled landing page. The actual command payload is JavaScript delivered by the C2 server at runtime, and it is never present in the static binary. No flutterInvoke.postMessage() activity was observed in the reviewed detonation telemetry; no bridge command executed. The binary produced a functioning, benign application UI in every environment that ran it.

 

What to Monitor in a Live Environment

Because behavioral sandboxes cannot replicate a live, responding C2, endpoint-level telemetry is the only reliable detection surface. The following runtime signals are expected based on binary string analysis and behavioral research:

Signal

Observable

Basis

WKWebView outbound HTTPS

Outbound connection to atsheisdomestic.org, etoftheappyrince.org, or healightejustb.org from a non-browser process

Observed

Hardware fingerprinting

ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformUUID spawned as child of non-system app

Assessed

Chrome profile modification

Write to ~/Library/Application Support/Google/Chrome/Secure Preferences by non-Chrome process

Assessed

Chrome crash-bubble suppression

killall "Google Chrome" followed by relaunch with --hide-crash-restore-bubble

Assessed

Sparkle silent replacement

File activity under ~/Library/Caches/com.app.*/org.sparkle-project.Sparkle/Installation/

Assessed

LaunchAgent installation

New plist in ~/Library/LaunchAgents/ referencing com.app.podcastsLounge, com.app.pdfBrain, or com.pdfninja.app

Assessed

 

Threat Intelligence Confirmation
The three recovered C2 domains are externally associated with CL-CRI-1089 / Operation FlutterBridge.

 

Findings

1. Symhash Invariance Across All Generations

All six payload dylibs, across three generations, two architectures, and two different Apple Developer certificates, share an identical exported-symbol fingerprint (605169623267c4eb73693b22b811dc7a). This property enables a single, generation-agnostic detection rule that will match future variants as long as the Flutter framework base is reused. [Observed]

2. Architecture-Keyed Structural Clustering

Structural hashing reveals two distinct clusters: all x86_64 dylibs share structural hash ffd773f157df70291f0910a45a1d8d9a; all arm64 dylibs share b4aa7255af4b016586090a5b451300fa. This confirms the same Flutter framework base was carried across all three generations with only the Dart AOT snapshot modified. [Observed]

3. Binary Growth Profile: Private-Only Expansion

The Gen 1 → Gen 2 size increase of +3,448,528 bytes (+48.7%) is confined entirely to private, non-exported Dart AOT code. Because the exported-symbol fingerprint is invariant, zero new exported entry points were introduced. This pattern is consistent with obfuscation metadata expansion and the addition of new internal command handlers. [Observed]

4. TLSH Divergence at Obfuscation Boundary

TLSH fuzzy hashing shows Gen 1 and Gen 2 x86_64 dylibs at low distance; code structure largely preserved. Gen 3 diverges significantly, confirming that enabling full Dart obfuscation fundamentally redistributes byte content and breaks TLSH-based clustering. This is directly relevant to threat hunting infrastructure that relies on fuzzy similarity. [Observed]

5. Cross-Generation Detection Sequence Quantified

The detection trajectory across generations correlates precisely with specific evasion changes: Gen 1 (moderate initial profile) → Gen 2 (elevated, widest detection window) → Gen 3 (low initial static visibility following certificate rotation and full Dart obfuscation adoption). This report assembles the sequence from the analyzed artifact set. [Observed]

 

Threat Actor Attribution

Attribute

Value/Confidence

Designation

CL-CRI-1089 [Observed]

Campaign Name

Operation FlutterBridge [Assessed]

Motivation

Financial, browser search hijack [Assessed]

Activity Window

Dec. 1, 2025, to Mar. 31, 2026 [Observed]

 

Certificate Rotation Timeline

The rotation pattern is consistent with preemptive identity procurement, the next certificate was enrolled before the previous was revoked, not after. The shortest observed gap from revocation to next-generation artifact first-seen is 31 days.

Event

Date

Evidence

Gen 0 fat binary first seen

Dec. 2, 2025

Observed

Gen 1 thin binaries first seen

Dec. 12, 2025

Observed

UBZDAAV97Y revoked by Apple

Dec. 31, 2025

Observed

Gen 2 first seen

Jan. 12, 2026

Observed

FW9NHQ8922 revoked by Apple

Jan. 31, 2026

Observed

Gen 3 self-signed artifacts first seen

Mar. 10, 2026

Observed

Dedicated to hunting and eradicating the world's most challenging threats.

SpiderLabs

Binary Architecture

Two-Component Deployment Model

Every FlutterShell deployment follows a consistent two-component architecture. A thin Mach-O stub launcher loads a large dynamically linked payload library containing the full Dart runtime and attacker-controlled logic.

Component

Role

Identifier

Arch

Size

Profile

Stub launcher

Initializes Flutter runtime

com.app.*

x86/arm64

150 KB

Low

Payload dylib

Flutter framework + Dart AOT

io.flutter.flutter.app

x86/arm64

10 MB

Moderate


Structural Profile

All six payload dylibs share an identical load command ordering and segment layout across all three generations and both architectures. Key markers include:

  • Single external dependency: Each payload dylib links only against /usr/lib/libSystem.B.dylib. No Foundation, AppKit, WebKit, or other macOS framework imports are present. [Observed]
  • No Objective-C sections: The absence of __OBJC and __DATA_CONST segments, present in all native Cocoa binaries, is a directly observable marker distinguishing Flutter-based payloads from legitimate macOS applications. [Observed]
  • LC_ID_DYLIB identifier: io.flutter.flutter.app, self-identifies as a Flutter runtime dylib. [Observed]
  • Segment layout: __TEXT (Dart AOT code, dominant section)· __DATA (4 KB, BSS only)· __LINKEDIT (~113 KB). [Observed]

 

Certificate Generation Analysis

Generation Overview

Generation

Identity

Masquerade

First Seen

C2 Domain

Profile

Gen 0 (fat)

UBZDAAV97Y

PodcastsLounge

Dec. 2, 2025

atsheisdomestic.org

Moderate

Gen 1 (thin)

UBZDAAV97Y

PodcastsLounge

Dec. 12, 2025

atsheisdomestic.org

Moderate

Gen 2

FW9NHQ8922

PDF-Brain

Jan. 12, 2026

etoftheappyrince.org

Elevated

Gen 3

Self-signed

PDF-Ninja

Mar. 10, 2026

healightejustb.org

Low

 

Evasion Significance of the Rotation

Certificate rotation is not merely operational hygiene for this actor; it is a primary evasion mechanism. Apple notarization of Gens 1 and 2 meant Gatekeeper quarantine passed at distribution time with no user warning. Certificate-based blocklists were only actionable after Apple applied revocation tags, by which point the actor had already moved to the next identity. Gen 3's shift to self-signed artifacts trades Gatekeeper clearance for complete independence from Apple CA infrastructure.

 

Binary Evolution and Capability Growth

Size Progression (x86_64 dylib)

Generation

File Size

Delta

Interpretation

Gen 1 (Dec. 2025)

7,077,984 bytes (6.75 MB)

,

Baseline. Partial Dart obfuscation; readable source paths in binary strings.

Gen 2 (Jan. 2026)

10,526,512 bytes (10.04 MB)

+3,448,528 B (+48.7%)

Growth entirely in private Dart AOT code. Zero new exported symbols. Consistent with obfuscation metadata expansion and addition of PDF processing library. (Syncfusion). ). [Observed] 

Gen 3 (Mar. 2026)

8,569,680 bytes (8.17 MB)

−1,956,832 B vs Gen 2

Smaller despite retained or expanded obfuscation. We assess that this reflects improved Dart tree-shaking removing dead-code and reflection metadata introduced in Gen 2. Size reduction does not imply capability reduction. [Assessed]


Private-Only Growth Confirmed
The exported-symbol fingerprint is identical across all three x86_64 generations and both arm64 generations (see Appendix A for hash values). Zero new exported entry points were added across the entire campaign lifecycle. All binary growth was internal, consistent with private function additions and obfuscation metadata expansion, not architectural changes.

 

Dart Obfuscation Adoption

Counting Dart runtime symbols matching the obfuscated pattern (_[Name]@[hash].method) directly measures the effect of enabling Dart compilation with obfuscation:

Generation

Obfuscated Dart Symbols

Status

Gen 1

92

Partial, normal Dart AOT mangling; class names readable in source paths

Gen 2

91

Comparable to Gen 1; hash suffixes changed due to snapshot rebuild, not obfuscation

Gen 3

707 (+669%)

Full Dart obfuscation confirmed: all internal class names replaced with 2- to3-character random identifiers (e.g., _Eae@403490068, _Xa@0150898) [Observed]


Bridge Command Renaming

The JavaScript-to-native bridge command names changed with each generation, defeating detection rules anchored to specific command strings [Assessed]:

Generation

App

Bridge Command

Likely Rationale

Gen 1

PodcastsLounge

exec_sync

Initial deployment; generic name; drew detection attention

Gen 2

PDF-Brain

pdf_sync

Renamed after exec_sync strings associated with detection signatures

Gen 3

PDF-Ninja

renderPDF

Camouflaged as expected behavior for a PDF application


Capability Delta: Added Dependencies per Generation

Internal Dart package paths leaked from the AOT snapshot reveal which libraries were added or removed per generation [Observed]:

Package

Gen 1

Gen 2

Gen 3

Purpose

sqflite

,

,

SQLite local data storage

just_audio_platform_interface

,

,

Audio playback (podcast lure)

syncfusion_pdfviewer_platform_interface

,

,

PDF rendering (PDF-Brain lure)

window_size

,

,

Window dimension control

url_launcher

,

External URL opening

webview_flutter_wkwebview

Core C2 delivery mechanism, present all generations

uuid

UUID generation for device fingerprinting


Gen 2 binary strings still contain the internal Dart package name podcasts_lounge and the original bundle ID com.app.podcastsLounge despite external rebranding to PDF-Brain. This confirms the actor recompiled from the same Dart source tree without renaming the package, establishing direct code lineage between Gen 1 and Gen 2. [Observed]

 

Technical Analysis of the FlutterInvoke Bridge

FlutterShell's architecture separates the command dispatcher from the commands delivered by the C2 at runtime. The application registers a named JavaScript message channel (flutterInvoke) within a WKWebView. The C2 server delivers JavaScript templates, confirmed via binary string extraction, which allows commands to be updated server-side without binary modification. [Assessed, Observed]

Extracted JavaScript Bridge Template

// Extracted from Gen 3 binary data

"if (typeof flutterInvoke !== 'undefined') {"
" flutterInvoke.postMessage(JSON.stringify(request));"
"} else if (window.flutterInvoke) {"
" window.flutterInvoke.postMessage(JSON.stringify(request));"
"}"
"<script src=\"welcome_page.js\"></script>"


Bridge Command and Capability Literals

renderPDF // bridge command name
setSparkleDelay // Sparkle update delay setter
Failed to summarize PDF // AI exfil error path
renderPDF not available in this build // error guard


Plugin Fingerprint Strings, All Generations

Four plugin registration strings appear across generations. The typo path_providerr (double 'r') is present in Gen 1 and Gen 3 and constitutes a durable binary fingerprint:

String

Gen 1 Offset

Gen 2 Offset

Gen 3

plugins.flutter.io/webview

0x556320

0x7de770

Present

plugins.flutter.io/shared_preferences

0x5dcf30

0x8e5710

,

plugins.flutter.io/path_providerr (typo)

0x5f8ce0

,

Present

plugins.flutter.io/url_launcher

,

0x8eec60

Present


Gen 2 Debug Strings, Unstripped from Release Build

Three logging strings were found in the Gen 2 binary that were not stripped before the release build. They confirm the C2 base URL and the presence of explicit HTTP error handling:

// Found in bf90fb31 binary, developer logging left in release build

  • Base URL: https://etoftheappyrince.org

  • Endpoint not found (404). Is the server running? URL: https://etoftheappyrince.org/...

  • Request URL: https://etoftheappyrince.org/api/pdfs

Dart Source Path Leakage, Gen 1 and Gen 2

Dart AOT snapshots retain Dart package URI paths from the source tree. These confirm the internal application structure and cross-generation code lineage:

// Gen 1 (134517796178), package paths retained in AOT snapshot
package:podcasts_lounge/services/sparkle_update_service.dart
package:podcasts_lounge/screens/settings_screen.dart
package:podcasts_lounge/widgets/episode_item.dart

// Gen 2 (bf90fb31), same package name despite external rebranding to PDF-Brain
package:podcasts_lounge/services/pdf_service.dart
com.app.podcastsLounge // original bundle ID still present in Gen 2 binary


Dart Symbol Obfuscation, Gen 1 vs Gen 3

The difference between Gen 1 readable symbols and Gen 3 obfuscated identifiers directly shows the effect of enabling full Dart obfuscation at compilation:

Generation

Symbol Pattern

Examples

Gen 1, partial obfuscation

Readable class name + numeric hash

_Image@17065589 · _Capability@1026248

Gen 3, full obfuscation enabled

2- to3-char random identifier + hash

_Eae@403490068 · _Oxf@884196681 · _Xa@0150898 · _ZU@44287047.LRbr


Gen 1 contained 92 symbols in the partial-obfuscation pattern. Gen 3 contained 707, a +669% increase confirming the compiler flag change. Observed, counted directly from binary string extraction.

 

Attack Chain

Each step is labeled with its evidence basis. Behavioral steps corroborated by external research are marked “[Assessed]”.

1

T1566/T1185

Initial Access, Malvertising [Assessed]

Google/YouTube ad placement targeting keywords such as "podcast app for Mac" and "free PDF converter." Shell company ad accounts procured per generation. User clicks ad; redirected to typosquatted distribution domain; downloads a signed app bundle.

2

T1204.002

Deliver , Signed, Notarized App [Observed]

App bundle carries a valid Apple Developer certificate at distribution time. Gatekeeper quarantine passes, no "unidentified developer" prompt for the user. Revocation tag applied retroactively after discovery.

3

T1204.002

Execution, Flutter Runtime Initialization Observed (structure)

Stub launcher initializes Flutter/Dart runtime; payload dylib loaded via dynamic linking. App presents a functional UI, podcast browser or PDF viewer. Static load command profile is consistent with a standard Flutter app bundle; no anomalous library imports.

4

T1071.001/T1102

C2 Establishment, WebView Beacon [Assessed]

WKWebView loads the attacker domain and fetches /getConfig or the landing page endpoint. C2 URLs confirmed in binary strings; no live C2 session was observed. Without a live responding server, execution stops here, no malicious behavior follows.

5

T1082

Discovery, Hardware UUID Fingerprinting [Assessed]

ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformUUID executed as a child process. Hardware UUID exfiltrated for persistent victim tracking.

6

T1176

Impact, Chrome Search Hijack [Assessed]

Modifies ~/Library/Application Support/Google/Chrome/Secure Preferences to inject sinterfumesco[.]com as default search provider. Kills Chrome; relaunches with --hide-crash-restore-bubble --disable-session-crashed-bubble to suppress browser warnings.

7

T1543.001

Persistence, Silent Sparkle Update [Assessed]

On Sparkle update cycle completion, a replacement bundle is staged in ~/Library/Caches/com.app.*/org.sparkle-project.Sparkle/Installation/ and silently opened. Enables payload rotation without user interaction.

 

Evasion Profile

#

Layer

Mechanism

Evidence Basis

Effective Against

1

Notarization abuse

Valid Apple notarization at distribution time, no static malicious content detectable

Observed, valid cert tag; revocation applied post-distribution

Gatekeeper, quarantine checks

2

Flutter framework camouflage

Structural profile identical to legitimate Flutter app bundle; sole dependency on libSystem

Observed, load commands, segments, single dylib dependency

Structure-based static detection

3

C2-conditional payload

No malicious behavior without live C2, no process trees, no network contacts in isolation

Observed, zero detonation results across all 10 artifacts

Dynamic sandbox analysis

4

Dart symbol obfuscation

Full obfuscation in Gen 3: all internal class names randomized; 707 obfuscated symbols confirmed

Observed, symbol count from binary string extraction

String-based signature matching

5

Bridge command renaming

exec_sync → pdf_sync → renderPDF across generations

Assessed, command names extracted from binary strings and embedded JS

Command-name string detection

6

Certificate rotation

New Apple Developer identity per generation; average gap <60 days from revocation to next cert

Observed, first-seen date deltas: 31d (Gen 1→2), 57d (Gen 2→3)

Certificate-based blocklisting

7

Symhash preservation (unintended signal)

Actor reuses Flutter framework base, exported-symbol fingerprint is invariant and constitutes an inadvertent detection surface

Observed, symhash identical across all 6 dylibs

N/A, this creates a detection opportunity, not an evasion

 

Defensive Implications

We recommend a tiered detection approach. Tier A behavioral rules are durable across all three generations, effective regardless of certificate rotation, binary obfuscation, or command renaming. Tier B static detection rules add coverage for Gen 1 and Gen 2 but require updating for Gen 3. Tier C documents remaining gaps.

Tier A, Behavioral Rules (Generation-Agnostic)

These signals are not produced by any legitimate application in a managed macOS environment.

  • A1, Chrome Relaunch with Hijack Domain (Highest Fidelity)

The combination of sinterfumesco[.]com and crash-suppression flags in a Chrome relaunch is campaign-specific tradecraft. No legitimate application produces this pattern.

title: FlutterBridge Chrome Hijack Relaunch (CL-CRI-1089)
status: experimental
detection:
selection:
event.process.name: 'Google Chrome'
event.process.command_line|contains|all:
- 'sinterfumesco'
- '--hide-crash-restore-bubble'
- '--disable-session-crashed-bubble'
condition: selection
falsepositives:
- None expected for this exact combination

  • A2, Hardware UUID Fingerprinting via Child Process

Legitimate applications call ioreg via API, not child processes. A non-system application spawning an ioreg child to harvest IOPlatformUUID is anomalous.

title: FlutterBridge Hardware UUID Fingerprint Child Process
detection:
selection:
event.process.name: 'ioreg'
event.process.command_line|contains|all:
- 'IOPlatformExpertDevice'
- 'IOPlatformUUID'
event.process.parent.executable|re: '^/(?!System|usr/bin|usr/sbin)'
filter_system:
event.process.parent.name|startswith: ['mdmclient', 'jamf', 'launchd']
condition: selection and not filter_system

  • A3, Sparkle Silent Bundle Replacement from Campaign Cache Path

title: FlutterBridge Sparkle Silent Bundle Replacement
detection:
selection:
event.process.name: 'open'
event.process.command_line|contains|all:
- 'org.sparkle-project.Sparkle'
- 'Installation'
event.process.command_line|contains:
- 'com.app.podcastsLounge'
- 'com.app.pdfBrain'
- 'com.pdfninja.app'


Tier B, Static Detection Logic

Two static detection rules are recommended. The first is generation-agnostic and anchored to the invariant exported-symbol fingerprint. The second covers the cross-generation plugin typo fingerprint.

Full rule bodies are in Appendix C. Summary:

Rule

Anchor

Coverage

Durability

Cluster pivot rule

Exported-symbol fingerprint + io.flutter.flutter.app identifier + file size range

All 6 dylibs, all generations

Durable, survives C2 rotation, cert rotation, command renaming. Breaks only if Flutter framework is replaced.

C2 domain strings rule

Network and hijack domain strings rule - Three C2 domains plus one browser hijack target

Gens 1 to 3

Retire by 2026-09; rotate if new domain confirmed

Plugin typo rule

plugins.flutter.io/path_providerr (double 'r') + webview plugin string

Gens 1 and Gen 3 confirmed; Gen 2 partial

Durable as long as same source tree is reused


Tier C, Coverage Gaps

Gap

Why It’s Not Covered

Our Recommendation

Network-level C2 detection

C2 domains rotate per generation; no live C2 was observed

Sinkhole known domains; monitor registrar cluster for new short-lived .org domains matching the naming pattern

Gen 3 self-signed detection at install

Self-signed binaries are blocked by default on macOS without user override

Alert on self-signed apps with io.flutter.flutter.app dylib identifier launched in user context

Production B73CHZ24Y8 samples

Not in this artifact set

Monitor for signed Mach-O apps with bundle ID com.pdfninja.app or Team ID B73CHZ24Y8

Delivery-stage interception

No telemetry at browser ad-click level

Submit distribution domains to Safe Browsing; report ad accounts to Google

 

Appendices

Supporting reference material, artifact tables, binary offset data, detection rule logic, and full IOC reference.

C2 Endpoint Strings, Binary Offsets

All URLs extracted directly from binary string data with confirmed offsets. C2 URLs were recovered from binary strings; no live C2 session was observed. [Observed]

Gen

URL

Offset

Role

1

https://atsheisdomestic.org/update-thanks.html

0x5879e0

WebView landing page

1

https://atsheisdomestic.org/api/update-delay

0x5d0c60

Update timing config

1

https://atsheisdomestic.org/api/subscribe

0x574380

C2 registration

1

https://atsheisdomestic.org/api/podcasts

0x5b2a20

Lure content endpoint

2

https://etoftheappyrince.org/update-thanks.html

0x8eba10

WebView landing page

2

https://etoftheappyrince.org/summarize-text

0x894670

AI text summarization / exfil

2

https://etoftheappyrince.org/api/update-delay

0x86dbc0

Update timing config

2

https://etoftheappyrince.org/api/pdfs

0x7fa456

PDF content endpoint

3

https://healightejustb.org/welcome_page.html

0x7905f0

Landing page (renamed)

3

https://healightejustb.org/welcome_page.js

0x80b4a0

Explicit JS payload file

3

https://healightejustb.org/api/central-config

0x756d10

Config endpoint (renamed)

3

https://healightejustb.org/summarize-text

0x80adf0

AI summarization, retained from Gen 2

3

https://healightejustb.org/checkForNewVersion

0x7f2f50

Sparkle version check


Gen 2 debug string leakage:
"Base URL: https://etoftheappyrince.org" at offset 0x8a3256, an unstripped developer logging line confirming the hardcoded base URL. Additionally: "Endpoint not found (404). Is the server running? URL: ...", HTTP error handler confirming explicit C2 error handling was compiled into the release build. [Observed]

Plugin Fingerprints

Plugin String

Gen 1

Gen 2

Gen 3

plugins.flutter.io/webview

✓ 0x556320

✓ 0x7de770

plugins.flutter.io/shared_preferences

✓ 0x5dcf30

✓ 0x8e5710

,

plugins.flutter.io/path_providerr (typo , double r)

✓ 0x5f8ce0

,

plugins.flutter.io/url_launcher

,

✓ 0x8eec60

 

Appendix C

Detection Rule Logic

All rules were validated against the analyzed artifacts. Full match rates: Gen 1, 20/20 strings; Gen 2, 17/17 strings; Gen 3, 23/23 strings.

C1, Cluster Pivot Rule (Generation-Agnostic)

rule MAL_FlutterShell_CL_CRI_1089_Dylib_Cluster {
  meta:
   description = "FlutterShell payload dylib , all generations , symhash cluster"
   cluster = "CL-CRI-1089"
   symhash = "605169623267c4eb73693b22b811dc7a"
   evidence_basis= "Observed: identical symhash across 6/6 dylibs"
   gen1_x86 = "134517796178a150a1585672be134169d6877082b598d840baa3f37b0222be26"
   gen2_x86 = "bf90fb31e6024d7e6616f5acd0e8aa28738a9095a508c1a986e1e974cb9e79a0"
   gen3_x86 = "2c5bc9e95e1e9b73e3ba8870a008802899866a2c0e2e10112aefddf7a96af04e"
  condition:
      /* Mach-O thin dylib, 64-bit */
uint32(0) == 0xFEEDFACF and uint32(12) == 6 and
      /* Size range spans all known generations */
filesize >= 6MB and filesize <= 12MB and
      /* LC_ID_DYLIB self-identifier */
"io.flutter.flutter.app" in (0..500) and
      /* Single external dependency */
"/usr/lib/libSystem.B.dylib" in (0..1000)
}


C2, C2 Domain IOC Rule (retire 2026-09)

rule Network_And_Hijack_Domains {
  meta:
   retire_date = "2026-09"
   cluster = "CL-CRI-1089"
 strings:
  $g1 = "atsheisdomestic.org" nocase ascii wide
  $g2 = "etoftheappyrince.org" nocase ascii wide
  $g3 = "healightejustb.org" nocase ascii wide
  $hijack = "sinterfumesco.com" nocase ascii wide
 condition:
  uint32(0) == 0xFEEDFACF and 1 of them
}


C3, Plugin Typo Cross-Generation Rule

rule MAL_FlutterShell_CL_CRI_1089_Plugin_Typo {
  meta:
   description = "Detects FlutterShell via persistent plugin name typo , durable across generations"
   cluster = "CL-CRI-1089"
 strings:
       $typo = "plugins.flutter.io/path_providerr" ascii wide // double 'r' , confirmed in Gen 1 + Gen 3
  $wv = "plugins.flutter.io/webview" ascii wide
  $sp = "plugins.flutter.io/shared_preferences" ascii wide
condition:
     $typo and 1 of ($wv, $sp)
}

 

Appendix D

Master IOC Reference

Category

Indicator/Value

Context/Description

Network Domain

atsheisdomestic.org

Gen 1 C2

Network Domain

etoftheappyrince.org

Gen 2 C2

Network Domain

healightejustb.org

Gen 3 C2

Network Domain

sinterfumesco.com

Hijack Target

SHA-256

363923500ce942bf1a953e8a4e943fbf1fb1b5ed6e5d247964c345b3ad5bfc34

Stub Gen 0

SHA-256

6c3f61d46d4de26b9cb16808bf17c33ae69f651a4b879e7b5612ff7f548e2a82

Stub Gen 1 x86

SHA-256

fc091ddb4d845280aeb7745cfdb6b7cb0013abc35db9e634f055b8e8fb0b5b1e

Stub Gen 1 arm64

SHA-256

134517796178a150a1585672be134169d6877082b598d840baa3f37b0222be26

Dylib Gen 1 x86

SHA-256

cc4f048e66c5ab3c0f1d767bb8fc464d082641f4888ea3cd14ea3775077c4bf2

Dylib Gen 1 arm64

SHA-256

bf90fb31e6024d7e6616f5acd0e8aa28738a9095a508c1a986e1e974cb9e79a0

Dylib Gen 2 x86

SHA-256

32da1437a2734224406c7e5e8d756f0c0cd58c0c959478571cbfc0cd564d018a

Dylib Gen 2 arm64

SHA-256

2c5bc9e95e1e9b73e3ba8870a008802899866a2c0e2e10112aefddf7a96af04e

Dylib Gen 3 x86

SHA-256

f544bfab72d380cc20692d8ec9d31ea666785fe225dccd55beab29a3c0fdfad2

Dylib Gen 3 arm64

Team ID

UBZDAAV97Y

Revoked, Gen 0-1

Team ID

FW9NHQ8922

Revoked, Gen 2

Team ID

B73CHZ24Y8

External Attribution

Behavioral

Chrome Relaunch Hijack

Use of --hide-crash-restore-bubble flags

Behavioral

Hardware Fingerprinting

Spawned ioreg child process

Behavioral

Sparkle Replacement

Persistence via bundle rotation

 

MITRE ATT&CK Mapping

Technique

Tactic

FlutterShell Behavior

Basis

T1566 Phishing

Initial Access

Google/YouTube malvertising lures

Assessed

T1204.002 Malicious File

Execution

User runs signed DMG app

Observed

T1059.004 Unix Shell

Execution

exec_sync/pdf_sync/renderPDF bridge commands

Assessed

T1543.001 Launch Agent

Persistence

Silent Sparkle bundle replacement

Assessed

T1553.002 Code Signing

Defense Evasion

Apple Developer cert procurement and notarization

Observed

T1027 Obfuscation

Defense Evasion

Dart obfuscation; command renaming; C2-conditional payload

Observed + Assessed

T1082 System Info Discovery

Discovery

ioreg IOPlatformUUID fingerprinting

Assessed

T1071.001 App Layer Protocol

C&C

HTTPS WebView to /getConfig + landing page

Assessed

T1102 Web Service

C&C

C2 delivered as web content over HTTPS

Assessed

T1176 Browser Modification

Impact

Chrome Secure Preferences hijack → sinterfumesco[.]com

Assessed

T1041 Exfil Over C2

Exfiltration

read_file results returned via flutterInvoke bridge

Assessed

T1185 Browser Session Hijacking

Collection

Chrome profile modification enables persistent search hijack

Assessed

About the Author

Maor is a cybersecurity professional specializing in Threat Intelligence, Threat Hunting, and Incident Response. Follow Maor on LinkedIn.

ABOUT LEVELBLUE

LevelBlue secures what's next with intelligence-led security delivering visibility and speed to stop threats faster. As the world’s largest and most analyst-recognized pure-play managed security services provider, our AI-powered managed services and cyber expertise across managed, advisory, and incident response services help clients operate with confidence. Learn more about us.

https://www.levelblue.com/resources/blogs/internal-blog/how-to-create-a-blog-post/

Latest Intelligence

Discover how our specialists can tailor a security program to fit the needs of
your organization.

Request a Demo