En oportunidades anteriores hemos hablado sobre técnicas de identificación y el perfilado de un código malicioso. En este caso, profundizamos sobre una técnica utilizada para ofuscar el malware mediante la inserción de lo que se conoce como junk bytes, utilizados para forzar errores en el desensamblado durante el análisis estático.
A la hora de analizar una muestra, a veces es necesario recurrir al análisis estático. Los algoritmos que generalmente se utilizan para el desensamblado son linear sweep y recursive traversal. El primer caso corresponde a un barrido de los opcodes (códigos de operación) donde paso a paso se realiza la traducción a los nemónicos de assembler (instrucciones). En el algoritmo recursive traversal, a diferencia del linear sweep, se respeta el flujo de ejecución.
Linear Sweep
Este algoritmo generalmente presenta dificultades para distinguir datos en la sección .text de un binario. En otras palabras, resulta difícil distinguir si se trata de instrucciones o de datos, dando lugar a la aparición de errores o decodificación incorrecta. De esta forma, es posible, por ejemplo, que interpreten strings (cadenas) como si fueran instrucciones, dando lugar a una codificación inválida.
Recursive traversal
Este algoritmo no presenta las mismas dificultades que el algoritmo lineal debido a que sigue el flujo de ejecución del programa. Por lo tanto, realiza una distinción válida entre los datos y las propias instrucciones del binario. Sin embargo, también posee un problema. Específicamente, este algoritmo es incapaz de seguir el flujo del programa y llegar donde se requiere la ejecución de código de forma indirecta, como por ejemplo, a través de los saltos indirectos.
Conociendo la teoría básica de cómo funciona el proceso de desensamblado, es posible conocer cómo algunos ciberdelincuentes utilizan técnicas de ofuscamiento para dificultar el análisis estático de los códigos maliciosos por parte de los analistas.
Inserción de junk bytes para dificultar el desensamblado con el algoritmo linear sweep
Para poder forzar errores en el proceso de desensamblado, es posible insertar bytes aleatorios y sin un sentido específico en lugares donde el desensamblador espere instrucciones. De esta forma, interpretará los bytes como instrucciones no válidas y dará lugar a la aparición de errores. Asimismo, existen formas diferentes de insertar los bytes de acuerdo al algoritmo que se esté utilizando.
En el caso del linear sweep, generalmente se utiliza una técnica conocida como branch flipping. Este método consiste en transcribir los saltos que cambian el flujo de ejecución mediante la negación de la condición que se evalúa y luego insertar un salto incondicional.
Suponiendo que antes de insertar los bytes se cuenta con un salto condicional tal como se visualiza en el recuadro de la izquierda. Específicamente, si la evaluación de la condición resulta exitosa, se produce un salto a la dirección “address”. Si ahora se reemplaza la instrucción por aquellas en el recuadro de la derecha, se dificultará el desensamblado. Al negar la condición del salto se asegura que el programa jamás va a caer en la sección “garbage” durante la ejecución. De esta forma, se realiza el salto incondicional “jmp” a la dirección “address” tal como se hacía anteriormente.
El flujo de ejecución del programa no ha sido alterado. Sin embargo, el desensamblado resultará con errores o incluso con instrucciones falsas si el desensamblador interpreta los junk bytes como instrucciones. Extrapolando esto a un escenario real, si existe un programa como el siguiente, puede observarse un posible salto condicional que podría modificarse para insertar junk bytes y confundir al desensamblador:
Este tipo de técnicas es bastante utilizada por los ciberdelincuentes para dificultar el análisis por parte de los analistas. De esa manera, es necesario utilizar diferentes tipos de algoritmos de desensamblado para obtener una representación fiel del binario y no siempre confiar en los resultados obtenidos por un único método de desensamblado. En la próxima entrega veremos más sobre la inyección de junk bytes para generar errores utilizando el algoritmo recursive traversal.
Fernando Catoira
Analista de Seguridad