Las técnicas de ofuscación de código han existido durante décadas, y han evolucionado a medida que la programación y la seguridad informática avanzaron. Su origen se remonta a los primeros días de la programación.

Entre los años 80 y 90, el desarrollo de software comercial y la necesidad de proteger la propiedad intelectual y los algoritmos subyacentes, popularizó estas técnicas de ofuscación en lenguajes como C y lenguaje ensamblador con el fin de dificultar la ingeniería inversa y la comprensión del código fuente por parte de terceros. 

Con el auge de Internet y la distribución de software, estas técnicas se convirtieron en una herramienta común para proteger aplicaciones, especialmente en el ámbito de la seguridad informática y el desarrollo de software propietario.

Aunque la ofuscación de código no proporciona una protección absoluta contra la ingeniería inversa, sí dificulta el análisis y la comprensión del código por parte de personas no autorizadas y desempeña un papel crucial al proporcionar una capa adicional de protección contra intentos maliciosos de explotar vulnerabilidades en el software.

Además, puede desalentar a los piratas informáticos y a competidores deshonestos en sus intentos de copiar o modificar el software protegido.

Como contrapartida, los ciberatacantes utilizan también estas técnicas para robustecer el desarrollo de sus códigos, lo que desafía a los analistas de malware a la hora de examinar código y comprender su funcionamiento.

¿Qué es la Ofuscación de Código?

La ofuscación de código consiste en transformar el código fuente de un programa en una forma más compleja y difícil de comprender, sin alterar su funcionalidad. El objetivo es dificultar la ingeniería inversa y desalentar a los actores malintencionados que buscan descifrar y comprometer el sistema. Hace que sea mucho más difícil para un atacante comprender su lógica y estructura interna.

En el ámbito de la seguridad de aplicaciones, las Metodologías de Desarrollo Seguro de Software deben contemplar distintas técnicas de análisis de seguridad y el análisis estático del código fuente, y una vez fixeados los errores detectados agregar capas de seguridad, entre otras técnicas utilizadas en esta línea sen encuentra la ofuscación al código.

Ejemplos de Técnicas de Ofuscación de Código

1. Ofuscación de Nombres de Variables y Funciones

Una de las técnicas más comunes de ofuscación de código implica cambiar los nombres de variables y funciones a formas crípticas o poco descriptivas, de manera que sigan siendo funcioanles pero resulten difíciles de entender para quien lea el código. Por ejemplo, un nombre de variable "contraseña" podría ofuscarse como "a1b2c3d4" para complicar la comprensión del código.

Veamos un ejemplo sencillo a continuación:

Supongamos que tenemos el siguiente código en C sin ofuscar:

Figura1-var-fx

Ahora, apliquemos ofuscación cambiando nombres de variables y funciones:

Figura2-var-fx

En este ejemplo, los nombres de las variables y la función suma fueron cambiados a nombres genéricos.

La idea es cambiar los nombres de variables, funciones y otros identificadores a nombres más difíciles de entender, como usar nombres aleatorios o sin significado haciendo que sea más difícil para alguien que lee el código entender su propósito sin un análisis más profundo.

Siempre debemos recordemos que la ofuscación no cambia la lógica del programa, solo dificulta la comprensión del mismo.

2. Reordenamiento de Código

Implica cambiar la estructura, sin modificar su funcionalidad. Se trata de cambiar el orden de instrucciones o bloques de código sin alterar la lógica del programa para dificultar la lectura secuencial.

Veamos un ejemplo básico de cómo se podría ofuscar un programa simple en C mediante el reordenamiento de código:

Figura3-rearrange

Ahora, aplicando una técnica de reordenamiento, se podría alterar el orden de las líneas y cambiar el flujo del programa mientras se mantiene la misma funcionalidad:

Figura4-rearrange

En este ejemplo, se ha cambiado el orden de la declaración de variables y la impresión del resultado, lo que dificulta la lectura y comprensión del flujo del programa.

3. Inserción de código inútil

La inserción de código inútil es una técnica de ofuscación que añade líneas de código sin funcionalidad real, redundantes o sin sentido lógico, y por supuesto complica la comprensión del programa sin alterar su lógica.

Supongamos que tenemos un programa simple en C que calcula el cuadrado de un número ingresado por el usuario:

