Español | English
rss facebook linkedin Twitter

Desincronización de flujos

La gran mayoría de los packers actuales hacen uso de todo un arsenal de técnicas de ofuscación que van desde las más complejas, capaces de ocultar por completo la estructura de un programa mediante el uso de una VM, hasta las más sencillas, destinadas a dificultar el análisis estático del código.

El grupo de las "sencillas" intenta aprovechar la forma en que están diseñados los desensambladores y como estos realizan el proceso de análisis de código, con el objetivo de generar la mayor confusión posible y que el resultado requiera un tratamiento previo, a menudo tedioso. Afectan tanto a los más simples, "linear sweep disassemblers", como a los más avanzados, "recursive traversal disassemblers". Destacan, quizás por su facilidad de programación y por estar presentes en la casi totalidad de libros de reversing, las denominadas "predicados opacos" y "solapamiento de instrucciones".

Comúnmente el solapamiento de instrucciones consiste en, como su propio nombre indica, la codificación de una o más instrucciones como parte de una o más instrucciones, de modo que todas tienen un offset de inicio distinto y entre ellas comparten algunos o todos los bytes. Así pues, su decodificación genera secuencias de instrucciones distintas en función del offset de inicio seleccionado. Esto es posible en la arquitectura x86 gracias a que las instrucciones no requieren alineación y tienen un tamaño variable.

Aunque a priori pueda parecer algo complicado, es bastante fácil y con un mínimo conocimiento de ensamblador se puede ver rápidamente. En esta presentación de Nick Harbour sobre un packer experimental se pueden encontrar algunos ejemplos gráficos.

Últimamente he tenido que lidiar con esta técnica debido a un problema surgido en un proyecto personal al que suelo dedicar parte de mi tiempo libre. En un punto especifico del desarrollo comprendí que no había tenido en cuenta, no como debería, el solapamiento de instrucciones y como consecuencia, tuve que rediseñar una parte que, en principio, daba por terminada. Podría haberme ahorrado esto si me lo hubiera planteado desde otra perspectiva.

Si consideramos lo que es obvio y es que esta técnica representa una alteración del flujo de ejecución y lo presentamos desde esta perspectiva, tendremos una visión mucho más divertida pero también más compleja.

Cuando usamos un desensamblador, uno profesional como puede ser IDA, el proceso de análisis debe comenzar en un punto de entrada especificado. A medida que se van decodificando instrucciones a partir de dicho punto, se va generando un CFG en el que se reflejan todas las transferencias de control detectadas hasta el momento. Al mismo tiempo, el desensamblador, implícitamente, nos está dibujando un "mapa de labels" en el que están presentes todos aquellos offsets donde comienza una instrucción válida, decodificada y analizada, y a la que se puede transferir el control del flujo.

Siempre que las instrucciones de transferencia de control hagan referencia a estos labels, se puede decir que el flujo principal de ejecución se encuentra sincronizado con el mapa de labels que le corresponde. Por el contrario, si en algún momento surge, se decodifica, una instrucción de transferencia de control que no respeta dicho mapa y transfiere el control de ejecución a sí misma o a otra instrucción en cualquier byte distinto del inicial, se puede decir que se ha producido una "ruptura de instrucción existente" y en consecuencia se ha producido una inmediata desincronización del flujo principal. Así pues, la decodificación continuará en el punto de ruptura y obtendrá una instrucción nueva dentro de otra ya analizada, y distinta, generando una versión alternativa del flujo y un nuevo mapa de labels.

Este "nuevo" flujo alternativo creado puede prolongarse hasta el final del desensamblado o simplemente durar unas cuantas instrucciones. Si en algún punto de la nueva decodificación se coincide con un offset del mapa de labels anterior, se produce una resincronización con el flujo principal y el flujo alternativo se da por terminado. Además, es posible que un flujo alternativo pueda contener a su vez uno o varios puntos de desincronización dando lugar a una maraña de flujos anidados.

La gran ventaja, desde mi punto de vista, de esta técnica es que resulta más fácil generar flujos alternativos, construir y ensamblar, que desensamblarlos y mostrarlos correctamente, por lo que sería posible llevarla al extremo y plagar un programa con puntos de desincronización y resincronización y convertirlo en una auténtica pesadilla. Por el contrario, su gran desventaja radica en la dificultad de prolongar la duración de los flujos alternativos, esto es, en la dificultad de crear diferentes secuencias de código solapadas y válidas.

En definitiva, toda técnica es más compleja de lo que pueda parecer y no se puede descartar ni la más mínima perversión.


Rubén Jiménez
S21sec e-crime

(+34 902 222 521)


24 horas / 7 días a la semana



© Copyright S21sec 2013 - Todos los derechos reservados


login