Desmantelando o KillDisk: reverso do componente destrutivo BlackEnergy

[post-views]
Fevereiro 29, 2016 · 12 min de leitura
Desmantelando o KillDisk: reverso do componente destrutivo BlackEnergy

Vamos pular a longa introdução sobre a ameaça BlackEnergy e ir direto ao estudo do componente de malware chamado “ololo.exe”, também conhecido pelo público como KillDisk. KillDisk é um módulo da estrutura BlackEnergy destinado à destruição de dados e à criação de caos/distração durante as operações APT.

https://www.virustotal.com/en/file/11b7b8a7965b52ebb213b023b6772dd2c76c66893fc96a18a9a33c8cf125af80/analysis/As principais ferramentas usadas em nossa análise de hoje são Process Monitor que é parte das utilidades SysInternals por Mark Russinovich e IDA Pro Disassembler. Todas as manipulações serão realizadas em ambiente virtual baseado no sistema operacional Windows XP. Começamos fazendo uma rápida configuração inicial da máquina virtual de teste, ligamos a máquina e criamos um snapshot chamado “Antes da infecção”. Vamos começar!

Para acompanhar todos os eventos relacionados ao nosso objeto de estudo, lançamos Process Monitor e garantimos que ele permaneça visível:BlackEnergy-killdisk-1E que ele rastreie o processo necessário:

BlackEnergy-killdisk-2Em seguida, carregamos o vírus em IDA Pro Disassembler e vemos a seguinte imagem.Essa é a função WinMain, ou seja, a principal, então começamos sua análise e vemos que a primeira (e única) coisa que ela faz é uma chamada de procedimento chamada sub_40E070:

 BlackEnergy-killdisk-3
Então vamos direto para esse procedimento. Aqui podemos encontrar procedimentos responsáveis por extrair extensões de arquivos para a memória e o primeiro procedimento logo após eles que chama algo interessante chamado sub_40E080:

BlackEnergy-killdisk-4Vamos dar uma olhada mais de perto no seu conteúdo:BlackEnergy-killdisk-5Seus interiores contêm uma chamada de sistema interessante CreateFile e alguns procedimentos sub_40C390 and sub_40C400. Então já é hora de tirar um snapshot do nosso laboratório virtual e continuar nossa jornada!

Vamos examinar a chamada da função CreateFile, configurar o BreakPoint nele e executar nossa amostra de estudo:

BlackEnergy-killdisk-6Uma pequena dica sobre esta função:

CreateFile a função cria ou abre um arquivo ou dispositivo de I/O. Os dispositivos de I/O mais comumente usados são: arquivo, fluxo de arquivo, diretório, disco físico, volume, buffer de console (CONIN$ or CONOUT$), drive de fita, recurso de comunicação, mailslot e pipe nomeado. A função retorna um identificador que pode ser usado para acessar o arquivo ou dispositivo para vários tipos de I/O dependendo do arquivo ou dispositivo e dos sinalizadores e atributos especificados.

Como sabemos a maioria dos compiladores passa argumentos para uma função através de uma pilha e como estamos lidando com uma amostra inicialmente escrita em C, e de acordo com as diretrizes C, os argumentos são empilhados na pilha da direita para a esquerda de modo que o primeiro argumento da função é empilhado por último na pilha e acaba sendo o primeiro elemento no topo dela. [1].

There is a special register for working with the stack – ESP (Extended Stack Pointer). The ESP, by definition, always points to the top of the stack:

BlackEnergy-killdisk-41Um olhar sobre a pilha:BlackEnergy-killdisk-7O último argumento que foi empilhado na pilha (aquele no seu topo) é o primeiro argumento aceito pela função (de acordo com a sintaxe da CreateFile função) e contém o nome do arquivo em nosso caso \.PHYSICALDRIVE0, portanto, o alvo é o primeiro drive físico.BlackEnergy-killdisk-8Agora analisamos outros argumentos enviados para a função pegando seus valores da pilha:BlackEnergy-killdisk-9Algumas conclusões:

  • lpFileName – ponteiro para a string ASCIIZ contendo o nome (caminho) do arquivo sendo aberto ou criado (como já descobrimos);
  • dwDesiredAccess – tipo de acesso ao arquivo: em nosso caso, o valor é 0C0000000h que significa GENERIC_READ+GENERIC_WRITE or acesso de leitura-escrita;
  • DwShareMode – um modo que possibilita o compartilhamento dos arquivos com diferentes processos e este parâmetro pode ter diferentes valores, em nosso caso o valor de 00000003b se traduz emFILE_SHARE_READ+FILE_SHARE_WRITE ou outros processos podem abrir arquivo para leitura/escrita;
  • IpSecurityAttributes – ponteiro para a estrutura SecurityAttributes (arquivo winbase.h) que define configurações de segurança relacionadas ao objeto de arquivo de kernel, se a segurança não for definida, um valor de NULL é usado;
  • dwCreationDistribution – é responsável por decidir ações quando o arquivo existe ou não, em nosso caso um valor de 3 significa OPEN_EXISTING or abrir arquivo se ele existir e retornar um erro se ele não existir;
  • DwFlagsAndAttributes – sinalizadores e atributos; este parâmetro é usado para definir características do arquivo criado e é ignorado no modo de leitura 0.
  • hTemplateFile – o parâmetro é usado apenas quando um novo arquivo é criado 0.
  • Se for bem-sucedida, a função retorna um novo manipulador de arquivo para ЕАХ E se a função falhar, um NULL é escrito em ЕАХ registro.

Muito bem, então, após chamar CreateFile obtemos um valor não-zero para EAX, o que significa que contém o manipulador do arquivo solicitado, por exemplo, \.PHYSICALDRIVE0:

BlackEnergy-killdisk-26Conseguimos nosso manipulador, então vamos prosseguir para a próxima chamada, sub_40C390:

BlackEnergy-killdisk-10Assim como antes, olhamos dentro do procedimento e vemos outra chamada interessante:BlackEnergy-killdisk-11Dica da função:

DeviceIoControl a função envia um código de controle diretamente para um driver de dispositivo especificado, fazendo com que o dispositivo correspondente execute a operação correspondente.

Um olhar na pilha logo antes de chamar esta função:BlackEnergy-killdisk-11Conclusões:

  • hDevice – descritor do dispositivo (reconhecemos um cara familiar aqui \.PHYSICALDRIVE0);
  • dwIoControlCode – código de operação. Em nosso caso, o valor é 0x70000h que significa IOCTL_DISK_GET_DRIVE_GEOMETRY ou a obtenção da informação referente à geometria dos discos (quantidade de cilindros, tipo de mídia, trilhas por cilindro, setores por trilha, bytes por setor);
  • lpInBuffer – buffer com dados de entrada. Não precisamos de nenhum dado de entrada, então NULL é;
  • nInBufferSize – tamanho do buffer de entrada em bytes igual a 0 no nosso caso;
  • lpOutBuffer – ponteiro para o buffer de saída. Seu tipo é definido pelo parâmetro dwIoControlCode 0x0011FCA8h;
  • nOutBufferSize – tamanho do buffer de saída em bytes 0x18h (24);
  • lpBytesReturned – ponteiro para uma variável que contém a quantidade de bytes que são escritos no output 0x0011FCA4h;
  • lpOverlapped – ponteiro para uma estrutura OVERLAPPED;
  • À medida que a chamada é processada, olhamos o buffer (no endereço que foi enviado como argumento para lpOutBuffer precisamente 0x0011FCA8h):

BlackEnergy-killdisk-12Vamos interpretar a resposta:

  • No endereço de 0x0011FCA4h obtemos um retorno da quantidade de bytes escritos no buffer de saída conforme pretendido pela função (marcado em verde), obtivemos tanto quanto pedimos, 24 símbolos.
  • No endereço de 0x0011FCA8h obtivemos informações sobre a geometria do primeiro disco físico (\.PHYSICALDRIVE0):
    • quantidade de cilindros – 0x519h (1305)
    • tipo de mídia – 0x0Ch (12) significando disco rígido fixo.
    • trilhas por cilindro – 0x0FFh (255)
    • setores por trilha – 0x3Fh (63)
    • bytes por setor – 0x200 (512)