Figura5.Useless-code

Para ofuscar este código utilizando la técnica de inserción de código inútil, agregando líneas adicionales que no afecten la lógica del programa:

Figura6.Useless-code

En este ejemplo, hemos agregado variables y operaciones que no tienen ningún impacto en la lógica del programa.

Estas líneas extras no alteran el cálculo del cuadrado del número ingresado, pero sin dudas hacen que el código sea más difícil de entender para alguien que lo esté revisando.

Una técnica similar es la de inserción de código falso, en la que podemos insertar fragmentos de código falso o sin sentido en el programa para dificultar aún más la comprensión de su lógica. Con esto se logra confundir a los posibles atacantes y llevarlos a realizar suposiciones incorrectas sobre el funcionamiento real del software.

Supongamos que tenemos un programa que simplemente calcula la suma de dos números ingresados por el usuario:

Figura7-false-code

Ahora, podemos aplicar la técnica de inserción de código falso añadiendo líneas adicionales que no alteren el funcionamiento del programa:

Figura-8-false-code

En este ejemplo, hemos agregado un bloque de código que define una variable fakeVar, calcula un resultado falso fakeResult basado en esa variable y muestra estos valores por pantalla. Estas líneas no afectan la funcionalidad del programa original, pero pueden confundir a alguien que esté intentando entender el propósito de esas variables ficticias.

4. Transformación de Estructuras de Control

Esta técnica de ofuscación de código consiste en cambiar la forma en que se organizan y presentan las instrucciones del código, modificando la estructura de las sentencias de control para hacer que el flujo del programa sea menos predecible y dificultar la comprensión de los flujos de ejecución y la lógica subyacente del software.

Veamos un ejemplo simple en C donde se aplica esta técnica:

Figura-9-transf

Para ofuscar este código utilizando la técnica de transformación de estructuras de control, podríamos reescribir las condiciones utilizando operadores ternarios y cambiar el flujo lógico para hacerlo menos evidente:

Figura-10-transf

En este ejemplo, la función funcionPrincipal se reescribe para utilizar un operador ternario en lugar de la estructura if-else. Esto hace que el código sea más compacto y menos legible a primera vista. Sin embargo, debemos tener en cuenta que la ofuscación puede dificultar la comprensión del código para otros desarrolladores, por lo que se debe usar con precaución, especialmente en entornos donde la legibilidad y el mantenimiento del código son importantes.

5. Cifrado y codificación

Veamos un ejemplo básico de cómo podríamos aplicar ofuscación a un programa en C usando técnicas de cifrado y codificación. Usaremos una combinación de cifrado XOR y codificación en base64:

Figura-11-cif.

Esta es solo una demostración básica y la seguridad que proporciona este tipo de ofuscación es limitada. En entornos reales, puede ser más compleja y el código puede ser revertido con esfuerzo suficiente.

Además, para aplicaciones de seguridad, es preferible utilizar métodos estándar y robustos en lugar de crear soluciones de seguridad caseras.

6. Sustitución de constantes por expresiones equivalentes

La técnica de sustitución de constantes por expresiones equivalentes es una forma de complicar el código reemplazando valores directos por expresiones más complejas que resulten en el mismo valor. Veamos un ejemplo sencillo en C:

Supongamos que tenemos un programa con una constante:

Figura-12-eq

Para ofuscar el código usando la técnica de sustitución de constantes por expresiones equivalentes, podríamos reemplazar la constante 5 por una expresión equivalente, como (15 - 10), manteniendo la misma lógica del programa, pero dificultando la lectura directa del valor:

Figura-13-eq.

Esta modificación hace que el programa calcule el mismo resultado (50 en este caso), pero alguien que lea el código tendrá que deducir la relación entre 15 y 10 para entender que representa el mismo valor que la constante 5 en el contexto del cálculo.

7. Eliminación de información redundante

La técnica de eliminación de información redundante puede ser empleada para hacer el código más críptico.

Veamos un ejemplo simple en C, suponiendo que tienes una función simple que suma dos números:

Figura-14-redun.

Ahora, utilizando la técnica de eliminación de información redundante, podemos ofuscar el código así:

Figura-15-redun.

En este ejemplo, hemos reemplazado palabras clave como int, return, printf, main, etc., con letras únicas o abreviaciones utilizando #define.

