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')
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
...
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')
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")
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.