O Stadeo é um conjunto de ferramentas desenvolvidas principalmente para facilitar a análise do Stantinko, uma botnet que é responsável por golpes de cliques, injeção de anúncios, fraudes em redes sociais, ataques de roubo de senhas e criptomineração.

Esse conjunto de ferramentas foi apresentado pela primeira vez no Black Hat USA 2020 e posteriormente publicado para uso gratuito.

Os scripts, escritos inteiramente em Python, usam técnicas de ofuscação de strings e achatamento de fluxo de controle (CFF) do Stantinko descritos em nossa postagem de março de 2020. Além disso, eles podem ser usados ​​para outros fins: por exemplo, já estendemos nossa abordagem para oferecer suporte à desofuscação do CFF apresentado no Emotet - um trojan que rouba credenciais bancárias e baixa payloads adicionais, como ransomware.

Nossos métodos de desofuscação usam IDA, que é uma ferramenta padrão da indústria de segurança, e Miasm, uma estrutura de código aberto que nos fornece várias análises de fluxo de dados, um mecanismo de execução simbólica, um mecanismo de execução simbólica dinâmica e os meios para remontar funções modificadas.

Você pode encontrar mais informações sobre o Stadeo aqui.

Exemplos de como usar

Para trabalhar com o Stadeo, primeiro precisamos configurar um servidor RPyC (Remote Python Call) dentro do IDA, que nos permite acessar a API IDA a partir de um interpretador Python arbitrário. Você pode usar este script para abrir um servidor RPyC no IDA.

Em todos os exemplos a seguir, configuramos um servidor RPyC que escuta em 10.1.40.164:4455 (sendo 4455 a porta padrão) e então nos comunicamos com o servidor a partir de um console Python.

Usaremos duas classes do Stadeo:

  • CFFStrategies para a desofuscação CFF
  • StringRevealer para a desofuscação de strings

Ambas as classes podem ser inicializadas para arquitetura de 32 bits e 64 bits.

Desofuscando apenas uma função

SHA-1 da amostra: 791ad58d9bb66ea08465aad4ea968656c81d0b8e

O código a seguir desofusca a função em 0x1800158B0 com o parâmetro no registro R9 definido como 0x567C e grava sua versão desofuscada no endereço 0x18008D000.

O parâmetro R9 é uma variável de controle para a fusão de funções do loop CFF (variável de fusão) e deve ser especificado usando expressões Miasm, que estão documentadas aqui; observe que é necessário usar RSP_init/ESP_init em vez de RSP/ESP para se referir aos parâmetros da pilha. Por exemplo, usaríamos ExprMem (ExprId (“ESP_init”, 32) + ExprInt (4, 32), 32) para apontar para o primeiro parâmetro de pilha em uma arquitetura de 32 bits.

from stadeo.cff.cff_strategies import CFFStrategies
from miasm.expression.expression import ExprId, ExprInt

cs = CFFStrategies(64)
cs.solve_loop(0x1800158B0,
              0x18008D000,
              context={ExprId("R9", 64): ExprInt(0x567c, 64)},
              ip='10.1.40.164')

Figura 1. Chamada para a função ofuscada.

Figura 2. CFG ofuscado (esquerda) e desofuscado (direita).

Processamento de funções acessíveis

SHA-1 da amostra: e0087a763929dee998deebbcfa707273380f05ca

O código a seguir reconhece apenas funções ofuscadas acessíveis em 0x1002DC50 e procura candidatos para mesclar variáveis. Funções reconhecidas e desofuscadas com sucesso começam em 0x10098000.

from stadeo.cff.cff_strategies import CFFStrategies
strat = CFFStrategies(32)
res = strat.process_merging(0x1002DC50,
                            0x10098000,
                            ip="10.1.40.164")

 

Saída parcial:

