As técnicas de ofuscação de código existem há décadas e evoluíram com o avanço da programação e da segurança digital. Elas remontam aos primórdios da programação.

Entre as décadas de 1980 e 1990, o desenvolvimento de software comercial e a necessidade de proteger a propriedade intelectual e os algoritmos subjacentes popularizaram essas técnicas de ofuscação em linguagens como C e linguagem assembly, a fim de dificultar a engenharia reversa e a compreensão do código-fonte por terceiros.

Com o surgimento da Internet e da distribuição de software, essas técnicas se tornaram uma ferramenta comum para proteger aplicativos, especialmente no campo da cibersegurança e do desenvolvimento de software proprietário.

Embora a ofuscação de código não ofereça proteção absoluta contra a engenharia reversa, ela dificulta a análise e o entendimento do código por pessoas não autorizadas e desempenha um papel fundamental no fornecimento de uma camada adicional de proteção contra tentativas mal-intencionadas de explorar vulnerabilidades no software.

Além disso, ela pode desestimular cibercriminosos e concorrentes desonestos a tentar copiar ou modificar o software protegido.

Por outro lado, os criminosos também usam essas técnicas para fortalecer o desenvolvimento do código, o que torna difícil para os analistas de malware examiná-lo e entender como ele funciona.

O que é ofuscação de código?

Ofuscação de código é a transformação do código-fonte de um programa em uma forma mais complexa e difícil de entender, sem alterar sua funcionalidade. O objetivo é tornar a engenharia reversa mais difícil e desestimular cibercriminosos que buscam invadir e comprometer um sistema. Isso torna muito mais difícil para um invasor entender sua lógica e estrutura internas.

No campo da segurança de aplicativos, as metodologias de desenvolvimento de software seguro devem contemplar diferentes técnicas de análise de segurança e análise estática do código-fonte e, depois que os erros detectados forem corrigidos, adicionar camadas de segurança.

Exemplos de técnicas de ofuscação de código

1. Ofuscação de nomes de variáveis e funções

Uma das técnicas mais comuns de ofuscação de código envolve a alteração dos nomes de variáveis e funções para formas crípticas ou não descritivas, de modo que elas permaneçam funcionais, mas sejam difíceis de entender para quem estiver lendo o código. Por exemplo, o nome de uma variável "senha" pode ser ofuscado como "a1b2c3d4" para tornar o código mais difícil de entender.

Vamos dar uma olhada em um exemplo simples abaixo:

Suponha que você tenha o seguinte código em C sem ofuscação:

Figura1-var-fx

Agora, vamos aplicar a ofuscação alterando os nomes das variáveis e das funções:

Figura2-var-fx

Neste exemplo, os nomes das variáveis e a função de soma foram alterados para nomes genéricos.

A ideia é alterar os nomes de variáveis, funções e outros identificadores para nomes mais difíceis de entender, como o uso de nomes aleatórios ou sem sentido, tornando mais difícil para alguém que esteja lendo o código entender sua finalidade sem uma análise mais aprofundada.

Devemos sempre lembrar que a ofuscação não altera a lógica do programa, apenas o torna mais difícil de entender.

2. Reordenação de código

Envolve a alteração da estrutura, sem alterar sua funcionalidade. Trata-se de mudar a ordem das instruções ou dos blocos de código sem alterar a lógica do programa para dificultar a leitura sequencial.

Vejamos um exemplo básico de como um programa C simples pode ser ofuscado pela reordenação de código:

Figura3-rearrange

Agora, ao aplicar uma técnica de reordenação, você pode alterar a ordem das linhas e mudar o fluxo do programa, mantendo a mesma funcionalidade:

Figura4-rearrange

Neste exemplo, a ordem da declaração de variáveis e a impressão do resultado foram alteradas, dificultando a leitura e a compreensão do fluxo do programa.

3. Inserção de código inútil

A inserção de código inútil é uma técnica de ofuscação que adiciona linhas de código sem funcionalidade real, redundantes ou sem sentido lógico e, é claro, complica a compreensão do programa sem alterar sua lógica.

Suponha que tenhamos um programa simples em C que calcula o quadrado de um número inserido pelo usuário:

Figura5.Useless-code

Para ofuscar esse código, você pode usar a técnica de inserção de código inútil, acrescentando linhas adicionais que não afetam a lógica do programa:

Figura6.Useless-code

Neste exemplo, adicionamos variáveis e operações que não têm impacto sobre a lógica do programa.

Essas linhas extras não alteram o cálculo do quadrado do número inserido, mas certamente tornam o código mais difícil de ser entendido por alguém que o esteja revisando.

Uma técnica semelhante é a inserção de código falso, em que podemos inserir fragmentos de código falsos ou sem sentido no programa para dificultar ainda mais a compreensão da lógica do programa. Isso confunde os possíveis invasores e os leva a fazer suposições incorretas sobre como o software realmente funciona.

Suponha que tenhamos um programa que simplesmente calcula a soma de dois números inseridos pelo usuário:

Figura7-false-code

Agora, podemos aplicar a técnica de inserção de código falso acrescentando linhas adicionais que não alteram a operação do programa:

Figura-8-false-code

Neste exemplo, adicionamos um bloco de código que define uma variável fakeVar, calcula um fakeResult com base nessa variável e exibe esses valores na tela. Essas linhas não afetam a funcionalidade do programa original, mas podem confundir alguém que esteja tentando entender a finalidade dessas variáveis fictícias.

4. Transformação da estrutura de controle

Essa técnica de ofuscação de código envolve a alteração da maneira como as instruções do código são organizadas e apresentadas, modificando a estrutura das instruções de controle para tornar o fluxo do programa menos previsível e dificultar a compreensão dos fluxos de execução e da lógica subjacente do software.

Vejamos um exemplo simples em C em que essa técnica é aplicada:

Figura-9-transf

Para ofuscar esse código usando a técnica de transformação da estrutura de controle, poderíamos reescrever as condições usando operadores ternários e alterar o fluxo lógico para torná-lo menos óbvio:

Figura-10-transf

Neste exemplo, a função funcionPrincipal é reescrita para usar um operador ternário em vez da estrutura if-else. Isso torna o código mais compacto e menos legível à primeira vista. No entanto, devemos observar que a ofuscação pode tornar o código mais difícil de ser entendido por outros desenvolvedores, portanto, deve ser usada com cautela, especialmente em ambientes em que a legibilidade e a manutenção do código são importantes.

5. Criptografia e codificação

Vejamos um exemplo básico de como podemos aplicar ofuscação a um programa em C usando técnicas de criptografia e codificação. Usaremos uma combinação de criptografia XOR e criptografia base64:

Figura1-var-fx

Esta é apenas uma demonstração básica e a segurança fornecida por esse tipo de ofuscação é limitada. Em ambientes reais, ela pode ser mais complexa e o código pode ser revertido com bastante esforço.

Além disso, para aplicativos de segurança, é preferível usar métodos padrão e robustos em vez de criar soluções de segurança caseiras.

6. Substituição de constantes por expressões equivalentes

A técnica de substituição de constantes por expressões equivalentes é uma forma de complicar o código, substituindo valores simples por expressões mais complexas que resultam no mesmo valor. Vamos dar uma olhada em um exemplo simples em C:

Suponhamos que você tenha um programa com uma constante:

Figura-12-eq

Para ofuscar o código usando a técnica de substituição de constantes por expressões equivalentes, poderíamos substituir a constante 5 por uma expressão equivalente, como (15 - 10), mantendo a mesma lógica do programa, mas dificultando a leitura direta do valor:

Figura-13-eq.

Essa modificação faz com que o programa calcule o mesmo resultado (50, nesse caso), mas quem estiver lendo o código terá de deduzir a relação entre 15 e 10 para entender que ele representa o mesmo valor que a constante 5 no contexto do cálculo.

7. Eliminação de informações redundantes

A técnica de eliminação de informações redundantes pode ser usada para tornar o código mais enigmático.

Vejamos um exemplo simples em C, supondo que você tenha uma função simples que soma dois números:

Figura-14-redun.

Agora, usando a técnica de eliminação de informações redundantes, podemos ofuscar o código da seguinte forma:

Figura-15-redun.

