Eliminando 99% de Código Obsoleto com SSA no Binary Ninja (Flare-On 12)
Descubra como a forma SSA (Static Single Assignment) no Binary Ninja pode ser utilizada para identificar e remover eficientemente código obsoleto, reduzindo drasticamente a complexidade de binários ofuscados.
MundiX News·23 de junho de 2026·7 min de leitura·👁 1 views
Ao enfrentar desafios de engenharia reversa, especialmente em competições como o Flare-On 12, deparamo-nos frequentemente com funções massivamente ofuscadas, repletas de cálculos que obscurecem a lógica principal. No sétimo desafio desta competição, uma função main apresentava mais de 4185 linhas de código descompilado, tornando a análise manual uma tarefa hercúlea. O objetivo deste artigo é detalhar o processo de como reduzir essa complexidade em cerca de 99%, transformando um emaranhado de código em uma função concisa de apenas 35 linhas, utilizando o poder do Binary Ninja e sua representação SSA.
A primeira etapa na análise de uma função ofuscada é tentar identificar a natureza do código obsoleto. Ao observar o grafo de fluxo de controle, percebe-se que ele não é excessivamente complexo, descartando técnicas como control flow flattening. Uma contagem das chamadas de função (current_function.call_sites) revela um número relativamente pequeno, sugerindo que o lixo computacional não reside em chamadas de função desnecessárias, mas sim em cálculos que não afetam diretamente o fluxo de execução ou os resultados das chamadas. A hipótese é que esses cálculos, embora possam modificar variáveis globais, o fazem de maneiras que não impactam a lógica essencial do programa, servindo apenas como um mecanismo de ofuscação. A questão que surge é: por que o descompilador não remove esse código automaticamente? A resposta reside no fato de que, tecnicamente, essas operações podem alterar variáveis globais, mesmo que essas alterações não sejam relevantes para a funcionalidade principal do programa. Para provar essa hipótese, é necessário demonstrar que os parâmetros das chamadas de função dependem de um subconjunto muito pequeno das variáveis da função.
Para realizar essa análise de forma eficaz, ferramentas que abstraiam os registradores e o estado da memória são cruciais. O Medium Level Intermediate Language (MLIL) do Binary Ninja, especialmente em sua forma SSA (Static Single Assignment), oferece exatamente essa capacidade. Na forma SSA, cada variável tem apenas uma definição, o que simplifica enormemente o rastreamento de dependências. O MLIL abstrai registradores e o stack, apresentando variáveis de forma mais limpa, e os argumentos de função são explicitamente definidos. A API do Binary Ninja permite acessar facilmente a definição e os usos de cada variável SSA. Ao aplicar essa abordagem à função main, descobriu-se que apenas uma pequena fração das milhares de variáveis SSA (aproximadamente 0.24%) era realmente importante para as chamadas de função. Isso confirmou a hipótese de que mais de 99% do código era obsoleto.
Com a identificação das variáveis e expressões importantes, o próximo passo é a remoção do código obsoleto. O Binary Ninja permite a criação de fluxos de trabalho (Workflows) personalizados para modificar as representações intermediárias do código. Ao registrar um novo estágio de análise que executa um script Python, é possível manipular o MLIL antes que o código de alto nível (HLIL) seja gerado. Este script, implementado em deobfuscator_logic.py, utiliza a técnica de backward slicing para identificar e remover expressões que não dependem das variáveis consideradas importantes. Um desafio adicional surge em funções com condicionais (MLIL_IF), onde as variáveis de condição podem não ser diretamente rastreáveis. Para resolver isso, um segundo passe, backprop_conds, é introduzido. Ele realiza um backward slicing a partir das variáveis de condição dos ifs, propagando a importância para as variáveis que levam a essas condições, garantindo que a lógica condicional essencial não seja removida. Ao integrar esses passes no fluxo de trabalho do Binary Ninja e configurar a ferramenta para utilizar este novo fluxo, o código ofuscado é efetivamente limpo, resultando em uma descompilação significativamente mais legível e manejável, transformando mais de 4000 linhas de código ofuscado em apenas 35 linhas de lógica clara.
🛡️⚡
Pare de pesquisar. Comece a hackear.
O MundiX é seu copiloto de pentest com IA: comandos exatos, análise de outputs e próximo passo na kill chain — em segundos.
Sem cartão para começar · Planos a partir de R$49/mês
Ao enfrentar desafios de engenharia reversa, especialmente em competições como o Flare-On 12, deparamo-nos frequentemente com funções massivamente ofuscadas, repletas de cálculos que obscurecem a lógica principal. No sétimo desafio desta competição, uma função main apresentava mais de 4185 linhas de código descompilado, tornando a análise manual uma tarefa hercúlea. O objetivo deste artigo é detalhar o processo de como reduzir essa complexidade em cerca de 99%, transformando um emaranhado de código em uma função concisa de apenas 35 linhas, utilizando o poder do Binary Ninja e sua representação SSA.
A primeira etapa na análise de uma função ofuscada é tentar identificar a natureza do código obsoleto. Ao observar o grafo de fluxo de controle, percebe-se que ele não é excessivamente complexo, descartando técnicas como control flow flattening. Uma contagem das chamadas de função (current_function.call_sites) revela um número relativamente pequeno, sugerindo que o lixo computacional não reside em chamadas de função desnecessárias, mas sim em cálculos que não afetam diretamente o fluxo de execução ou os resultados das chamadas. A hipótese é que esses cálculos, embora possam modificar variáveis globais, o fazem de maneiras que não impactam a lógica essencial do programa, servindo apenas como um mecanismo de ofuscação. A questão que surge é: por que o descompilador não remove esse código automaticamente? A resposta reside no fato de que, tecnicamente, essas operações podem alterar variáveis globais, mesmo que essas alterações não sejam relevantes para a funcionalidade principal do programa. Para provar essa hipótese, é necessário demonstrar que os parâmetros das chamadas de função dependem de um subconjunto muito pequeno das variáveis da função.
Para realizar essa análise de forma eficaz, ferramentas que abstraiam os registradores e o estado da memória são cruciais. O Medium Level Intermediate Language (MLIL) do Binary Ninja, especialmente em sua forma SSA (Static Single Assignment), oferece exatamente essa capacidade. Na forma SSA, cada variável tem apenas uma definição, o que simplifica enormemente o rastreamento de dependências. O MLIL abstrai registradores e o stack, apresentando variáveis de forma mais limpa, e os argumentos de função são explicitamente definidos. A API do Binary Ninja permite acessar facilmente a definição e os usos de cada variável SSA. Ao aplicar essa abordagem à função main, descobriu-se que apenas uma pequena fração das milhares de variáveis SSA (aproximadamente 0.24%) era realmente importante para as chamadas de função. Isso confirmou a hipótese de que mais de 99% do código era obsoleto.
Com a identificação das variáveis e expressões importantes, o próximo passo é a remoção do código obsoleto. O Binary Ninja permite a criação de fluxos de trabalho (Workflows) personalizados para modificar as representações intermediárias do código. Ao registrar um novo estágio de análise que executa um script Python, é possível manipular o MLIL antes que o código de alto nível (HLIL) seja gerado. Este script, implementado em deobfuscator_logic.py, utiliza a técnica de backward slicing para identificar e remover expressões que não dependem das variáveis consideradas importantes. Um desafio adicional surge em funções com condicionais (MLIL_IF), onde as variáveis de condição podem não ser diretamente rastreáveis. Para resolver isso, um segundo passe, backprop_conds, é introduzido. Ele realiza um backward slicing a partir das variáveis de condição dos ifs, propagando a importância para as variáveis que levam a essas condições, garantindo que a lógica condicional essencial não seja removida. Ao integrar esses passes no fluxo de trabalho do Binary Ninja e configurar a ferramenta para utilizar este novo fluxo, o código ofuscado é efetivamente limpo, resultando em uma descompilação significativamente mais legível e manejável, transformando mais de 4000 linhas de código ofuscado em apenas 35 linhas de lógica clara.
📤 Compartilhar & Baixar
🧰 Ferramentas recomendadas
Divulgação: alguns links são patrocinados. Podemos receber comissão se você comprar — sem custo extra para você. Só indicamos o que faz sentido para a comunidade.