lacorte.citypython * backend * segurança ofensiva

cat~/writing/pt-br/ransomhub-esxi.md

Ransomware RansomHub
Análise técnica do ransomware para ESXi do grupo RansomHub
1.5k palavras · engenharia-reversa, malware, esxi

índice

Resumo

Este relatório apresenta minha análise detalhada das principais funções do ransomware para ESXi do grupo RansomHub.

CTI do RansomHub

O RansomHub surgiu como uma operação de ransomware-as-a-service (RaaS), especialmente após a desarticulação de outros grupos grandes como o LockBit. O grupo parece ter crescido rápido na segunda metade de 2024. Vale notar que o RansomHub é tido como ligado a ex-afiliados de outros grupos, como LockBit e BlackCat, e adotou várias táticas desses predecessores, incluindo estratégias de “living-off-the-land” e alvos multiplataforma, tanto Linux quanto Windows.

As atividades do grupo têm algumas características distintas. Consta que o RansomHub dá aos afiliados o controle direto sobre os pagamentos de resgate, o que o tornou atraente para criminosos vindos de outros grupos. Foi associado a vários ataques de repercussão, incluindo os contra empresas como Halliburton e Kawasaki Europe. Além disso, o RansomHub explora vulnerabilidades em diversos setores, inclusive saúde, e é conhecido pelas práticas de exfiltração de dados.

O RansomHub atraiu um conjunto notável de afiliados, incluindo pessoas que antes trabalhavam nas operações hoje extintas do BlackCat e do LockBit. Por isso é visto como uma nova potência no ecossistema de ransomware, usando técnicas sofisticadas de extorsão e oferecendo pagamentos significativos aos afiliados.


Análise Estática de Código

Processo Daemon

Ao iniciar a execução, a primeira coisa que o ransomware faz depois de interpretar a linha de comando é verificar a presença do argumento -verbose. Se esse argumento não for informado, o programa se configura para rodar como processo daemon, ou seja, em segundo plano.

A execução em segundo plano começa com a criação de um processo filho pela chamada de sistema fork(), que duplica o processo atual criando uma instância separada.

Se a criação do processo der certo, o processo pai (o original) espera a conclusão do filho, onde o ransomware seguirá com sua execução principal, usando waitpid().

No processo filho, onde o ransomware executa, a função setsid() é usada para criar uma nova sessão. Isso desacopla o processo de qualquer terminal de controle. Se setsid() falhar, o processo é encerrado imediatamente com exit(1).

fig.1Daemonização: tudo que o locker faz antes de encostar em um único arquivo.

Os descritores padrão do processo filho, stdin, stdout e stderr, também são fechados com close(). Isso impede que o daemon continue interagindo com o terminal. Os descritores fechados são então redirecionados para /dev/null e duplicados com dup(), forçando qualquer tentativa de acessar os canais padrão a cair em /dev/null.

child_proc_pid_var = fork();
if ( child_proc_pid_var < 0 )
  exit(1);

if ( child_proc_pid_var > 0 )
{
  puts("running...");

  waitpid(child_proc_pid_var, stat_loc, 0);
  
  puts("done");
  exit(0);
}

if ( setsid() < 0 )
  exit(1);
  
close(0);
close(1);
close(2);

open("/dev/null", 2);

dup(0);
dup(0);

Decifragem da Configuração

Antes de executar a parte principal do malware, o ransomware precisa decifrar sua configuração, e para isso exige que o operador forneça uma senha como argumento. Essa senha é usada em operações de decifragem baseadas em XOR sobre a configuração cifrada.

Com a senha informada, o programa a transforma com operações de manipulação binária e depois compara o resultado com o dado esperado dentro do malware. Se a senha estiver errada, uma mensagem de erro é exibida e o processo termina.

Se a senha estiver correta, o malware usa operações XOR para decifrar os dados. O processo resulta em um JSON no formato abaixo:

{
    "master_public_key": ,   chave pública curve25519
    "extension": ,           extensão que será adicionada aos arquivos cifrados.
    "note_file_name": ,      nome do arquivo da nota de resgate.
    "note_full_text": ,      texto completo da nota de resgate.
    "note_short_text": ,     texto personalizado na nota, adicionado pelo operador.
    "encryption_files": ,    lista de extensões de arquivo que devem ser cifradas.
    "remove_vms_snapshot": , indica se os snapshots das máquinas virtuais devem ser removidos.
    "shutdown_vms": ,        indica se as máquinas virtuais devem ser desligadas.
    "self_delete": ,         indica se o ransomware deve se apagar após a execução.
}

Preparando o ESXi para a Cifragem