Neste exemplo, substituímos palavras-chave como int, return, printf, main etc. por letras simples ou abreviações usando #define.

Além disso, modificamos a estrutura do código para torná-lo menos legível e, nesse ponto, é importante observar que a ofuscação excessiva pode tornar a manutenção do código extremamente difícil; portanto, use-a com cautela e sempre documente claramente a finalidade e a operação do código.

8. Manipulação de estruturas de dados

A manipulação da estrutura de dados é uma técnica comum para ofuscar o código. Vamos dar uma olhada em um exemplo simples em C que usa essa técnica para ofuscar uma cadeia de texto:

Figura-16-manipulacion.

Nesse exemplo, a função ofuscar pega uma cadeia de caracteres e executa uma manipulação simples dos dados invertendo os caracteres. A mensagem original é passada para essa função para ser ofuscada. Ao executar o programa, você verá como a mensagem é invertida e a ofuscação desejada é obtida.

As técnicas descritas aqui são apenas algumas das existentes, pois há muitas outras técnicas mais avançadas para ofuscar o código, como o uso de ponteiros complicados, renomeação de variáveis aleatórias, entre outros métodos, que podem tornar o código ainda mais difícil de entender.

Além do fato de que você pode desenvolver seus próprios ofuscadores, há softwares especializados para tarefas de ofuscação personalizáveis, como Proguard, Dotfuscator, FLOSSfuscator ou C++ Obfuscator. É claro que o mais adequado depende do tipo de software e da linguagem que você está desenvolvendo.

Ofuscação de código por cibercriminosos

As técnicas de ofuscação servem como uma camada protetora contra a reversão do código-fonte, e devemos usá-las ao codificar nosso software, mas os cibercriminosos também fazem uso delas para não serem detectados e se infiltrarem profundamente nos sistemas das vítimas.

Alguns cibercriminosos estão envolvidos no desenvolvimento de malware e, é claro, quando os analistas querem examinar uma amostra de algum malware, eles encontram camadas complexas de ofuscação. Isso torna o trabalho deles desafiador, pois precisam ser especialistas em técnicas de reversão; alguns códigos maliciosos são projetados cirurgicamente e, em alguns casos, têm a capacidade de fazer download de novos códigos de diferentes repositórios e usar técnicas para passar despercebidos pelos sistemas de segurança e pelos usuários finais.

No exemplo a seguir, que se baseia na campanha Operação Red Octopus: malware direcionado a organizações de alto nível no Equador, você pode ver que, caso o arquivo mal-intencionado baixado esteja sendo executado com privilégios elevados, ele continua a desfundir um comando a ser executado por meio da função de sistema das APIs do Windows. Esse comando invoca o interpretador do PowerShell para executar um código malicioso codificado em base64.

Tanto os comandos quanto muitas cadeias de caracteres usadas pelo arquivo malicioso são ofuscados por meio de diferentes algoritmos implementados pelos cibercriminosos. A grande maioria desses algoritmos baseia-se no uso do operador lógico XOR com chaves diferentes para cada comando e/ou cadeia de caracteres.

Na captura de tela a seguir, você pode ver como é a rotina usada para ofuscar um comando que é então executado chamando a função do sistema.

Figura17-pulporojo
Exemplo de um comando ofuscado em que um algoritmo é aplicado para ofuscá-lo e, em seguida, executá-lo com a função do sistema.
Figura18-pulporojo
Exemplo de uma cadeia de caracteres ofuscada usando o operador lógico XOR com várias chaves para desofuscar seu conteúdo.

Como podemos ver, o código é ofuscado, depois desofuscado e executado, e é nesse sentido que o analista deve optar por analisá-lo de forma dinâmica ou estática para poder deduzir essa operação.

Conclusões

A ofuscação de código tornou-se, sem dúvida, uma tática vital para fortalecer a segurança de software e proteger a propriedade intelectual e a integridade, a confidencialidade e a disponibilidade dos dados no mundo digital.

Embora não seja uma medida infalível, sua implementação pode impedir os invasores e dificultar seus esforços para comprometer sistemas de computador confidenciais. A compreensão e a aplicação adequadas das técnicas de ofuscação de código são essenciais para manter a integridade e a segurança dos sistemas.