QILING é uma estrutura de código aberto que oferece a possibilidade de emular arquivos binários de diferentes formatos, por exemplo, PE, ELF, COM, entre outros. Ele é muito útil ao analisar uma amostra de malware e compreender melhor uma funcionalidade específica do código malicioso sem recorrer à execução completa da amostra.

Por sua vez, QILING faz uso de outra estrutura conhecida como Unicorn, que é um emulador de instruções de CPU de plataforma cruzada baseado no emulador QEMU. Assim, enquanto a Unicorn cuida de toda a emulação em um nível inferior, QILING cuida da interpretação do sistema operacional ao qual o arquivo a ser emulado pertence, podendo lidar com chamadas API ou chamadas de sistema (syscalls) e gerenciamento de memória, entre outras coisas.

Graças a isto, a estrutura consegue enganar o arquivo binário, fazendo-o acreditar que ele está sendo executado no sistema operacional para o qual foi projetado, quando na verdade não é este o caso e podemos acabar, por exemplo, "executando" um arquivo binário do Windows em um sistema operacional Linux.

Outras características do QILING incluem:

  • Suporte multiplataforma: Windows, MacOS, Linux, BSD, UEFI, DOS, MBR, Ethereum Virtual Machine;
  • Suporte para diferentes arquiteturas: x86, x86_64, ARM, ARM64, MIPS, 8086;
  • Plugin para integração com a ferramenta IDAPro;
  • Emulação de arquivos em um ambiente isolado ou em sandbox;
  • Permite acesso à memória, registros e chamadas API ou syscalls;
  • Permite modificar a memória ou os registros em tempo de execução;
  • Permite a depuração de um arquivo usando a ferramenta GDB;
  • Permite a emulação de código de shellcode.

NOTA: Este emulador não se destina a executar os arquivos rapidamente, mas se destaca pela possibilidade de poder analisá-los enquanto eles estão funcionando.

Finalmente, ao emular um arquivo binário, temos que fornecer ao emulador uma pasta chamada "rootfs", que conterá diferentes bibliotecas ou drivers de acordo com o sistema operacional ao qual o arquivo pertence para poder funcionar. Mais tarde, veremos um exemplo de como utilizar esta estrutura.

Capacidades do QILING ao emular um binário

Manipulando o emulador

Agora que já mencionamos brevemente para que serve esta estrutura e como ela funciona, continuaremos a explicar algumas de todas as ações que ela proporciona ao emular um arquivo binário.

Quando executamos um arquivo binário, ele pode ser executado no todo ou em parte, dando-lhe um endereço inicial e um endereço final. Na captura de tela seguinte, você pode ver como é executada uma execução parcial de um arquivo binário.

Imagem 1. Emulação parcial de um binário.

NOTA: Os endereços de memória podem variar de acordo com o tipo de arquivo que queremos executar.

Por outro lado, podemos configurar o nível de detalhe (verbose) com o qual queremos ver as informações geradas pelo emulador enquanto ele está funcionando, e salvá-las em um arquivo de registro ou simplesmente visualizá-las através do console.

Os níveis de detalhe que a estrutura permite podem ser:

  • OFF - Exibe apenas mensagens de advertência;
  • DEFAULT - Usado por padrão, exibe chamadas API e erros;
  • DEBUG - Exibe mais informações do que "DEFAULT"; por exemplo, os endereços onde as DLLs são carregadas;
  • DISASM - Exibe todas as instruções executadas;
  • DUMP - O modo mais detalhado, capaz de exibir os dumps de registro, entre outras coisas.

Imagem 2. Informações exibidas usando o modo DEFAULT.

Imagem 3. Informações exibidas usando o modo DEBUG.

Outra característica interessante desta estrutura é a possibilidade de se enganchar em diferentes ações, como por exemplo:

  • Código assembler do arquivo binário;
  • Endereços de memória.

Desta forma, podemos dizer ao emulador que quando um determinado endereço de memória é atingido, para invocar e executar uma função que tenhamos desenvolvido. Por outro lado, podemos também dizer a ele para executar uma função para cada linha de código assembler que é executada pelo emulador.

Nas imagens a seguir, você pode ver exemplos de como é possível enganchar as ações mencionadas acima.

Imagem 4. Exemplo de como enganchar um endereço de memória para executar a função "get_deciphered_string".

Imagem 5. Exemplo de como enganchar cada linha de código.

Para os casos específicos de chamadas API ou syscalls, QILING permite fazer um hijack dessas chamadas em momentos diferentes; ou seja, antes, durante ou após a execução da chamada em questão. Desta forma, poderíamos modificar os atributos a serem utilizados pela chamada API ou modificar o código da API para algo desenvolvido por nós.

Na captura de tela seguinte, você pode ver como é possível realizar um hijack de algumas APIs do Windows.

