Como pesquisador de malware , você frequentemente se depara com amostras complexas e fortemente ofuscadas. E o GuLoader, o malware com o qual estamos lidando hoje, é um exemplo clássico.
Dê uma olhada neste pseudocódigo, produzido pela descompilação de seu código assembly — é feio e ilegível.
Diante de um quebra-cabeça como esse, você pode ficar perdido. Por onde você deveria começar? Como você lida com a análise dessa amostra? Vamos decompô-lo.
Neste artigo, então, exploraremos estratégias para desofuscar esse código, usando o GuLoader como referência. Você aprenderá sobre:
O artigo é baseado na análise de malware GuLoader publicada anteriormente pela ANY.RUN. Visite nosso blog para encontrar a amostra que analisaremos, bem como as instruções de descompactação e um script Ghidra que automatiza parcialmente muito do que abordaremos a partir deste ponto.
Este artigo se concentra na análise estática. Mas se você quiser analisar uma amostra do GuLoader dinamicamente, você pode usar a caixa de proteção de malware em nuvem ANY.RUN .
Inscreva-se para uma avaliação gratuita de 14 dias do plano Enterprise usando seu e-mail comercial. Aproveite a vida útil estendida de instâncias de VM, tarefas ilimitadas e versões do Windows 7 a 11.
A análise dinâmica permite que você veja como o malware funciona no mundo real, vinculando seu comportamento à sua configuração técnica. É uma maneira de verificar suas ações em diferentes configurações do sistema e coletar IOCs.
Sem demora, vamos mergulhar.
Depois de descompactar a amostra, começamos com uma análise manual do shellcode e percebemos rapidamente que ele está ofuscado. Estudar o código nos permite agrupar as técnicas de ofuscação que o GuLoader emprega em categorias:
Este é um bom momento para parar, analisar os métodos de ofuscação usados e desenvolver uma estratégia de desofuscação.
Por exemplo, em nosso caso, a maioria dessas técnicas introduz código que não altera o resultado final da execução. Portanto, muitas vezes podemos "NOP" com segurança para melhorar a legibilidade. Mas prossiga com cuidado — nem todo código ofuscado é irrelevante para a operação do programa, como logo descobriremos.
Agora, vamos examinar essas técnicas de ofuscação individualmente e ver como derrotá-las.
O código está repleto de muitas instruções XMM. Estes aparecem desordenados e adicionam complexidade ao processo de análise. Nós os encontramos desde o primeiro byte do shellcode desempacotado.
Observe que muitos mecanismos de emulação tropeçam neles devido à falta de suporte padrão. Colocamos os motores embutidos de Angr, Triton e Ghidra em teste - todos ficaram aquém.
No caso do GuLoader, as instruções XMM não afetam o comportamento pretendido do código. Você encontrará métodos de ofuscação semelhantes em muitos malwares. Consequentemente, podemos substituir com segurança todas as instruções XMM por "NOP", conforme mostrado na tabela a seguir:
Veja como fica o resultado no desmontador Ghidra:
As instruções JMP incondicionais segmentam o código em partes menores. Esse método é frequentemente usado para evitar a detecção por software antivírus e outras ferramentas de segurança. Além disso, pode tornar o trabalho dos analistas mais demorado e frustrante, pois eles devem pular entre esses blocos, especialmente ao lidar com uma grande quantidade de código. O GuLoader e outros malwares geralmente empregam essa técnica.
Este método de ofuscação é bastante fácil de derrotar. O desmontador no código descompilado geralmente concatena com sucesso esses blocos, melhorando a legibilidade do código, mesmo com esses saltos incondicionais presentes. Dessa forma, podemos deixar os pequenos blocos como estão sem a necessidade de mesclá-los.
Instruções de montagem inúteis geralmente entram em jogo como uma camada extra de ofuscação. Essas instruções não executam nenhuma função tangível, normalmente deixando os valores de registro, fluxo de execução ou memória inalterados.
Você também os encontrará no GuLoader.
Procure instruções que não executam nenhuma ação ("NOP", "FNOP") e aquelas que mudam ou giram em zero bits ("SHL reg, 0"; "ROL reg, 0"). Outras instruções não impactantes como "OR reg, 0", "XOR reg, 0", "CLD", "WAIT" também estão presentes.
Lidar com instruções de comparação falsas é mais desafiador do que apenas substituir as inúteis por "NOP". Não podemos remover todas as instruções de comparação, pois algumas são necessárias para o funcionamento correto do código. Uma maneira de lidar com isso é “marcar” todas as instruções de comparação que encontramos. Se não forem encontradas instruções que usem o resultado da comparação, é seguro fazer um NOP. Se encontrarmos um salto condicional ou similar, desmarcamos a comparação para evitar a remoção.
A tabela a seguir mostra um exemplo em que todas as instruções de comparação, exceto "CMP EDX,0x0", foram substituídas seletivamente por NOP:
O GuLoader também emprega a tática de ofuscação de usar instruções "PUSHAD" falsas, juntamente com uma instrução "POPAD" correspondente. Eles podem modificar temporariamente os valores do registro, mas são anulados pelo "POPAD" restaurando os valores originais do registro.
Nossa pesquisa mostrou que todas as instruções "PUSHAD" no GuLoader são estranhas. Portanto, resolvemos isso substituindo "PUSHAD", "POPAD" e as instruções intermediárias por NOP:
No entanto, nem todas as instruções "POPAD" no GuLoader são inúteis. Deixamos intocados aqueles sem um "PUSHAD" correspondente.
Outra técnica de ofuscação semelhante à anterior é o uso de falsas instruções "PUSH". Essas instruções colocam um valor na pilha apenas para removê-lo imediatamente.
Um exemplo é a inclusão de uma instrução "PUSH SS", possivelmente seguida por instruções que modificam um registrador ou local de memória. No entanto, o "POP SS" subsequente restaura o ponteiro da pilha ao seu valor inicial.
Derrotar instruções PUSH falsas se assemelha ao processo para PUSHAD falso, mas é crucial deixar os registradores não pressionados inalterados.
Predicados opacos são declarações condicionais que sempre retornam verdadeiro ou falso, mas são difíceis de analisar ou prever. Estes são encontrados no código do GuLoader e complicam o entendimento da lógica.
Por exemplo, um par de instruções como “MOV BL, 0xB6” e “CMP BL, 0xB6” pode ser seguido por um salto condicional como “JNZ ADDR”. A comparação sempre retorna false, pois o valor comparado é igual ao valor movido, tornando o salto desnecessário e desconcertante.
Superar predicados opacos pode parecer desafiador devido à exigência de "prever" a condição de salto. No entanto, nossa situação é mais direta, pois todos os predicados opacos se enquadram nos blocos "PUSHAD" e "POPAD". Portanto, simplesmente substituímos todos os predicados entre essas instruções por NOP.
Expressões aritméticas ofuscadas estão entre as técnicas mais interessantes usadas pelo GuLoader. Eles tornam mais difícil entender as operações reais executadas. Essas expressões incorporam ações aritméticas como adição ou subtração. Às vezes, eles são misturados com outras ofuscações, como comparações falsas, predicados opacos e instruções inúteis.
Um exemplo é mover um valor constante para um registrador e executar operações aritméticas:
Outra instância é colocar um valor constante na pilha e realizar cálculos na memória:
Para desofuscar as expressões aritméticas do GuLoader, adotamos uma abordagem semelhante à manipulação de comparações falsas. Marcamos todas as instruções "MOV" em que o segundo argumento é um valor escalar e todas as instruções "PUSH" em que o argumento é escalar. Ao encontrar uma operação aritmética, atualizamos o valor constante na primeira instrução e substituímos o atual por NOP. Dessa forma, a primeira instrução tem o resultado e as instruções aritméticas subsequentes são substituídas por NOP.
Abaixo estão exemplos com operações “MOV” e “PUSH” otimizadas:
Seja cauteloso com os tamanhos dos operandos. Preservar o tamanho correto durante as operações é crucial.
Neste artigo de especialistas do ANY.RUN , usamos o GuLoader como um exemplo do mundo real para destacar as estratégias típicas de desofuscação. Começamos analisando o código, depois agrupamos táticas de ofuscação semelhantes e, finalmente, pensamos em maneiras únicas de lidar com cada uma delas.
Mas é crucial entender que essas técnicas não são uma solução única para todos. Cada amostra de malware pode apresentar suas estratégias de ofuscação exclusivas que precisam de contramedidas exclusivas.
Nossa discussão destaca a importância de dividir uma tarefa complexa como a desofuscação em etapas claras e gerenciáveis. Lembre-se de que a desofuscação bem-sucedida de qualquer malware envolve análise detalhada, detecção de padrões de ofuscação e criação de estratégias otimizadas.