Agora nos referimos ao procedimento três, sub_40C400:

BlackEnergy-killdisk-13Dentro do procedimento duas chamadas imediatamente atraíram nossa atenção, especificamenteSetFilePointerEx e WriteFile:

BlackEnergy-killdisk-14Uma dica sobre a função:

SetFilePointerEx a função move o ponteiro do arquivo do arquivo aberto especificado.

Como de costume, olhamos dentro da pilha de chamadas da função:BlackEnergy-killdisk-15

  • hfile – nosso familiar descritor de dispositivo \.PHYSICALDRIVE0;
  • liDustanceToMove – posição inicial para o início de uma gravação 0;
  • lpNewFilePointer – ponteiro para uma variável que recupera um novo ponteiro de posição do arquivo 0x0011FCA8h. Em um caso quando o parâmetro está VAZIO (NULL), um novo ponteiro não é retornado no arquivo;
  • dwMoveMethod – tamanho do buffer de entrada em bytes. No nosso caso 0x200h (512) – bytes no setor;

Agora nossa “besta” usa a WriteFile função para preencher o arquivo com zeros ao qual já estabeleceu acesso direto e confiável.

Uma dica sobre a função:

A WriteFile função escreve dados no arquivo ou dispositivo de entrada/saída (I/O) especificado começando pela posição especificada no arquivo. Esta função é projetada para operação síncrona e assíncrona.

Lista de argumentos da função:BlackEnergy-killdisk-16Tudo bem, para garantir que não perdemos nada interessante, damos uma olhada no painel e entre todas as coisas prestamos atenção na pilha:BlackEnergy-killdisk-17Tudo pronto para a ação:

  • 1 – aguardando na chamada da função WriteFile
  • 2 – Process Monitor está ansiosamente esperando qualquer pequeno movimento dos músculos da nossa “besta”
  • 3 – argumentos empurrados dos registradores para a pilha
  • 4 – e bem, a própria pilha
  • Um espaço dedicado na janela Hex View-EBP marcamos uma área que está sendo endereçada pelo segundo argumento da função (buffer de dados) e você pode confiar em mim nesta – contém exatamente 0x200h (512) de zeros.

Executamos o comando pressionando a tecla F8 e observamos a mudança:BlackEnergy-killdisk-18Como esperávamos, um olho atento de Process Monitor está ligado e fixa a gravação de 512 bytes a partir da posição 0.

Em seguida, o procedimento sai e é chamado novamente, mas agora com valores diferentes:

Agora pegamos os próximos 512 bytes que é particularmente visível em 0x200 (ao contrário dos argumentos anteriores da pilha que marcamos com um quadro azul)BlackEnergy-killdisk-19E eles têm o mesmo destino que os anteriores:BlackEnergy-killdisk-20Esse carrossel continua até a verificação do endereço 0x40C865h retornar o valor do EBX registrador igual a 0x100h (255 vezes ):

BlackEnergy-killdisk-21Para garantir que isso aconteceu, colocamos um ponto de parada na instrução que segue nosso ciclo vicioso de “destruição”:

BlackEnergy-killdisk-22Como um todo recebemos um 256 de operações de regravação:BlackEnergy-killdisk-23Basicamente é isso, nossa “besta” fez seu trabalho sujo e fecha o manipulador de arquivos chamando a CloseHandle função.

Dica da função:

Uma CloseHandle função fecha um identificador de objeto de um objeto aberto.

Argumentos da função:

BlackEnergy-killdisk-24Nossa visão tradicional da preparação da pilha:

BlackEnergy-killdisk-25E o que temos no registrador ESI? BlackEnergy-killdisk-26Como esperado, nosso descritor familiar 0x44h é passado para a pilha como argumento da função.