Imagem 6. Exemplo de Hijack em diferentes funções das APIs do Windows.

Finalmente, mencionaremos outras ações que poderiam ser realizadas com este este framework:

  • Manipulação da stack:
    • Carregar um valor: ql.stack_push(VALOR)
    • Ler: ql.stack_read(OFFSET)
  • Manipulação de registros:
    • Leitura: ql.reg.read(REGISTRO)
    • Escrita: ql.reg.write(REGISTRO, VALOR)
  • Manipulação da memória:
    • Leitura: ql.mem.read(DIRECCION, TAMAÑO)
    • Escrita: ql.mem.write(DIRECCION, VALOR)
    • Busca de bytes: ql.mem.search(BYTES)

Exemplo

Abaixo, compartilhamos um exemplo de um caso em que o QILING pode ser útil para análise de malware.

Para isso, usaremos uma amostra do Conti ransomware com o hash 8FBC27B26C4C4C4C582B5764EACF897A89FE74C0A88D, para o sistema operacional Windows.

Esta amostra tem diferentes strings que o malware usa como “blacklist”, para detectar aquelas pastas cujo conteúdo não será criptografado. Por exemplo, ele não criptografará o conteúdo da pasta "Windows".

Como estas strings são criptografadas dentro da amostra, nosso objetivo é emular a rotina de descodificação para obter os nomes destas pastas.

A imagem a seguir mostra um exemplo do carregamento de bytes que representam uma string criptografada e a chamada para uma função encarregada de decodificá-los.

Imagem 7. Carga de bytes e chamada para uma função de desencriptação. O resultado é armazenado no registro EAX.

Imagem 8. lógica usada na função de desencriptação.

Para emular esta lógica, precisamos primeiro saber o endereço de memória onde a função encarregada de chamar a função de decodificação começa (este seria o endereço de entrada para o emulador). Neste caso, o endereço é 0x411AF0. Por outro lado, precisamos de um endereço para dizer ao emulador onde ele deve terminar. Olhando a Imagem 7, vemos que podemos usar o endereço 0x411B79.

Finalmente, como o resultado da função de decodificação é armazenado no registro EAX e então esse valor é carregado na memória, vamos enganchar o endereço de memória 0x411B73, que está encarregado de executar essa instrução.

Tendo obtido estes endereços de memória, procedemos a escrever um script que emula o malware usando estes endereços e exibe o resultado na tela.

A imagem a seguir mostra como seria o código do script.

Imagem 9. código de script para imitar o malware via QILING.

Imagem 10. Resultado da execução.

Embora o script mostrado na Imagem 9 seja usado para decodificar uma determinada cadeia, esta amostra contém outras cadeias que usam uma lógica similar à vista na Imagem 7.

Portanto, se pararmos para olhar o código na imagem, vemos que após chamar a função que decodifica a string, o resultado é carregado na memória executando a instrução "mov [ebp+var_158],eax". Se olharmos a Imagem 11, vemos que esta instrução é repetida após cada chamada para as funções que descodificam as strings, modificando o offset onde o resultado tem que ser armazenado para não interferir com outras.

Imagem 11. armazenamento em memória de diferentes strings decodificadas.

Portanto, podemos modificar o script na Imagem 9 para emular o malware até que ele termine de carregar todas as strings decodificadas, e em vez de nos prender a um endereço de memória, desta vez leremos cada instrução que for executada pelo malware e quando virmos algo como "mov [ebp+var_158],eax" imprimimos o valor para a tela.

Imagem 12. Adaptamos o roteiro para detectar quando as strings foram decodificadas e imprimir o valor.

Imagem 13. Resultado da execução.

Como podemos ver na imagem anterior, obtivemos todas as strings que representam as pastas cujo conteúdo não será criptografado por esta amostra Conti.

Por outro lado, é importante lembrar que esta amostra é para um sistema operacional Windows e a emulação foi realizada em um sistema operacional Linux.

Conclusão

QILING é um framework muito poderoso e versátil que nos dá a oportunidade de executar diferentes binários ou shellcodes  independentemente do sistema operacional que utilizamos.

Devido à possibilidade de fazer diferentes tipos de modificações dinamicamente, tais como remendar um arquivo binário enquanto ele está sendo emulado, ou o fato de poder modificar chamadas para a API do Windows, entre outras coisas, é muito prático e interessante usar QILING para ver o comportamento do binário, seja ele um malware ou outros.

Por outro lado, ter a possibilidade de emular uma seção de um binário por meio de uma execução parcial é muito prático para casos em que se está analisando uma amostra de malware e se deseja apenas ter um melhor conhecimento de uma funcionalidade específica sem recorrer à execução completa da amostra. Ou mesmo para automatizar a análise dependendo de quanto conhecimento você tem sobre um determinado tipo de malware.

Veja mais: