FlutterShell — Operation FlutterBridge (macOS Backdoor)

CL-CRI-1089 spreads a Flutter-built macOS backdoor through Google/YouTube malvertising fronted by Google-verified shell companies; apps masquerade as AI PDF/podcast tools, hijack Chrome traffic, and exfiltrate browser and credential data.
Threat
FlutterShell / FlutterBridge
Severity
ACTIVE ITW
Type
macOS backdoor / stealer
Initial Access
Malvertising (Google/YouTube)
Version
v0.1 · 2026-06-24
Author
HuntPack
Confidence
High (Unit 42-attributed)
01

Executive Summary

FlutterShell is a macOS backdoor distributed in a malvertising campaign Palo Alto Unit 42 codenamed Operation FlutterBridge (actor CL-CRI-1089, active since at least 2023; successor to the 2025 JSCoreRunner/FileRipple campaign). The crew buys Google and YouTube ads through a rotating set of Google-verified shell companies (e.g. AdsParkPro LTD, Advantage Web Marketing LLC, SOFT WE ART LIMITED → PACIFIC TRADE SOLUTIONS LTD), pushing fake desktop apps to macOS users in the US, Canada, Australia, France and Germany.

The malware is built with the Flutter framework and ships as notarized apps with valid Apple Developer IDs — masquerading as AI productivity tools: PodcastsLounge, PDF-Brain and PDF-Ninja. Its WebView architecture exposes a JavaScript-to-native bridge that grants arbitrary command execution, file-system manipulation, environment-variable exfiltration, system fingerprinting and browser-session theft. The malicious logic is pulled dynamically from attacker infrastructure, so the signed app body stays "clean" and the behavior can change without re-notarization. A core capability is Chrome traffic hijacking — the app rewrites Chrome configuration to route the victim's browsing through an attacker-controlled intermediary.

The strongest defensive angle on macOS is the post-install behavior: a freshly-installed "PDF/podcast" app modifying Google Chrome's preference files and spawning shells that read env vars and reach external infrastructure. Code-signing is no defense here — the apps are legitimately notarized — so hunt the behavior, not the signature.

Defender priority: Hunt macOS endpoints for the app bundles PodcastsLounge / PDF-Brain / PDF-Ninja, any non-Chrome process modifying Chrome's Preferences/Secure Preferences, and LaunchAgent persistence written by these apps. No public hashes/C2 were released — detection is behavior-led; fill §10 as Unit 42 publishes atomics.

02

Source Review & Web Hunter Notes

TierSourceKey FindingCarry Forward
1Palo Alto Unit 42 — Operation FlutterBridge (via The Hacker News, 2026-06-04)Actor CL-CRI-1089; Flutter/WebView JS-native bridge; notarized masquerading apps (PodcastsLounge, PDF-Brain, PDF-Ninja); Chrome hijack; dynamic external payload; shell-company ad frontsYes
2Unit 42 JSCoreRunner / FileRipple reporting (Aug 2025)Prior campaign lineage; same actor's malvertising-to-macOS-backdoor playbookPartial

No atomic IOCs (file hashes, C2 domains/IPs, exact bundle IDs) were published in the available reporting. This pack is behavior-led; §10 uses REPLACE_WITH_* placeholders so atomics drop in unmodified when Unit 42 releases its appendix. Nothing here is fabricated.

03

Hunt Brief & Attack Chain

Hunt Hypotheses (fidelity-ordered)

  1. H1 (high): An app bundle named PodcastsLounge / PDF-Brain / PDF-Ninja executes from /Applications or a user Downloads path.
  2. H2 (high): A non-Chrome process rewrites Chrome Preferences / Secure Preferences — the traffic-hijack step.
  3. H3 (high): A LaunchAgent/LaunchDaemon plist is written referencing one of the masquerading apps (persistence).
  4. H4 (medium): A FlutterShell app spawns /bin/sh/bash running curl/env — arbitrary command exec + env-var exfil via the JS bridge.
  5. H5 (medium): The app fetches its dynamic payload from external infra shortly after first launch.
  6. H6 (low): Quarantine attribute / first-run of a notarized "PDF/podcast" app downloaded via an ad click.

Attack Chain

StepBehaviorTelemetryHunt Angle
1 · MalvertisingGoogle/YouTube ad via verified shell company → fake app siteDnsRequest, NetworkConnectIP4Q5
2 · DeliveryUser downloads notarized FlutterShell app (PDF/podcast lure)FileCreateInfo, ProcessRollup2Q1, Q6
3 · ExecutionFlutter app launches; WebView JS-to-native bridge activeProcessRollup2Q1
4 · Dynamic payloadApp fetches malicious logic from attacker infraNetworkConnectIP4, DnsRequestQ5
5 · Browser hijackRewrites Chrome config to route traffic via intermediaryFileCreateInfo (Chrome prefs)Q2
6 · Collection / exfilShell exec, env-var read, browser-session theftProcessRollup2 CommandLineQ4
7 · PersistenceLaunchAgent/LaunchDaemon plistFileCreateInfo (LaunchAgents)Q3
04

Consolidated IOC Table

TypeValueConfidenceActionContext
appnamePodcastsLoungehighhuntFlutterShell masquerade
appnamePDF-BrainhighhuntFlutterShell masquerade (AI PDF lure)
appnamePDF-NinjahighhuntFlutterShell masquerade (AI PDF lure)
behaviorNon-Chrome process rewriting Chrome PreferenceshighhuntTraffic-hijack step
ad-frontAdsParkPro LTD / Advantage Web Marketing LLC / SOFT WE ART LIMITED / PACIFIC TRADE SOLUTIONS LTDmediumenrichGoogle-verified ad shell companies
sha256REPLACE_WITH_FLUTTERSHELL_APP_HASHpendingdetectPopulate from Unit 42 appendix
domainREPLACE_WITH_DYNAMIC_PAYLOAD_C2pendingdetectDynamic payload / intermediary host
05

Affected Surface & Telemetry Matrix

SurfaceRequired TelemetryPriorityGap Risk
macOS endpoints (US/CA/AU/FR/DE users)ProcessRollup2 (macOS sensor)HighMedium — requires Falcon macOS coverage
Chrome profile config filesFileCreateInfo / file-write eventsHighMedium — app-data writes not always logged
LaunchAgent/Daemon persistenceFileCreateInfo on plist pathsHighLow
Egress to dynamic-payload infraNetworkConnectIP4, DnsRequestMediumHigh — no published infra to anchor
JS-to-native bridge command execProcessRollup2 (child shells)MediumMedium — in-WebView logic opaque
06

ATT&CK Mapping

TacticTechniqueObserved BehaviorQuery / Control
Resource DevelopmentT1583.008 MalvertisingGoogle/YouTube ads via verified shell companiesQ5 / web filtering
Initial Access / ExecutionT1204.002 Malicious FileUser installs notarized fake PDF/podcast appQ1, Q6
ExecutionT1059.004 Unix ShellJS-native bridge spawns /bin/shQ4
Defense EvasionT1036.005 MasqueradingValid Developer ID + notarization; AI-tool brandingQ1
PersistenceT1543.001 Launch AgentLaunchAgent/Daemon plistQ3 / M1018
CollectionT1539 / T1552.001 Browser session & env-var theftReads Chrome session data, env varsQ2, Q4
Defense EvasionT1539 Browser Hijack (config rewrite)Rewrites Chrome Preferences to proxy trafficQ2
Command & ControlT1105 Ingress Tool TransferDynamic payload pulled from attacker infraQ5
07

Native Audit-Log Hunts

  • macOS Unified Log / MDM: Inventory installed apps for PodcastsLounge, PDF-Brain, PDF-Ninja (and any AI PDF/podcast app installed via ad click); flag those with recently-issued Developer IDs.
  • LaunchAgents/Daemons sweep: Enumerate ~/Library/LaunchAgents, /Library/LaunchAgents, /Library/LaunchDaemons for plists pointing at these app bundles.
  • Chrome config integrity: Diff ~/Library/Application Support/Google/Chrome/Default/Preferences and Secure Preferences against a known-good baseline; look for injected proxy / startup-URL entries.
  • Secure web gateway: Hunt ad click-throughs to non-official "PDF tool" download domains and alert on first-seen app-download domains advertised via Google/YouTube.
