Siguiendo en la línea de técnicas de análisis de programas, en esta ocasión vamos a hablar sobre una técnica que se encuentra en algún punto intermedio entre las técnicas estáticas y las dinámicas, la ejecución simbólica. Es un tipo de análisis que cubre distintas limitaciones del análisis estático y dinámico y proporciona una solución para hacer frente a la limitada visión semántica del fuzzing, por lo que es utilizado por una enorme cantidad de aplicaciones de seguridad, como por ejemplo en detección de vulnerabilidades, análisis de malware o generación de casos de prueba.
La idea principal detrás de la ejecución simbólica reside en ejecutar el programa objetivo de forma simbólica, es decir, los valores de entrada y las variables se representan como valores simbólicos en lugar de valores concretos. Estos valores se usan para generar condiciones de ruta, que son fórmulas lógicas que representan el estado del programa y las transformaciones entre estados del programa.
Para implementar la ejecución simbólica es necesario un dominio simbólico. Este dominio consiste en un estado simbólico que asigna variables a expresiones simbólicas y una condición de ruta (PC) para cada camino. Inicialmente, el estado simbólico es un mapa vacío y la ejecución simbólica solo mantiene una condición de ruta configurada como verdadero. La ejecución de sentencias condicionales actualizan la fórmula de condición de ruta, mientras que las asignaciones actualizan el estado simbólico. Cuando se alcanza un punto de bifurcación (producido por una sentencia condicional como un if), conceptualmente el intérprete bifurca guardando la condición de ruta sobre la rama que se ha seguido y la inversa como la condición de ruta para la rama que no se ha seguido.
Uno de los principales desafíos a los que se enfrenta la ejecución simbólica es la selección de ruta, puesto que cuando se encuentra una bifurcación debe elegir que camino seguir primero. Esta elección es importante porque los bucles con condiciones simbólicas pueden provocar árboles de profundidad infinita y puede que no terminen nunca. Por lo tanto, el manejo de bucles es un componente integral en la estrategia de selección de ruta. Existen varios enfoques, como por ejemplo:
- Rutas aleatorias: Seleccionar estados aleatoriamente recorriendo el árbol de estados desde el principio.
- Búsqueda en profundidad: Aplicar el algoritmo de búsqueda DFS sobre el árbol de estados.
- Búsqueda en amplitud: Aplicar el algoritmo de búsqueda BFS sobre el árbol de estados.
- Heurísticas: Útiles para objetivos específicos, como por ejemplo calcular un peso para los estados en base a como de lejos está la instrucción más cercana descubierta y seleccionar estados en función de ese peso para maximizar la cobertura de código.
Las técnicas de ejecución simbólica tienen una escalabilidad limitada debido al problema conocido como path explosion: como se crean nuevas rutas en cada rama, el número de rutas en un programa aumenta exponencialmente con el número de sentencias condicionales en cada camino, lo que deriva en un número exponencial de fórmulas con un tamaño exponencial al número de ramas y un tiempo de ejecución exponencial al número de rutas en el programa. Para poder sobrevivir a este fenómeno se deben implementar distintas compensaciones.
Por otra parte, a diferencia del fuzzing, la ejecución simbólica tiene una visión semántica extremadamente alta, es decir, puede razonar sobre cómo desencadenar estados específicos del programa utilizando las condiciones de ruta acumuladas para producir una entrada adecuada a la aplicación cuando una de las rutas ejecutadas ha desencadenado una condición en la que el análisis está interesado.