Además, modificamos la estructura del código para hacerlo menos legible, y en este punto es importante tener en cuenta que la ofuscación excesiva puede hacer que el mantenimiento del código sea extremadamente difícil, por lo que se debe usar con precaución y siempre documentar de manera clara el propósito y funcionamiento del código.

8. Manipulación de estructuras de datos

La manipulación de estructuras de datos es una técnica común para ofuscar el código. Veamos un ejemplo simple en C que utiliza esta técnica para ofuscar una cadena de texto:

Figura-16-manipulacion.

En este ejemplo, la función ofuscar toma una cadena y realiza una manipulación simple de los datos invirtiendo los caracteres. El mensaje original se pasa a esta función para ser ofuscado. Al ejecutar el programa, se verá cómo el mensaje se invierte logrando la ofuscación deseada.,

Las técnicas que describimos aquí son solo algunas de las existentes ya que hay muchas otras técnicas más avanzadas para ofuscar código, como el uso de punteros complicados, renombrado de variables de forma aleatoria, entre otros métodos, que pueden dificultar aún más la comprensión del código.

Más allá de que tu puedes desarrollar tus propios ofuscadores, existe software especializado para lograr tareas de ofuscación personalizables tales como, Proguard, Dotfuscator , FLOSSfuscator , o C++ Obfuscator, por supuesto cual será la mas apropiada dependerá del tipo de software y lenguaje que estes desarrollando.

La ofuscación de Código del lado de los atacantes

Las técnicas de ofuscación sirven como una capa protectora ante el reversing de código fuente, y debemos utilizarlas a la hora de codear nuestro software, pero también los ciberatacantes hacen uso de ellas para no ser detectados y poder infiltrarse en las profundidades de los sistemas víctimas.

Una parte de los cibertacantes se dedican al desarrollo de malware, y por supuesto cuando los analistas quieren examinar una muestra de algún tipo de malware se encuentran con complejas capas de ofuscación. Eso vuelve su trabajo desafiante ya que deben ser expertos en técnicas de reversing; hay códigos maliciosos que están quirúrgicamente diseñados y cuentan en algunos casos con la capacidad de descargar nuevos códigos de distintos repositorios y usar técnicas para pasar desapercibidos por los sistemas de seguridad y los usuarios finales.

En el siguiente ejemplo, que está basado en la campaña Operación Pulpo Rojo: malware dirigido a organismos de alto perfil de Ecuador , se observa que en caso de que el archivo malicioso descargado esté corriendo con privilegios elevados, procede a desofuscar un comando que va a ser ejecutado por medio de la función system de las APIs de Windows. Este comando invoca al interprete PowerShell para ejecutar un código malicioso que está codificado en base64.

Tanto los comandos como muchas cadenas de caracteres que son utilizadas por el archivo malicioso se encuentran ofuscados a través de distintos algoritmos implementados por los cibercriminales. La gran mayoría de estos algoritmos se basan en hacer uso del operador lógico XOR con diferentes claves para cada comando y/o cadenas de caracteres.

En la siguiente captura de pantalla, se puede ver cómo es la rutina utilizada para des ofuscar un comando que luego va a ser ejecutado por medio de la llamada a la función system.

Figura17-pulporojo
Ejemplo de un comando ofuscado donde se le aplica un algoritmo para desofuscarlo y luego ejecutarlo con la función system
Figura18-pulporojo

Ejemplo de una cadena de caracteres ofuscada que utiliza el operador lógico XOR con múltiples claves para des ofuscar su contenido.

Como pudimos observar el código se encuentra ofuscado, luego se des ofusca y se ejecuta., Y en esta línea es que el analista, debe optar por analizarlo de forma dinámica o estática para poder deducir este funcionamiento.

Conclusiones

Sin dudas la ofuscación de código se ha convertido en una táctica vital para fortalecer la seguridad del software y proteger la propiedad intelectual, y la integridad, confidencialidad, y disponibilidad de los datos en el mundo digital.

Aunque no es una medida infalible, su implementación puede disuadir a los atacantes y dificultar sus esfuerzos para comprometer sistemas informáticos sensibles. La comprensión y aplicación adecuadas de las técnicas de ofuscación de código son esenciales para mantener la integridad y la seguridad de los sistemas.