08

CrowdStrike LogScale CQL Hunt Queries

Pick your tenant's cloud first — every "Open in Falcon" button below uses this selection.
Q1 · FlutterShell masquerading app execution (macOS)
CONF HIGHFP LOWCOST LOW

Looks for: Execution of the known FlutterShell app bundles on macOS endpoints.

// HUNT: FlutterShell masquerading app execution (Operation FlutterBridge)
// MITRE: T1204.002, T1036.005
// CONF: high  FP: low  COST: low | REQUIRES: ProcessRollup2 (macOS)
#event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/
| ImageFileName=/\/(PodcastsLounge|PDF-Brain|PDF-Ninja)(\.app)?\//i
| table([ComputerName, UserName, ImageFileName, CommandLine, SHA256HashData], limit=200)
Q2 · Non-Chrome process rewriting Chrome preferences
CONF HIGHFP MEDCOST LOW

Looks for: A process other than Chrome writing Chrome's Preferences/Secure Preferences — the traffic-hijack step. FP: some MDM/profile tools touch Chrome policy; exclude known management agents.

// HUNT: Chrome preference rewrite by non-Chrome process (FlutterShell hijack)
// MITRE: T1539
// CONF: high  FP: medium  COST: low | REQUIRES: FileCreateInfo (macOS)
// TUNING: exclude MDM/endpoint-mgmt agents that legitimately set Chrome policy
#event_simpleName=/FileRename/
| TargetFileName=/\/Google\/Chrome\/.*(Secure Preferences|Preferences)$/i
| ContextBaseFileName!=/^(Google Chrome|com\.google\.Chrome|chrome)$/i
| table([ComputerName, UserName, ContextBaseFileName, TargetFileName], limit=200)
Q3 · LaunchAgent persistence for masquerading app
CONF HIGHFP MEDCOST LOW

Looks for: A LaunchAgent/LaunchDaemon plist written for one of the FlutterShell apps. FP: legitimate apps add LaunchAgents; the app-name match keeps this tight.

// HUNT: FlutterShell LaunchAgent persistence (Operation FlutterBridge)
// MITRE: T1543.001
// CONF: high  FP: medium  COST: low | REQUIRES: FileCreateInfo (macOS)
// TUNING: broaden to any new plist if app name is obfuscated at install time
#event_simpleName=/FileRename/
| TargetFileName=/\/Library\/Launch(Agents|Daemons)\/.*(PodcastsLounge|PDF-Brain|PDF-Ninja|flutter).*\.plist$/i
| table([ComputerName, UserName, ContextBaseFileName, TargetFileName], limit=200)
Q4 · Masquerading app spawning shell with env/curl
CONF MEDFP MEDCOST LOW

Looks for: One of the FlutterShell apps spawning /bin/sh/bash running curl or reading env vars — JS-bridge command exec + exfil. FP: some legit apps shell out; the parent app-name anchor keeps FP low.

// HUNT: FlutterShell JS-bridge shell exec / env exfil (Operation FlutterBridge)
// MITRE: T1059.004, T1552.001
// CONF: medium  FP: medium  COST: low | REQUIRES: ProcessRollup2 (macOS)
// TUNING: scope to the masquerade app-name parents; exclude approved apps that legitimately shell out
#event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/
| ParentBaseFileName=/(PodcastsLounge|PDF-Brain|PDF-Ninja)/i
| ImageFileName=/\/(sh|bash|zsh|curl)$/i
| table([ComputerName, UserName, ParentBaseFileName, ImageFileName, CommandLine], limit=200)
Q5 · Dynamic-payload / C2 egress (infra placeholder)
CONF MEDFP MEDCOST LOW

Looks for: Network egress initiated by a FlutterShell app — the dynamic-payload pull / hijack proxy. FP: populate DomainName once Unit 42 releases infra; until then review any egress from these apps.