Antes de iniciar o processo de cifragem, o ransomware executa uma série de passos para manipular o ESXi e garantir que o processo ocorra sem interferência.

Desabilitar o Auto Start das Máquinas Virtuais

A primeira ação do malware é desabilitar a função de auto start das máquinas virtuais no host ESXi.

Isso impede que as VMs reiniciem sozinhas ou tentem subir depois da cifragem, o que poderia corromper arquivos. O comando usado é:

/bin/sh -c vim-cmd hostsvc/autostartmanager/enable_autostart 0

Encerramento do Syslog

Para dificultar o registro e a análise, o ransomware encerra o processo responsável pelo syslog (vmsyslogd), que gerencia os logs do sistema.

Ele usa o comando abaixo para localizar e encerrar todas as instâncias em execução:

/bin/sh -c for i in $(ps -Cc | grep vmsyslogd | awk '!/grep/ {print $1}' | grep -o '[0-9]*'); do kill -9 $i;  done;

O comando usa ps para listar os processos, grep para localizar o vmsyslogd e kill -9 para forçar o encerramento de cada processo encontrado.

Removendo Snapshots

O ransomware verifica se está configurado para remover snapshots das máquinas virtuais, impedindo a recuperação após o ataque. Se essa opção estiver ativa, ele executa:

/bin/sh -c for i in $(vim-cmd vmsvc/getallvms | awk '{print $1}' | grep -o '[0-9]*'); do vim-cmd vmsvc/snapshot.removeall $i; done;

Parando as Máquinas Virtuais

O ransomware pode ser configurado para interromper a execução das máquinas virtuais. Se estiver, ele executa os seguintes passos:

  1. Verifica se o argumento -skip_vms foi especificado. Se sim, lê o arquivo com os nomes das máquinas virtuais que não devem ser desligadas e guarda a lista em uma variável.
  2. Obtém a lista de todas as máquinas virtuais em execução, com o comando:
/bin/sh -c localcli --formatter json vm process list 2>/dev/null
  1. O ransomware interpreta a saída JSON, comparando o campo Display Name de cada máquina virtual com a lista de máquinas configuradas para serem poupadas.
  2. Para todas as máquinas virtuais que não estão na lista de exclusão, o ransomware executa o comando a seguir para desligá-la à força, usando o World ID correspondente da VM:
/bin/sh -c localcli vm process kill --type=force --world-id=%d 2>/dev/null

Thread Pool

Depois da inicialização, o ransomware entra na fase principal de execução.

Ele começa inicializando um thread pool, onde o número de threads é calculado como o dobro do número de processadores disponíveis.

thread_pool_var = thread_pool_init_fn(2 * processors_count);

Depois de inicializar o thread pool, o ransomware chama uma função para procurar os arquivos que devem ser cifrados. O caminho inicial da busca é indicado pelo argumento -path.

Quando a busca termina, uma função espera que todas as tarefas pendentes no thread pool acabem antes de destruí-lo:

thread_pool_wait_fn(thread_pool_var);
thread_pool_destroy_fn(thread_pool_var);

Busca Recursiva

Para achar os arquivos que devem ser cifrados, o ransomware usa um algoritmo de busca recursiva que percorre diretórios e arquivos.

Escrita da Nota

Antes de processar o conteúdo de um diretório, o ransomware cria a nota de resgate. A nota é gerada concatenando o caminho do diretório atual com o nome do arquivo de nota definido na configuração:

strcpy(note_path_var, path_var);
strcat(note_path_var, "/");
strcat(note_path_var, *((const char **)&config + 2)); /* nome do arquivo de nota, vindo da config */

note_file_handle_var = fopen(note_path_var, "w");
if ( note_file_handle_var )
{
    fwrite(*((const void **)&config + 3), 1, strlen(*((const char **)&config + 3)), note_file_handle_var); /* conteúdo da nota, vindo da config */
    fclose(note_file_handle_var);
}

Busca de Arquivos

A busca por arquivos começa abrindo o diretório com a função opendir(). Em seguida, cada entrada do diretório é analisada em um laço while usando readdir().

Se a entrada for um diretório, ela é concatenada com o caminho atual e a função de busca recursiva é chamada de novo com o caminho novo.

Se a entrada for um arquivo, o ransomware verifica se é um arquivo de máquina virtual e se não pertence a uma lista de VMs a ignorar.

A lista de extensões usada por padrão para dizer que se trata de um arquivo de máquina virtual é:

.vmdk, .vmx, .vmsn. vswp, .vmxf, .vhd, .vhdx, .iso, .vmx.lck, .nvram, .img

Se os critérios forem atendidos, o arquivo é adicionado como nova tarefa no thread pool para ser cifrado.

Cifragem de Arquivos

Assim que um arquivo entra no thread pool, o processo de cifragem começa.

Geração de Chaves

Primeiro, o ransomware gera uma chave privada aleatória para o algoritmo curve25519. Dessa chave privada deriva-se uma chave pública, usada depois para criar uma chave compartilhada. A chave pública gerada pelo ransomware é combinada com uma chave pública mestra (guardada na configuração decifrada), e daí se deriva a chave compartilhada. Essa chave compartilhada será a chave do processo de cifragem simétrica.

Depois de gerar as chaves, o ransomware verifica se o processador da máquina onde está rodando suporta o conjunto de instruções AES-NI. Se o processador for compatível, ele usa AES-256 com otimizações SIMD. Caso contrário, usa o algoritmo ChaCha20 com um nonce aleatório.

Processamento do Arquivo

A cifragem do arquivo é feita de forma intermitente: o ransomware pula blocos de dados entre as operações de cifragem. A quantidade de dados “pulada” entre cada operação depende do tamanho do arquivo que está sendo cifrado.

A tabela abaixo mostra os valores de salto entre os blocos cifrados, definidos conforme o tamanho do arquivo:

Tamanho do ArquivoTamanho do Salto
≤ 2 MB0 B
> 2 MB ≤ 8 MB1 MB
> 8 MB ≤ 32 MB2 MB
> 32 MB ≤ 128 MB3 MB
> 128 MB ≤ 512 MB4 MB
> 512 MB ≤ 2 GB5 MB
> 2 GB ≤ 8 GB6 MB
> 8 GB ≤ 32 GB7 MB
> 32 GB ≤ 128 GB8 MB
> 128 GB ≤ 512 GB9 MB
> 512 GB10 MB

Se o parâmetro -fast for especificado, o valor do salto é multiplicado por quatro, aumentando a quantidade de dados pulada entre as partes cifradas, o que acelera o processo.

Depois de definir o tamanho do salto, o ransomware cria uma estrutura de dados que guarda informações como o tamanho do salto, a chave pública do arquivo, o nonce e outras variáveis que serão usadas para decifrar o arquivo depois. Essa estrutura é então escrita no fim do arquivo cifrado.

A fórmula usada para calcular o número de partes que precisam ser cifradas:

part_size = ((skip_size + 1MB) + file_size -1 ) / skip_size 1MB

Determinado o número de partes, o ransomware percorre o arquivo em um laço e cifra cada parte. Para cada uma, lê 1MB de dados, cifra com o algoritmo escolhido (AES-256 ou ChaCha20) e escreve os dados cifrados de volta no arquivo. O offset é atualizado a cada iteração, pulando a quantidade definida pelo tamanho do salto.

Abaixo, um trecho do código decompilado que cifra o arquivo:

skip_size = calculate_skip_size(file_size);
part_size = skip_size + 0x100000;
parts_count = (part_size + file_size - 1) / part_size;

if ( parts_count )
{
  offset = 0;
  for ( i = 0; i < parts_count; ++i )
  {
    fseek(file_handle, offset, 0);

    bytes_read = fread(file_1mb_buffer, 1, 0x100000, file_handle);
    if ( !bytes_read )
      break;

    if ( have_aes_ni )
      aes_encrypt(file_1mb_buffer,file_1mb_buffer, bytes_read, &v33, aes_context);
    else
      chacha_encrypt(chacha_context, file_1mb_buffer, bytes_read);

    fseek(file_handle, -bytes_read, 1);

    fwrite(file_1mb_buffer, 1, bytes_read, file_handle);

    offset += part_size;
  }
}

Cifrados todos os dados, o arquivo é renomeado com a extensão configurada no ransomware pela função rename().

Auto-Exclusão

Após a execução, se estiver configurado para isso, o ransomware se apaga com:

unlink(argv[0]);

Regra Yara

rule RansomHub_ESXi_Ransomware
{
    meta:
        description = "Detects RansomHub ESXi Ransomware"
        author = "@Gabriel-Lacorte"
    strings:
        $cmd_autostart = "vim-cmd hostsvc/autostartmanager/enable_autostart 0" ascii wide

        $cmd_syslog = "for i in $(ps -Cc | grep vmsyslogd | awk '{print $1}'); do kill -9 $i; done;" ascii wide

        $cmd_snapshot = "for i in $(vim-cmd vmsvc/getallvms | awk '{print $1}'); do vim-cmd vmsvc/snapshot.removeall $i; done;" ascii wide
    condition:
        any of ($cmd_*)
}

cd~/writing

lacorte.citytake a chill pillinternet privacy now!