Vemos os valores retornados pela função no registrador EAX: BlackEnergy-killdisk-27Se a função terminar com sucesso o valor retornado não é nulo.

Se a função terminar com um erro o valor retornado é nulo.

Esta ação não passou despercebida pelo olhar atento do nosso Process Monitor:

BlackEnergy-killdisk-28Tudo funcionou como o normal (se se pode chamar de normal a destruição da parte mais importante do drive do sistema) e o arquivo está fechado.

Vamos prosseguir:BlackEnergy-killdisk-29Para uma percepção mais confortável eu renomeei a função para Eraser. Depois de destruir com sucesso os dados em PhysicalDrive0, um contador localizado no ESI registrador é incrementado por 1, verificado com valor de 0x0Ah (10) e lança a operação de regravação com um novo valor:BlackEnergy-killdisk-30e assim por diante até chegar a 10.

Desta forma a besta continua andando por todos os drives físicos e apagando os primeiros 512 * 255 = 128kb (isto depende da quantidade de bytes em um setor que a “besta” aprendeu ao conhecer a geometria do disco).

Desta vez, no entanto, o resultado da função CreateFileW será o seguinte valor: BlackEnergy-killdisk-31O que significa que em nosso “laboratório improvisado” não existe um segundo disco físico (ou bem, nenhum tipo de disco com um número sequencial maior que 0) e o ciclo não tem nada para regravar.part1_op_enAgora que estamos armados com o conhecimento recém-adquirido, vamos tentar desabilitar a função destrutiva que acabamos de aprender modificando o código de uma forma que nossa modificação possa permitir ignorar a regravação dos dados:

BlackEnergy-killdisk-33Vamos lembrar o procedimento sub_40E080 que contém a chamada da função CreateFileW. Como sabemos que o valor de 0xFFFFFFFFh apareceria após a conclusão da função CreateFileW apenas no caso de um drive físico não estar presente e em tal cenário a função “destruição de dados” realiza uma saída, vamos mudar uma instrução de ramo condicional localizada no endereço 0x0040C7E4h para uma incondicional:

 BlackEnergy-killdisk-34
IDA Pro é um instrumento legal, então só precisamos escrever um comando.

A única coisa que precisamos considerar é o tamanho do comando antes e depois, então vamos verificar quantos bytes são usados pela sequência condicional:

BlackEnergy-killdisk-35Como podemos ver, uma instrução de ramificação condicional é igual a 6 bytes enquanto a sequência incondicional levará apenas 5 e, portanto, devemos adicionar outro comando – NOP, para manter as coisas iguais:BlackEnergy-killdisk-36NOP nos próximos comandos, independentemente do que contivessem antes (mesmo que vejamos um 0 ali):

BlackEnergy-killdisk-37Fazemos uma verificação dupla para garantir que os endereços dos comandos estejam agora igualados com base no endereço do comando seguinte:

BlackEnergy-killdisk-38e pressionamos cancelar. Feito.

Após essas manipulações, o procedimento parecerá simples: entrar e sair, formando um loop vazio (já que não sabemos o que outras partes do programa podem interferir, deixamos assim nesse estado).BlackEnergy-killdisk-39A prova final identificada pelo Process Monitor indica que nenhum disco físico foi danificado em resultado da execução do KillDisk dissecado:

BlackEnergy-killdisk-40Malware leu as informações sobre o disco, estudou e continuou em seus negócios que analisaremos minuciosamente em nossos próximos artigos.

Fontes:

[1] – Hacker Disassembling Uncovered. Kris Kaspersky, 2009.

Traduzido do original por Andrii Bezverkhyi | CEO SOC Prime

Este artigo foi útil?

Curta e compartilhe com seus colegas.
Junte-se à plataforma Detection as Code da SOC Prime para melhorar a visibilidade das ameaças mais relevantes para o seu negócio. Para ajudá-lo a começar e obter valor imediato, agende uma reunião agora com os especialistas da SOC Prime.