// HUNT: FlutterShell dynamic-payload egress (Operation FlutterBridge)
// MITRE: T1105, T1583.008
// CONF: medium  FP: medium  COST: low | REQUIRES: NetworkConnectIP4 (macOS)
// TUNING: add published C2 domains to DomainName when Unit 42 releases infra
#event_simpleName=/NetworkConnectIP4/
| ContextBaseFileName=/(PodcastsLounge|PDF-Brain|PDF-Ninja)/i
| table([ComputerName, ContextBaseFileName, RemoteAddressIP4, RemotePort], limit=200)
Q6 · First-run of fake PDF/podcast app from Downloads
CONF MEDFP MEDCOST LOW

Looks for: A FlutterShell app first executing from a user Downloads/quarantine path (ad-driven install). FP: any legitimately downloaded app runs from Downloads once; pair with the app-name list.

// HUNT: First-run FlutterShell app from Downloads (Operation FlutterBridge)
// MITRE: T1204.002
// CONF: medium  FP: medium  COST: low | REQUIRES: ProcessRollup2 (macOS)
// TUNING: a legit downloaded app runs from Downloads once — gate on the masquerade app-name list to cut FP
#event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/
| ImageFileName=/\/(Downloads|Desktop)\/.*(PodcastsLounge|PDF-Brain|PDF-Ninja)/i
| table([ComputerName, UserName, ImageFileName, ParentBaseFileName, SHA256HashData], limit=200)
09

CrowdStrike Custom IOA Recommendations

IOA NamePatternExclusionsAction
FlutterShell — Masquerade App ExecmacOS process image matches PodcastsLounge|PDF-Brain|PDF-NinjaNoneDetect (high)
FlutterShell — Chrome HijackNon-Chrome process writes Chrome Secure PreferencesMDM/endpoint-mgmt agentsDetect (high)
FlutterShell — App-Parented ShellMasquerade app spawns sh/bash/curlNoneDetect (medium)

Q1 is the strongest IOA promotion candidate. Q2/Q4 promote after the MDM-agent exclusions are validated. Falcon macOS sensor coverage is a prerequisite for all of these.

10

Machine-Readable IOC Appendix

Falcon IOC Management CSVbulk import
type,value,action,severity,expiration,description,tags
sha256,REPLACE_WITH_FLUTTERSHELL_APP_HASH,prevent,critical,2026-12-24,FlutterShell macOS app,campaign:FlutterBridge
sha256,REPLACE_WITH_FLUTTERSHELL_VARIANT_HASH,prevent,critical,2026-12-24,FlutterShell variant,campaign:FlutterBridge
domain,REPLACE_WITH_DYNAMIC_PAYLOAD_C2,detect,high,2026-09-24,Dynamic payload / hijack proxy,campaign:FlutterBridge
domain,REPLACE_WITH_FAKE_APP_DOWNLOAD_DOMAIN,detect,high,2026-09-24,Malvertising app download site,campaign:FlutterBridge
Behavioral Signatureshunt logic
macOS process image ~ PodcastsLounge | PDF-Brain | PDF-Ninja
non-Chrome process writes ~/Library/Application Support/Google/Chrome/.../Secure Preferences
LaunchAgent/Daemon plist referencing a masquerade app
masquerade app parents /bin/sh|bash|zsh|curl (env-var read / exfil)
network egress whose initiating image is a masquerade app
Named Toolingcontext
Campaign: Operation FlutterBridge (Palo Alto Unit 42)
Actor: CL-CRI-1089 (active since 2023; succeeds JSCoreRunner/FileRipple, 2025)
Backdoor: FlutterShell (Flutter + WebView JS-to-native bridge)
Masquerade: PodcastsLounge, PDF-Brain, PDF-Ninja (AI PDF/podcast tools)
Signing: valid Apple Developer IDs + notarization (NOT a trust signal here)
Delivery: Google/YouTube ads via verified shell companies
Capability: arbitrary cmd exec, file ops, env-var exfil, Chrome traffic hijack, browser-session theft
Payload: pulled dynamically from attacker infra (app body stays clean)
Ad-Front Shell CompaniesTI enrichment
AdsParkPro LTD
Advantage Web Marketing LLC
SOFT WE ART LIMITED  (renamed -> PACIFIC TRADE SOLUTIONS LTD)
Note: Google-verified advertiser identities; links to Ukrainian individuals (Unit 42)
Action: report fraudulent advertiser IDs to Google; block their ad-download domains at SWG
11

Detection Validation Gates

GateCheckPass Criteria
Telemetry readyFalcon macOS sensor reporting ProcessRollup2 + FileCreateInfoBoth event types present from macOS hosts in last 24h
Benign baselineRun Q2 over 7 daysMDM/policy agents that touch Chrome prefs identified and excluded
Positive testLab macOS: write a test file to Chrome's Default dir from a non-Chrome process; create a dummy app named PDF-NinjaQ1 and Q2 fire
PromotionQ1 promoted to Custom IOAZero FP across macOS pilot ring
12

Hardening — Tiered & Deployable

Immediate (this week)
  • Block the masquerade apps — add PodcastsLounge/PDF-Brain/PDF-Ninja bundle IDs to MDM block / Falcon prevent once hashes land (M1038).
  • SWG block the ad-front download domains and report the shell-company advertiser IDs to Google (M1021).
  • Baseline Chrome preference integrity and alert on out-of-band rewrites (M1042).
Near term (1–4 weeks)
  • Gatekeeper to "App Store and identified developers" + restrict app install to MDM-approved sources — notarization alone is not sufficient here, so pair it with an allow-list (M1038).
  • Managed Chrome policy via MDM (lock proxy / startup URLs / extension install) so a config rewrite is overridden by policy (M1042).
  • User awareness on ad-driven "AI PDF/podcast tool" downloads; steer to vetted software portals (M1017).
Strategic (1–3 months)
  • macOS application allow-listing (MDM + Falcon) so only approved apps run on managed fleet (M1038).
  • Egress filtering / DNS security on macOS segments to surface dynamic-payload pulls and hijack-proxy traffic (M1037, M1031).
13

Deployable Playbooks

1 · Hunt the masquerade apps + LaunchAgents across a Mac (shell)