skipping 0x1002dc50 with val None: 0xbadf00d
mapping: 0x1001ffc0 -> 0x10098000 with val @32[ESP_init + 0x8]: 0x6cef
skipping 0x100010f0 with val None: 0xbadf00d
mapping: 0x1000f0c0 -> 0x100982b7 with val @32[ESP_init + 0x8]: 0x2012
mapping: 0x1003f410 -> 0x10098c8a with val @32[ESP_init + 0x4]: 0x21a4
mapping: 0x1003f410 -> 0x10098f3d with val @32[ESP_init + 0x4]: 0x772a
skipping 0x1004ee79 (library func)
...

As funções mapeadas foram desofuscadas corretamente e as funções ignoradas não foram consideradas ofuscadas. O formato das linhas de mapeamento na saída é:

mapping: %obfuscated_function_address% -> %deobfuscated_function_address% with val %merging_variable%: % merging_variable_value%

O valor padrão de merging_variable_value é 0x0BADF00D. As linhas de salto seguem o mesmo padrão, mas não há uma unobstructed_function_address. As funções de biblioteca reconhecidas pela IDA são simplesmente ignoradas sem processamento adicional.

Processando todas as funções

SHA-1 da amostra: e575f01d3df0b38fc9dc7549e6e762936b9cc3c3

Usamos o código a seguir apenas para lidar com o CFF apresentado no Emotet, cuja implementação do CFF se encaixa na descrição de achatamento do fluxo de controle aqui.

Preferimos essa abordagem porque o método CFFStrategies.process_all () não tenta reconhecer variáveis ​​de fusão que não estão presentes no Emotet e procura apenas um loop CFF por função; por isso é mais eficiente.

As funções reconhecidas e desofuscadas com sucesso são gravadas sequencialmente a partir de 0x0040B000. O formato de saída é o mesmo do método process_merging usado no exemplo de processamento de funções acessíveis, mas é claro que não haverá nenhuma variável ​​fusionada.

from stadeo.cff.cff_strategies import CFFStrategies
strat = CFFStrategies(32)
res = strat.process_all(0x0040b000,
                        ip="10.1.40.164")

 

Saída parcial:

mapping: 0x00401020 -> 0x0040b000 with val None: 0xbadf00d
skipping 0x00401670 with val None: 0xbadf00d
mapping: 0x00401730 -> 0x0040b656 with val None: 0xbadf00d
...

Figura 3. Exemplo de função ofuscada (esquerda) e desofuscada (direita) no Emotet.

Redirecionando referências para funções desofuscadas

O Stadeo não atualiza automaticamente as referências de recursos após a desofuscação. No exemplo abaixo, demonstramos como parchear uma chamada de função na Figura 1 a partir do exemplo Unfocusing a single function. A referência corrigida é mostrada na Figura 4.

Usamos o código a seguir para parchear chamadas, cujo quarto parâmetro é 0x567C, para a função ofuscada em 0x1800158B0 com aquela desofuscada em 0x18008D000. Tenha em conta que é necessário garantir que o IDA reconheça corretamente os parâmetros da função e possivelmente corrigi-los.

from stadeo.utils.xref_patcher import patch_xrefs
patch_xrefs(0x1800158B0,
            0x18008D000,
            {3: 0x567c},
            ip='10.1.40.164')

Figura 4. Referência parcheada.

Revelando strings ofuscadas em uma função

A função StringRevealer.process_funcs() revela strings ofuscadas na função especificada e devolve um mapa das strings desofuscadas e seus endereços.

Tenha em conta que o fluxo de controle da função de destino já deve ter sido desofuscado.

No exemplo a seguir, desofuscamos as strings da função em 0x100982B7, apresentadas na Figura 5. A função em si foi desofuscada no exemplo anterior de Processamento de funções alcançáveis.

from stadeo.string.string_revealer import StringRevealer
sr = StringRevealer(32)
strings = sr.process_funcs([0x100982B7],
                           ip="10.1.40.164")

Figura 5. Parte da função em 0x100982B7 que claramente monta uma string.

O conteúdo da variável strings após a execução é:

{0x100982B7: {'SeLockMemoryPrivilege'}}

Esperamos que as ferramentas do Stadeo e este tutorial de uso possam ser úteis. Se você tiver alguma dúvida, entre em contato conosco através do e-mail threatintel[at]eset.com ou abra um problema em https://github.com/eset/stadeo.