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:
E que ele rastreie o processo necessário:
Em 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:

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:
Vamos dar uma olhada mais de perto no seu conteúdo:
Seus 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:
Uma pequena dica sobre esta função:
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:
Um olhar sobre a pilha:
O ú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.
Agora analisamos outros argumentos enviados para a função pegando seus valores da pilha:
Algumas 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 é
0C0000000hque significaGENERIC_READ+GENERIC_WRITEoracesso 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 em
FILE_SHARE_READ+FILE_SHARE_WRITEououtros 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
3significaOPEN_EXISTINGorabrir 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, umNULLé 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:
Conseguimos nosso manipulador, então vamos prosseguir para a próxima chamada, sub_40C390:
Assim como antes, olhamos dentro do procedimento e vemos outra chamada interessante:
Dica da função:
Um olhar na pilha logo antes de chamar esta função:
Conclusões:
- hDevice – descritor do dispositivo (reconhecemos um cara familiar aqui
\.PHYSICALDRIVE0); - dwIoControlCode – código de operação. Em nosso caso, o valor é
0x70000hque significaIOCTL_DISK_GET_DRIVE_GEOMETRYou 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
0no 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):
- No endereço de
0x0011FCA4hobtemos 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
0x0011FCA8hobtivemos 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)
- quantidade de cilindros –
Agora nos referimos ao procedimento três, sub_40C400:
Dentro do procedimento duas chamadas imediatamente atraíram nossa atenção, especificamenteSetFilePointerEx e WriteFile:
Como de costume, olhamos dentro da pilha de chamadas da função:
- 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:
Tudo bem, para garantir que não perdemos nada interessante, damos uma olhada no painel e entre todas as coisas prestamos atenção na pilha:
Tudo 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:
Como 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)
E eles têm o mesmo destino que os anteriores:
Esse carrossel continua até a verificação do endereço 0x40C865h retornar o valor do EBX registrador igual a 0x100h (255 vezes ):
Para garantir que isso aconteceu, colocamos um ponto de parada na instrução que segue nosso ciclo vicioso de “destruição”:
Como um todo recebemos um 256 de operações de regravação:
Basicamente é isso, nossa “besta” fez seu trabalho sujo e fecha o manipulador de arquivos chamando a CloseHandle função.
Dica da função:
Argumentos da função:
Nossa visão tradicional da preparação da pilha:
E o que temos no registrador ESI?
Como esperado, nosso descritor familiar 0x44h é passado para a pilha como argumento da função.
Vemos os valores retornados pela função no registrador EAX:
Se 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:
Tudo 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:
Para 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:
e 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:
O 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.
Agora 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:
Vamos 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:

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:
Como 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:
NOP nos próximos comandos, independentemente do que contivessem antes (mesmo que vejamos um 0 ali):
Fazemos uma verificação dupla para garantir que os endereços dos comandos estejam agora igualados com base no endereço do comando seguinte:
e 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).
A prova final identificada pelo Process Monitor indica que nenhum disco físico foi danificado em resultado da execução do KillDisk dissecado:
Malware 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