#!/bin/bash
# masquerade app bundles
mdfind "kMDItemKind == 'Application'" 2>/dev/null | grep -Ei 'PodcastsLounge|PDF-Brain|PDF-Ninja'
ls -la /Applications | grep -Ei 'PodcastsLounge|PDF-Brain|PDF-Ninja'
# persistence
for d in ~/Library/LaunchAgents /Library/LaunchAgents /Library/LaunchDaemons; do
  grep -lEi 'PodcastsLounge|PDF-Brain|PDF-Ninja|flutter' "$d"/*.plist 2>/dev/null
done

2 · Check Chrome preference integrity (shell)

PREF="$HOME/Library/Application Support/Google/Chrome/Default/Secure Preferences"
[ -f "$PREF" ] && /usr/bin/python3 -c 'import json,sys;d=json.load(open(sys.argv[1]));print(d.get("proxy"),d.get("session",{}).get("startup_urls"))' "$PREF"

3 · Remove a confirmed FlutterShell app + persistence (shell)

sudo rm -rf "/Applications/PDF-Ninja.app" "/Applications/PDF-Brain.app" "/Applications/PodcastsLounge.app"
for d in ~/Library/LaunchAgents /Library/LaunchAgents /Library/LaunchDaemons; do
  grep -lEi 'PodcastsLounge|PDF-Brain|PDF-Ninja|flutter' "$d"/*.plist 2>/dev/null | xargs -r sudo rm -f
done

Capture the .app bundle and its Info.plist (Developer ID / Team ID) for hashing and abuse-reporting to Apple before deletion — that Team ID is your tenant-local atomic IOC.

14

Containment Runbook

PhaseActionsOwnerEvidence
IsolateNetwork-contain the Mac in Falcon; preserve the app bundle + Chrome profileSOC L2Containment timestamp, bundle copy
EradicateRemove the masquerade app + LaunchAgent plists; reset Chrome (delete tampered Preferences/Secure Preferences)SOC L2File hashes, plist export
CredentialsRotate browser-saved creds, SSO sessions, and any secrets exposed via env vars on the hostIAMReset audit log
RecoverReinstall Chrome; restore from clean baseline; re-enroll in MDM allow-listEndpoint EngRebuild ticket
LessonsReport the abused Apple Developer/Team IDs and Google advertiser IDs; promote Q1 to IOADetection Eng / TIThis pack, updated
15

Detection Coverage Map

TechniqueBehaviorCQLIOACoverage
T1583.008Malvertising deliveryQ5Partial
T1204.002 / T1036.005Masquerade app install + execQ1, Q6YesGood
T1539Chrome traffic hijack (config rewrite)Q2YesGood
T1543.001LaunchAgent persistenceQ3Good
T1059.004 / T1552.001Shell exec + env-var exfilQ4CandidatePartial
T1105Dynamic payload pullQ5Gap (no infra)
In-WebView JS bridge logicGap

Coverage is strong on the app-execution, Chrome-hijack and persistence anchors (Q1/Q2/Q3) and weaker on the in-WebView JS logic and the dynamic-payload C2 (no published infra). All queries assume Falcon's macOS sensor is deployed — without it this pack has no telemetry. Validation gates (§11) must pass before promoting Q1 to a Custom IOA.

16

Hunt Summary Ticket

TITLE: FlutterShell / Operation FlutterBridge (CL-CRI-1089) — macOS backdoor hunt
SEVERITY: High — active in-the-wild macOS backdoor + stealer
SCOPE: macOS endpoints (US/CA/AU/FR/DE users); requires Falcon macOS sensor
HYPOTHESIS: Notarized fake PDF/podcast app (FlutterShell) hijacks Chrome + exfiltrates via JS-native bridge
QUERIES: Q1 app exec · Q2 Chrome pref rewrite · Q3 LaunchAgent · Q4 app-parented shell · Q5 C2 egress · Q6 Downloads first-run
DO FIRST: Run Q1, Q2 across macOS fleet; inventory PodcastsLounge/PDF-Brain/PDF-Ninja
FINDINGS: ____________________________________________
GAPS: in-WebView JS bridge logic; dynamic-payload C2 (no published infra)
ACTIONS: Block masquerade apps; managed Chrome policy; report Apple Team IDs + Google advertiser IDs; promote Q1 to IOA
OWNER: ____________________   VERSION: v0.1 (2026-06-24)
17

Changelog

v0.32026-07-24CQL correctness pass. All table() calls now carry an explicit row limit: the default is 200 and truncation is silent, so a capped result was indistinguishable from a complete one. Atomic-IOC sweeps (filters over 5+ hashes or C2 IPs) use limit=max so a wide infection is never silently under-scoped; behavioural hunts use limit=200, where exceeding the cap indicates the query needs tuning. Where present, event names that do not exist in the Falcon data model were corrected (e.g. ServiceInstalled is a Sysmon concept, not a Falcon event; ElfFileWritten is ELFFileWritten) — such queries could never return a row. No detection logic, fields, or IOCs changed.
v0.22026-06-29CQL syntax review (crowdstrike-logscale-v3): removed invalid FileCreateInfo event (Q2, Q3).
v0.12026-06-24Initial HuntPack — FlutterShell / Operation FlutterBridge (Unit 42, 2026-06-04). 6 behavior-led macOS CQL queries; atomic IOC slots pending vendor appendix.
18

References

TierSourceUsed ForAccess Date
1The Hacker News — FlutterShell Backdoor Spreads to macOS via Malicious Google and YouTube Ads (Unit 42, 2026-06-04)Operation FlutterBridge, CL-CRI-1089, masquerade apps, Flutter/WebView bridge, Chrome hijack, shell-company ad fronts, dynamic payload2026-06-24
RMITRE ATT&CKTechnique IDs and M-number mitigation mapping (macOS)2026-06-24