analizador de vulnerabilidades mediante análisis dinámico
TRANSCRIPT
Agradecimientos
Ha sido un ano interesante, y destacara en mi vida por algo: las pocas horas que he dormido. La
conciliacion del trabajo y los estudios es algo que ya admiraba en la gente que veıa que lo hacıa, pero esta
vez me ha tocado a mı, y es duro. Al final, como siempre, quien mas me ha apoyado has sido tu, mi gran
apoyo. Porque siempre sabes lo que decir, porque siempre estas ahı. Este trabajo es para ti, mi vida, mi amor,
Rossmery. Porque tu sonrisa, tus palabras, tu me das la fuerza que necesito en los dıas difıciles para continuar.
Simplemente, gracias, carino.
Mama, gracias por ser como eres y por todo lo que me has ayudado, y ayudas, en esta vida. Eres
increıble, la mejor. Te quiero.
Como no, Brinxer, Brian. Gracias por ser mi amigo, por todo lo que me aportas. Al menos, este ano
no te he abandonado tanto como los anos de grado, ¡al final ire aprendiendo y todo! Para lo que necesites, sabes
que aquı estoy.
El master ha sido una gran experiencia en la cual he adquirido muchos conocimientos de este
apasionante mundo que es la ciberseguridad. Desde luego, es innegable que todos los profesores del master
son unos grandes docentes, y por ello, ¡muchas gracias! Todos habeis hecho una gran labor. Me gustarıa dar
un agradecimiento especial a Hector Ramos Morillo, el cual tambien me impartio clases en el grado, y olvide
nombrar en los agradecimientos de mi trabajo de fin de grado por mi mala cabeza con falta de memoria a
corto plazo... No cualquiera tiene ese entusiasmo que inspiras ni se esfuerza en que los alumnos aprendamos
los conceptos como lo haces en tus clases, ¡gracias! Me gustarıa dar otro agradecimiento especial a Francisco
Jose Mora Gimeno, tutor del presente trabajo, el cual junto a todos los profesores del master, es un increıble
docente, y tiene una habilidad natural para inspirar la pasion por la ciberseguridad, ademas de que en sus clases
se nota que le gusta la docencia, lo cual nos motiva a los alumnos. La ayuda brindada durante la realizacion de
este trabajo es inestimada (ya solo tener que leerse este documento puede ser, como mınimo, una tarea ardua,
ya que hay partes en las que yo mismo me dormirıa si las leo detenidamente D:), ¡gracias!
I
“La ignorancia es la felicidad.”
— Cifra, Matrix
“No es bueno dejarse arrastrar por los suenos y olvidarse de vivir.”
— Albus Percival Wulfric Brian Dumbledore
II
Resumen
En el presente trabajo se detalla la ampliacion de BOA, un analizador de vulnerabilidades de proposito
general. El punto de partida es un analizador totalmente funcional que permite anadir diferentes modulos
gracias a su arquitectura modular de manera que se puedan ejecutar tecnicas de analisis estatico a traves de
un flujo de trabajo, el cual es totalmente modificable. Las partes relevantes del presente trabajo se detallan
en los capıtulos 5 y 6, en los cuales se explican el diseno e implementacion realizados, respectivamente. Por
ultimo, se detallan los resultados obtenidos de las pruebas realizadas en el capıtulo 7 y las conclusiones en el
capıtulo 8.
El diseno realizado ha tenido como objetivo el refinamiento de la arquitectura de BOA, la cual
permitıa la ejecucion de modulos que utilizaran tecnicas de analisis estatico. Con este refinamiento se consigue
la posibilidad de ejecutar modulos que empleen tecnicas de analisis dinamico de modo que la separacion entre
estos modulos quede claramente definida y que cada uno tenga acceso a la informacion que necesita. Una vez
hecho, se obtiene una arquitectura refinada que sigue manteniendo todos los principios de diseno que BOA ya
tenıa en la version a partir de la cual nosotros empezamos el presente trabajo.
Partiendo del diseno, se realiza la implementacion de manera que se puedan materializar modulos
que utilicen el analisis dinamico. Con la finalidad de probar el correcto funcionamiento de todo lo realizado, se
implementan los modulos necesarios para obtener un fuzzer funcional de caja gris y guiado por la cobertura,
y con diferentes caracterısticas opcionales a partir de la configuracion como estar basado en la gramatica o en
mutaciones. Se utiliza una caracterıstica muy importante de BOA, que son las dependencias, para implementar
un modulo que utiliza un algoritmo genetico, el cual es el estandar de facto entre los fuzzers mas extendidos
como AFL.
III
Indice
1. Introduccion 1
2. Motivacion y Objetivos 4
3. Estado del Arte 6
3.1. Introduccion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
3.2. Analisis Dinamico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
3.2.1. Tecnicas Principales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.2.1.1. Instrumentacion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.2.1.2. Fuzzing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.2.2. Proyectos Reconocidos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.2.2.1. AFL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3.2.2.2. Forks de AFL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
3.2.2.3. OSS-Fuzz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.2.2.4. DynamoRIO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
4. Tecnologıas 22
4.1. Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
4.1.1. exrex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
4.1.2. Lark . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
4.2. Intel PIN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
4.3. Firejail . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
IV
INDICE
4.4. GitHub . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
4.4.1. GitHub Actions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
5. Diseno 28
5.1. Introduccion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
5.2. Enfoque . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
5.2.1. Ambito . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
5.3. Objetivos de Diseno . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
5.3.1. Objetivos de Diseno Previos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
5.3.1.1. Reusabilidad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
5.3.2. Nuevos Objetivos de Diseno . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
5.3.2.1. Refinamiento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
5.3.2.2. Compatibilidad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
5.4. Modulos Principales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
5.4.1. Modulos Intermediarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
5.5. Elementos Concretos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
5.5.1. Fichero de Reglas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
5.5.1.1. Argumentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
5.5.1.2. Otros Cambios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
5.5.2. Uso de Modulos como Dependencias . . . . . . . . . . . . . . . . . . . . . . . . . . 35
5.5.3. Algoritmo Genetico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
5.6. Arquitectura Software . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
5.7. Limitaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
6. Implementacion 43
6.1. Introduccion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
6.1.1. Nuevos Elementos en la Estructura del Proyecto . . . . . . . . . . . . . . . . . . . . 43
6.2. Soporte al Analisis Dinamico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
6.3. Modulos de Deteccion de Fallos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
V
INDICE
6.3.1. Modulo Exit Status . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
6.4. Modulos de Generacion de Entradas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
6.4.1. Modulo Random String . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
6.4.2. Modulo Input Seed . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
6.4.3. Modulo Grammar Lark . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
6.4.3.1. Asignacion de Probabilidad a las Producciones . . . . . . . . . . . . . . . . 47
6.4.3.2. Estructuras de Datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
6.4.3.3. Lımites Soft y Hard en el Recorrido de las Gramaticas . . . . . . . . . . . . 49
6.5. Modulos de Seguridad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
6.5.1. Modulo Basic Fuzzing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
6.5.1.1. Cobertura de Codigo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
6.5.1.2. Procesamiento Paralelo . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
6.5.1.3. Modulo como Generador . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
6.5.2. Modulo Genetic Algorithm Fuzzing . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
6.5.2.1. Funcion de Crossover . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
6.5.2.2. Funcion de Mutacion y Mutadores . . . . . . . . . . . . . . . . . . . . . . 59
6.5.2.3. Power Schedules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
7. Resultados 62
7.1. Prueba Sintetica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
7.2. Pruebas con LAVA-M . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
8. Conclusion 66
8.1. Relacion con Estudios Cursados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
8.2. Trabajo Futuro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
Bibliografıa y Referencias 68
Anexos
A. Cambios Introducidos en BOA 71
VI
INDICE
B. Codigos de Error en BOA 73
VII
Indice de figuras
1.1. Analogıa entre la arquitectura de Von Neumann y el fuzzing . . . . . . . . . . . . . . . . . . . 2
1.2. Logo de BOA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2.1. Arquitectura inicial de BOA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
3.1. Ejemplo de AST con Pycparser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.2. Sintaxis Intel vs AT&T en lenguaje ensamblador . . . . . . . . . . . . . . . . . . . . . . . . 8
3.3. Ejemplo practico y manual de instrumentacion . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.4. Ejemplo de gramaticas regular y libre de contexto . . . . . . . . . . . . . . . . . . . . . . . . 13
3.5. Formulas de los power schedules de ASTFast . . . . . . . . . . . . . . . . . . . . . . . . . . 17
3.6. Arquitectura de AFLGo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
3.7. Arquitectura de OSS-Fuzz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
4.1. Logo de Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
4.2. Logo de Intel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
4.3. Logo de Firejail . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
4.4. Logo de GitHub . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
4.5. Logo de Sphinx . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
5.1. Interaccion entre los nuevos modulos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
5.2. Estructura basica del ADN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
5.3. Relacion entre genotipo y fenotipo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
5.4. Arquitectura software de BOA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
VIII
INDICE DE FIGURAS
6.1. Ejecucion de BOA (modulo de analisis dinamico) . . . . . . . . . . . . . . . . . . . . . . . . 43
6.2. Ejemplo de gramatica con Lark y asignacion de probabilidades . . . . . . . . . . . . . . . . . 48
6.3. Algoritmo Roulette Wheel Selection en Python . . . . . . . . . . . . . . . . . . . . . . . . . . 58
6.4. Nuevos caminos descubiertos por los diferentes power schedules . . . . . . . . . . . . . . . . 60
7.1. Prueba sintetica: test basic buffer overflow.c . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
7.2. Prueba sintetica con el modulo basic fuzzing . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
7.3. Pruebas con binarios de LAVA-M . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
IX
Indice de algoritmos
1. CGF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2. AFL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3. Algoritmo Genetico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
X
Siglas
ADN Acido DesoxirriboNucleico. 37, 39, 57, 59
AFL American Fuzzy Lop. 4, 14–19, 35, 46, 60
ASCII American Standard Code for Information Interchange. 46
ASLR Address Space Layout Randomization. 53
AST Abstract Syntax Tree. 7, 32
BOA Buffer Overflow Annihilator. 2–5, 22, 23, 25–27, 29–31, 33, 35, 40, 42, 44, 45, 47, 54, 62, 66, 71
BOAF BOA Fail. 32, 34, 35, 44, 45, 52
BOAI BOA Input. 32, 34, 35, 44, 45, 47, 52, 56, 62, 67
BOALC BOA LifeCycle. 32, 35, 45
BOAM BOA Module. 31, 35, 44, 45, 47, 67
BOAPM BOA Parser Module. 31, 32, 44, 45, 67
BOAR BOA Report. 32
BOAR BOA Runner. 32, 45
CD Continuous Deployment. 27
CFG Control Flow Graph. 10, 11, 18
CGF Coverage-Guided Fuzzing. 14, 16, 18, 65
CI Continuous Integration. 27, 71
CLI Command-Line Interface. 71
CPU Central Processing Unit. 24
DAG Directed Acyclic Graph. 35
DGF Directed Greybox Fuzzing. 18, 65
GB GigaByte. 62
XI
Siglas
GDB GNU Debugger. 6
GNU GNU’s Not Unix!. 1, 10, 12, 62, 63
JIT Just-In-Time. 24
JSON JavaScript Object Notation. 33
KISS Keep It Simple, Stupid!. 44
PSO Particle Swarm Optimization. 18
RNN Recurrent Neural Network. 14
SQL Structured Query Language. 12
UTF-8 8 bit Unicode Transformation Format. 42, 46
XML eXtensible Markup Language. 33, 34
XII
Capıtulo 1
Introduccion
“En 1946, cuando Grace Murray Hopper termino su servicio militar, se unio al Laboratorio de
Computacion de la Facultad de Harvard, donde continuo el trabajo que inicio en el Mark II y Mark III. Los
operadores rastrearon un error hasta el Mark II, el cual fue debido a una polilla atrapada en un rele, lo cual
acuno el termino bug. Este bicho/bug fue eliminado con sumo cuidado y se pego, literalmente, en el libro de
registro. Debido a este primer bicho/bug, aun hoy en dıa llamamos a los errores o glitches en un programa un
bug [1].”
La busqueda de errores en programas informaticos es algo que, inevitablemente, cualquiera tendra
que afrontar si desarrolla programas informaticos, desde el nivel mas bajo (e.g. ensamblador) hasta el mas alto
(e.g. programacion visual). La manera mas clasica e intuitiva de buscar errores es probar a ejecutar y comprobar
si funciona, lo cual puede parecer simple, pero no lo es tanto como lo parece y puede aumentar en complejidad
a medida que se buscan mejores resultados. Por supuesto, esto tiene sus ventajas e inconvenientes, donde su
mayor ventaja es la sencillez y velocidad, pero su mayor inconveniente, entre otros, son los falsos negativos,
ya que estos pueden llevarnos a pensar que todo funciona correctamente cuando no es ası. A todas las tecnicas
que, de alguna manera, ejecutan los programas con el objetivo de buscar errores, se dice que son tecnicas de
analisis dinamico (aunque esta area no solo se dedica a la busqueda de errores, nosotros la trataremos como si
ası fuera), y han evolucionado mucho desde los primeros dıas de la informatica.
El teorema del mono infinito es sencillo de explicar, un poco mas difıcil de entender en profundidad,
y aunque parezca no tener utilidad, es increıblemente util por sus repercusiones. Este teorema dice que, si
tuvieramos un mono muy longevo, y que estuviera escribiendo en una maquina de escribir sin descanso y
pulsando cualquier tecla, de manera aleatoria, en algun momento, despues de mucho tiempo, el mono acabarıa
escribiendo cualquier texto existente, y un ejemplo podrıa ser El Quijote [2]. Este teorema se basa en dos
principios: recursos ilimitados y estadıstica basica. Aunque pueda parecer que este teorema no es util, sı que lo
es. Este teorema nos dice que con una fuente de aleatoriedad y con recursos suficientes (nunca vamos a tener
recursos ilimitados), podemos generar cualquier dato, y cualquier programa puede ser tratado como una caja
negra con una entrada y una salida (esto es ası porque cualquier programa sigue la arquitectura de Von Neumann
[3]). Siguiendo con la analogıa (la figura 1.1 muestra esta analogıa de manera mas grafica), el texto generado
por nuestro mono serıa la entrada de un programa en el que estamos buscando errores, y solo tendrıamos que
analizar la salida para saber si ha fallado (en un sistema GNU/Linux, el analisis de la salida puede ser tan
sencillo como simplemente comprobar que el codigo de salida es distinto de cero o tan complejo como analizar
1
1. INTRODUCCION
que una imagen se ha generado siguiendo un patron esperado). Al final, este teorema es la base de la principal
tecnica de analisis dinamico que vamos a tratar en el presente trabajo: fuzzing.
(a) Arquitectura de Von Neumann [4]
(b) Teorema del mono infinito, fuzzing y la arquitectura de Von Neumann
Figura 1.1: Analogıa entre la arquitectura de Von Neumann y el fuzzing
Aunque hay varias tecnicas empleadas en la busqueda de errores en el analisis dinamico, la mas
extendida sin lugar a dudas es el fuzzing, y es debido a la sencillez de la tecnica y los grandes avances que
han habido en los ultimos anos. En el caso mas simple, esta tecnica se aplica de manera analoga al teorema
del mono infinito, pero el avance de la inteligencia en la aplicacion de dicha tecnica ha hecho que sea mucho
mas viable, pues aunque el teorema del mono infinito es posible en la teorıa, pero en la practica es totalmente
inviable, pues requiere de recursos computacionales infinitos o tiempo infinito, y no disponemos de ninguno de
ellos (aun y con todo, han habido diversos intentos de llevarlo a la practica, y algunos incluso con cierto exito
[5]).
Es importante destacar que para poder aplicar una tecnica de analisis dinamico necesitaremos recurrir
a algun tipo de instrumentacion, al menos en el caso general, ya que no es totalmente necesario. Esta tecnica,
la instrumentacion, se basa en la modificacion de un binario o fichero de codigo fuente y que puede perseguir
diferentes finalidades (e.g. medir el tiempo de alguna subrutina, saber que funciones y/o metodos han sido
ejecutados). En el presente trabajo se utilizan tecnicas de instrumentacion que seran explicadas en profundidad.
Junto al analisis dinamico encontramos tambien el analisis estatico, area la cual trata de analizar
programas sin llegar a ejecutarlos. Este enfoque es justamente el punto de partida del presente trabajo, un
analizador de vulnerabilidades que emplea el analisis estatico: BOA1 [6]. El resultado ha sido un analizador de
vulnerabilidades con soporte para el analisis estatico y analisis dinamico. Partimos de este punto ya que es un
paso natural para dicho analizador, pues al ser una de sus principales objetivos de diseno la modularidad y la
extensibilidad, ha sido posible dar soporte a estos dos tipos de analisis sin necesidad de crear una herramienta
1Repositorio de BOA: https://github.com/cgr71ii/BOA/
2
1. INTRODUCCION
totalmente nueva y separada del analizador original. El dotar de soporte para el analisis dinamico a BOA ha
sido el objeto del presente trabajo, y mas en concreto se han creado los modulos necesarios para la tecnica de
fuzzing, aunque otras tecnicas tambien se podrıan implementar de manera sencilla. BOA es una herramienta de
codigo libre y esta disponible en GitHub.
Figura 1.2: Logo de BOA
3
Capıtulo 2
Motivacion y Objetivos
La busqueda de vulnerabilidades es un proceso que, tradicionalmente, ha sido manual debido a
diversos factores, los cuales han dificultado su automatizacion. Algunos de estos factores estan relacionados
con la complejidad, la contextualizacion o falta de tecnologıa que obtuviera unos resultados utiles en un tiempo
razonable, por poner algunos ejemplos. Estas dificultades, con el tiempo, se han ido supliendo en mayor o
menor medida, y hoy en dıa existen proyectos como AFL++1 que son capaces de encontrar vulnerabilidades
en un tiempo razonable en proyectos reales y que nadie habıa descubierto antes debido a la complejidad de las
vulnerabilidades encontradas [7].
Como hemos comentado, hoy en dıa existen proyectos ampliamente utilizados para la busqueda
de vulnerabilidades, al menos cuando hablamos de analisis dinamico, pero muchos de estos proyectos se
centran en implementar tecnicas concretas, lo cual debido a que el diseno inicial de las herramientas no se
suele centrar en la extensibilidad o modularidad de la misma, dificulta la adicion de tecnicas novedosas si no
encajan correctamente con la arquitectura inicialmente disenada para estos proyectos.
Con el fin de intentar suplir este problema, ampliamos la arquitectura inicial de BOA, la cual se puede
observar en la figura 2.1, con el fin de dar soporte al analisis dinamico y ası se puedan crear nuevos modulos
de manera sencilla aprovechando sus objetivos de diseno [6]. De esta manera, tambien se abre la puerta a que
nuevas tecnicas puedan anadirse de manera sencilla, ya no solo para el analisis estatico, sino tambien para el
analisis dinamico. Para poder cumplir con esto, los objetivos concretos que nuestro analizador busca cumplir a
la hora de dar soporte al analisis dinamico son los siguientes:
• Dar soporte al analisis dinamico: actualmente, BOA dispone de soporte para analisis estatico. Una de
las tareas principales del presente trabajo es dar soporte al analisis dinamico de manera que se puedan
implementar modulos concretos para utilizar diferentes enfoques de este analisis aprovechando todas las
cualidades de la herramienta.
• Implementar una funcionalidad mınima: una vez BOA tenga soporte para modulos de analisis dinamico,
se implementaran los modulos necesarios para ofrecer una funcionalidad mınima. Realizando esto,
cualquier usuario que quiera utilizar estos modulos, no tendra que desarrollar los suyos propios si no
es lo que quiere, y ası se podran utilizar modulos de analisis dinamico nada mas instalar BOA. La
1Repositorio de AFL++: https://github.com/AFLplusplus/AFLplusplus
4
2. MOTIVACION Y OBJETIVOS
funcionalidad mınima va a consistir en implementar los modulos necesarios para aplicar fuzzing (i.e.
crear un fuzzer).
• Preservar los objetivos iniciales de BOA: BOA se diseno con unos objetivos iniciales, y estos deben
mantenerse para no perder los beneficios que este aporta: automatizacion del analisis de vulnerabilidades,
diseno de una arquitectura modular, personalizacion, ampliacion, perfiles de configuracion y sencillez y
completitud en los resultados.
• Documentar el desarrollo: el proposito es facilitar la comprension del analizador a quien quiera ampliar
alguna funcionalidad, modificar su diseno o curiosear. Entender el sistema para el que se quiere anadir
un nuevo modulo puede ser esencial segun las necesidades.
• Evaluar el funcionamiento: el proposito es evaluar el correcto funcionamiento de la mınima funcionalidad
de los modulos que el analizador implementa.
Figura 2.1: Arquitectura inicial de BOA
5
Capıtulo 3
Estado del Arte
3.1 Introduccion
El objetivo principal del presente trabajo es disenar e implementar tecnicas de analisis dinamico, lo
cual se va a realizar ampliando el diseno e implementacion del proyecto BOA. Para ello, vamos a realizar el
analisis del estado del arte actual referente al analisis dinamico, sus tecnicas principales y algunas herramientas
ampliamente extendidas.
3.2 Analisis Dinamico
El analisis dinamico de software consiste en la ejecucion de software sobre infraestructura concreta.
Pueden haber diversos objetivos para ello, pero principalmente nos centraremos en la relacion de este tipo de
analisis con la busqueda de vulnerabilidades.
Constantemente estamos realizando analisis dinamico, y un ejemplo muy sencillo que demuestra
esto son los depuradores (e.g. GDB). Cuando utilizamos un depurador con el objetivo de encontrar algun error,
estamos realizando analisis dinamico, pero seguramente habra mucha gente que no haya caıdo en esto. La razon
de por que esto es analisis dinamico es porque estamos analizando un programa a la vez que lo ejecutamos, y
la clave aquı para entender esto es que la automatizacion de la tecnica no tiene que ser de un 100% para que
estemos aplicando analisis dinamico. Habran tecnicas y/o herramientas que tengan un mayor o menor grado de
automatizacion.
Cuando hablamos de analisis dinamico, lo normal es que el grado de automatizacion sea bastante
elevado, pero esto no tiene porque ser ası en el total de los casos como ya hemos visto con el ejemplo del
depurador. Cada dıa surgen herramientas cada vez mas sofisticadas con un gran nivel de automatizacion e
inteligencia para la busqueda de vulnerabilidades [8].
Por otro lado, es importante comentar que este tipo de analisis, normalmente, se presta bastante a la
paralelizacion. Esto es un gran punto a favor, y debido a ello surgen plataformas comunitarias que comparten
recursos con la finalidad de encontrar vulnerabilidades [9][10].
6
3. ESTADO DEL ARTE
Por ultimo, comentar que el analisis dinamico nos presenta una serie de ventajas e inconvenientes
[11] (al final, cuando se analizan estos puntos, queda claro que el analisis dinamico y el analisis estatico son
tecnicas complementarias, no excluyentes, por lo que normalmente se requeriran de ambos enfoques para un
analisis en profundidad), los cuales comentamos de manera superficial:
• Alta precision: a diferencia del analisis estatico, la precision en el analisis dinamico suele ser elevada
debido a que el nivel de abstraccion es mucho menor, pues ejecutamos el programa en lugar de analizarlo.
Debido a que en el analisis puede haber lugar a errores, en la ejecucion obtenemos informacion exacta
de que es lo que ha ocurrido, lo cual hace que la precision sea mucho mas elevada.
• Sencillez en la gestion de caracterısticas dinamicas: debido a que es necesaria la ejecucion del programa,
las caracterısticas dinamicas de la ejecucion (e.g. hilos, polimorfismo) se resuelven de manera natural,
pues no hay que realizar un analisis de las mismas para estimar el comportamiento.
• La ejecucion es necesaria: a diferencia del analisis estatico, en el analisis dinamico es necesario realizar
la ejecucion del programa para llevar a cabo el analisis. Esto conlleva su parte positiva y negativa.
• Resultados dependientes de cada ejecucion: cuando realizamos una ejecucion, los resultados de esa
ejecucion seran independientes del resto de ejecuciones, lo cual suele llevar a la necesidad de realizar
multiples ejecuciones si la tecnica aplicada no obtiene toda la informacion necesaria para finalizar el
analisis. En el caso del analisis estatico esto no es ası, y los resultados no son dependientes de la
ejecucion, sino que el analisis es completo una vez realizado.
• Suele requerir largos tiempos para finalizar el analisis: debido a que es necesario ejecutar el programa
bajo analisis, y es bastante probable que no una unica vez, esto hace que los tiempos de espera sean
elevados, y que estos tiempos varıen de un programa a otro.
3.2.1 Tecnicas Principales
3.2.1.1 Instrumentacion
La instrumentacion es una tecnica de analisis dinamico que consiste en modificar un fichero de
codigo o un binario. Esta modificacion puede perseguir diferentes objetivos: analizar el comportamiento ante
el cambio insertado, modificar el comportamiento inicial para que se realice otra accion (un ejemplo, de uso
muy extendido, podrıa ser la piraterıa de videojuegos, pues haciendo uso de ingenierıa inversa se detectan los
cambios necesarios a realizar para evitar comprar el producto, y estos cambios se realizan con instrumentacion),
cobertura de codigo, analisis temporal, etc.
Como hemos comentado, la instrumentacion se suele hacer sobre un fichero de codigo o un binario, y
aunque no son los unicos posibles objetivos, son los mas comunes. Cuando hacemos instrumentacion sobre un
fichero de codigo, encontramos la ventaja de que tenemos el contexto completo, y por ello la instrumentacion
tendra una mejor precision en el objetivo que estemos buscado; como parte negativa encontramos la dificultad
de tener que analizar el fichero (esto requerira, en la mayorıa de los casos, construir un Abstract Syntax Tree
(AST) con alguna herramienta como, por ejemplo, Pycparser1, el cual habra que recorrer y analizar; ejemplo
en la figura 3.1) y, ademas, que dicho analisis sera totalmente dependiente del lenguaje de programacion,
1Repositorio de Pycparser: https://github.com/eliben/pycparser
7
3. ESTADO DEL ARTE
por lo que habra que utilizar diferentes herramientas para diferentes lenguajes. En el caso de los binarios
tendremos la desventaja de perder el contexto del programa, por lo que estaremos mas limitados respecto a lo
que queramos hacer, pero la mayor ventaja es que hay acciones mucho mas sencillas de detectar a nivel de
lenguaje ensamblador, y ademas, es mucho mas sencillo analizar este lenguaje y aunque es cierto que existen
diferentes sintaxis, hay dos predominantes que son la sintaxis de Intel y la de AT&T (ejemplo en la figura
3.2). Al igual que pasa a nivel de fichero de codigo, a nivel de binario somos dependiente de la arquitectura, y
mas concretamente del juego de instrucciones, por lo que se necesitara soporte para diferentes arquitecturas si
queremos hacer instrumentacion binaria en diferentes plataformas, pero esto no suele ser muy comun como en
el caso de la instrumentacion a nivel de fichero de codigo y posibles diferentes lenguajes de programacion.
#include <stdio.h>
void main()
{
printf("Hello World!\n");
}
(a) Fichero de C del que obtenemos el AST
FileAST
FuncDe f
(main)
Decl
FuncDecl
(void main())
TypeDecl
(”void”)
Compound
({· · ·})
FuncCall
(print f ())
ID
(”print f ”)ExprList
Constant
(”Hello World!\n”)
(b) Arbol Sintactico Abstracto (AST)
Figura 3.1: Ejemplo de AST con Pycparser
int suma(int a, int b)
{
return a + b;
}
(a) Codigo
push rbp
mov rbp ,rsp
mov DWORD PTR [rbp -0x4],edi
mov DWORD PTR [rbp -0x8],esi
mov edx ,DWORD PTR [rbp -0x4]
mov eax ,DWORD PTR [rbp -0x8]
add eax ,edx
pop rbp
ret
(b) Sintaxis Intel
push %rbp
mov %rsp ,%rbp
mov %edi ,-0x4(%rbp)
mov %esi ,-0x8(%rbp)
mov -0x4(%rbp),%edx
mov -0x8(%rbp),%eax
add %edx ,%eax
pop %rbp
retq
(c) Sintaxis AT&T
Figura 3.2: Sintaxis Intel vs AT&T en lenguaje ensamblador
8
3. ESTADO DEL ARTE
La instrumentacion es una tecnica ampliamente utilizada y con gran utilidad. Un ejemplo practico de
esto podrıa ser imaginar un sistema en el que la funcion suma de la figura 3.2 se utilizara para saber cual es
la cantidad de dinero que se le tiene que devolver a los clientes, y si quisieramos que esta funcion devolviera
siempre el valor 100, serıa sencillo de realizar como se puede observar en la figura 3.3:
(a) Obtenemos las cadenas hexadecimales original y de reemplazo
(b) Realizamos la instrumentacion manualmente
Figura 3.3: Ejemplo practico y manual de instrumentacion
Para llevar a cabo la instrumentacion que se puede observar en la figura 3.3, se han llevado a cabo los
siguientes pasos:
1. Buscar un editor hexadecimal (e.g. hexedit2) con el que poder modificar binarios.
2. Desensamblar el binario objetivo y buscar las instrucciones necesarias en hexadecimal para realizar el
reemplazo. Necesitaremos un punto de referencia de alguna instruccion que podamos intercambiar sin
que esto cause problemas (en nuestro caso han sido las instrucciones cuyo codigo hexadecimal es 8B 45
F8 01 D0). Para llevar a cabo este paso, existen herramientas muy utiles como Compiler Explorer3.
3. Una vez tengamos la cadena hexadecimal original (i.e. 8B 45 F8 01 D0) y la cadena hexadecimal
que queramos intercambiar (i.e. B8 64 00 00 00), abrimos el editor hexadecimal, buscamos la cadena2Manual de Hexedit: https://linux.die.net/man/1/hexedit3Sitio web de Compiler Explorer: https://godbolt.org/
9
3. ESTADO DEL ARTE
hexadecimal original e intercambiamos dicha cadena por la cadena que nosotros queremos para obtener
el comportamiento deseado.
Una vez hemos visto la instrumentacion manual, y mas concretamente la instrumentacion binaria,
cabe destacar otras tecnicas como el hooking. Esta tecnica consiste en facilitar la instrumentacion, e incluso
hacerlo una caracterıstica de un software. De manera breve, el hooking son puntos dentro de un programa
que, de manera intencional, permiten anadir comportamiento. Esto es muy util y facilita en muchas ocasiones
situaciones complejas. Un ejemplo de hooking lo podemos encontrar en la carga de librerıas dinamicas de los
sistema GNU/Linux, donde la variable de entorno LD PRELOAD tiene una prioridad alta a la hora de cargar
librerıas dinamicas y puede utilizarse para indicar la ruta de una librerıa dinamica que redefina una librerıa de
la librerıa estandar (e.g. funcion malloc), y debido a que la prioridad de esta variable de entorno es mayor que
la de la carga de la librerıa estandar, cuando se invoque a la funcion sobreescrita, se invocara la que hemos
definido en la librerıa dinamica indicada en la variable de entorno.
Por ultimo, destacar uno de los ejemplos donde la instrumentacion ayuda mas a obtener resultados:
la cobertura de codigo. La cobertura de codigo es la obtencion de una metrica que nos indique cuanto codigo
ha sido ejecutado. Gracias a la instrumentacion, es posible automatizar este proceso, aunque el resultado de
la automatizacion no es perfecto a dıa de hoy, pero ha sido un metodo que tradicionalmente se ha realizado
manualmente y que gracias a la instrumentacion, cada vez existen mas metodos automaticos y con mejores
resultados. En cuanto a la granularidad de la cobertura de codigo, existen diferentes niveles:
• Cobertura de sentencia o lınea: un 100% de cobertura significa que se ha ejecutado cada sentencia (lınea).
• Cobertura de rama o decision: un 100% de cobertura significa que se ha ejecutado cada rama, o decision,
en sus vertientes verdadera y falsa. Cuando hablamos de rama nos referimos a toda aquella condicion
cuyo resultado ejecute una rama u otra del programa (un ejemplo muy claro es una estructura if, donde
su condicion final (no hablamos de condicion parcial) se resolvera como verdadera o falsa). Un 100% de
cobertura de rama implica un 100% de cobertura de sentencias.
• Cobertura de condicion: un 100% de cobertura significa que se ha ejecutado cada condicion en sus
vertientes verdadera y falsa. A diferencia de la cobertura de rama, aquı sı que hablamos de condiciones
parciales (e.g. if (a> 0 && b> 0) se tendra en cuenta cada decision parcial, es decir, a> 0 y por separada
b > 0). Un 100% de cobertura de condicion no implica un 100% de cobertura de ramas.
• Cobertura de condicion multiple: un 100% de cobertura significa que se ha ejecutado cada condicion no
parcial con todas sus posibles combinaciones. Un 100% de cobertura de condicion multiple implica un
100% de cobertura de condicion.
• Cobertura de condicion-decision: un 100% de cobertura significa que hay un 100% de cobertura de rama
y tambien un 100% de cobertura de condicion. Con esta condicion, conseguimos solucionar el problema
en el que un 100% de cobertura de condicion no implica un 100% de cobertura de rama.
• Cobertura de bucles: un 100% de cobertura significa que se ha ejecutado cada bucle con 0, 1 y multiples
iteraciones.
• Cobertura de caminos: un 100% de cobertura significa que se han ejecutado todos los posibles caminos
de un Control Flow Graph (CFG). Un CFG es un grafo dirigido que plasma todos los posibles caminos
de ejecucion de un programa concreto (basicamente es como si cada arista del grafo fuera el salto que
10
3. ESTADO DEL ARTE
se realiza de una instruccion, o nodo, a otra). Un 100% de cobertura de caminos implica un 100% de
cobertura de bucles y ramas.
3.2.1.2 Fuzzing
El fuzzing es la tecnica de analisis dinamico mas extendida a la hora de buscar vulnerabilidades.
En su vertiente mas simple se trata en aplicar fuerza bruta generando entradas aleatorias para programas y
comprobando si dichas entradas hacen que el programa falle. En los inicios sı que se aplicaba esta tecnica de
esta manera, pero los tiempos evolucionan y las tecnicas tambien. A dıa de hoy, el fuzzing es un area de estudio
muy activo en el que se intenta que estas entradas cada vez sean mas inteligentes y ası se consiga encontrar
errores lo mas rapido posible.
Aunque puede haber fuzzers de muchos tipos, estos se suelen clasificar en tres categorıas principales:
• Fuzzers de caja blanca o whitebox: estos fuzzers hacen un gran esfuerzo por realizar un analisis muy
profundo del sistema bajo analisis antes de comenzar a ejecutarlo. Tecnicas comunmente utilizadas, las
cuales son propias del analisis estatico, son el analisis simbolico, ejecucion simbolica, analisis del CFG,
etc. Una vez se tiene informacion del sistema, es cuando se comienza a realizar ejecuciones del sistema,
pero ya con una gran base de informacion que permite realizar ejecuciones mucho mas precisas, pero al
coste de haber realizado un analisis bastante pesado.
• Fuzzers de caja negra o blackbox: estos fuzzers tratan al sistema como una caja negra, es decir, no intentan
conseguir ninguna informacion del objetivo como sı que se hace con los fuzzers de caja blanca. Estos
sistemas centran todos sus esfuerzos en la generacion de entradas, las cuales podran ser, desde lo mas
sencillo, totalmente aleatorias, hasta lo mas preciso, como puede ser la generacion de entrada basadas en
el dominio del sistema. Una de las partes negativas de este tipo de analisis es que, para obtener buenos
resultados, habra que darle bastante informacion al fuzzer para que las entradas generadas sean correctas.
• Fuzzers de caja gris o greybox: estos fuzzers intentan juntar lo mejor de los fuzzers de caja blanca y
de caja negra. El analisis que se realiza no es tan profundo como en los de caja blanca, sino que se
intenta que sea ligero y que aporte la mayor cantidad de informacion utilizable posible. Por otro lado, la
generacion de entradas se guiaran por la informacion que hemos obtenido, por lo que no tendremos que
hacer tantos esfuerzos como si no tuvieramos nada de informacion como sucede cuando utilizamos caja
negra. Este enfoque es el mas utilizado a dıa de hoy, pues consigue, de una manera bastante acertada,
obtener los beneficios de los enfoques de caja blanca y caja negra y reduce las desventajas que ambos
enfoques tienen.
Independientemente del tipo de fuzzer que se implemente, la generacion de entradas es algo que se
tendra que llevar a cabo en todos ellos. Dependiendo de como se genere la entrada, se realiza una clasificacion
nuevamente, pero cabe destacar que esta clasificacion no es disjunta, ya que un fuzzer puede estar clasificado
en varios de los siguientes conjuntos:
• Fuzzers lexicos: estos fuzzers generan entradas basicamente aleatorias pero cada unidad mınima de
informacion independiente se espera que se genere de manera correcta. Este tipo de generacion, aunque
trivial, puede ser util si se combina con otro tipo de tecnicas. Una de las tecnicas mas empleadas en
11
3. ESTADO DEL ARTE
la generacion de entradas para fuzzers es la mutacion, y esta consiste en la aplicacion de diferentes
transformaciones a las entradas dada una probabilidad. Por ejemplo, si se trabaja a nivel de byte, lo cual
es muy comun, algunos mutadores (es como se conoce a las funciones que realizan mutaciones) podrıan
ser: eliminacion de byte, generacion de byte aleatorio, intercambio de 2 bytes, etc. Aquellos fuzzers que
utilizan mutaciones se dice que estan basados en mutaciones o que son mutation-based.
• Fuzzers sintacticos: estos fuzzers generan entradas teniendo en cuenta la estructura que deberıan de tener
dichas entradas. De esta manera, al tener informacion del formato que espera la entrada, podremos
generar entradas de manera mucho mas eficiente sin tener que gastar tiempo en generar una entrada
correcta de manera fortuita, lo cual, normalmente, es muy improbable. Una de las tecnicas mas empleadas
en estos fuzzers es el uso de gramaticas, las cuales explicaremos en breve debido a la gran importancia
que tienen. Aquellos fuzzers que utilizan gramaticas se dice que estan basados en gramaticas o que son
grammar-based.
• Fuzzers semanticos: estos fuzzers generan entradas teniendo en cuenta el contexto del sistema, y es que es
posible generar una entrada que a nivel lexico y sintactico sean correctas, pero que sea incorrecta a nivel
semantico (un ejemplo de esto podrıa ser la entrada 0/0 a la utilidad bc de GNU, y es que esta entrada es
correcta lexica y sintacticamente, pero incorrecta semanticamente debido a que la division de 0 con 0 no
tiene sentido para el contexto de la utilidad bc). Para obtener este tipo de informacion contextual hay que
recurrir a tecnicas que nos permitan obtenerla, y algunos ejemplos de tecnicas que se suelen aplicar son: el
analisis de manchas, o taint analysis, la cual consiste en descubrir que variables pueden ser manipuladas,
directa o indirectamente, por el usuario, y de esta manera se puede saber que informacion manipulamos
y deberıamos de tener en cuenta a la hora de saber si algo puede fallar por ello; ejecucion concolica
(acronimo de concreto y simbolico), la cual se basa en centrarse en la ejecucion de una parte concreta
de manera que en cada condicion se analicen las restricciones de manera simbolica y seamos capaces de
obtener conclusiones respecto a las condiciones sin necesidad de resolverlas; ejecucion simbolica, la cual
va un paso mas alla que la ejecucion concolica e intenta solucionar las condiciones que se encuentran,
con un valor exacto cuando sea posible, con un rango cuando no sea posible un valor, y en el peor de los
casos sin un valor. Al final, todas estas tecnicas tienen como objetivo el tener el control de poder ejercer
caminos del sistema a voluntad, de manera que seamos capaces de solucionar los problemas relacionados
con la semantica del sistema, los cuales estan relacionados con las condiciones y valores descritos en el
mismo y que estas tecnicas intentan rastrear, solucionar, inferir, etc., al final todo depende de la tecnica
concreta a aplicar. Algo a tener en cuenta es que estas tecnicas son propias del analisis estatico, por lo
que los fuzzers semanticos seran mas propensos a ser de caja blanca debido a que estos analisis suelen ser
pesados. Por ultimo, comentar que si utilizaramos alguna de estas tecnicas, como puede ser la ejecucion
simbolica, en el ejemplo de la utilidad bc se detectarıa una condicion donde en la division, si tenemos un
valor 0 en el denominador, llegamos a un error, el cual acabarıa detectandose en algun momento como
un error semantico a evitar.
• Fuzzers de dominio especıfico: estos fuzzers generan entradas de un dominio concreto. Cuando hablamos
de dominios concretos, nos referimos a conjuntos especıficos, no a entradas genericas para sistemas.
Un ejemplo sencillo de entender podrıa ser un fuzzer que analice paginas web y utilice entradas que
intenten provocar una inyeccion SQL. Estos fuzzers se podrıan considerar especializados, y pueden
utilizar tecnicas de las anteriormente mencionadas si ası consiguen mejorar los resultados, pero no hay
tecnicas concretas a utilizar en este tipo de fuzzers que sean propias de los mismos. Una de las mayores
dificultades de este tipo de fuzzers reside en intentar generalizar para obtener una herramienta comun.
12
3. ESTADO DEL ARTE
Como hemos comentado anteriormente, las gramaticas (en realidad hablamos de gramaticas formales)
son muy importantes a la hora de trabajar con fuzzers. Es cierto que no son estrictamente necesarias, pero
normalmente se utilizan. Una gramatica esta formada por una serie de reglas (formalmente conocido como
producciones, las cuales tienen la forma α → β ), y estas reglas definen como se puede generar una cadena
de un alfabeto (cabe destacar que solo indica como se pueden formar, no como deben hacerlo), es decir,
definen un lenguaje. Hay infinitas gramaticas, pero dependiendo de su forma se pueden clasificar en base a
la Jerarquıa de Chomsky [12], la cual define cuatro tipos de gramaticas, donde la anterior es un subconjunto
de la siguiente: gramaticas regulares (tipo 0), gramaticas libres de contexto o context-free (tipo 1), gramaticas
dependientes del contexto o context-sensitive (tipo 2) y gramaticas recursivamente enumerables (tipo 3). De
todos estos tipos de gramatica, donde tipo 0 ⊂ tipo 1 ⊂ tipo 2 ⊂ tipo 3, en la practica solo se utilizan las
gramaticas regulares y las libres de contexto, donde las gramaticas regulares son aquellas que un automata
finito (mecanismo simple que va desde el estado actual al estado siguiente sin guardar ninguna informacion)
puede verificar si una cadena pertenece a una gramatica concreta de este tipo, y las gramaticas libres de contexto
son aquellas que un automata con pila (mecanismos que funciona como el automata simple pero que debido
a que guarda informacion sobre el estado, es capaz de verificar reglas mas complejas) puede verificar si una
cadena pertenece a una gramatica concreta de este tipo. Como hemos dicho antes, una gramatica esta formada
por reglas, y estas reglas estan formadas por sımbolos (se diferencia entre parte izquierda y parte derecha de la
regla, o produccion, donde α es una serie de sımbolos de la parte izquierda y β es otra serie de sımbolos de
la parte derecha en la produccion α → β ). Los sımbolos de las reglas pueden ser terminales o no terminales,
donde los terminales son aquellos que son elementos simples, elementos del lenguaje, y un sımbolo no terminal
es aquel que en una regla, una produccion, se sustituye por una serie de sımbolos terminales y/o no terminales.
Un ejemplo de gramatica lo podemos encontrar en la figura 3.4, donde la gramatica regular valida todas las
cadenas que tengan la palabra “Rossmery” una o mas veces, y la gramatica libre de contexto valida todas las
cadenas que contengan cualquier numero o cualquier combinacion de numeros con las operaciones basicas
de sumar, restar, multiplicar y dividir. Con lo aquı explicado, se ha realizado una pequena introduccion a las
gramaticas, lo suficiente para que posteriormente se puedan utilizar sin problemas o que se pueda profundizar
si es necesario para comprender otros conceptos.
S→ “R” O
A→ “o” B
B→ “s” C
C→ “s” D
D→ “m” E
E→ “e” F
R→ “r” G
G→ “y” H
H→ S | ε
(a) Gramatica regular
S→ E
N→ “0” N | “1” N | “2” N | “3” N | “4” N
| “5” N | “6” N | “7” N | “8” N | “9” N | ε
a→ “+ ”
b→ “− ”
c→ “∗ ”
d→ “/”
E→ N
| N a E
| N b E
| N c E
| N d E
(b) Gramatica libre de contexto
Figura 3.4: Ejemplo de gramaticas regular y libre de contexto
Una vez visto todo lo anterior, cabe destacar que, hoy en dıa, la manera mas habitual de implementar
un fuzzer, al menos en lo que a su flujo principal se refiere, es utilizando la cobertura como metrica de guıa. Lo
13
3. ESTADO DEL ARTE
mas normal al utilizar esta metrica es acabar con un fuzzer de caja gris si no se incorporan tecnicas adicionales.
A estos fuzzers se los conoce como fuzzer guiada por la cobertura, o Coverage-Guided Fuzzing (CGF). Si
simplificamos el algoritmo, queda como se puede observar en el algoritmo 1.
Algoritmo 1: CGFInput: Set of Seed Inputs S
1 q← S
2 p← Ø
3 while not timeout and not q.empty() do4 i← q.remove()
5 m← mutate(i)
6 c← code coverage(m)
7 if new code coverage(c, p) then8 p← p∪ c
9 q.append(m)
10 end
11 end
Por ultimo, cabe destacar que, como en la mayorıa de areas de la ingenierıa informatica, la inteligencia
artificial ha llegado, y ya hay esfuerzos por aplicar tecnicas de machine learning al fuzzing [13]:
• Algoritmos evolutivos: normalmente se aplican algoritmos donde las entradas generadas se mutan, y un
ejemplo podrıa ser un algoritmo genetico, y el caso mas conocido es el de AFL4 [14][15]. En realidad,
este tipo de algoritmos es un subconjunto dentro del aprendizaje por refuerzo, pero se hace una especial
mencion por ser el tipo de algoritmos mas empleados en el fuzzing.
• Aprendizaje supervisado: en el terreno del aprendizaje supervisado se suelen aplicar redes neuronales,
y concretamente deep learning. Una forma habitual de aplicar este tipo de aprendizaje es utilizar redes
neuronales recurrentes, o Recurrent Neural Network (RNN), que son un tipo de redes neuronales que
tienen en cuenta el orden de las secuencias, lo cual es muy util para generar entradas, lo cual es lo que
necesita un fuzzer. Hay herramientas que utilizan estas redes neuronales para generar entradas [16].
• Aprendizaje por refuerzo: este tipo de aprendizaje es el mas empleado en el fuzzing debido a que se basa
en la repeticion, por lo que su similitud con lo que es el fuzzing es destacable. Debido a esa proximidad
conceptual entre el aprendizaje por refuerzo y el fuzzing hace que hayan tantos esfuerzos en este tipo de
aprendizaje en el fuzzing. Entre otros ejemplos, se ha utilizado Deep Q-Learning [17] con la finalidad
de generar automaticamente una gramatica del formato PDF [18], y tambien se ha utilizado el algoritmo
SARSA [19] para la generacion de entradas, concretamente de paquetes IPv6 basandose en paquetes
previos [20].
Gran parte de la informacion de la subseccion ha surgido de la misma fuente: [21].
3.2.2 Proyectos Reconocidos
De las tecnicas anteriormente descritas, hay ejemplos ampliamente utilizados o novedosos, de los
cuales vamos a explicar someramente su funcionamiento o caracterısticas mas importantes.4Repositorio de AFL: https://github.com/google/AFL
14
3. ESTADO DEL ARTE
3.2.2.1 AFL
American Fuzzy Lop (AFL) es el fuzzer de referencia por ser el primero que aplico con gran exito
el fuzzing, y por exito nos referimos a que fue capaz de encontrar una gran cantidad de vulnerabilidades que
habıan pasado desapercibidas en sistemas ampliamente extendidos [22].
Debido a que AFL es un fuzzer de caja gris basado en mutaciones, el algoritmo que sigue es muy
similar al algoritmo CGF, pero con algunas variaciones. El algoritmo concreto que utiliza AFL es un algoritmo
genetico, el cual se explicara mas adelante en profundidad (en concreto, se explica en la subseccion Algoritmo
Genetico).
Algoritmo 2: AFLInput: Set of Seed Inputs S
Output: Crashing Inputs S′
1 q← S
2 while not timeout and not q.empty() do3 s← q.remove()
4 p← assign energy(s)
5 for i f rom 1 to p do6 s′ = mutate input(s)
7 t = run(s′)
8 if crash(t) then9 S′← S′∪ t
10 else11 if is interesting(s′) then12 q.append(s′)
13 end
14 end
15 end
16 end
En el algoritmo AFL hay una serie de funciones que anaden cambios sobre el algoritmo CGF basico,
y los puntos relevantes que hay que entender se explican a continuacion:
• La funcion assign energy(input) trata de cuantificar como de buena es una entrada, es decir, basicamente
le da una puntuacion. Esta puntuacion que se le da a la entrada, cuando mayor sea, mas veces se realizaran
mutaciones sobre ella, y el objetivo de esto es que a mayor puntuacion, mayor probabilidad de descubrir
fallos dentro del programa bajo analisis. La tecnica principal que utiliza AFL para asignar esta puntuacion
es, aunque no identica, similar a la funcion inversa de la eficiencia de la ejecucion con esa entrada, es
decir, cuando mas rapida haya sido su ejecucion, mayor puntuacion asignara. Esta tecnica que utiliza AFL
intenta premiar las ejecuciones rapidas con la finalidad de hacer que la eficiencia general del sistema sea
mayor, pero esto no mejora la exploracion de los diferentes caminos.
• La funcion mutate input(input) realiza transformaciones sobre la entrada proporcionada con el fin de
generar nuevas entradas, y esta caracterıstica es justamente lo que hace que AFL sea mutation-based.
Entre otras, las mutaciones que AFL implementan, a nivel de bit y/o byte, son: generacion, eliminacion,
15
3. ESTADO DEL ARTE
flip, operaciones aritmeticas basicas, etc.
• La funcion is interesting(mutated input) comprueba si la mutacion de la entrada es lo suficientemente
interesante como para que se inserte en la cola y se utilice para futuras ejecuciones. Para determinar si
una entrada es interesante, se utiliza la cobertura de codigo, y en concreto AFL utiliza la cobertura de
ramas. Cuando mas diferente sea la entrada a nivel de cobertura de rama del resto de entradas ya vistas,
mas interesante sera. Esto arregla, en parte, el problema explicado con la funcion assign energy(input),
pues las entradas ya vistas obtendran una menor puntuacion en lo relativo a lo interesantes que son debido
a que se habran ejecutado muchas veces.
3.2.2.2 Forks de AFL
Como hemos comentado, AFL es el fuzzer de referencia, y por ello hay tantos forks, es decir, otros
proyectos que parten de AFL. Debido a esto, vamos a comentar los mas interesantes por haber sido los que
mas novedades han incluido, pero novedades que han hecho que la implementacion original se mejore en algun
sentido.
Uno de los forks mas interesantes es AFLFast5 [23]. Este proyecto hace dos contribuciones notables:
modelar los CGF como una manera sistematica de recorrer el estado de espacios de una cadena de Markov, lo
cual es una contribucion teorica con posibles aplicaciones practicas, y mejorar la exploracion de ramas de AFL
utilizando los power schedules.
Una cadena de Markov es un conjunto de estados y transiciones donde la transicion de un estado
a otro tiene una probabilidad asociada, y dicha probabilidad solo depende del estado actual en el que nos
encontremos, no del camino realizado para llegar a dicho estado (esto se puede ver como un grafo dirigido
ponderado). Para modelar un CGF como una cadena de Markov se tuvo en cuenta que una entrada i ejerciera
una rama r y se querıa saber que dado este estado, cual era la probabilidad de, dada la entrada i, mutarla para
obtener i′ y llegar a la rama r′. Es decir, gracias a esta modelacion es posible saber como de probable es,
dada una entrada y el camino que ejerce, llegar a otra rama concreta. Esto, aunque parezca muy teorico, tiene
grandes utilidades practicas cuando se trabaja con CGF, y en el caso de AFLFast se utilizo para explicar los
retos y posibilidades de mejora para un CGF, y especıficamente para AFL. Ademas, se utilizo para mejorar su
exploracion en AFLFast.
La otra gran mejora de AFLFast son los power schedules, que son unas formulas que ayudan a
decidir como queremos que se realice la exploracion. Estas formulas estan basadas en la modelacion de la
cadena de Markov, y consiguen definir diferentes funciones que realizan diferentes acciones, y, claramente, la
mas empleada es justamente la que consigue que se mejore la exploracion inicial que hace AFL consiguiendo
explorar mas ramas. Esto era algo necesario debido a que AFL premiaba la velocidad de ejecucion para
decidir como realizar la exploracion, y premiaba los tiempos de ejecucion mas bajos, lo cual llevaba a que
la exploracion fuera hacia las ejecucion con menor tiempo de ejecucion, y esto suele ser los casos en los que
la entrada es invalida sintacticamente (es muy sencillo generar una entrada invalida sintacticamente, y esto se
suele detectar en los primeros instantes de la mayorıa de sistemas, lo cual hace que la ejecucion sea muy rapida
porque lleva a un fallo conducido por un error). La implementacion de estos power schedules se realiza en la
funcion assign energy(input) del algoritmo de AFL, los cuales son formulas, como ya hemos comentado, y se
pueden observar en la figura 3.5.
5Repositorio de AFLFast: https://github.com/mboehme/aflfast
16
3. ESTADO DEL ARTE
f ast : p(i) = min
(α(i)
β· 2
s(i)
f (i),M
)
COE : p(i) =
0 if f(i)> µ
min(
α(i)β·2s(i),M
)otherwise
explore : p(i) =α(i)
β
quad : p(i) = min(
α(i)β· s(i)
2
f (i),M)
lin : p(i) = min(
α(i)β· s(i)
f (i),M)
exploit : p(i) = α(i)
Figura 3.5: Formulas de los power schedules de ASTFast
En las formulas de la figura 3.5 tenemos los siguientes elementos: α(i) es la funcion de recompensa
que AFL utiliza y ya se ha explicado anteriormente; β es una constante que, a mayor valor, mayor exploracion
se realizara de una misma rama (menos ramas nuevas se encontraran porque se exploraran las ya descubiertas,
lo cual puede conducir a ramas mas profundas pero con el sacrificio de una menor exploracion); s(i) indica
la cantidad de veces que la entrada i ha sido obtenida de la cola; f (i) indica la cantidad de entradas que
han obtenido la misma cobertura de rama que la entrada i; µ es la media de entradas obtenidas de la cola y la
cantidad de caminos unicos descubiertos; M es una cota superior para limitar la cantidad maxima de ejecuciones
para una entrada concreta. El power schedule exploit es el utilizado en AFL, y fast es el utilizado por defecto
por AFLFast.
El power schedule fast, si ignoramos la parte que es la fraccion del power schedule exploit, ya que
este, con la explicacion de α(i) y β se puede intuir que es el comportamiento de AFL pero ponderado por β para
favorecer la exploracion cuando mayor sea el valor, la segunda fraccion del producto es lo interesante. Debido
a que tenemos una funcion exponencial, este power schedule premia que una misma entrada sea seleccionada
multiples veces de la cola, y esto es logico debido a que si obtenemos varias veces la misma entrada significa
que ha sido seleccionada como interesante varias veces. Pero no termina ahı, ya que el resultado de esta funcion
exponencial decrementa su valor cuando mas entradas hayan diferentes que ejercen el mismo camino. Al final,
como conclusion obtenemos que al utilizar el power schedule fast obtendremos entradas que sean interesantes
y que a la vez sean unicas, y esto es ası porque su formula fuerza a ello, ya que premia la repeticion de la
seleccion pero castiga que el recorrido haya sido ejercido mas veces por otras entradas. Siguiendo el mismo
razonamiento, el resto de power schedules son sencillos de comprender.
Otro de los forks que mas han aportado al fuzzing y que han partido de AFL ha sido AFLGo6 [8]. La
6Repositorio de AFLGo: https://github.com/aflgo/aflgo
17
3. ESTADO DEL ARTE
mayor contribucion por parte de este proyecto es el concepto de Directed Greybox Fuzzing (DGF), el cual es
complementario al concepto de CGF.
La manera en la que se hace fuzzing cuando hablamos de DGF no deja de ser la misma que cuando
hacemos CGF, pero la exploracion es diferente. En el caso de aplicar DGF, la exploracion hacia las ramas es
dirigida, y con esto queremos decir que no es aleatorio el llegar a un punto determinado, sino que se fuerza
dicha exploracion al punto concreto. Para ello, ahora no solo nos basaremos en metricas de la cobertura de
camino, sino que se realizaran analisis previos sobre el sistema bajo analisis y se buscara informacion concreta
que pueda ayudarnos a ejercer diferentes ramas. De esta manera, obtenemos informacion sintactica, o incluso
semantica del sistema, y esto nos ayudara a dirigir las entradas iniciales para llegar a unas entradas objetivo a
traves de una serie de mutaciones. En el caso de estas mutaciones, antes el objetivo era obtener nuevos caminos
inexplorados con una profundidad concreta, pero ahora, en el caso de los DGF, las mutaciones tienen el objetivo
de realizar transformaciones para llegar a unas entradas objetivo, las cuales hemos obtenido con la ayuda de la
informacion que hemos obtenido al realizar los analisis de los que hablamos. Estos analisis se pueden realizar
con diferentes tecnicas, y una de ellas es la construccion del CFG, el cual nos ayudara a obtener la informacion
necesaria.
Este proyecto de nuevo realiza sus modificaciones anadiendo un power schedule, pero este es diferente
a los anadidos por AFLFast, ya que este power schedule es una funcion de distancia, la cual se calcula sobre
la entrada que se este procesando en ese momento con la entrada objetivo que se ha obtenido del analisis con
tecnicas como el CFG. En la figura 3.6 se puede observar la arquitectura de AFLGo, la cual muestra de manera
mas grafica lo explicado.
Figura 3.6: Arquitectura de AFLGo
Por ultimo, decir que otro de los forks que mas han destacado de AFL ha sido AFL++ [24]. AFL++
pretende ser la continuacion de AFL, pues este dejo de mantenerse hace ya tiempo. De entre todas su novedades,
destacamos las siguientes:
• Power schedules de AFLFast.
• Soporte para la instrumentacion de binarios de diferentes arquitecturas. Se utiliza Unicorn7.
• Posibilidad de anadir nuevos mutadores a traves de librerıas e integracion de mutadores como MOpt8.
El mutador MOpt utiliza un algoritmo de enjambre, concretamente Particle Swarm Optimization (PSO)
[25], el cual busca el mejor camino posible basandose en la posicion actual (se trata de un algoritmo
evolutivo), con lo que consigue mejorar la cobertura de codigo.7Repositorio de Unicorn: https://github.com/unicorn-engine/unicorn8Repositorio de MOpt: https://github.com/puppet-meteor/MOpt-AFL
18
3. ESTADO DEL ARTE
3.2.2.3 OSS-Fuzz
El fuzzing es una tecnica que se presta a la paralelizacion debido a la naturaleza de la tecnica. Si
tenemos una tecnica que facilmente puede aplicarse con paralelizacion, seguramente esto se pueda extender
a un sistema distribuido, y efectivamente no hay nada que nos lo evite, y ejemplo de ello es OSS-Fuzz9, una
plataforma colaborativa distribuida para hacer fuzzing [26].
El principal objetivo de OSS-Fuzz es aplicar fuzzing continuo y distribuido. Para ello, todo lo que
tiene que hacer cualquiera que quiera aplicarlo es crear una interfaz como OSS-Fuzz indica, la cual es muy
simple (es una simple funcion que recibe unos bytes y que son la entrada generada para el sistema objetivo),
y subirla a un repositorio de codigo publico, el cual sera el sistema que se quiera analizar con OSS-Fuzz.
En esta interfaz se tendra que aplicar la entrada recibido al sistema objetivo de fuzzing. Una vez hecho esto,
simplemente habra que solicitar en el repositorio de OSS-Fuzz que se haga fuzzing utilizando tu interfaz, y el
resto del proceso es realizado por OSS-Fuzz de manera automatica. En los casos en los que OSS-Fuzz encuentre
errores, se le notificara al desarrollador para que los arregle y actualice el repositorio con dichos arreglos.
La arquitectura de OSS-Fuzz, la cual se puede observar en la figura 3.7, consiste en lo siguiente:
1. Un desarrollador tiene un repositorio de codigo sobre el que quiere aplicar fuzzing. A este repositorio le
anade una nueva interfaz siguiendo las instrucciones de OSS-Fuzz para la integracion del mismo.
2. El desarrollador crea un fichero con la configuracion e instruccion de construccion del proyecto. Este
fichero se le entrega a OSS-Fuzz junto con una solicitud para aplicar fuzzing sobre el repositorio objetivo.
3. Una vez aceptada la solicitud del desarrollador por parte de OSS-Fuzz, se descarga, construye y configura
el proyecto del desarrollador. A partir de este punto, el desarrollador esta a la espera de resultados.
4. Una vez construido el proyecto del desarrollador, este se entrega a la plataforma de fuzzing de OSS-Fuzz,
OSS-Fuzz GCS bucket.
5. En este punto se utiliza ClusterFuzz10, herramienta que realiza de manera distribuida el fuzzing. Este
proceso consistira en utilizar la interfaz inicial que el desarrollador creo para poder aplicar el fuzzing,
pero la herramienta por detras que realiza realmente el fuzzing sera algunos de los fuzzers que OSS-Fuzz
soporte (e.g. AFL).
6. Cuando ClusterFuzz encuentre algun error, se registrara una notificacion en un listado11. Los errores que
se listen aquı seran privados durante los primeros 90 dıas, y despues de ese tiempo se volvera publico.
7. Las notificaciones publicadas en el listado de errores nombran al desarrollador, de manera que a este
le llegara una notificacion para enterarse de la situacion. Mediante esta notificacion podra acceder a la
informacion.
8. Una vez el desarrollador sepa que hay un error, lo tendra que arreglar, y una vez lo haya arreglado, en el
mensaje de los cambios que arreglan el error tendra que estar presente el texto “Credit to OSS-Fuzz”, ya
que esta es la manera en la que OSS-Fuzz detecta que se ha arreglado el error anteriormente descubierto.
9. Una vez el desarrollador ha arreglado el problema, ClusterFuzz verifica si el problema se ha arreglado, y
en el caso de que ası sea, el error que estaba en el listado se vuelve publico.
9Repositorio de OSS-Fuzz: https://github.com/google/oss-fuzz10Repositorio de ClusterFuzz: https://github.com/google/clusterfuzz11Listado de errores encontrados por OSS-Fuzz: https://bugs.chromium.org/p/oss-fuzz/issues/list
19
3. ESTADO DEL ARTE
Figura 3.7: Arquitectura de OSS-Fuzz
3.2.2.4 DynamoRIO
DynamoRIO12 es un framework de instrumentacion binaria. Aunque hay herramientas que esta
instrumentacion la realizan sobre un binario y devuelven el binario instrumentado, DynamoRIO la realiza
directamente sobre el proceso en ejecucion, por lo que no es necesario modificar ni obtener una version
modificada de un binario original.
En realidad, DynamoRIO es una maquina virtual, y la manera en la que consigue instrumentar el
codigo es obteniendo una copia del codigo en ejecucion y llevando dicha copia a su maquina virtual. De esta
manera, consigue realizar la instrumentacion sobre el proceso, de manera que no es necesario mayores permisos
en el sistema. Esto puede tener sus ventajas e inconvenientes, donde una de sus ventajas puede ser la seguridad,
siempre que no se consiga una salida de la maquina virtual cuando un fallo se intenta explotar en el binario
objetivo, pero por otro lado una desventaja puede ser la eficiencia, ya que ejecutar una maquina virtual en lugar
del propio sistema anade una carga considerable. Por otro lado, al utilizar una maquina virtual, DynamoRIO
soporta varias arquitecturas, ası que se podra utilizar con binarios que no tengan soporte en la maquina nativa
del desarrollador si DynamoRIO sı que tiene soporte.
De entre todas las caracterısticas de DynamoRIO, algunas de las mas interesantes se comentan a
continuacion:
• Soporte a binarios de diferentes arquitecturas: IA-32, AMD64, ARM, y AArch64.
• Instrumentacion adaptativa: permite en cualquier punto del proceso bajo analisis eliminar, anadir o
modificar instrucciones. Esto es sencillo de realizar porque hay una maquina virtual detras, y por ello
otras herramientas no permiten esta caracterıstica.
• Se puede acceder e instrumentar las instrucciones no solo del binario bajo analisis, sino tambien el de
las librerıas y todo el espacio de direccionamiento virtual del binario (el analisis no esta restringido a la
12Repositorio de DynamoRIO: https://github.com/DynamoRIO/dynamorio
20
3. ESTADO DEL ARTE
seccion de codigo del binario).
• Gran cantidad de herramientas y ejemplos ya disponibles que realizan tareas tıpicas (e.g. conteo de
instrucciones).
Por ultimo, comentar que aunque sea una herramienta que lleva muchos anos en marcha, a dıa de hoy
sigue siendo una de las herramientas de instrumentacion binaria mas conocidas y estables, ademas de ser una
de las piezas principales en muchos proyectos actuales como WinAFL13.
13Repositorio de WinAFL: https://github.com/googleprojectzero/winafl
21
Capıtulo 4
Tecnologıas
4.1 Python
Python1 es el lenguaje de programacion inicial que se utilizo para desarrollar BOA, y es el lenguaje
con el que se ha continuado para realizar el presente trabajo. El utilizar Python no fue ni ha sido una decision
arbitraria, sino fundamentada. Debido a los objetivos que perseguıa el proyecto en un inicio y que se mantienen,
Python proporciona muchas facilidades:
• Debido a la gran modularidad y facilidad de ampliacion como objetivos principales, Python, gracias a
su naturaleza dinamica, hace muy sencillo, si se parte de un diseno que lo permita, el anadir diferentes
modulos y realizar la carga de los mismos de manera dinamica. No es que esto no se pueda hacer con
otros lenguajes de programacion, es solo que Python hace esto muy sencillo de realizarse.
• Debido a que un objetivo del proyecto es permitir al usuario poder implementar sus propios modulos,
normalmente estos modulos tendran dependencias que no estaban previamente en el proyecto. Con
Python es muy sencillo la instalacion de dependencias siempre que estas esten disponibles en repositorios
como PyPi2, ya que se podran instalar facilmente con pip3, el gestor de paquetes de Python. Es destacable
que Python tenga un gestor de paquetes oficial, ya que esto no suele ser ası para la mayorıa de lenguajes
de programacion (no es normal que tengan un gestor de paquetes por defecto, no que no existan gestores
de paquetes para otros lenguajes de programacion).
• Python es un lenguaje de programacion popular, y lo es, entre otras muchas cosas, porque es sencillo y
aporta muchas facilidades a la hora de programar. Esto hace que sea ideal para un proyecto de codigo
libre, o open source, como lo es BOA.
• Documentar un proyecto en Python es sencillo y, mejor aun, existen diferentes herramientas que extraen
la documentacion y automatizan el proceso de crear una documentacion detallada del codigo. Esto es ası
porque hay una caracterıstica especial en el lenguaje que son los docstrings. Los docstrings, o cadenas
de documentacion, son unas cadenas, como las cadenas normales, pero que si se anaden en ciertos sitios
1Sitio web de Python: https://www.python.org/2Sitio web de PyPi: https://pypi.org/3Sitio web de pip: https://pip.pypa.io/en/stable/
22
4. TECNOLOGIAS
especiales, pasan a ser un atributo propio de los diferentes objetos (e.g. si se pone detras de la definicion
de una funcion, la funcion tendra un atributo doc que facilitara acceder al mismo), y esto junto a la
introspeccion (i.e. caracterıstica de un lenguaje de programacion que le permite analizarse ası mismo
para, por ejemplo, saber que funciones tiene definida una clase) del lenguaje hace sencilla esta tarea. Un
ejemplo de como realizar introspeccion en Python es a traves de la funcion dir.
Figura 4.1: Logo de Python
4.1.1 exrex
La librerıa exrex4 realiza una unica accion, y esta es generar cadenas aleatorias a partir de una
expresion regular. Cuando tenemos una expresion regular, la librerıa estandar nos permite verificar si una
cadena cumple con esa expresion regular, pero no tenemos ningun metodo para generar de manera aleatoria
cadenas que cumplan una expresion regular dada. Justamente esto es lo que hace exrex.
La librerıa exrex anade una capa de analisis sobre la librerıa de expresiones regulares de Python,
y partiendo de una expresion regular, utiliza la librerıa de expresiones regulares de la librerıa estandar para
analizar la expresion regular y ası poder obtener la informacion que es util para saber que partes individuales e
independientes hay en la expresion regular. Una vez tiene estas partes independientes, debido a que estas estan
categorizadas en un conjunto de unos 10 elementos, dependiendo de la categorıa, exrex realiza las acciones
necesarias para cumplir con ellas. Una vez tiene la informacion necesaria, es capaz de generar la informacion
aleatoria de manera iterativa porque consigue un conjunto acotado gracias a la categorizacion descrita.
Aunque exrex funciona increıblemente bien para los casos mas simples, hay otros casos donde no
funciona tan bien, pero lo que esta claro es que cumple su cometido de manera eficaz. En el caso de BOA,
hemos utilizado esta librerıa para generar entradas cuando el usuario proporciona una expresion regular, ya
que esto aporta gran flexibilidad a la hora de describir como deberıan de generarse las entradas aleatorias para
un fuzzer. Ademas, tambien se ha utilizado para los casos en los que se incluye una expresion regular en una
gramatica dada que describe como deberıa de generarse las entradas.
4Repositorio de exrex: https://github.com/asciimoo/exrex
23
4. TECNOLOGIAS
4.1.2 Lark
El librerıa Lark5 sirve para analizar gramaticas de contexto libre, las cuales se explicaron, de manera
resumida, anteriormente en la subseccion 3.2.1.2. La principal caracterıstica de Lark es el analisis de gramaticas
escritas por el usuario y comprobar si una cadena pertenece a la gramatica analizada.
En nuestro caso, no hemos utilizado Lark tal y como esta pensado, sino que simplemente hemos
hecho servir la parte que en la que analiza una gramatica y crea una estructra interna con la informacion acerca
de la misma, y la parte en la que comprueba si una cadena pertenece o no a la gramatica no ha sido algo
que hayamos utilizado. Hemos utilizado la parte analıtica de la gramatica porque conseguıamos solucionar el
problema de tener que analizar una gramatica por nosotros mismos, y con Lark ya tenıamos la informacion
facilmente accesible para poder analizar las partes que nos fueran relevantes. En concreto, el posprocesamiento
que hemos realizado, una vez Lark nos devolvıa una estructura facil de recorrer y analizar, ha sido dividir
las posibles producciones, incluso teniendo en cuenta aquellas reglas que podıan tener varias producciones, y
construir un grafo teniendo en cuenta los sımbolos no terminales. Una vez construido este grafo, es facil recorrer
la gramatica eligiendo caminos aleatorios del grafo y generando la informacion necesaria cuando encontramos
sımbolos terminales (en el caso de utilizar una expresion regular, se utiliza exrex). Gracias a la construccion de
este grafo, podemos generar cadenas aleatorias que cumplen la gramatica.
4.2 Intel PIN
Intel PIN6 es una herramienta de instrumentacion binaria. Es muy similar a DynamoRIO, la cual ya
comentamos anteriormente en la subseccion DynamoRIO, pero una de las grandes diferencias es que Intel PIN
no es de codigo abierto, y no hay mucha informacion para profundizar acerca de como consigue su cometido,
al menos en ciertos detalles, ya que el procedimiento general sı que esta explicado.
Aunque Intel PIN es muy similar a DynamoRIO, tienen algunas diferencias. Una de las diferencias
mas destacables es que Intel PIN asegura ser posible realizar la instrumentacion binaria sobre un proceso ya
iniciado, lo cual nos hace pensar que, a diferencia de DynamoRIO, Intel PIN no parece utilizar una maquina
virtual (de hecho, en su documentacion [27] indican el procedimiento general que sigue para la instrumentacion
y no tiene nada que ver con una maquina virtual). Tiene sentido que Intel PIN tenga tanto control sobre los
procesos como para llegar a instrumentar un proceso ya iniciado, ya que es una herramienta creada por uno de
los principales fabricantes de CPU del mundo, y esto hace que su control en el dominio de la aplicacion sea
muy elevado. Otra de sus grandes desventajas es que solo tiene compatibilidad para las arquitecturas soportadas
en los procesadores Intel, y esto es ası porque seguramente el diseno e implementacion de la herramienta
haya tenido en cuenta estas arquitecturas, lo cual sea uno de los motivos por los que esta herramienta tiene
tanta flexibilidad, ya que se apoya en un diseno e implementacion muy poco generalizado, lo cual, aunque
normalmente es negativo, no tiene porque ser ası en este caso (eso sı, otros procesadores como AMD no
deberıan de poder utilizar esta herramienta) debido a que puede aprovechar caracterısticas concretas de estos
procesadores y realizar optimizaciones.
En la documentacion de Intel PIN, a este se le compara con un compilador Just-In-Time (JIT). Un
compilador JIT es aquel que realiza la compilacion en dos pasos, donde el primer paso es la transformacion5Repositorio de Lark: https://github.com/lark-parser/lark6Sitio web de Intel PIN: https://software.intel.com/content/www/us/en/develop/articles/pin-a-dynamic-binary-instrumentation-tool.html
24
4. TECNOLOGIAS
del codigo de alto nivel a codigo intermedio (e.g. codigo Java a bytecode), y donde el segundo paso es la
transformacion de codigo intermedio a codigo maquina, pero este segundo paso se suele hacer cuando es
necesario, y suele hacerse en el momento en el que se hacen las llamadas a funciones (a esta tecnica se la
conoce como compilacion dinamica), ya que algo comun es la compilacion de funciones cuando son necesarias
(es algo similar a lo que se hace en el patron de diseno lazy load). Pero Intel PIN no trabaja con un lenguaje
de alto nivel, ni si quiera con uno intermedio, sino directamente con el codigo maquina, por lo que el concepto
parece no casar muy bien. En la documentacion explican que es una analogıa, una manera de pensar en Intel
PIN, donde la transformacion que realiza es partiendo del codigo maquina que se quiere instrumentar, y la
compilacion a codigo maquina es la que se realiza con el codigo maquina interceptado del proceso objetivo
mas las instrucciones anadidas en el proceso de instrumentacion. Al final, como Intel PIN funciona es cogiendo
la primera instruccion ejecutable de un proceso (tambien podrıa coger la instruccion que se esta ejecutando en
un proceso ya iniciado, ya que es posible una vez entendemos como funciona Intel PIN), genera las nuevas
instrucciones , o realiza la accion de instrumentacion que sea necesaria, compila este nuevo resultado y realiza
la ejecucion en un nuevo proceso, pero Intel PIN se asegura de que el control le vuelva al terminar el bloque
compilado, para ası repetir el procedimiento de instrumentacion y no perder el control.
En BOA hemos utilizado esta herramienta para realizar la instrumentacion binaria necesaria para
realizar el analisis de cobertura de codigo.
Figura 4.2: Logo de Intel
4.3 Firejail
Firejail7 es una herramienta que permite ejecutar aplicaciones dentro de un sandbox con un alto nivel
de configurabilidad que permite mucha flexibilidad y un grano muy fino. Ademas, algo muy interesante es que
Firejail viene con una gran cantidad de perfiles preconfigurados, y por ello es posible descargarlo y ya poder
utilizarlo con aplicaciones tan complejas como un navegador, ya que limitar los recursos que un navegador
utilizar puede llegar a ser una tarea ardua.
Figura 4.3: Logo de Firejail
7Repositorio de Firejail: https://github.com/netblue30/firejail
25
4. TECNOLOGIAS
La manera en la que Firejail consigue hacer su funcion de sandbox es utilizando 2 caracterısticas del
kernel de Linux: los espacios de nombre [28], mas conocido como Linux namespaces, y seccomp-bpf [29].
Empezando por seccomp-bpf, se trata de una caracterıstica implementada en el kernel de Linux cuyo objetivo
es minimizar la exposicion del espacio de kernel al espacio de usuario, lo cual significa que los procesos en
el espacio de usuario tendran menos acceso a zonas del kernel, y esto lo hace limitando, filtrando, el acceso a
llamadas del sistema que no se utilizan o se utilizan de un modo determinado. Con seccomp-bpf es posible filtrar
estas llamadas al sistema que no se utilizan y ası evitar posibles problemas de seguridad (tanto al proceso al
que se le aplica como a todos sus hijos). Por otro lado, los espacios de nombre de Linux son una caracterısticas
del kernel de Linux que permiten darle a un proceso una vision distorsionada de los recursos, es decir, un
espacio de nombre (hay diferentes espacios de nombre para que cada espacio de nombre individual gestione un
recurso global, donde un ejemplo de recurso global podrıa ser la red) le hace creer a un proceso que es el unico
que tiene acceso a cierto recurso. Al final, el objetivo de los espacios de nombre es dar la posibilidad de crear
contenedores, donde los contenedores son una abstraccion que significa que un proceso tenga acceso a ciertos
recursos de manera individual, sin poder acceder a otros recursos fuera del contenedor.
Cabe destacar que debido a que Firejail utiliza caracterısticas del kernel de Linux, necesita tener
permisos de administracion (i.e. usuario root). Por ello, Firejail se utiliza haciendo uso del bit SUID, lo cual
significa que cuando se ejecute Firejail, se estara ejecutando como si lo hubiera hecho el usuario root. Este
tipo de binarios hace que las vulnerabilidades sean mucho mas crıticas, pues si se encuentra una vulnerabilidad
en Firejail que permite ejecutar acciones arbitrarias, estas acciones se realizarıan con permisos de usuario
administrador. Es algo que se critica de Firejail, pues es algo a tener muy en cuenta, y es vital actualizar la
herramienta para evitar este tipo de problemas.
Hemos utilizado Firejail para dar la posibilidad de crear un sandbox en las ejecuciones que se realicen
mientras se aplica fuzzing. Esto es algo que puede ser muy relevante a la hora de buscar vulnerabilidades, ya que
una vulnerabilidad podrıa hacer que el equipo en el que se este ejecutando BOA falle por dicha vulnerabilidad
y hayan danos (e.g. consumicion de recursos de manera ilimitada).
4.4 GitHub
GitHub8 es un servicio de alojamiento de proyectos que utiliza Git9 como sistema de control de
versiones.
GitHub ha sido la herramienta que hemos utilizado para almacenar y distribuir BOA. En GitHub
hemos creado los documentos necesarios para poder realizar las tareas necesarias con el fin de poder utilizar
BOA sin ningun problema (i.e. dependencias, instalacion, construccion, documentacion y ejemplos de ejecucion).
Por otro lado, tambien en el mismo repositorio de BOA hemos almacenado la documentacion del
proyecto creada con Sphinx10, la cual tiene muchos mas detalles de la implementacion, aunque tambien del
diseno, de BOA. Ademas, tambien hemos utilizado Read the Docs1112 para distribuir la documentacion creada
con Sphinx.
8Sitio web de GitHub: https://github.com/9Sitio web de Git: https://git-scm.com/
10Sitio web de Sphinx: https://www.sphinx-doc.org/en/master/11Sitio web de Read the Docs: https://readthedocs.org/12Documentacion de BOA en Read the Docs: https://boa-docs.readthedocs.io/en/latest/
26
4. TECNOLOGIAS
Figura 4.4: Logo de GitHub
Figura 4.5: Logo de Sphinx
4.4.1 GitHub Actions
GitHub Actions13 es una caracterıstica que GitHub que permite realizar Continuous Integration (CI),
o integracion continua, y Continuous Deployment (CD), o despliegue continuo, y que no estuvo disponible de
manera gratuita hasta 2019.
La CI se basa en que con cualquier cambio, es necesario ejecutar las pruebas para comprobar que el
cambio no haya introducido ningun error (esto intenta evitar la maxima de que cuando mas tarde se encuentre
un error, mayor sera el esfuerzo necesario por arreglarlo), mientras que el CD se basa en la comprobacion de que
los nuevos cambios son aptos para el entorno de produccion, por lo que se realiza la construccion del proyecto,
se realizan las pruebas, se distribuye la nueva version si no hubo ningun problema durante la ejecucion de las
pruebas, se monitoriza el resultado por parte de los usuarios y se vuelve a empezar. Estas dos metodologıas de
desarrollo estan cada vez mas extendidas a la hora de realizar cualquier proyecto y, gracias a GitHub Actions,
ahora cualquiera puede llevarlas a cabo de manera muy sencilla y sin necesidad de una infraestructura y niveles
de configuracion elevados.
Con BOA hemos utilizado GitHub Actions, y en concreto hemos utilizado CI. Cada vez que habıa
algun cambio, las pruebas se ejecutaban y se nos notificaba de los resultados. Gracias a esta metodologıa
pudimos evitar diferentes errores en varios puntos del desarrollo.
13Sitio web de GitHub Actions: https://github.com/features/actions
27
Capıtulo 5
Diseno
5.1 Introduccion
El diseno es la base de todo buen sistema, y es uno de los grandes pilares del presente trabajo. En este
capitulo se pretende tomar una serie de decisiones que encaminen de manera correcta el desarrollo de manera
que permitan cumplir una serie de objetivos. La materializacion de lo aquı explicado se vera reflejado en el
capıtulo Implementacion.
5.2 Enfoque
El enfoque a aplicar al analizador es algo determinante, ya que tendra consecuencias durante todo el
desarrollo. Como ya hemos comentado, el enfoque a aplicar al analizador sera el de utilizar analisis dinamico,
y mas en concreto se quiere llegar a un fuzzer.
Hay diferentes tipos de fuzzers (se explico anteriormente en Fuzzing), y el objetivo a lograr sera la
de disenar e implementar uno de caja gris, y mas concretamente estara basado en mutaciones y en gramaticas
(caracterısticas de un fuzzer lexico y sintactico). La razon de porque esta especificacion se justifica en el exito
que tiene esta configuracion a la hora de la busqueda de vulnerabilidades. Se tratara de un fuzzer de caja gris
porque estara guiado por la cobertura, ya que es una tecnica que ha demostrado ser eficaz y que tiene grandes
ventajas (e.g. identificacion de fallos unicos; util si se utiliza para la cobertura de codigo, ya que es una buena
metrica para los algoritmos de busqueda de caminos en la ejecucion). Por otro lado, comentar que utilizaremos
un algoritmo genetico, el cual hay que tener en cuenta en el diseno como se explica en la subseccion Uso
de Modulos como Dependencias, y este algoritmo no seguira el algoritmo CGF clasico, sino que se utilizara
un algoritmo genetico clasico como se explica en la subseccion Algoritmo Genetico, pues este permite la
aplicacion de una tecnica comun en este tipo de algoritmos como es el crossover, aunque ya hay ejemplos de
fuzzers que tambien lo aplican [30], pero no son numerosos.
No hay que perder de vista que lo que se busca es anadir soporte para cualquier tipo de modulo que
utilice el analisis dinamico, no solo fuzzers. El diseno e implementacion se materializara de esta manera, pero
el resultado final deseado es que cualquier tecnica pueda implementarse.
28
5. DISENO
Como conclusion, el enfoque a aplicar sera el analisis dinamico, y se implementaran modulos de
fuzzing (cabe destacar que, por ahora, no se podra utilizar esta tecnica con un enfoque de caja blanca, ya que
para ello necesitarıamos utilizar modulos de analisis estatico y analisis dinamico, lo cual entra dentro de la
seccion Limitaciones) para dotar de una funcionalidad mınima y comprobar el correcto funcionamiento. Ası,
obtendremos como resultado un analizador de proposito general en el que se pueden implementar modulos
con tecnicas basadas en analisis estatico o analisis dinamico.
5.2.1 Ambito
La definicion del ambito de actuacion del analizador es algo esencial, ya que este puede trabajar a
diferentes niveles. En concreto, cuando hablamos de tecnicas de analisis dinamico, se suele trabajar a nivel de
codigo fuente de alto nivel y ejecutable (este ejecutable, binario, suele ser el resultado de compilar el fichero
de codigo fuente de algo nivel), o a nivel binario (hay mas niveles, como el nivel de lenguaje intermedio, pero
no esta tan extendido como los citados).
Es necesario decidir cual sera el ambito de actuacion, pues el resto de decisiones tambien dependeran
de esta decision. Debido a que el diseno actual de BOA esta basado en un unico objetivo en lugar de varios, es
mas conveniente trabajar a nivel de binario, ya que la integracion entre el analisis estatico y el analisis dinamico
serıa mas sencillo por tener una relacion uno a uno a nivel de argumentos necesarios. Ademas, trabajar con
un ejecutable y tambien con un fichero de codigo, aunque aumentarıa la precision y darıa contexto, tambien
dificultarıa el analisis, pues serıa necesario realizar analisis sobre el fichero de codigo y el ejecutable. Al final,
no es que sea peor este enfoque que el de utilizar un binario, pero para el presente trabajo se toma la decision de
trabajar a nivel de binario por ser mas conveniente. Cabe destacar que, en caso de quererse, tambien se podrıa
aplicar este tipo de nivel, el de fichero de codigo mas ejecutable, pero todo el comportamiento recaerıa sobre la
implementacion concreta y no se podrıa generalizar al resto del analizador. Esta ultima opcion podrıa hacerse,
por ejemplo, procesando el fichero de codigo de algo nivel y generando el ejecutable, siendo este ultimo el que
se analizarıa.
5.3 Objetivos de Diseno
Los objetivos de diseno estan encaminamos a cumplir con los objetivos principales que se comentaron
en el capıtulo Motivacion y Objetivos, pero no hay que olvidar que estamos ampliando un proyecto ya existente,
y que este tiene tambien sus objetivos de diseno propios. Como partimos de una base ya existente, tenemos una
serie de principios de diseno existentes, y nuestra voluntad es no alterarlos, o alterarlos lo mınimo posible, para
minimizar la perdida de caracterısticas de BOA.
5.3.1 Objetivos de Diseno Previos
Como hemos comentado, BOA tiene una serie de objetivos de diseno existentes [6], los cuales vamos
a comentar someramente.
• Automatizacion: objetivo principal del proyecto, pues la busqueda de vulnerabilidades ha sido un proceso
tradicionalmente manual para llegar a encontrar aquellas vulnerabilidades mas profundamente arraigadas
29
5. DISENO
en los proyectos. Hoy en dıa es mas sencillo intentar la busqueda de vulnerabilidades con un proceso
automatico ya que se obtienen resultados muy eficaces, sobre todo facilita la tarea si se trata de grandes
proyectos. Por supuesto, este principio sigue siendo deseable ahora con el analisis dinamico.
• Modularidad: cuando mas modular sea un diseno, mas sencillo sera de extender. Es uno de los pilares
del diseno con BOA, y sigue siendo deseable ahora que vamos a aplicar analisis dinamico.
• Usabilidad: la usabilidad, o sencillez de uso, es lo que hace que los usuarios no tengan que invertir
muchos esfuerzos para poner la herramienta en marcha. Con BOA, todo lo que se necesita es un objetivo
a analizar y un fichero de reglas, de los cuales hay varios disponibles y bien documentados para conseguir
acercarnos a cumplir este objetivo.
• Extensibilidad: la modularidad favorece la extensibilidad, y es algo que es necesario en el proyecto
debido a que se quiere dar la posibilidad a los usuarios de anadir sus propios modulos implementando
sus propias tecnicas si no es que ya estan implementadas. Como ya se comento, uno de los objetivos es
permitir a los usuarios seguir pudiendo anadir sus propios modulos, ası que este principio de diseno es
deseable cuando se de soporte al analisis dinamico.
• Flexibilidad: proporcionar flexibilidad es algo esencial para que la usabilidad de los modulos sea elevada
y ası un usuario pueda, por ejemplo, alterar el comportamiento de un modulo con un cambio mınimo sin
tener que invertir demasiados esfuerzos. Una de las maneras en las que se consigue esta flexibilidad es
a traves de los parametros que se pueden definir en los ficheros de reglas de boa, y la razon de ello se
explica en la subseccion Fichero de Reglas.
• Personalizacion: es deseable que un usuario sea capaz de personalizar el comportamiento o formato de
ciertos elementos. Esto se consigue a traves de caracterısticas propias del paradigma de programacion
orientada a objetos, como son las interfaces y metodos abstractos, ya que estos permiten a los usuarios
realizar redefiniciones de aquellas partes que quieran personalizar.
• Independencia del lenguaje de programacion: se pretende que los ficheros de codigo analizados en el
analisis estatico tengan una independencia de su lenguaje de programacion con el analisis. Este objetivo
de diseno se mantiene debido a que el analisis dinamico se va a realizar a nivel de binario y no nos afecta.
5.3.1.1 Reusabilidad
Ademas de lo anterior, otro objetivo de diseno que se va a explotar en el presente trabajo (un ejemplo
se puede observar en la subseccion Uso de Modulos como Dependencias) y que ya cumplıa BOA desde un
principio es la reusabilidad, pero que no se habıa especificado previamente como objetivo de diseno.
La manera en la que se consigue la reusabilidad es gracias a las dependencias. De manera breve, las
dependencias son una caracterıstica de BOA que permiten definir, valga la redundancia, dependencias en un
modulo.
5.3.2 Nuevos Objetivos de Diseno
Por otro lado, surgen nuevos objetivos de diseno propios del presente trabajo. Estos nuevos objetivos
de diseno estan relacionados con el trabajo ya realizado y con la ampliacion del analisis dinamico.
30
5. DISENO
5.3.2.1 Refinamiento
Uno de los principales objetivos del presente trabajo es refinar la arquitectura actual, de manera que se
pueda aplicar tecnicas de analisis dinamico ademas de tecnicas de analisis estatico que ya se venıan aplicando.
Al final, esto se resume en que vamos a anadir soporte al analisis dinamico, pero este soporte se suma al del
analisis estatico, y por ello vamos a refinar el diseno actual.
Normalmente, cuando se crea una herramienta de analisis estatico o analisis dinamico, se centra en
una de las dos tecnicas (hay excepciones como puede ser crear un fuzzer de caja blanca, ya que necesita de
tecnicas de ambos tipos de analisis), pero en nuestro caso vamos a dar soporte a ambos tipos de analisis en una
herramienta, BOA, que se centra en la modularidad y que, gracias a eso, es posible realizar este refinamiento
sin que sea necesaria una gran refactorizacion del diseno actual, lo cual hace posible integrar ambos tipos de
analisis aunque no sea la norma.
5.3.2.2 Compatibilidad
Debido a que partimos de un proyecto que ya tiene un diseno y una implementacion, la compatibilidad
es algo a tener en cuenta, sobre todo el intentar preservar la compatibilidad hacia atras. La compatibilidad hacia
atras es un tipo de compatibilidad que indica que los nuevos cambios no afectan a lo ya realizado, y todo lo que
antes podıa hacerse, se seguira pudiendo hacer en el nuevo sistema. Con este objetivo de diseno buscamos que el
diseno e implementacion anterior relativos al analisis estatico sigan funcionando como ya venıan haciendolo,
incluso con los mismos ficheros de configuracion. Hay veces en los que la compatibilidad se cumple, pero
en cierto grado, y en nuestro caso el grado de compatibilidad roza el 100%, y no es completo porque hay
algunos cambios en el fichero de configuracion (estos se explican en la subseccion Fichero de Reglas), pero
son mınimos.
Debido a que perseguimos el cumplir el objetivo de compatibilidad, esto ayuda a preservar otros
objetivos previamente definidos en el proyecto como la modularidad, ya que estos no se veran afectados. Esto
podremos conseguirlo si las modificaciones sobre la arquitectura del sistema es una superposicion sobre la
antigua, de manera que se preserven todas las propiedades de la misma.
5.4 Modulos Principales
El diseno principal de BOA se ha basado en la construccion de una serie de modulos principales.
BOA ya tenıa definidos una serie de modulos principales, los cuales se describen someramente a continuacion:
• Modulos de busqueda de vulnerabilidades o de seguridad, o BOA Module (BOAM): el objetivo de estos
modulos es el de implementar alguna tecnica que realice la busqueda de vulnerabilidades (e.g. analisis
de manchas).
• Modulos de analisis del lenguaje de programacion o interoperabilidad, o BOA Parser Module (BOAPM):
el objetivo de estos modulos es proporcionar interoperabilidad entre el analizador y los modulos de
analisis, es decir, estos modulos son una especie de intermediarios que proporcionan informacion acerca
de los ficheros de codigo objetivo del analisis. Normalmente, estos modulos seran los que proporcionen
31
5. DISENO
alguna estructura de datos como un AST para realizar el analisis, por lo que tambien abstraera del
lenguaje de programacion concreto de manera parcial, no total, pues dependera del analizador realizado
el resultado de la estructura de datos a analizar (si se utiliza un analizador que tenga compatibilidad con
varios lenguajes de programacion, esto nos independizara del lenguaje de programacion, lo cual era un
objetivo de diseno y que conseguimos a traves de estos modulos).
• Modulos de ciclo de vida o BOA LifeCycle (BOALC): el objetivo de estos modulos es proporcionar un
mecanismo a traves del cual poder definir un flujo de trabajo. Estos modulos seran la manera en la que
se defina como se va a realizar la ejecucion del resto de modulos.
• Modulos de informe o BOA Report (BOAR): el objetivo de estos modulos es mostrar los resultados
del analisis, y estos modulos permitiran la personalizacion debido a que se podra especificar como se
muestran los resultados (e.g. mensajes, HTML).
• Modulos de severidad: el objetivo de estos modulos es definir una jerarquıa de niveles que, de manera
cuantitativa, clasifican la severidad de las vulnerabilidades encontradas por un analisis concreto.
Por otro lado, el nuevo diseno ha conducido a la necesidad de una serie de modificaciones y nuevos
modulos principales, y estos consisten en los modulos intermediarios o BOA Runner (BOAR).
5.4.1 Modulos Intermediarios
Los modulos intermediarios, o BOA Runner, son una serie de nuevos modulos que se han disenado
para dar soporte al analisis dinamico. Estos modulos, los cuales estan al mismo nivel jerarquico que los modulos
ya existentes, crean una nueva jerarquıa de modulos cuyo objetivo esta claramente definido, y este es permitir
la comunicacion entre los artefactos bajo analisis y el analizador. Esta nueva jerarquıa contiene los siguientes
elementos:
• Modulos de analisis del lenguaje de programacion o interoperabilidad, o BOA Parser Module (BOAPM):
explicados anteriormente en Modulos Principales. Debido a que estos modulos entran dentro de la
definicion de estos modulos, a nivel de diseno forman parte de esta nueva jerarquıa. Aun ası, es cierto
que hay que diferenciar estos modulos del resto, pues estos modulos son propios del analisis estatico, y
no se podran utilizar conjuntamente con el resto, que se utilizaran para implementar tecnicas de analisis
dinamico.
• Modulos de generacion de entradas o BOA Input (BOAI): el objetivo de estos modulos es generar, con
algun criterio, las entradas que se tendran que proporcionar a los binarios que se ejecuten con alguna
tecnica de analisis dinamico. Pueden haber muchos ejemplos de este tipo de modulos, y el mas simple de
ellos es un modulo que genere cadenas, o bytes, aleatorios. Otro ejemplo mas sofisticado podrıa ser un
modulo al que se le proporcione una especificacion del sistema bajo analisis y sea capaz de crear entradas
mas semanticas.
• Modulos de deteccion de fallos o BOA Fail (BOAF): el objetivo de estos modulos es detectar cuando una
ejecucion se deberıa considerar que ha fallado, o incluso poder diferenciar entre un fallo semantico o una
vulnerabilidad. Siempre que haya que ejecutar un binario, lo cual habra que hacer siempre en cualquier
tecnica de analisis dinamico, lo que se buscara es provocar fallos, y la deteccion de estos puede ser mas
32
5. DISENO
o menos semantica. Un ejemplo simple podrıa ser comprobar si el error de salida de la ejecucion en un
sistema Linux es diferente de 0, pero un ejemplo mas sofisticado podrıa ser comprobar si algun artefacto
generado es como deberıa de ser, como podrıa ser una imagen, ya que quiza detectar esto podrıa significar
un error en el programa. Estos modulos abren la puerta a la busqueda no solo de vulnerabilidades, sino
tambien de errores semanticos.
5.5 Elementos Concretos
Hay una serie de elementos concretos dentro de BOA que han necesitado de un diseno previo, y
este diseno es el que conducira al resultado final y que nos evitara problemas en el momento que se llegue
al desarrollo. Cuando antes se tomen las decisiones de diseno sobre estos elementos concretos, antes nos
podremos dar cuenta de si hay algun impedimento a nivel estructural entre los mismos o que puede dificultar
la conexion entre los diferentes componentes.
5.5.1 Fichero de Reglas
El fichero de reglas1 es el elemento principal para poder definir el comportamiento de BOA, ya que
es donde el usuario especificara que es lo que quiere hacer, y se trata de un fichero XML (la justificacion de
porque utilizar un fichero XML y no otro formato como JSON es debido a que la configuracion necesaria
en BOA se define de manera muy jerarquica y, en algunos casos, recursiva, por lo que el formato XML es
el mas conveniente, tanto para analizar como para recorrer como por legibilidad). Debido a que el presente
trabajo trata de una ampliacion, no de la creacion de un sistema, aquı solo comentaremos las nuevas opciones
y modificaciones que se han realizado sobre el fichero de reglas. La descripcion completa de las mismas ya se
describio con anterioridad en otro trabajo [6].
Debido a que ahora tendremos que dar soporte tanto a tecnicas de analisis estatico como analisis
dinamico, por ahora la interoperabilidad entre ambos tipos de analisis no estara disponible, y sera necesario
especificar el tipo de analisis. Para ello, se ha creado un nuevo atributo llamado analysis y que tendra que
especificarse de manera obligatoria en el elemento boa rules. Este atributo solo acepta los valores static o
dynamic.
Uno de los cambios que se han realizado, el cual ha sido debido por lo explicado en la subseccion
Modulos Intermediarios, ha sido el cambio de localizacion del elemento boa rules.parser2. En su lugar, ahora
este elemento tiene que estar contenido en el elemento boa rules.runners, de manera que con esto conseguimos
una integracion que sea mas correcta y ası el diseno tenga sentido. El elemento boa rules.runners tiene el
objetivo de contener la configuracion de los modulos intermediarios.
Por otro lado, dentro boa rules.runners tambien tenemos los elementos boa rules.runners.inputs
y boa rules.runners.fails, los cuales son la configuracion de los modulos de generacion de entradas y de
generacion de fallos, respectivamente. Estos modulos son los nuevos modulos del analisis dinamico, los cuales
seran obligatorios cuando el atributo boa rules.analysis tenga el valor dynamic (cuando el valor sea estatic, el
1Ejemplo documentado del fichero de reglas: https://github.com/cgr71ii/BOA/blob/master/boa/rules/
EXAMPLE-static_or_dynamic-verbose_name.xml2El signo ‘.’ sirve para indicar la separacion entre elementos o elementos y atributos
33
5. DISENO
elemento boa rules.runners.parser sera obligatorio). Los elementos y atributos que ambos elementos pueden
contener, boa rules.runners.inputs y boa rules.runners.fails, son los siguientes (utilizaremos ‘*’ para referirnos
a boa rules.runners.{inputs,fails}):
• Elemento *.module name: este elemento es el que tiene que contener el nombre del fichero que contiene
el comportamiento para el modulo.
• Elemento *.class name: este elemento es el que tiene que contener el nombre de la clase del fichero
especificado en el elemento *.module name que contiene el comportamiento a ejecutar para el modulo.
• Elemento *.args sorting: este elemento es la manera en la que especificamos si es necesario realizar
un posprocesamiento para ordenar los elementos definidos en *.args. Esta opcion intenta subsanar una
limitacion de implementacion de la librerıa utilizada para procesar el fichero XML [6]. Normalmente no
sera necesario activar esta opcion al no ser que se realice un posprocesamiento manual de los argumentos
y se confıe en que el orden definido en el XML es el mismo en el que llega a los modulos (i.e. no se
mantiene la monotonıa).
• Elemento *.args: este elemento es la manera a traves de la cual se le proporciona la configuracion a los
modulos. Su explicacion detallada esta en la subseccion Argumentos.
5.5.1.1 Argumentos
Los argumentos han sido la eleccion a traves de la cual realizar la configuracion de los modulos BOAI
y BOAF. Esto se ha hecho de esta manera para dotar de la mayor flexibilidad posible a la hora de realizar la
configuracion, pero como parte negativa tiene que requiere de un mayor trabajo por parte del usuario y provoca
un mayor acople entre configuracion y modulo, pues una configuracion seguramente no se pueda extrapolar a
un modulo diferente debido a los argumentos.
La flexibilidad que proporcionan los argumentos radica en su definicion recursiva. Los argumentos
son un elemento especial dentro del fichero de configuracion que se pueden definir en diferentes elementos
(e.g. en todos los elementos de boa rules.runners, en todos los elementos de boa rules.modules). Como hemos
dicho, son un elemento especial, y eso es debido a que se pueden definir de manera recursiva, y los elementos
que se pueden definir de manera recursiva son los siguientes:
• Elemento element: este elemento es la manera en la que se pueden definir valores primitivos. Se trata de
un elemento con etiqueta de opertura pero sin etiqueta de cierre.
• Elemento list: este elemento es la manera en la que se puede definir una serie de elementos element,
list y dict (combinados o de un solo tipo, se permite cualquier configuracion), donde se supone que
todos contienen un mismo significado semantico (e.g. numeros, animales, nombres) y no es necesario
diferencia unos de otros, sino que todos se procesaran de la misma manera (al no ser que se realice algun
preprocesamiento para diferenciarlos debido a que no podıan diferenciarse por no saber previamente el
numero de elementos que habrıan).
• Elemento dict: este elemento es la manera en la que se puede definir una serie de elementos diferenciables
unos de otros. La manera de diferenciar unos de otros sera poniendoles un nombre, igual que sucede con
los diccionarios de Python o, en general, con las tablas hash.
34
5. DISENO
• Atributo name: este atributo puede estar presente en los elementos element, list y dict. Tendra que estar
presente cuando el elemento padre del elemento en el que este este atributo sea un elemento dict.
• Atributo value: este atributo solo podra estar presente en el elemento elemento, y siempre sera obligatorio
que este presente. El valor de este atributo sera el que especifique la configuracion actual.
Lo unico a tener en cuenta cuando se utilizan los argumentos es que este se tiene que inicializar con
un elemento dict, y una vez definido, dentro del elemento dict se pueden definir tantos elementos como se
quiera que sean de los elementos permitidos. Una vez BOA los analice, le proporcionara estos elementos en
forma de diccionario a los modulos de los elementos donde se hayan definido (e.g. BOAM, BOAI, BOAF).
5.5.1.2 Otros Cambios
Tambien se han producido otros cambios que se pueden considerar menores, y que se han introducido
debido a que ahora BOA tendra el componente dinamico debido al analisis dinamico.
El principal cambio menor que ha habido esta relacionado con las variables de entorno, las cuales
ahora se podra especificar si se cargan del entorno del usuario, si se les proporciona un valor que se cargara en
la ejecucion de los modulos (i.e. no se cargara en el entorno actual del usuario) y si son variables de entorno
obligatorias u opcionales.
5.5.2 Uso de Modulos como Dependencias
La reusabilidad es un objetivo de diseno que, siempre que sea posible, deberıa de ser deseable. En
el caso del presente trabajo es un objetivo de diseno, pues se quiere realizar un diseno en el que se utilice un
bloque basico que se encargue de realizar las tareas basicas del fuzzing, es decir, un fuzzer basico y generico,
y otro modulo en el que se pueda realizar la gestion de comportamientos mas avanzados (este modulo mas
avanzado podrıa hacerse de diferentes formas, y en nuestro caso vamos a utilizar un algoritmo genetico, lo cual
se explica en la subseccion Algoritmo Genetico, pero perfectamente se podrıa intercambiar por otro algoritmo
como el visto en la subseccion AFL, el cual se utiliza en la herramienta AFL).
BOA tiene una caracterıstica muy util, la cual es el uso de dependencias entre modulos. El uso de
dependencias entre modulos ofrece la posibilidad de disenar una estructura modular jerarquica que permite
obtener resultados de unos modulos antes que de otros y obtener estos resultados por el modulo que definio la
dependencia. La manera en la que se consigue hacer funcionar este mecanismo es a traves de la construccion
de un grafo acıclico dirigido, o Directed Acyclic Graph (DAG).
Ademas de lo dicho, otra posibilidad de utilizar las dependencias, la cual es la que se va a explotar
en el presente trabajo, es dejar que el modulo que definio la dependencia pueda controlar directamente como
se comporta el modulo que es dependencia (i.e. delegacion de comportamiento del modulo que es dependencia
en el modulo que definio la dependencia), y esto es como si el modulo que definio la dependencia estuviera
actuando tambien a modo de ciclo de vida, por lo que el modulo BOALC normalmente no se querra que
afecte al modulo que es dependencia. Esta forma de utilizar las dependencias no estaba contemplada en el
diseno anterior de BOA, pero para el presente trabajo si que lo estara pues generaliza el funcionamiento de las
dependencias y no entra en conflicto con el funcionamiento actual de las mismas.
35
5. DISENO
Respecto al diseno de los modulos que mas adelante se implementaran proponemos, como hemos
comentado anteriormente de manera breve, un diseno donde esten los siguientes modulos (se puede observar
la interaccion entre los mismos en la figura 5.1):
1. Modulo simple sin dependencias: este modulo simple sera el que sea mas general y aplique fuzzing con
un unico objetivo. No deberıa de tener dependencias de otros modulos y su configuracion tendra que ir
destinada a un unico objetivo de fuzzing, no a varios como sucede en el algoritmo CGF (i.e. debera de
ejecutar un unico sistema con una unica entrada).
2. Modulo no simple con dependencias: este modulo no simple sera el que tenga un comportamiento
mas definido y que tendra como objetivo la ejecucion multiple del objetivo bajo analisis. Tendra como
dependencia al modulo simple, y tendra que realizar la ejecucion de objetivo bajo analisis utilizando este
modulo, por lo que tendra que ser este modulo no simple el que tenga el control del comportamiento del
modulo simple para realizar la ejecucion tantas veces como sea necesario. Este modulo no simple tendra
que implementar un algoritmo donde hayan multiples ejecuciones del mismo objetivo bajo analisis como
el algoritmo CGF. En concreto, se implementara un algoritmo genetico como se explica en la subseccion
Algoritmo Genetico.
Figura 5.1: Interaccion entre los nuevos modulos
36
5. DISENO
5.5.3 Algoritmo Genetico
Como ya se ha comentado en el presente capıtulo varias veces, vamos a emplear un algoritmo
genetico para la implementacion del fuzzer, y esto es algo que hay que tener en cuenta en la etapa de diseno
como se explica en la subseccion Uso de Modulos como Dependencias. El enfoque del algoritmo genetico que
vamos a implementar es el tradicional, con algun matiz, y es el que vamos a explicar y a detallar, pero los
detalles concretos de implementacion se comentaran mas adelante en el capıtulo Implementacion.
Un algoritmo genetico es un algoritmo evolutivo, los cuales son un conjunto de algoritmos que tratan
de solucionar problemas de busqueda mediante la optimizacion, es decir, que intenta maximizar o minimizar
alguna metrica para buscar la solucion optima en un espacio de busqueda. La razon por la que se llaman
algoritmos evolutivos es debido a que este tipo de algoritmos se basan en la evolucion de los seres vivos, ya que
los seres vivos presentan maneras impresionantes de como millones de anos han favorecido a la resolucion de
problemas complejos. En concreto, un algoritmo genetico se basa en la manera en la que la genetica funciona,
lo cual de manera resumida se basa en que un padre y una madre tienen un hijo, el cual tiene, aproximadamente,
un 50% de los genes de ambos progenitores.
Entrando mas en detalle, hablamos de individuos que forman parte de una poblacion en lugar de
madre, padre e hijo. Cada individuo tiene un material genetico, el cual se denomina Acido DesoxirriboNucleico
(ADN). El ADN esta formado por cromosomas, que, a su vez, se divide en componentes independientes y que
tienen una representacion atomica, los cuales se llaman genes (la posicion de estos genes es relevante, ya que
posiciones diferentes pueden expresar una informacion radicalmente distinta, y dicha posicion se denomina
locus). La union de los genes forman un cromosoma, y la union de los cromosomas es el ADN de un individuo
(estos terminos biologicos son relevantes para comprender el origen del algoritmo genetico, y se puede observar
de una manera mas grafica en la figura 5.2). Cuando hablamos de material genetico hay que diferenciar entre
genotipo y fenotipo, los cuales estan relacionados como se puede observar en la figura 5.3. El genotipo es
lo que acabamos de comentar, y es el valor de los genes de un cromosoma (esto se puede ver como el valor
de los nucleotidos, configuracion la cual representa un gen y esto, a su vez, representa una caracterıstica del
individuo). Por otro lado, esta el fenotipo, que es la representacion fısica del genotipo, es decir, como se observa
el comportamiento del genotipo fısicamente, en la naturaleza (al final, se diferencian en que el genotipo es el
fenotipo sin tener en cuenta los cambios del individuo por el ambiente). En muchas ocasiones el fenotipo se
puede obviar debido a que solo nos interesa el valor numerico de los genes (e.g. el algoritmo mas comun para
el entrenamiento de las redes neuronales es back-propagation, pero tambien se puede utilizar un algoritmo
genetico para entrenar los pesos de la red, para lo cual solo nos importa el valor numerico del genotipo [31]),
pero en otros casos no. Un ejemplo donde el fenotipo no se puede obviar es en el caso de la generacion de
polıgonos unidos aleatoriamente con el objetivo de que la union de dichos polıgonos generen un “coche”
valido, y el genotipo es la representacion de ese “coche” recorriendo un circuito [32]. Otro posible ejemplo,
mas cercano a la realidad, somos nosotros mismos, los seres humanos, donde nuestro genotipo es nuestro
material genetico en crudo, mientras que nuestro fenotipo es la representacion de nuestro caracter, nuestra
personalidad, ya que en ello influye tanto nuestras experiencias (i.e. el ambiente) como nuestros genes, los
cuales nos predisponen a diferentes acciones, decisiones, etc. En nuestro caso, al aplicarlo a nuestro fuzzer, nos
interesara tanto el genotipo como el fenotipo, donde el genotipo seran las entradas que generemos, y el fenotipo
el resultado de aplicar la entrada generada sobre el sistema bajo analisis (e.g. si ha fallado, el nivel de cobertura
de codigo alcanzado, tiempo de ejecucion, si se habıa recorrido anteriormente el mismo camino o no). En
muchas ocasiones, la representacion del fenotipo no es sencilla, pero en nuestro caso es facil identificarlo.
37
5. DISENO
Figura 5.2: Estructura basica del ADN
Genotipo
Fenotipo
Ambiente
+ =
Figura 5.3: Relacion entre genotipo y fenotipo
38
5. DISENO
El esquema general de un algoritmo genetico, el cual se puede observar en el algoritmo 3, consta de
los siguientes puntos [33]:
1. Creacion de poblacion inicial: se generan los N primeros individuos, normalmente de manera aleatoria,
aunque tambien se puede proveer una primera generacion para obtener resultados mas rapidamente.
2. Evaluacion de los individuos: calculo de recompensa, o fitness, para cada individuo. Hay que evaluar
que tan bien realiza cada individuo su tarea, y esto normalmente esta asociado al fenotipo. Para ello,
es necesaria una funcion de fitness, la cual puede ser simple, aunque en muchas ocasiones no lo sera,
o incluso puede llevarnos al caso en el que pensemos que tengamos una buena funcion de fitness y en
realidad nos este afectado el efecto cobra (i.e. circunstancia en la que pensamos que hemos dado con la
solucion pero la funcion de fitness que hemos definido se puede optimizar mas alla del problema inicial
que habıamos planteado, haciendo que aunque en principio pareciera que el problema convergıa, diverja
al final [34]) y en realidad no estemos solucionando el problema que queremos solucionar, y es que la
realidad es que la obtencion de esta funcion no suele ser trivial aunque lo parezca a primera vista.
3. Mientras no se hayan generado los nuevos N individuos de la poblacion:
a) Seleccion de los mejores individuos: con la intencion de generar un nuevo individuo, se obtienen
los mejores individuos, lo cual no significa que los “peores” no sean elegidos, sino que tendran
una menor probabilidad. Hay muchas estrategias para la seleccion (e.g. ranking, torneo), pero
normalmente se utiliza el algoritmo Roulette Wheel Selection, el cual se basa en el concepto de
una ruleta donde el area circular se divide de manera proporcional al resultado de la funcion de
fitness, de manera que todos los individuos pueden salir escogidos, pero a mayor resultado de
fitness, mayor probabilidad de ser escogido. La seccion se basa en la “supervivencia del mas apto”,
y esto se decido a traves de un proceso cuantitativo, el cual sera mas o menos justo en funcion de
la funcion de fitness. La cantidad de individuos resultantes de la seleccion suelen ser dos, y de ahı
la analogıa con el padre y la madre, pero no siempre es ası y pueden haber casos en los que, debido
a la definicion del problema a resolver, se escojan mas individuos.
b) Generacion del nuevo individuo:
1) Funcion de union o crossover: una vez escogidos los “padres”, hay que obtener un unico
individuos uniendo el material genetico de ambos. Hay muchas estrategias posibles, y algunas
estrategias funcionan mejor para unos problemas y peor para otros. Una estrategia muy comun
es escoger un gen del padre y otro de la madre de manera intercalada hasta obtener el ADN
del hijo.
2) Funcion de mutacion o mutation: hasta donde conocemos, los seres vivos sufren mutaciones,
las cuales pueden ser positivas (e.g. evolucion del ser humano hasta que ha podido caminar
erguido) o negativas (e.g. enfermedades geneticas). Debido a ello, y sobretodo con el afan de
evitar caer en el problema de no tener la suficiente informacion genetica de los “padres” como
para tener todos los valores del dominio de los genes posible (e.g. si los genes de los padres
estan formados por letras, y ningun individuo de la generacion actual tiene la letra ‘z’, la letra
‘z’ nunca estara en ningun individuo, ası que el dominio no esta completo con los individuos de
la generacion ni lo estara en las proximas si no se aplican mutaciones), se realizan mutaciones,
las cuales suelen tener una baja probabilidad de suceder.
4. Volver al paso 2 hasta realizar las epocas que se hayan definido o se cumpla el criterio elegido. Una epoca
es realizar este procedimiento una vez de manera completa.
39
5. DISENO
Algoritmo 3: Algoritmo GeneticoInput: Population Length L
Input: Max. Epochs E
1 population← create new population(L)
2 epoch← 0
3 while epoch < E do4 f itness← evaluate population(population)
5 new population← []
6 for individual← 1 to population do7 parents← selection(population, f itness)
8 new individual← crossover(parents)
9 new individual← mutation(new individual)
10 new population[individual]← new individual
11 epoch← epoch+1
12 end13 population← new population
14 end
Entre los diferentes matices que anadimos que se diferencian del algoritmo genetico clasico es el
uso de varias funciones de mutacion. Esta decision incrementa la probabilidad de generar entradas, a traves
de la mutacion, que acaben en una entrada que provoque el descubrimiento de algun fallo mas que si solo
utilizaramos una unica funcion de mutacion (esto es ası porque una unica funcion de mutacion podrıa quedarse
en un conjunto de entradas demasiado restrictivo, como podrıa ser el ejemplo de una unica funcion de mutacion
que simplemente cambiara un byte por otro, y el conjunto de entradas generado por esta unica funcion serıa
demasiado restrictivo porque, por ejemplo, nunca se generarıan entradas de longitud mas corta que la longitud
mınima de la funcion de crossover ni mas grandes que la longitud mas larga de la funcion de crossover).
Por ultimo, comentar que la razon de elegir un algoritmo genetico en lugar de otro tipo de algoritmo
es debido a su alta eficacia a la hora de buscar vulnerabilidades. Es un algoritmo que ha sido muy utilizado en
los ultimos anos por las herramientas mas extendidas de fuzzing [24], y con bastante exito [7].
5.6 Arquitectura Software
La arquitectura de BOA no ha variado mucho de la original, la cual ya vimos en la figura 2.1. La
nueva arquitectura anade un nuevo tipo de modulo, el cual es el modulo intermediario que se detallo en la
subseccion Modulos Intermediarios. Este nuevo tipo de modulo se subdivide en modulos que se aplican para
el analisis estatico y modulos que se aplican para el analisis dinamico. El cambio, a nivel de diseno ha sido
simple, ya que los objetivos de diseno que BOA habıa definido en el momento en el que se inicio el proyecto
favorecen este tipo de cambios, sobretodo la modularidad con la que se definio la arquitectura inicial. La nueva
arquitectura se puede observar en la figura 5.4.
40
5. DISENO
Figura 5.4: Arquitectura software de BOA
5.7 Limitaciones
El analisis estatico sufre de una gran limitacion, la cual es debido al Teorema de Rice [6]. Aunque en
el caso mas simple del analisis dinamico no tengamos la limitacion del Teorema de Rice (si se utiliza un analisis
mas semantico en la tecnica empleada, el problema asociado a este teorema surge), tenemos otras limitaciones.
En el caso mas simple del analisis dinamico, tendremos que generar entradas, y como estamos
en el caso mas simple, vamos a imaginar que el sistema bajo analisis tiene un conjunto finito de entradas.
En este caso, aunque el conjunto tenga un numero muy elevado de elementos, y siempre suponiendo un
comportamiento determinista por parte del sistema, se podra comprobar si hay vulnerabilidades de manera
que las propiedades de robustez (i.e. no obtenemos falsos negativos, lo cual es lo mismo que decir que nuestro
analizador encuentra todas las vulnerabilidades, lo cual incluye a aquellas que no lo son pero se han detectado
como tal) y completitud (i.e. no obtenemos falsos positivos, lo cual es lo mismo que decir que nuestro analizador
nunca encuentra vulnerabilidades que en realidad no lo son, lo cual no incluye a todas las vulnerabilidades
que realmente lo son) se cumplan, lo cual indica que encontramos todas las vulnerabilidades que hay sin
equivocarnos en su deteccion (suponemos que tenemos un sistema ideal para la deteccion de vulnerabilidades).
Ahora bien, incluso en este caso, totalmente atıpico, la explosion combinatoria puede ser muy elevada: si
tenemos un sistema que devuelve la letra del DNI, y suponemos que la gestion de la entrada de la longitud de
los numeros del DNI es correcta, y ademas que nuestro analizador es consciente de estos lımites de longitud, la
cantidad de entradas diferentes que habrıa que generar para estar seguros de que no hay ninguna vulnerabilidad
41
5. DISENO
es de 264 = 18.446.744.073.709.551.616, suponiendo que son necesarios 8 numeros, lo cual corresponde a 8
bytes en codificaciones tıpicas como UTF-8, lo cual corresponde a 64 bits. Esto, si suponemos que tardamos
1 nanosegundo por cada ejecucion completa de nuestro analizador para comprobar una entrada, se traduce en,
aproximadamente, 600 anos de ejecucion.
En la practica, la exploracion de todo el espacio de entradas es inviable, y por ello se realizan analisis
mas inteligentes. Aun ası, lo normal es tener una explosion combinatoria de casos a probar, y lo normal es tener
un conjunto infinito de posibles entradas, no finito, y aquı es donde reside una de las mayores limitaciones del
analisis dinamico, la cual se intenta paliar con busquedas mas inteligentes del espacio de posibles entradas.
Ya centrandonos en las limitaciones concretas del diseno de BOA, una de las mayores limitaciones
del diseno actual es la imposibilidad de juntar ambos tipos de analisis en una unica ejecucion. Aunque pueda
parecer irrelevante, esto puede ser util, pues hay tecnicas que necesitan de ambos enfoques. Un ejemplo podrıa
ser un fuzzer de caja blanca, el cual necesita de ambos tipos de analisis.
42
Capıtulo 6
Implementacion
6.1 Introduccion
Si el diseno era uno de los grandes pilares del presente trabajo, el otro lo es la implementacion. En
el presente capıtulo se van a materializar las decisiones tomadas y explicadas durante el presente trabajo. Es
cierto, que a nivel de usuario puede parecer que no han habido muchos cambios, como se puede observar en la
figura 6.1, pero todos los cambios que han habido van a explicarse con detalle.
Figura 6.1: Ejecucion de BOA (modulo de analisis dinamico)
6.1.1 Nuevos Elementos en la Estructura del Proyecto
La estructura del proyecto se mantiene casi en su totalidad, pero debido a los cambios introducidos,
hay nuevos elementos y otros se han visto modificados. Los cambios se detallan a continuacion:
• Directorio boa/grammars: directorio en el que se deberıan de poner las gramaticas para que puedan ser
43
6. IMPLEMENTACION
correctamente leıdas por el modulo que las procesa, concretamente con Lark. El modulo en cuestion se
detalla en la subseccion Modulo Grammar Lark.
• Directorio boa/modules/static analysis: directorio que contiene los modulos BOAM que anteriormente
estaban en el directorio boa/modules. Los modulos BOAM que se almacenan en este directorio son
aquellos que utilizan analisis estatico.
• Directorio boa/modules/dynamic analysis: nuevo directorio donde se almacenan los modulos BOAM
que utilizan analisis estatico, entre otros ficheros relevantes para las tecnicas de analisis dinamico.
• Directorio boa/modules/dynamic analysis/instrumentation: directorio donde se almacenan los ficheros y
directorios relacionados con la instrumentacion para las tecnicas de analisis dinamico. En concreto, lo
que se almacenan son pintools y todo lo necesario para su construccion.
• Fichero boa/modules/dynamic analysis/instrumentation/modules.txt: fichero que lista el nombre de los
pintools presentes en el mismo directorio en el que esta este fichero.
• Fichero boa/modules/dynamic analysis/instrumentation/make.sh: script que permite construir los pintools
de manera automatica. Tiene un parametro obligatorio, el cual es para indicar la ruta a la instalacion de
Intel PIN, y otro opcional para indicar la arquitectura con la que compilar los pintools, donde la principal
utilidad de esta opcion opcional es la de construir los pintools para sistemas de 32 o 64 bits.
• Directorio boa/runners: nuevo directorio que contiene todos los modulos intermediarios.
• Directorio boa/runners/static analysis: nuevo directorio que contiene los diferentes tipos de modulos
intermediarios relacionados con el analisis estatico.
• Directorio boa/runners/static analysis/parser modules: directorio que contiene los modulos BOAPM
que anteriormente estaban en el directorio boa/parser modules.
• Directorio boa/runners/dynamic analysis: nuevo directorio que contiene los diferentes tipos de modulos
intermediarios relacionados con el analisis dinamico.
• Directorio boa/runners/dynamic analysis/inputs modules: nuevo directorio que contiene modulos BOAI.
• Directorio boa/runners/dynamic analysis/fails modules: nuevo directorio que contiene modulos BOAF.
6.2 Soporte al Analisis Dinamico
Como se ha comentado en numerosas ocasiones durante el presente trabajo, el objetivo es dar soporte
a modulos que ejecuten tecnicas de analisis dinamico junto a los modulos que ya ejecutan tecnicas de analisis
estatico. Hay que admitir que, gracias a los principios sobre los que BOA esta basado, el anadir este soporte
no ha sido una tarea ardua, lo cual quiza indica que se esta cumpliendo el principio Keep It Simple, Stupid!
(KISS), pero esto no es seguro.
Los principales cambios que ha sido necesario hacer se comentan a continuacion.
• Se ha anadido el soporte necesario para el nuevo formato de las reglas, el cual se explica en la subseccion
Fichero de Reglas.
44
6. IMPLEMENTACION
• Los modulos BOALC pueden, opcionalmente, especializarse en modulos de analisis estatico o analisis
dinamico.
• Se han anadido las clases con metodos abstractos necesarios para los modulos BOAI y BOAF. Estas
clases contienen metodos para obtener entradas en el caso de los modulos BOAI y para detectar fallos en
el caso de los modulos BOAF.
• Se distribuyen las instancias de los modulos BOA Runner (BOAR) a sus correspondientes BOAM. Los
modulos BOAPM pasan ahora a formar parte de los modulos intermediarios junto con los modulos BOAI
y BOAF.
• Se han anadido nuevas pruebas para probar modulos de analisis dinamico.
6.3 Modulos de Deteccion de Fallos
Estos modulos tienen como objetivo el detectar cuando ha habido un fallo en la ejecucion del binario
bajo analisis. Debido a que el caso de uso que vamos a emplear no es complejo, nos ha bastado con un unico
modulo de deteccion de fallos.
6.3.1 Modulo Exit Status
Este modulo tiene como objetivo detectar si la ejecucion de un binario ha tenido exito o no en funcion
del codigo de salida, o de error, del mismo. Esta es una manera muy sencilla de detectar si ha habido algun
error, y es una simple comprobacion donde si el codigo de error es 0 no ha habido ningun error y si es distinto,
ha habido algun error. Este enfoque no esta falto de desventajas, a pesar de que es uno de los mas empleados
para la deteccion, las cuales comentamos a continuacion:
• Hay muchos sistemas que, independientemente del resultado, siempre devuelven un codigo de error 0.
En estos casos, no se podran detectar errores con este enfoque.
• Las senales son un buen mecanismo para detectar vulnerabilidades y errores, y estas, en las terminales
mas tıpicas, se reflejan a traves del codigo de error. Para la mayorıa de terminales (e.g. bash, zsh), se
utiliza la formula 128 + n, donde n es la senal, como codigo de error para detectar si una senal ha
provocado la finalizacion de la ejecucion. Esto, en la mayorıa de ocasiones es ası, y es una manera
muy efectiva para detectar vulnerabilidades (normalmente la senal SIGSEGV, que es la senal 11 y que
las terminales devuelven como codigo 139, indican una vulnerabilidad buffer overflow). Pero debido a
que el desarrollador es quien decide que codigos de error se utilizan para una finalizacion de ejecucion
controlada, lo cual es muy habitual para indicar diferentes tipos de errores (BOA es un caso como
se puede ver en el anexo Codigos de Error en BOA), este puede hacer que un codigo de salida que
normalmente esta reservado para las senales, se utilice para indicar otra cosa, por lo que se solapan
ambas situaciones y no somos capaces de detectar esta situacion con este modulo, lo cual nos lleva a
falsos positivos.
45
6. IMPLEMENTACION
6.4 Modulos de Generacion de Entradas
Estos modulos tienen como objetivo el generar entradas para que, posteriormente, otro tipo de modulo,
no este, se la proporcione como argumentos. La manera en la que se generen entradas puede variar en muchos
sentidos, y debido a ello hemos implementado tres modulos genericos que pueden utilizarse para diferentes
situaciones, y en concreto los modulos que hemos implementado se basan en el fuzzing lexico y el sintactico.
6.4.1 Modulo Random String
Este modulo tiene como objetivo la generacion de cadenas aleatorias. Las cadenas generadas constan
de letras American Standard Code for Information Interchange (ASCII) mayusculas y de dıgitos numericos.
Es un modulo muy simple que seguramente no se vaya a utilizar en la mayorıa de casos, pero es util para la
realizacion de pruebas.
Este modulo solo tiene dos opciones, las cuales son para indicar la longitud de la cadena a generar
y si la longitud indicada es fija o la longitud maxima de un valor aleatorio, donde el valor por defecto es 10
caracteres.
6.4.2 Modulo Input Seed
Este modulo tiene como objetivo la generacion de cadenas a partir de cadenas semillas proporcionadas
por el usuario. Es muy tıpico a la hora de aplicar tecnicas de analisis dinamico el partir de un conjunto de
entradas validas (este modulo no tiene en cuenta el que sean validas o no, por lo que se puede utilizar para
ambos enfoques), las cuales normalmente se veran modificadas en algun punto de la ejecucion de la tecnica.
Las opciones que incluye son las siguientes:
• Conjunto de cadenas semilla, donde es necesario proporcionar un mınimo de 1 cadena.
• Opcionalmente, y por defecto activado, generacion de cadenas aleatorias:
• Probabilidad de proporcionar una cadena aleatoria en lugar de una cadena semilla. La probabilidad
por defecto es del 10%.
• Longitud maxima de las cadenas aleatoria, donde la longitud sera un numero aleatorio entre 0 y
este valor. La longitud maxima por defecto es de 100 caracteres.
• Opcionalmente, limitar la cantidad de cadenas aleatorias generadas en total. Por defecto, no estan
limitadas y se generaran cadenas aleatorias siempre que se cumpla la probabilidad indicada.
• Opcionalmente, indicar un conjunto de caracteres validos a generar. Esto se realiza a traves de una
expresion regular, ası que se puede ser muy especıfico. Esto puede ser util para limitar caracteres
no deseables de la codificacion UTF-8.
Aunque simple, este modulo es muy similar al comportamiento que suelen tener fuzzers como AFL
con las entradas, ya que nos da informacion acerca de las entradas que acepta el binario bajo analisis, y eso es
muy util.
46
6. IMPLEMENTACION
6.4.3 Modulo Grammar Lark
Este modulo tiene como objetivo la generacion de cadenas aleatorias a partir de una gramatica, lo cual
hace que la configuracion de nuestro fuzzer sea grammar-based si se utiliza este modulo para la generacion
de entradas (este es un ejemplo en el que se puede ver claramente que con BOA es posible anadir/quitar
caracterısticas en funcion de los diferentes modulos utilizados, ya que este modulo es totalmente compatible,
como los otros modulos BOAI con los modulos BOAM de fuzzing que se explican en la seccion Modulos de
Seguridad). Las opciones que incluye son las siguientes:
• Lımite soft, el cual tiene un valor por defecto de 100. Este valor se explica en la subseccion Lımites Soft
y Hard en el Recorrido de las Gramaticas.
• Lımite hard, el cual tiene un valor por defecto de 200. Este valor se explica en la subseccion Lımites Soft
y Hard en el Recorrido de las Gramaticas.
• Longitud maxima de las cadenas generadas por exrex para las expresiones regulares de las producciones
que las utilicen en la gramatica.
6.4.3.1 Asignacion de Probabilidad a las Producciones
Es muy comun que en la estructura de un lenguaje hayan elementos mas comunes que otros, y esto se
puede indicar a traves de la asignacion de probabilidades a los diferentes elementos del lenguaje. Debido a que
es algo comun, hemos implementado un mecanismo a traves del cual asignar probabilidades a las producciones
de las reglas de la gramatica definida.
Las gramaticas definidas en Lark aceptan comentarios en los ficheros, y debido a que desde BOA
necesitamos leer este fichero, procesamos los comentarios para obtener las probabilidades de las producciones.
Esto se puede ver como si hubieramos definido un modo de indicar directivas sobre estos ficheros para BOA, y
hacemos uso de los comentarios de Lark para que los ficheros de gramaticas sigan siendo compatibles con Lark
independientemente de que estos se utilicen a traves de BOA, pero podrıamos utilizar directamente directivas
con un formato concreto, ya que luego podrıamos eliminar estas directivas y tener el cuenta el resto para Lark
(esto es justamente lo que hace el compilador gcc con las directivas de preprocesamiento, las cuales elimina, o
mas bien ignora, antes de compilar el fichero de lenguaje).
Los comentarios en los ficheros de gramaticas para Lark se especifican con ‘//’, y con BOA se tendran
en cuenta aquellas lıneas que empiecen con ‘//∼’, por lo que podrıamos decir que este es el formato para
especificar una directiva a procesar por BOA. El formato esperado para el valor de las directivas esta formado
por diferentes campos separados por ‘:’, y los diferentes campos son los siguientes:
1. Constante “BOA”: para diferenciar de un comentario fortuito y para mejorar la legibilidad, se espera esta
constante.
2. Nombre de regla que contenga sımbolos no terminales: debido a que vamos a recorrer la gramatica, los
puntos por los que recorreremos la gramatica sera a traves de las producciones que contengan sımbolos
no terminales, y es en estos puntos donde tiene sentido especificar las probabilidades de los diferentes
caminos a tomar.
47
6. IMPLEMENTACION
3. Indice de la alternativa a la que se le va a asignar la probabilidad: en una regla se pueden indicar varias
producciones de la misma regla, llamadas alternativas de la regla, y estas se separan por el caracter ‘|’,y esta es la manera comun de definir una gramatica. Empezando por el ındice 0, habra que indicar a que
alternativa queremos asignarle la probabilidad.
4. Probabilidad: numero entre 0 y 1 que indica la probabilidad de que se seleccione la alternativa. La
probabilidad sera un valor relativo sobre todas las alternativas de reglas con producciones que contengan
sımbolos no terminales, lo cual significa que la probabilidad asignada para una alternativa sera sobre el
total de alternativas de esa regla.
Una vez procesamos todas las directivas, realizamos un posprocesamiento de las probabilidades
obtenidas y terminamos de asignar las probabilidades no especificadas, de manera que todas las probabilidades
sumen en total un 100% para cada regla teniendo en cuenta las alternativas de cada una. Hacemos esto para
evitar tener que definir dos comportamientos diferentes: uno para las alternativas con probabilidad asignada y
otro para las alternativas sin probabilidad asignada.
En la figura 6.2 se muestra un ejemplo donde se especifica una gramatica que acepta todas las cadenas
con dıgitos numericos y los caracteres ‘+’, ‘-’, ‘*’ y ‘/’, que es lo mismo que decir que acepta operaciones
aritmeticas basicas. En este ejemplo se asigna una probabilidad del 0.5% a la primera alternativa de la regla
expr, que es la alternativa que anade un dıgito numerico de n caracteres definido con una expresion regular,
y de esta forma nos queda que al resto de alternativas de la misma regla se les asignara una probabilidad de
(1−0.005)/4 = 0.24875, que es lo mismo que un 24.88% para las otras alternativas. De esta manera, vemos
que el resultado de asignar esta probabilidad al dıgito numerico es conseguir que sea muy poco comun en las
entradas generadas con esta configuracion en los momentos en los que, recorriendo la gramatica, se escoja la
regla expr (debido a la definicion de la gramatica, esto consigue que el recorrido de la gramatica sea mucho
mas largo, pues le hemos asignado una probabilidad baja justamente a la regla que finalizaba el recorrido). Si
no hubieramos asignado esta probabilidad, cada alternativa de la regla expr tendrıa un 25% de probabilidades
de ser escogida durante la exploracion.
start : expr
NUM : /[0−9]+/
ADD : ”+ ”
SUB : ”− ”
MUL : ”∗ ”
DIV : ”/”
expr : NUM
| NUM ADD expr
| NUM SUB expr
| NUM MUL expr
| NUM DIV expr
//∼ BOA : expr : 0 : 0.005
Figura 6.2: Ejemplo de gramatica con Lark y asignacion de probabilidades
48
6. IMPLEMENTACION
6.4.3.2 Estructuras de Datos
Para poder realizar un recorrido correcto por la gramatica, esta es procesada por Lark y, despues,
nosotros procesamos la informacion en crudo que Lark contiene para obtener informacion que almacenamos
en diferentes estructuras de datos con la finalidad de poder procesar correctamente la gramatica y, sobretodo,
recorrerla correctamente.
En concreto, utilizamos 5 variables con diferentes estructuras de datos:
1. Variable rules names: esta variable utiliza un conjunto. La informacion que contiene este conjunto son
las reglas de sımbolos no terminales.
2. Variable terminals names: esta variable utiliza un conjunto. La informacion que contiene este conjunto
son los nombres de las reglas de sımbolos terminales.
3. Variable terminals is re: esta variable utiliza un diccionario, o tabla hash. La informacion que contiene
este diccionario son valores booleanos para saber si las reglas de sımbolos terminales, utilizando el
nombre de estas reglas, se han definido con una expresion regular o si, por el contrario, tiene un valor
constante.
4. Variable terminals patterns: esta variable utiliza un diccionario, o tabla hash. La informacion que contiene
este diccionario son las expresiones regulares de aquellas reglas de sımbolos terminales que no se
definieron con valores constantes, sino con una expresion regular.
5. Variable graph: esta variable utiliza un diccionario, o tabla hash, pero este a su vez contendra listas (se
utiliza una lista porque es necesario saber el orden de las alternativas por lo explicado en la subseccion
Asignacion de Probabilidad a las Producciones) con la finalidad de representar un grafo. La informacion
que contiene este grafo son los caminos que hay entre las diferentes reglas de sımbolos no terminales
definidos en la gramatica.
De las estructuras de datos anteriores, la mas importante con diferencia es el grafo. Este grafo que
construimos nos sirve para recorrer los diferentes elementos de la gramatica, y a la vez que vamos recorriendo
este grafo, vamos aplicando las probabilidades que cada alternativa tenga asignada para seleccionar el siguiente
camino a escoger. Cuando llegamos a una alternativa concreta, esta se recorre sımbolo por sımbolo, de manera
que aquellos sımbolos que sean terminales simplemente se genera el contenido (en el caso de encontrarnos con
un sımbolo terminal especificado con una expresion regular, utilizamos exrex para generar la cadena de manera
aleatoria), y en el caso de los sımbolos no terminales se continua el recorrido por dicho sımbolo no terminal,
como si de una pila se tratara, que es justamente como hay que recorrer una gramatica de contexto libre como
ya se explico anteriormente utilizando la Jerarquıa de Chomsky en la subseccion Fuzzing.
6.4.3.3 Lımites Soft y Hard en el Recorrido de las Gramaticas
Las gramaticas suelen contener bucles infinitos en su definicion, y esto de lo mas normal, no es un
error ni ambiguedad. Debido a estos bucles, el recorrido, incluso utilizando probabilidades en las alternativas,
puede conducirnos a recorridos de la gramatica muy elevados o incluso infinitos si de esa manera se ha definido
la gramatica o se han indicado las probabilidades. Debido a que esto es un problema, y puede causar bucles
infinitos en el modulo directamente, hemos creado en este modulo los conceptos de lımite soft y lımite hard.
49
6. IMPLEMENTACION
Los lımites soft y hard estan relacionados con intentar frenar la generacion de la gramatica. Conforme
se va recorriendo la gramatica, la cual se recorre a traves de un grafo (esta estructura de datos se explica en
la subseccion Estructuras de Datos), se va incrementando a su vez un contador, el cual contiene los pasos
realizados conforme se va explorando el grafo. El recorrido del grafo se realiza de manera aleatoria de acuerdo
a unas probabilidades, uniformes si el usuario no las ha especificado, y conforme mas se explora la gramatica,
si no se llega a un punto en el que se termine la exploracion, en algun momento el contador de pasos empezara
a ser muy elevado, y en consecuencia, la entrada generada a partir de la gramatica tambien, y esto lo podemos
evitar con estos lımites.
El lımite soft es un lımite que tiene que ser menor que el lımite hard si queremos aprovechar su
utilidad, sino podemos poner ambos lımites con el mismo valor, y lo que sucedera es que cuando el contador de
pasos de la exploracion alcance el lımite hard sera que ya no se explorara mas y se parara la exploracion en ese
mismo instante, lo cual, seguramente, haga que la entrada generada no sea consistente con la gramatica. Debido
a que normalmente no queremos una entrada que sea inconsistente con la gramatica, esto es que no cumple la
gramatica porque se ha quedado a medias durante la generacion, tenemos el lımite soft. El lımite soft, cuando
se alcanza, se dejan de tener en cuenta las probabilidades definidas para la exploracion y se realiza un analisis
del grafo priorizando los caminos que nos lleven a sımbolos terminales en lugar de a sımbolos terminales. El
objetivo de realizar esta priorizacion es intentar que se termine la generacion de la entrada lo antes posible.
Para llevar a cabo esto, se realiza una exploracion del grafo dada una profundidad, donde a mayor profundidad,
mejor camino se encontrara para finalizar la exploracion, pues esta profundidad significa la cantidad de pasos
hacia el futuro que van a ser analizados teniendo en cuenta que queremos finalizar la exploracion. Este enfoque
funciona de manera muy efectiva para finalizar la exploracion de manera anticipada, pero aun ası esta el lımite
hard por si la profundidad especificada no es suficiente para una gramatica que sea compleja y, debido a ello,
se ralentice demasiado el procedimiento de finalizar previamente la exploracion y sea necesario cortar por lo
sano en algun momento, el cual sera el lımite hard.
6.5 Modulos de Seguridad
Estos modulos tienen como objetivo la ejecucion de tecnicas que permitan detectar errores y/o
vulnerabilidades, eso ya dependera del objetivo con el que se utilice el analizador, pues sus caracterısticas le
permiten emplearse para ambos casos, aunque el uso principal se supone que es la busqueda de vulnerabilidades.
Se puede ver a estos modulos como el algoritmo de busqueda a emplear para solucionar el problema, que es la
busqueda de vulnerabilidades. Seguramente estos modulos sean los mas interesantes por el objetivo que tienen.
Hemos implementado dos modulos, donde uno de ellos se utilizara como dependencia por el otro
(la razon se explico anteriormente en la subseccion Uso de Modulos como Dependencias), aunque esto no es
necesario y el modulo basico, es decir, el que es dependencia, puede funcionar por sı solo, pero esto no se aplica
al contrario. De esta manera conseguimos evitar duplicar comportamiento y conseguimos un modulo generico
donde se pueden encajar diferentes modulos basicos siempre que se cumplan las restricciones marcadas por
este.
50
6. IMPLEMENTACION
6.5.1 Modulo Basic Fuzzing
Este modulo tiene como objetivo la ejecucion basica de la tecnica de fuzzing. De manera simple, este
modulo ejecuta el binario bajo analisis las veces especificadas y anade al informe los fallos encontrados. Las
opciones que incluye son las siguientes:
• Ejecucion del binario bajo analisis N iteraciones. El valor por defecto es ejecutar una unica vez el binario.
• Opcionalmente, proporcionar las entradas por tuberıa, o pipe, en lugar de por parametros.
• Opcionalmente, anadir los argumentos, entradas y salidas al registro de log. Estos valores se anaden de
manera binaria y se desaconseja en caso de ejecuciones intensas porque hace que la salida generada
ocupe mucho espacio (en el caso de guardarse el fichero de log).
• Opcionalmente, anadir argumentos adicionales al binario bajo ejecucion. Esto es util, por ejemplo, si
queremos realizar pruebas sobre un modo de funcionamiento concreto (e.g. decodificacion Base64 con
el binario ‘base64’ con la opcion ‘-d’).
• Opcionalmente, indicar un pintool1 para obtener metricas de la cobertura de codigo. Para ello, tambien
habra que indicar donde esta el binario ‘pin’ a traves de la variable de entorno PIN BIN.
• Opcionalmente, ejecutar el binario bajo analisis a traves de una terminal, lo cual hace que la ejecucion
a traves de la librerıa ‘subprocess’ pierda el control, lo cual puede ser un problema de seguridad. La
motivacion detras de esta opcion es que el proceso de division de argumentos, el cual se hace de manera
automatica, no se realice correctamente y la ejecucion no funcione como se espera, lo cual se arregla
si se deja este proceso a una terminal en lugar del proceso que realiza el modulo. Solo se aconseja esta
opcion si se detectan comportamiento anomalos con la gestion de argumentos.
• Ejecucion con N procesos, es decir, ejecucion multiprocesamiento. El valor por defecto es utilizar un
unico proceso. Esto se explica en detalle en la subseccion Procesamiento Paralelo.
• Opcionalmente, no ejecutar el modulo. Esta opcion deberıa de utilizarse cuando este modulo vaya a ser
utilizado como dependencia por otro modulo y la ejecucion vaya a ser controlada por este modulo que
define la dependencia. Esto se explico en la subseccion Uso de Modulos como Dependencias y se entrara
en detalles de implementacion en la subseccion Modulo Genetic Algorithm Fuzzing.
• Opcionalmente, evitar que las entradas generadas de los fallos se anadan al informe final. Por defecto,
las entradas generadas de los fallos se anaden al informe final para saber que entrada causo un error. Si
la cantidad de errores es demasiado elevada y, ademas, la cantidad de falsos positivos puede tambien ser
muy elevada (e.g. la generacion de un formato incorrecto lleva a la deteccion de un posible error, pero en
la gran mayorıa de ocasiones sera simplemente un error estructural, no un error ni una vulnerabilidad),
puede ser deseable no mostrar la entrada, sobre todo si son entradas de gran longitud que pueden ocupar
mucho espacio si se almacenan en disco.
• Opcionalmente, indicar un software de sandboxing diferente a Firejail. Ademas de la herramienta, tambien
se pueden indicar unos argumentos diferentes.
El flujo principal de funcionamiento del modulo es el siguiente:
1pintool: ejecutable que utiliza Intel PIN para la instrumentacion y el analisis de codigo
51
6. IMPLEMENTACION
1. Obtencion de una entrada para pasar como argumento al binario bajo analisis (modulo BOAI).
2. Preparacion de los argumentos para el binario bajo analisis.
3. Ejecucion del binario bajo analisis con sus argumentos como entrada.
4. Deteccion de si la ejecucion previa ha fallado o ha tenido exito (modulo BOAF).
5. Procesamiento de las entradas a anadir al informe final para avisar de errores y/o vulnerabilidades.
6. Todo lo anterior se repite las N iteraciones que se hayan especificado.
6.5.1.1 Cobertura de Codigo
La cobertura de codigo es la metrica que hemos utilizado para guiar el fuzzing, lo cual es lo que se
suele hacer en los fuzzers de caja gris, y el que hemos implementado es, justamente, un fuzzer de caja gris.
Para obtener esta metrica hemos utilizado Intel PIN, y hemos implementado diferentes niveles de cobertura de
codigo. Los niveles de cobertura de codigo que hemos implementado han sido a nivel de lıneas y de rama.
Aunque hemos implementado dos niveles de cobertura de codigo, la cantidad de pintools resultantes
han sido tres:
1. Pintool inst count.so: este pintool, basado en el pintool inscount0.so de los ejemplos, cuenta el total de
instrucciones ejecutadas en el binario. Para realizar esto, lo que hace el pintool es anadir una instruccion
adicional antes de cada instruccion del binario, la cual es una llamada a una funcion definida en el mismo
pintool que lo unico que hace es incrementar un contador para luego devolvernos el valor cuando termine
la ejecucion. Este pintool, el cual es muy simple, anade el doble de instrucciones a ejecutar, lo cual es
una carga adicional a tener en cuenta. Ademas, el resultado no sera muy util, pues un numero mayor de
instrucciones ejecutadas no necesariamente indica una mayor cobertura de codigo, pues, por ejemplo, un
bucle puede ejecutarse muchas veces, lo cual incrementara el resultado pero no la cobertura.
2. Pintool bb count.so: este pintool, basado en el pintool inscount1.so de los ejemplos, cuenta el total
de bloques basicos ejecutados en el binario. Un bloque basico es la encapsulacion de un conjunto de
instrucciones en las que es seguro que si se ejecuta la primera instruccion, tambien lo hara la ultima del
bloque basico, pues el objetivo es realizar esta encapsulacion para juntar las instrucciones que los saltos,
condicionales e incondicionales, separan. Este pintool es igual al anterior en desventajas, y la unica
ventaja que obtenemos a diferencia del anterior es que ahora no necesitaremos el doble de instrucciones,
por lo que sera mas eficiente.
3. Pintool branch coverage numeric hash.so2: este pintool, en su version original, recorrıa las instrucciones
del binario a instrumentar e iba comprobando las direcciones de los diferentes segmentos del binario
para comprobar si se trataba de instrucciones ejecutables, en cuyo caso anotaba dichas direcciones e iba
actualizando la direccion mas alta y mas baja encontradas hasta el momento, ası que basicamente se trata
de un pintool que realiza una traza de las direcciones ejecutables. En nuestro caso necesitabamos ir un
paso mas alla, ası que estas direcciones, las cuales sobre un mismo binario cambian a cada ejecucion
2Aunque se ha modificado, este pintool esta basado en el que recomienda AFL++ (https://aflplus.plus/docs/binaryonly_
fuzzing/) si se utiliza Intel PIN: https://github.com/mothran/aflpin/blob/master/aflpin.cpp
52
6. IMPLEMENTACION
debido a la proteccion Address Space Layout Randomization (ASLR)3 de los sistema Linux, pasamos a
calcular un conjunto de direcciones absolutas. Para el calculo de direcciones absolutas, lo que hacemos
es obtener la direccion base del binario, y una vez aplicada con una simple resta, conseguimos un sistema
que, para un mismo binario con ejecucion determinista, obtenemos un resultado de la traza determinista.
Las instrucciones absolutas obtenidas no tienen relacion con el direccionamiento del sistema, ya que sino
Intel PIN estarıa claramente saltandose la medida ASLR, lo cual no es tan simple (tampoco imposible),
pero el conjunto de direcciones que obtenemos de la traza es determinista a traves de ejecuciones
determinista, por lo que ahora somos capaces de saber cuando ejecutamos un mismo camino en el
binario, ya que se realiza una instrumentacion a nivel de rama. Una vez tenemos todos estos datos,
ademas de ya poder hacer una simple cuenta y obtener una puntuacion de cobertura de codigo, tambien
utilizamos el algoritmo MurmurHash, en concreto su version 2, para obtener un identificador numerico
del camino obtenido. Utilizamos MurmurHash porque es un algoritmo no criptografico, lo cual significa
que sus caracterısticas no van encaminadas al objetivo de que la funcion no sea invertible, sino a otras
ventajas como la velocidad, evitar colisiones, etc. Aunque existe la version 3 de dicho algoritmo, esta no
es determinista segun el hardware, por lo que utilizamos su version anterior, ya que no es algo relevante
en nuestro caso.
El formato de los pintools, de los existentes y de los nuevos si se quieren anadir mas, es que acepte
el argumento ‘-o’ para indicar una ruta al fichero de salida tras la ejecucion, y que el formato de dicho fichero
sea, obligatoriamente, un primer valor numerico que indique una puntuacion (tiene que ser una puntuacion
a maximizar, no a minimizar) absoluta, no relativa, que indique la cobertura alcanzada. Opcionalmente, el
formato tambien acepta otro valor numerico, el cual tendra que estar separado por un tabulador del primer
valor, que sea un identificador unico que represente las instrucciones ejecutadas, es decir, que a diferentes
ejecuciones con mismas instrucciones ejecutadas devuelva el mismo identificador.
Aunque la herramienta utilizada sea Intel PIN, la adaptacion del modulo para utilizar otra herramienta
diferente de instrumentacion no deberıa de ser difıcil siempre que se respete el formato del fichero de salida
para evitar tener que realizar cambios mas profundos en el modulo.
6.5.1.2 Procesamiento Paralelo
El procesamiento paralelo es algo que surge de manera natural a la hora de aplicar fuzzing. Debido
a que es tan natural y ambos conceptos casan tan bien debido a la naturaleza independiente entre diferentes
instancias de ejecucion del binario bajo analisis (esto no sera ası si el binario bajo analisis se comporta de
manera distinta al detectar instancias de si mismo, aunque en estos casos lo que habrıa que hacer para obtener
buenos resultados es utilizar una herramienta de sandboxing como Firejail, que es la que se soporta, aunque
tambien es cierto que el objetivo puede ser comprobar esta caracterıstica tan peculiar que comentamos), es
por ello por lo que hemos implementado el procesamiento paralelo, de manera que ejecutamos el binario bajo
analisis tantas veces como se haya configurado, donde la mejor opcion normalmente sera indicar la cantidad
total de nucleos del sistema si se quiere aprovechar todo el poder de procesamiento hardware.
Para implementar el procesamiento paralelo hay dos tecnicas principales, las cuales se explican a
continuacion.3ASLR: medida de proteccion que cambia las instrucciones absolutas del espacio de direccionamiento de los procesos por un
direccionamiento relativo para evitar la explotacion de vulnerabilidades
53
6. IMPLEMENTACION
• Procesamiento multinucleo (multiprocesamiento o multiprocessing): este tipo de procesamiento es aquel
que utiliza diferentes procesos para aprovechar los diferentes nucleos de un sistema. Debido a que
es a nivel de proceso, los recursos entre los diferentes procesos no se comparten, al menos no de
manera sencilla. Este tipo de procesamiento se suele utilizar cuando la relacion entre los objetivos
del procesamiento paralelo son independientes entre sı, son procesos complejos por sı mismo y no
requieren de comunicacion entre los mismos. Si se cumplen estas caracterısticas, estaremos hablando
de un procesamiento paralelo real en funcion de los nucleos del sistema y del numero de procesos a
ejecutar.
• Procesamiento multihilo (multithreading): este tipo de procesamiento es aquel que realiza una gestion del
gestor de procesos para compartir el tiempo de ejecucion entre pequenos caminos de ejecucion, o hilos.
Debido a que se realiza una gestion del gestor de procesos para poder compartir una misma ventana
temporal para varios caminos de ejecucion, no estamos hablando de un procesamiento paralelo real,
sino de pseudoparalelismo. Este tipo de procesamiento se realiza dentro del mismo proceso, por lo que
el acceso a los recursos es compartido (esto puede ser muy peligroso en algunas ocasiones, ya que las
variables locales ahora es como si fueran variables globales, y un cambio por un hilo sera reflejado para
el resto aunque estos otros no hayan efectuado el mismo cambio). Esto tipo de procesamiento es muy
util aunque no sea un paralelismo real, ya que aprovecha los recursos de manera eficiente (e.g. mientras
un fichero esta leyendo de disco, otro puede realizar procesamiento, y esto sı que se ejecutarıa sobre un
paralelismo real gracias a las arquitecturas superescalares [35]) y, ademas, consigue darle al usuario una
sensacion de paralelismo real. Este tipo de procesamiento paralelo se suele aplicar cuando es necesario
aplicar varias tareas a la vez, estas tareas son dependientes entre sı y/o es necesario el acceso compartido.
Un ejemplo muy tıpico donde se utiliza esta tecnica es cuando es necesario escribir en varios ficheros
ademas de algun tipo de procesamiento.
En nuestro caso, para implementar el procesamiento paralelo, hemos utilizado el multiprocesamiento.
Esto nos ha permitido aumentar la velocidad de ejecucion, y aunque es cierto que la eficiencia no es un objetivo
de diseno de BOA, sı que es necesario aumentar la velocidad de ejecucion en la medida de lo posible cuando
se aplica fuzzing debido a que se van a realizar muchas ejecuciones. Por otro lado, cada ejecucion del binario
bajo analisis requerira de sus propios recursos, ademas de que estas ejecuciones seran un proceso por sı mismo,
y estos procesos pueden generar otros procesos hijos a su vez, por lo que el multithreading estaba totalmente
descartado por esta parte.
En concreto, se ha utilizado la librerıa multiprocessing, de la librerıa estandar de Python. Con esta,
hemos realizado los siguientes pasos para llevar a cabo el procesamiento paralelo:
1. Se prepara la estructura de datos que va gestionar el procesamiento paralelo. En concreto, utilizamos una
piscina o pool, ya que no requerimos de orden a la hora de ejecutar los procesos.
2. Almacenamos los argumentos de cada proceso tantas veces como procesos se hayan indicado que se
ejecutaran.
3. Una vez preparados todos los argumentos, se realiza la ejecucion paralela y se obtienen los datos de los
mismos. Los datos pueden, y seguramente estaran desordenados, por lo que gracias a un identificador que
se proporcionaba como argumento y se devolvıa como resultado, ordenamos los resultados. El motivo de
ordenar los resultados es poder hacer una asociacion con los datos que se tienen de las entradas de cada
proceso.
54
6. IMPLEMENTACION
4. Se limpian los recursos que ya no son necesarios.
6.5.1.3 Modulo como Generador
Los generadores son un tipo de objetos de Python, los cuales permiten terminar su ejecucion en un
punto concreto y al ejecutarla de nuevo, se continua por donde se habıa terminado en la anterior ejecucion. Estos
objetos son utiles porque permiten obtener resultados intermedios, lo cual no siempre es posible y dependera
el problema y de la solucion que se aplique, pero en el caso de poder, esto permite obtener pequenos conjuntos
de datos a procesar, lo cual se conoce como batching, o ejecucion por lotes, donde el conjunto devuelto es un
batch o un lote. Los generadores son un objeto que se puede recorrer, por lo que es una gran abstraccion para
realizar una ejecucion por lotes. La motivacion detras de realizar este tipo de ejecucion, ademas de la propia
ventaja de obtener resultados sin tener que esperar al procesamiento global, es la de poder liberar recursos.
Para el modulo de fuzzing ha sido necesario hacer una ejecucion por lotes, y para ello hemos hecho
uso de los generadores. Hemos creado una funcion a modo de wrapper del procesamiento general del modulo
que es un generador (una funcion en Python es un generador cuando utiliza la palabra reservada yield en lugar
de return), la cual ira procesando las diferentes ejecuciones del binario bajo analisis. La razon de utilizar esta
tecnica ha sido la de liberar recursos, pues conforme se iban almacenando datos, la cantidad de memoria se
iba consumiendo rapidamente. Esta situacion se agravo en el modulo que se explica en la subseccion Modulo
Genetic Algorithm Fuzzing.
Ademas de lo comentado, otra ventaja de haber utilizado generadores para este modulo es la ya
comentada acerca de la observacion de resultados sin necesidad de esperar a los resultados finales. Para ello, ha
sido necesaria la gestion manual del objeto que contiene la informacion de los informes, pero esto tambien ha
permitido que podamos ir liberando recursos del informe final, lo cual tiene la parte negativa de que el conteo
final de errores/vulnerabilidades encontrados estara falseado, ya que se descontaran los errores/vulnerabilidades
encontrados en este modulo porque hemos ido procesando y liberando los recursos del informe a medida que
se iba realizando el procesamiento en lugar de esperar al final de la ejecucion del modulo. Esto es importante
ya que una ejecucion de este modulo, de cualquier fuzzer en general, normalmente sera del orden de las horas
si no se esta realizando una pequena prueba.
6.5.2 Modulo Genetic Algorithm Fuzzing
Este modulo tiene como objetivo la aplicacion de un algoritmo genetico utilizando un modulo como
dependencia que aplique fuzzing. Debido a este diseno, este modulo se podrıa intercambiar por otros algoritmos
y reutilizar la dependencia que comentamos y ası evitar la duplicacion de codigo. Las opciones que incluye son
las siguientes:
• Cantidad de epocas del algoritmo genetico, donde el valor por defecto es una epoca. Este valor indica la
cantidad de veces que va a ejecutarse el algoritmo genetico.
• Aunque no es una opcion que ponga directamente el usuario, el tamano de la poblacion del algoritmo
genetico viene indicado por el parametro que se haya especificado acerca de las iteraciones en el modulo
explicado en la subseccion Modulo Basic Fuzzing.
55
6. IMPLEMENTACION
• Valor de elitismo, el cual tiene un valor por defecto de 1. El elitismo es una tecnica habitual en los
algoritmos geneticos que lo que hace es anadir los N mejores individuos a la siguiente generacion
directamente sin aplicar crossover, con lo cual nos garantizamos el no perder a los mejores individuos.
Esta tecnica nos garantiza no perder el mejor resultado encontrado hasta el momento y, normalmente,
una convergencia mas rapida.
• Probabilidad de aplicar crossover, donde el valor por defecto es del 95%.
• Probabilidad de aplicar mutacion, donde el valor por defecto es del 5%.
• Opcionalmente, se puede indicar una expresion regular a aplicar para los mutadores.
• Opcionalmente, se puede indicar que la granularidad con la que se trabajara sera el byte en lugar del
caracter. Esta opcion sera, normalmente, deseable.
• Opcionalmente, se podra desactivar el anadir las entradas generadas al informe final. Esto es util para
evitar que el tamano del fichero, si se redirige la salida a un fichero, lo cual es habitual, ocupe demasiado
disco debido a entradas generadas demasiado grandes.
• Opcionalmente, se podra especificar un numero fijo de entradas a obtener del modulo BOAI para anadir
a la nueva generacion de manera fija, y el valor por defecto es 0. Esto es util si se quiere utilizar entradas
como semilla y no se quiere que estas semillas se pierdan conforme mas avance el algoritmo genetico.
Es una manera de tener el caso base en todo momento disponible.
• Opcionalmente, mostrar las entradas del informe final mientras ejecutamos el modulo. La razon de esto
se explico con detalle en la subseccion Modulo como Generador.
• Power schedule a utilizar, donde el valor por defecto es “fast”. Los power schedules se explicaron
con anterioridad en la subseccion Forks de AFL y los detalles de implementacion se detallaran en la
subseccion Power Schedules.
• Valor β de los power schedules, el cual tiene un valor por defecto de 1.
El modulo implementa el algoritmo descrito en Algoritmo Genetico. Aun ası, describimos el flujo
principal de funcionamiento del modulo a continuacion:
1. Obtencion de la instancia del modulo que aplica fuzzing y que deberıa estar definida como dependencia
del presente modulo. Esta instancia sera la que aplique fuzzing y que sera totalmente controlada por este
modulo.
2. Si estamos en la primera epoca, delegar la generacion de la poblacion al modulo dependencia, el cual
utilizara el modulo BOAI configurado. Si no es la primera epoca, se utilizara la poblacion generada por
el algoritmo genetico.
3. Utilizar el modulo dependencia para aplicar el fuzzing. Recoger todos los datos de las ejecuciones de la
poblacion actual.
4. Calculo y actualizacion de metricas para obtener el valor de la funcion de fitness. En concreto, estos
valores estan relacionados con los power schedules, los cuales se detallan en la subseccion Power
Schedules.
56
6. IMPLEMENTACION
5. En funcion de si la instrumentacion nos devuelve un identificador unico de la ejecucion, si la rama ya
se habıa ejecutado con anterioridad durante la misma epoca, dichas ejecuciones no se procesaran para la
actual generacion. Los detalles relativos a la instrumentacion se explicaron con detalle en la subseccion
Cobertura de Codigo.
6. En funcion del power schedule escogido, se calcula el valor de la funcion de fitness.
7. Se anaden los errores/vulnerabilidades encontradas al informe final. Se tendra en cuenta si las ejecuciones
se habıan anadido con anterioridad al informe final utilizando los identificadores de la instrumentacion,
en cuyo caso no se volveran a anadir una segunda vez. A diferencia del paso 5, para esto se tiene en
cuenta todas las ejecuciones que se han realizado a lo largo del algoritmo genetico (todas las epocas), no
solo las ejecuciones de la epoca actual.
8. Se aplica el crossover. Esto se detalla en la subseccion Funcion de Crossover.
9. Se aplica la mutacion. Esto se detalle en la subseccion Funcion de Mutacion y Mutadores
10. Si ası se configuro, se muestran los errores/vulnerabilidades que se han encontrado durante la epoca
actual.
11. Se vuelve al paso 2 hasta que se hayan ejecutado todas las epocas.
La representacion de los individuos de la generacion se basa en las entradas que se generen, es
decir, su representacion esta basada en caracteres, o bytes, lo cual hace que la representacion sea dinamica(i.e. la longitud cambia conforme avanza el algoritmo) y variable de un individuo a otro (i.e. la longitud de
las representaciones entre individuos puede ser diferente). Esto es algo que habra que tener en cuenta, pues,
habitualmente, la representacion de los individuos suele ser estatica y no variable, pero no es un problema para
el algoritmo en sı.
6.5.2.1 Funcion de Crossover
La funcion de crossover es la que obtiene dos individuos de la poblacion, de la generacion que se
este procesando, y obtiene un nuevo individuo a partir de la informacion de los dos anteriores escogidos. Lo
que hace esta funcion es, de alguna manera, mezclar el ADN de ambos individuos. El crossover se realizara
casi siempre, aunque un mecanismo habitual es asignarle una probabilidad de llevarlo a cabo, y eso es lo que
hemos hecho. De esta manera, si debido al azar surge que no hay que aplicar el crossover, se seleccionara
de manera aleatoria uno de los dos individuos que se han escogido anteriormente y pasara directamente a la
siguiente generacion sin aplicar ningun procedimiento adicional.
La funcion de crossover que hemos utilizado esta basada en la empleada por el fuzzer VUzzer4 [30].
VUzzer utiliza una funcion de crossover donde coge los primeros 5 elementos de uno de los individuos padre
y el resto de elementos partiendo del elemento numero 5 hasta el final del otro individuo padre, y la union de
estos elementos es el nuevo individuo. Esta es una funcion de crossover muy tıpica en los algoritmos geneticos,
lo curioso de VUzzer es que utilice un punto de corte fijo. En nuestro caso, hemos hecho uso de la misma
tecnica, pero en lugar de utilizar un punto de corte fijo, hemos cogido un valor aleatorio entre 0 y la longitud
maxima de los elementos de ambos individuos padre. De esta manera, generalizamos la funcion de crossover.
4Repositorio de VUzzer: https://github.com/vusec/vuzzer
57
6. IMPLEMENTACION
Debido a que la representacion de los individuos es dinamica y variable, al coger el punto de ruptura
con un valor entre 0 y el maximo de las longitudes de los individuos padre, puede suceder que se coja un punto
de corte que haga que el nuevo individuo sea exactamente igual que uno de los dos individuos padre, y aunque
esto pueda parecer un problema, no lo es para nada, ya que lo unico que significa es que uno de los individuos
padre pasara a la siguiente generacion, lo cual es parecido a lo que ocurre al aplicar elitismo, pero en este caso,
en lugar de elitismo, la fortuna ha sido quien ha hecho que este individuo pase a la siguiente generacion.
Hasta ahora hemos hablado de como hemos aplicado el crossover, pero no hemos explicado como
hemos realizado la seleccion de ambos individuos con los que se obtiene el nuevo individuo. Esta seleccion
es vital, y hay muchas maneras de realizarla, pero en nuestro caso hemos utilizado uno de los algoritmos mas
tıpicos para esta tarea, el cual es el algoritmo Roulette Wheel Selection (ver figura 6.3). Este algoritmo se basa
en la seleccion de un individuo en funcion de una puntuacion numerica, donde en este caso, dicha puntuacion
es el valor de la funcion de fitness. Estas puntuaciones se escalan de manera que la suma de todas ellas sea
uno, y se realiza un recorrido a traves de las puntuaciones escaladas ordenadas de menor a mayor. Antes de
hacer este recorrido, se genera un numero aleatorio entre 0 y 1, de manera que conforme vamos realizando este
recorrido, comprobamos si el valor aleatorio es menor que los valores que recorremos, en cuyo caso ya tenemos
individuo seleccionado. De manera conceptual, es como si tuvieramos todos los individuos representados en
una ruleta donde el area ocupada por cada individuo fuera directamente proporcional a su valor de fitness, y al
hacer girar la ruleta, seleccionamos uno de estos individuos. Este metodo hace que todos los individuos tengan
la posibilidad de ser escogidos, pero claramente aquellos individuos que mejor lo han hecho segun la funcion
de fitness, deben de tener mas probabilidades de salir escogidos.
def roulette_wheel_selection(rewards):
""" Roulette wheel selection algorithm .
Arguments :
rewards (list): list of rewards.
Returns:
int: index of the provided *rewards *.
"""
likelihood = sorted(zip(range(len(rewards)), rewards), key=lambda item: item [1]) # <
accumulated = 0.0
# Update likelihood in order to obtain the accumulated values
for idx , (l_idx , l_l) in enumerate(likelihood):
accumulated += l_l
likelihood[idx] = (l_idx , accumulated)
likelihood = map(lambda item: (item[0], item [1] / accumulated), likelihood) # Normalize
# Obtain index with higher likelihood
random_number = random.random ()
for l_idx , l_l in likelihood:
if l_l >= random_number:
return l_idx
return likelihood [ -1][0]
Figura 6.3: Algoritmo Roulette Wheel Selection en Python
58
6. IMPLEMENTACION
6.5.2.2 Funcion de Mutacion y Mutadores
La mutacion es el procedimiento que se utiliza en los algoritmos geneticos para proveer de variedad
genetica a los individuos y ası evitar caer en situaciones donde no se tenga el total de toda la variedad genetica
cubierta, lo cual llevarıa a falta de informacion genetica a lo largo de las generaciones o epocas. La mutacion
se aplica a cada componente del ADN de cada individuo, y para aplicar una mutacion se tiene que cumplir la
probabilidad asociada a la misma, la cual suele ser un valor bajo.
En nuestro caso no hemos utilizado una unica funcion de mutacion, o mutador, sino que hemos
empleado varios:
1. Mutacion replace: a cada componente del individuo, si le toca mutar, reemplazara su componente por
otra de manera aleatoria.
2. Mutacion insert: a cada componente del individuo, si le toca mutar, insertara entre la componente
actual y la siguiente una nueva componente aleatoria. En el caso de mutar, no se tiene en cuenta esta
nueva componente para volver a mutar, lo cual harıa que la expansion en longitud de las componentes
fuera bastante mas mayor que la expansion que se realiza de manera proporcional a la probabilidad de
mutacion.
3. Mutacion swap: a cada componente del individuo, si le toca mutar, intercambiara su componente actual
por otra de manera aleatoria.
4. Mutacion delete: a cada componente del individuo, si le toca mutar, eliminara su componente. En el caso
de mutar, se tiene en cuenta que se acaba de eliminar esta componente para recorrer de manera correcta
las componentes.
La seleccion del mutador es aleatoria para cada individuo de la generacion. Ademas, la mutacion se
realizara a cada caracter o a cada byte, eso dependera de la granularidad configurada en el modulo.
6.5.2.3 Power Schedules
Los power schedules, los cuales ya explicamos anteriormente en la subseccion Forks de AFL, son una
serie de formulas introducidas por AFLFast que cambian la manera en la que se realiza la exploracion de las
entradas generadas. Basicamente son una funcion de fitness, y nosotros hemos implementado estas funciones
justamente para ello.
Una de las partes mas importantes para poder realizar la implementacion de estas formulas es tener
un mecanismo de instrumentacion que nos permita detectar cuando estamos ejecutando un camino de nuestro
binario bajo analisis que ya habıamos ejecutado anteriormente. Gracias a los pintools que tenemos, esto nos
es posible, concretamente gracias al pintool branch coverage numeric hash.so, y la razon se explica en la
subseccion Cobertura de Codigo. Una vez cumplimos con lo explicado, somos capaces de obtener las metricas
necesarias para calcular los resultados de los diferentes power schedules.
Como hemos comentado, nosotros utilizamos los power schedules como funcion de fitness. Debido
a que era necesario comprobar si dichas funciones se habıan implementado correctamente, y debido a que
utilizamos un enfoque diferente al utilizado en el algoritmo CGF, el cual es el que utiliza AFLFast, era
59
6. IMPLEMENTACION
necesario para nosotros el cerciorarnos de la efectividad de las mismas de la misma manera que se hizo
en el estudio original de AFLFast [23]. En el estudio original comparan los diferentes power schedules en
funcion de los fallos unicos descubiertos por cada uno de ellos, ası que este mismo experimento realizamos
nosotros. Para nuestra sorpresa, hemos replicado la mayorıa de los resultados obtenidos por el estudio, lo cual
se puede observar en la figura 6.4, la cual son los resultados de nuestros experimentos. Nuestros experimentos5
se realizaron sobre el binario base64 del dataset LAVA-M [36] (se comentara mas en detalle este dataset en el
capıtulo Resultados), donde las conclusiones fueron las siguientes:
• El power schedule que mas fallos unicos descubre es “fast”. Esto sucede igual con AFLFast.
• Los power schedules que siguen a “fast” son “COE” y “lin”. Esto sucede igual con AFLFast.
• El power schedule “explore” parece estancarse conforme pasa el tiempo. Esto sucede igual con AFLFast.
• El power schedule “exploit”, que es el que utiliza AFL, es uno de los que menos fallos unicos descubre.
Esto sucede igual con AFLFast.
• El power schedule “explore” descubre casi el mismo numero de fallos unicos que “exploit”. Esto no
sucede igual con AFLFast, pues “exploit” deberıa de ser el power schedule que menos fallos unicos
descubre con diferencia.
• El power schedule “quad” es el que menos fallos unicos descubre. Esto no sucede igual con AFLFast,
pues este power schedule deberıa de descubrir muchos mas fallos unicos, mas o menos deberıa de estar
al nivel de “lin”.
• El power schedule “COE” se acerca mas a “lin” que a “fast”. Esto no sucede igual con AFLFast, ya que
“COE” se acerca mucho a “fast”.
02,0
004,0
006,0
008,0
0010,0
0012,0
0014,0
000
50
100
150
200
Tiempo (segundos)
Fallo
suni
cos
fastCOE
explore (β = 10)quadlin
exploit
Figura 6.4: Nuevos caminos descubiertos por los diferentes power schedules
5La configuracion concreta fue: https://github.com/cgr71ii/BOA/blob/a136e79cc19e359b20bd6d636a71b39cb7956267/
boa/rules/specific-rules/rules-dynamic-seed_input_genetic_alg_fuzzing_base64.xml
60
6. IMPLEMENTACION
A pesar de que no hemos replicado al 100% la comparativa, hemos de decir que los resultados
se asemejan bastante, y las conclusiones mas relevantes se cumplen. Ademas, hay que tener en cuenta dos
diferencias principales en esta comparativa, donde la primera es el valor de β , el cual nos era desconocido el
empleado en el estudio, y segundo, que nosotros utilizamos un algoritmo distinto al algoritmo CGF. Justamente
debido a esto ultimo, el haber replicado parte de la comparacion puede que nos este indicando que nuestra
implementacion del algoritmo genetico no sea tan distinta al algoritmo CGF, y aunque no vamos a decir que
sean equivalentes, puede que sı que sean similares en eficiencia.
61
Capıtulo 7
Resultados
El ultimo paso a realizar es poner a prueba la eficacia de BOA. Para ello, vamos a realizar una serie
de pruebas y a mostrar los resultados obtenidos de las mismas.
Por un lado, vamos a realizar un prueba muy simple sobre un fichero sintetico, el cual contiene una
vulnerabilidad de buffer overflow. Por otro lado, vamos a realizar pruebas sobre el dataset LAVA-M, cuyas
pruebas son mas realistas que un entorno sintetico pero no llega a ser un caso real. La razon por la que solo
hemos realizado una prueba sintetica ha sido para comprobar un caso muy simple y porque el resto de esfuerzos
se han puesto en las pruebas con LAVA-M.
No se han realizado pruebas sobre casos reales debido a que es difıcil determinar si el desempeno del
analizador ha sido el esperado, ya que los sistemas reales pueden, y seguramente contengan vulnerabilidades,
pero su deteccion, e incluso la demostracion de las mismas puede ser algo difıcil de realizar y que no dejarıa
claro el correcto desempeno de BOA.
Las pruebas realizadas se han llevado a cabo en un sistema GNU/Linux, concretamente con una
distribucion Ubuntu 20.04.02 y sobre un kernel 5.11.0-27-generic. Ademas, se han utilizado 5 nucleos y 8 GB
de memoria.
7.1 Prueba Sintetica
La unica prueba sintetica que se ha realizado ha sido sobre un binario que provenıa de un fichero de
codigo en C, el cual se puede observar en la figura 7.1. Una vez compilado y obtenido el binario objetivo del
analisis, se han obtenido los resultados que se pueden observar en la figura 7.2.
Para llevar a cabo esta prueba, se ha utilizado el modulo basic fuzzing, pues no era necesario el
modulo que emplea el algoritmo genetico. Ademas, se ha utilizado el modulo BOAI random string con una
longitud maxima de 30 caracteres y con aleatoriedad en dicha longitud.
El resultado de esta prueba ha sido que se ha detectado, sin ningun problema, el buffer overflow. En
concreto, el codigo de salida ha sido 134, lo cual significa que se ha detectado la senal 6, la cual es SIGABRT.
Aunque esta senal puede tener diferentes significados, nos indica que podemos estar ante un buffer overflow.
62
7. RESULTADOS
int main(int argc , char** argv)
{
char name [20];
if (argc > 1)
{
strcpy(name , argv [1]);
}
else
{
strcpy(name , "Master Chief");
}
printf("Hello , %s.\n", name);
return 0;
}
Figura 7.1: Prueba sintetica: test basic buffer overflow.c
Figura 7.2: Prueba sintetica con el modulo basic fuzzing
7.2 Pruebas con LAVA-M
LAVA-M [36] es un dataset que consta de 4 binarios modificados, donde los binarios base son base64,
md5sum, uniq y who, que forman parte de la utilidades de GNU. Estos binarios realizan las mismas tareas
que los originales, pero con la diferencia de que se han insertado puntos en los binarios que simulan una
vulnerabilidad, y con simulacion nos referimos a que se sale del binario con un codigo de error determinado (i.e.
inyeccion de vulnerabilidades). Estos binarios son muy utiles para comprobar la efectividad de diversos analisis,
y eso es lo que hemos hecho. En concreto, cada binario tiene una cantidad de vulnerabilidades introducidas:
• Binario base64: 44 vulnerabilidades.
• Binario md5sum: 57 vulnerabilidades.
63
7. RESULTADOS
• Binario uniq: 28 vulnerabilidades.
• Binario who: 2136 vulnerabilidades.
Debido a que estos binarios estan disponibles precompilados solo para arquitecturas de 32 bits,
tuvimos que compilarlos a mano1 debido a que no estan disponibles precompilados, o al menos no es sencillo
encontrarlos.
Una vez tuvimos los binarios disponibles, solo pudimos realizar pruebas con dos de ellos, que fueron
base642 y uniq3. Las razones por la que no realizamos pruebas con md5sum y who fueron las siguientes:
• El binario md5sum realizo las inyecciones de vulnerabilidades utilizando los valores de los ficheros
binarios del sistema. En una rapida comprobacion, como es logico, los binarios del sistema que hemos
utilizado no contienen los mismos binarios que los de los investigadores que crearon LAVA-M (e.g.
/usr/bin/true, /usr/bin/echo), lo cual puede deberse a diferentes causas como versiones diferentes, o
incluso a arquitecturas diferentes, como seguramente sea el caso porque, como ya hemos comentado, los
binarios originales estaban compilados para un sistema de 32 bits, por lo que, seguramente, el sistema
era de 32 bits. Debido a que se utilizaron estos valores, las posibilidades de que alcanzaramos alguna
vulnerabilidad eran mınimas sino es que nulas.
• El binario who solo admite ficheros como entrada, no admite que se le pase el valor por pipe. Debido a
que nuestra implementacion, actualmente, solo admite pasar valores o por parametros o por pipe, no ha
sido posible probar este binario. No se descarta una futura ampliacion donde se introduzcan mas perfiles
de parametros como es el caso de los ficheros.
Una vez explicado todo lo anterior, los resultados de las pruebas pueden observarse en la figura 7.3.
base64 uniq
0
1,000
2,000
24901 0
309
1,777
Fallos unicos Vulnerabilidades Ramas unicas
Figura 7.3: Pruebas con binarios de LAVA-M
Respecto a los resultados observados en la figura 7.3, realizamos las siguientes observaciones:1Los binarios de LAVA-M para 64 bits estan disponibles en: https://github.com/cgr71ii/BOA/tree/master/tests/
binaries/LAVA-M2Configuracion para base64: https://github.com/cgr71ii/BOA/blob/fe7768ab128439960cdf314af6ddffbb45440623/
boa/rules/specific-rules/rules-dynamic-seed_input_genetic_alg_fuzzing_base64.xml3Configuracion para uniq: https://github.com/cgr71ii/BOA/blob/fe7768ab128439960cdf314af6ddffbb45440623/boa/
rules/specific-rules/rules-dynamic-seed_input_genetic_alg_fuzzing_uniq.xml
64
7. RESULTADOS
• En ambos casos no se consigue explotar con exito las vulnerabilidades de LAVA-M. Una posible razon
es que nuestro fuzzer no tenga la inteligencia suficiente en el analisis que realiza como para generar
unas entradas mas dirigidas (este problema es justamente el que intenta solucionar AFLGo, que es un
DGF en lugar de nuestro enfoque, que es un CGF). Otra explicacion, mas sencilla, es que no hayamos
invertido todo el tiempo necesario como para encontrar esta serie de vulnerabilidades, pues nosotros
hemos ejecutado 5000 epocas con un tamano de poblacion de 100 individuos, lo que significa que hemos
realizado 500000 ejecuciones, lo cual quiza no sea un numero de entradas generadas suficiente. El tiempo
que se ha invertido para esta configuracion ha sido de, aproximadamente, unas 55 horas para cadabinario, lo cual es un total de 110 horas de ejecucion.
• En lo referente a los fallos unicos encontrados, base64 es un binario que, debido a su semantica, puede
fallar si no se cumple el formato de Base64, y por ello encontramos diferentes formas de hacer que falle y
conseguimos llegar a 249 fallos unicos. Por otro lado, uniq es una herramienta de manipulacion de texto
que funciona a nivel binario, por lo que no tenemos esta limitacion semantica y no es tan sencillo hacer
que falle, y por ello nos encontramos con 0 fallos unicos.
• La cantidad de ramas unicas ejecutadas es meramente informativo e ilustra que ha habido una exploracion
exitosa haciendo uso del power schedule fast.
65
Capıtulo 8
Conclusion
En el presente trabajo se detalla el desarrollo realizado en la ampliacion del diseno e implementacion
del analizador de vulnerabilidades BOA. Esta ampliacion se inicio a partir de una propuesta de trabajo futuro
sobre dicho analizador, lo cual llevo al actual trabajo. Se inicio definiendo una serie de objetivos, y se obtuvo
como resultado un sistema refinado que sigue manteniendo igual, o muy parecidos, todos sus principios de
diseno.
Las mejoras realizadas sobre BOA han conducido a un analizador de proposito general con soporte
total a tecnicas tanto de analisis estatico como de analisis dinamico. No podemos olvidar la gran capacidad de
modularidad que tiene su arquitectura, lo cual permite la ampliacion de nuevos modulos.
Por ultimo, comentar que, igual que cuando BOA se termino, el resultado ha sido muy satisfactorio.
Partiendo de un sistema ya funcional, ha sido posible realizar una ampliacion que conduce a un sistema poco
comun, pues no es habitual encontrar sistemas que soporten tanto tecnicas de analisis estatico como de analisis
dinamico y que, ademas, sean de proposito general. Tambien comentar que uno de los objetivos cuando se
inicio BOA era la de que fuera un proyecto de codigo libre, lo cual hemos cumplido a lo largo del presente
trabajo, pues creemos que ya esta en un punto lo suficientemente maduro como para que, si alguien lo desea,
pueda utilizarlo.
8.1 Relacion con Estudios Cursados
Aunque BOA es un trabajo que ya estaba iniciado, el master en ciberseguridad cursado ha sido una
gran ayuda para poder comprender conceptos empleados durante el presente trabajo. Han habido asignaturas
que incluso hablaban sobre el analisis estatito/dinamico y sobre las vulnerabilidades y su explotacion a nivel
binario, todo lo cual es muy util cuando se utilizan tecnicas de analisis dinamico, lo cual era uno de los puntos
centrales del trabajo.
Por otro lado, aunque entendemos que dentro de los masteres, en general, hay diferentes perfiles,
y el master en ciberseguridad no es una excepcion, en nuestro caso el grado en ingenierıa informatica ha
jugado tambien un papel muy importante. Tanto asignaturas basicas como las mas especializadas nos han
ayudado a poder realizar este trabajo de manera satisfactoria, donde las asignaturas mas relevantes han sido las
66
8. CONCLUSION
relacionadas con el testing, la programacion, las relacionadas con teorıa de la computacion e incluso algunas
que mostraban nociones de inteligencia artificial (las asignaturas relacionadas con matematicas tambien estan
en el lote ;), si no, ¿como poder justificar formulas como las de los power schedules?).
8.2 Trabajo Futuro
Igual que este trabajo se basa en una lınea de trabajo futuro anterior, terminamos el presente trabajo
de la misma manera que empezo, indicando otras posibles ideas para continuar:
• Fuzzing colaborativo distribuido (e.g. OSS-Fuzz, CollabFuzz1 [37]).
• Creacion de un repositorio distribuido que permita la descarga de diferentes modulos (e.g. BOAM,
BOAI, BOAPM). Serıa posible crear una comunidad unificada que implementara multitud de tecnicas en
diferentes modulos y estos estuvieran disponibles para descargar para el resto de usuarios.
1Repositorio de CollabFuzz: https://github.com/vusec/collabfuzz
67
Bibliografıa y Referencias
[1] Sharron Ann Danis. Rear Admiral Grace Murray Hopper. https://ei.cs.vt.edu/~history/
Hopper.Danis.html. Accessed: 2021-08-18.
[2] Christopher RS Banerji, Toufik Mansour y Simone Severini. “A notion of graph likelihood and an infinite
monkey theorem”. En: Journal of Physics A: Mathematical and Theoretical 47.3 (2013), pag. 035101.
[3] Mark Priestley. Routines of Substitution: John von Neumann’s Work on Software Development, 1945–1948.
Springer, 2018.
[4] Srivats Shankar. “Looking into the Black Box: Holding Intelligent Agents Accountable [NUJS Law
Review]”. En: 10 (ene. de 2017), pag. 451.
[5] Jesse Anderson. “A million monkeys and Shakespeare”. En: Significance 8.4 (2011), pags. 190-192.
[6] Cristian Garcıa Romero. Diseno e Implementacion de un Analizador de Vulnerabilidades. http://
rua.ua.es/dspace/handle/10045/107803. Accessed: 2021-08-18. 2020.
[7] AFL++ Trophies. https://aflplus.plus/#trophies. Accessed: 2021-08-20.
[8] Marcel Bohme y col. “Directed greybox fuzzing”. En: Proceedings of the 2017 ACM SIGSAC Conference
on Computer and Communications Security. 2017, pags. 2329-2344.
[9] Emre Guler y col. “Cupid: Automatic Fuzzer Selection for Collaborative Fuzzing”. En: Annual Computer
Security Applications Conference. 2020, pags. 360-372.
[10] Sebastian Osterlund y col. “CollabFuzz: A Framework for Collaborative Fuzzing”. En: Proceedings of
the 14th European Workshop on Systems Security. 2021, pags. 1-7.
[11] Anjana Gosain y Ganga Sharma. “A survey of dynamic program analysis techniques and tools”. En:
Proceedings of the 3rd International Conference on Frontiers of Intelligent Computing: Theory and
Applications (FICTA) 2014. Springer. 2015, pags. 113-122.
[12] Gerhard Jager y James Rogers. “Formal language theory: refining the Chomsky hierarchy”. En: Philosophical
Transactions of the Royal Society B: Biological Sciences 367.1598 (2012), pags. 1956-1970.
[13] Gary J Saavedra y col. “A review of machine learning applications in fuzzing”. En: arXiv preprint
arXiv:1906.11133 (2019).
[14] AFL. https://lcamtuf.coredump.cx/afl/. Accessed: 2021-08-20.
[15] afl-fuzz: crash exploration mode. https://lcamtuf.blogspot.com/2014/11/afl-fuzz-crash-
exploration-mode.html. Accessed: 2021-08-23.
[16] Patrice Godefroid, Hila Peleg y Rishabh Singh. “Learn&fuzz: Machine learning for input fuzzing”.
En: 2017 32nd IEEE/ACM International Conference on Automated Software Engineering (ASE). IEEE.
2017, pags. 50-59.
68
BIBLIOGRAFIA Y REFERENCIAS
[17] Jianqing Fan y col. “A theoretical analysis of deep Q-learning”. En: Learning for Dynamics and Control.
PMLR. 2020, pags. 486-489.
[18] Konstantin Bottinger, Patrice Godefroid y Rishabh Singh. “Deep reinforcement fuzzing”. En: 2018 IEEE
Security and Privacy Workshops (SPW). IEEE. 2018, pags. 116-122.
[19] Richard S Sutton. “Generalization in reinforcement learning: Successful examples using sparse coarse
coding”. En: Advances in neural information processing systems (1996), pags. 1038-1044.
[20] Sheila Becker, Humberto Abdelnur, Thomas Engel y col. “An autonomic testing framework for IPv6
configuration protocols”. En: IFIP International Conference on Autonomous Infrastructure, Management
and Security. Springer. 2010, pags. 65-76.
[21] Andreas Zeller y col. The Fuzzing Book. Retrieved 2021-03-12 11:41:11+01:00. CISPA Helmholtz
Center for Information Security, 2021. URL: https://www.fuzzingbook.org/.
[22] AFL: Trophies. https://lcamtuf.coredump.cx/afl/#bugs. Accessed: 2021-08-23.
[23] Marcel Bohme, Van-Thuan Pham y Abhik Roychoudhury. “Coverage-based greybox fuzzing as markov
chain”. En: IEEE Transactions on Software Engineering 45.5 (2017), pags. 489-506.
[24] Andrea Fioraldi y col. “AFL++: Combining Incremental Steps of Fuzzing Research”. En: 14th USENIX
Workshop on Offensive Technologies (WOOT 20). USENIX Association, ago. de 2020.
[25] James Kennedy y Russell Eberhart. “Particle swarm optimization”. En: Proceedings of ICNN’95-international
conference on neural networks. Vol. 4. IEEE. 1995, pags. 1942-1948.
[26] OSS-Fuzz. https://google.github.io/oss-fuzz/. Accessed: 2021-08-23.
[27] Pin 3.2 User Guide. https://software.intel.com/sites/landingpage/pintool/docs/
81205/Pin/html/. Accessed: 2021-08-24.
[28] Namespaces in operation, part 1: namespaces overview. https://lwn.net/Articles/531114/.
Accessed: 2021-08-24.
[29] Namespaces in operation, part 1: namespaces overview. https://www.kernel.org/doc/Documentation/
prctl/seccomp_filter.txt. Accessed: 2021-08-24.
[30] Sanjay Rawat y col. “VUzzer: Application-aware Evolutionary Fuzzing.” En: NDSS. Vol. 17. 2017,
pags. 1-14.
[31] Felipe Petroski Such y col. “Deep neuroevolution: Genetic algorithms are a competitive alternative for
training deep neural networks for reinforcement learning”. En: arXiv preprint arXiv:1712.06567 (2017).
[32] HTML5 Genetic Algorithm 2D Car Thingy. https://rednuht.org/genetic_cars_2/. Accessed:
2021-08-27.
[33] Introduction to Optimization with Genetic Algorithm. https://towardsdatascience.com/introduction-
to-optimization-with-genetic-algorithm-2f5001d9964b. Accessed: 2021-08-27.
[34] Reward Hacking in Evolutionary Algorithms. https://towardsdatascience.com/reward-hacking-
in-evolutionary-algorithms-c5bbbf42994b. Accessed: 2021-08-27.
[35] Gordon Steven y col. “A superscalar architecture to exploit instruction level parallelism”. En: Microprocessors
and Microsystems 20.7 (1997), pags. 391-400.
[36] Brendan Dolan-Gavitt y col. “Lava: Large-scale automated vulnerability addition”. En: 2016 IEEE
Symposium on Security and Privacy (SP). IEEE. 2016, pags. 110-121.
[37] Sebastian Osterlund y col. “CollabFuzz: A Framework for Collaborative Fuzzing”. En: EuroSec. Abr. de
2021.
69
Anexos
70
Anexo A
Cambios Introducidos en BOA
BOA es un proyecto que hemos ampliado, y esta ampliacion se ha realizado partiendo de la version
0.4. Debido a los cambios introducidos, la version actual es la 1.0, cambios que han sido numerosos y de los
cuales se describen los mas relevantes a continuacion.
• El codigo de BOA se ha liberado y ahora esta disponible en GitHub.
• La documentacion de Sphinx ahora esta disponible en Read the Docs.
• Se ha anadido soporte para modulos de analisis dinamico:
• Se han anadido nuevos modulos de analisis dinamico: modulo basico para aplicar fuzzing y modulo
que aplica un algoritmo genetico sobre una dependencia que permita aplicar fuzzing.
• Se han anadido nuevos modulos para generar entradas: modulo basico de generacion de entradas
aleatorias, modulo para la generacion de entradas basado en gramaticas y modulo para la generacion
de entradas basado en entradas correctas como semillas.
• Se han anadido un nuevo modulo para la deteccion de fallos: modulo que detecta fallos basado en
el codigo de salida de ejecucion.
• Se han anadido ficheros con el comportamiento necesario para obtener metricas de cobertura de
codigo a diferentes niveles.
• Se han anadido ficheros de dependencias con la finalidad de automatizar el proceso de instalacion. Estos
ficheros se han dividido segun el uso que se le vaya a dar a BOA para evitar la necesidad de instalar todas
las dependencias del proyecto si no son necesarias.
• Se ha automatizado la ejecucion de las pruebas mediante CI con GitHub Actions.
• Se han anadido nuevas pruebas para verificar el correcto funcionamiento del analisis dinamico ante
cambios.
• Se han cambiado los mensajes de errores, avisos e informacion por el uso de la librerıa de logging de
Python.
• Se han anadido nuevos parametros a la CLI, todos ellos para la gestion del sistema de logging de la
librerıa estandar de Python y para poder depurar la ejecucion.
71
A. CAMBIOS INTRODUCIDOS EN BOA
• Ahora las excepciones mantienen el rastro de otras excepciones con el objetivo de conseguir una traza
que pueda recorrerse hacia atras y ası se pueda detectar el origen de los errores mas sencillamente.
• Se han anadido nuevos codigos de error, y algunos ya existentes se han visto modificados por el cambio
introducido con los modulos intermediarios.
• Actualizacion del fichero de reglas, que sirve a modo de documentacion, para documentar las nuevas
opciones relacionadas con el analisis dinamico.
72
Anexo B
Codigos de Error en BOA
BOA define una serie de codigos de error con la finalidad de poder guiar al usuario en caso de que
el analisis de BOA no tenga exito por cualquier motivo. Estos codigos de error se utilizan con la llamada de
sistema exit, y dicho codigo de error se muestra antes de finalizar la ejecucion del analisis. En la actual version
de BOA, version 1.0, los codigos de error definidos son los siguientes:
• Codigos de error generales:
• Codigo 0: no es un codigo de error, pero lo indicamos aquı porque es el codigo que inicia la
numeracion. Se trata del codigo que indica que no hay ningun error, el cual es el valor 0 por
convencion.
• Codigo 1: error desconocido.
• Codigo 2: fichero no encontrado (error generico de fichero).
• Codigo 3: error relacionado con la configuracion del sistema de logging (e.g. formato incorrecto).
• Codigos de error de los argumentos de entrada:
• Codigo 10: error generico de los argumentos de entrada (e.g. no se introducen todos los argumentos
obligatorios).
• Codigo 11: formato de los argumentos incorrecto.
• Codigos de error en los modulos (general):
• Codigo 200: no se han podido cargar algunos modulos obligatorios y algunos modulos de usuario
(al menos uno de ambos).
• Codigo 201: no se ha podido cargar algun modulo obligatorio (al menos uno).
• Codigo 202: no se ha podido cargar algun modulo de usuario (al menos uno).
• Codigo 203: no se ha podido cargar alguna instancia (al menos una).
• Codigo 204: error al intentar eliminar un modulo que no ha podido ser cargado.
• Codigo 205: no se ha podido cargar una instancia de alguna de las clases abstractas.
• Codigo 206: una instancia no esta utilizando la clase abstracta que se supone que deberıa de estar
utilizando.
73
B. CODIGOS DE ERROR EN BOA
• Codigo 207: no ha sido posible inicializar el importador de modulos.
• Codigos de error en los modulos (dependencias):
• Codigo 210: se intenta utilizar un modulo que no existe como dependencia.
• Codigo 211: se intenta utilizar un modulo como dependencia de sı mismo.
• Codigo 212: se detecta una dependencia cıclica.
• Codigo 213: no se encuentra algun callback de los indicados en las dependencias en el archivo de
reglas.
• Codigos de error del archivo de reglas:
• Codigo 30: el numero de modulos y de clases no coincide.
• Codigo 31: el formato empleado para hacer referencia a un modulo y una clase a traves de una
unica cadena de texto no esta bien formado y no permite encontrar la referencia.
• Codigo 32: no se ha podido abrir el archivo de reglas (e.g. no se tienen los permisos adecuados).
• Codigo 33: no se ha podido leer el archivo de reglas.
• Codigo 34: no se ha podido cerrar el archivo de reglas.
• Codigo 35: el archivo de reglas no cumple con los requisitos sintacticos y/o semanticos especificados
por BOA en el analisis del mismo.
• Codigo 36: no se encuentran los argumentos para los modulos.
• Codigos de error de los ciclos de vida:
• Codigo 40: algun modulo de seguridad ha lanzado una excepcion durante la ejecucion de un ciclo
de vida.
• Codigo 41: un ciclo de vida no ha podido finalizar correctamente.
• Codigo 42: un ciclo de vida no ha podido encontrar algun modulo cuando se iba a ejecutar.
• Codigo 43: no se ha podido cargar la instancia de clase abstracta de los ciclos de vida.
• Codigo 44: no se ha podido cargar alguna instancia de ciclo de vida.
• Codigo 45: alguna instancia de un ciclo de vida no estaba extendiendo la clase abstracta de los
ciclos de vida.
• Codigo 46: los ciclos de vida pueden crearse y asignar su uso para un tipo de analisis, estatico
o dinamico, y si se utiliza un ciclo de vida para un analisis con el que se ha indicado que no es
compatible, este error sera el aviso de esta situacion.
• Codigos de error de los informes (general):
• Codigo 500: argumentos obligatorios (modulo que ha localizado una vulnerabilidad, descripcion
de la vulnerabilidad y nivel de severidad de la vulnerabilidad) sin valor.
• Codigo 501: tipo de datos de los argumentos no esperado (e.g. si se especifica la fila donde se ha
localizado una vulnerabilidad, el tipo de datos tiene que ser un entero).
• Codigo 502: argumento que indica el modulo que ha localizado una vulnerabilidad no cumple con
el formato esperado.
• Codigo 503: no se ha podido anadir correctamente una entrada al informe.
74
B. CODIGOS DE ERROR EN BOA
• Codigo 504: error generico.
• Codigo 505: no se ha podido cargar la instancia de la clase abstracta del informe.
• Codigo 506: no se ha podido encontrar un modulo de informe.
• Codigo 507: alguna instancia de un informe no estaba extendiendo la clase abstracta de los informes.
• Codigos de error de los informes (niveles de severidad):
• Codigo 510: no se ha podido establecer una asociacion entre un nivel de severidad asignado por el
usuario y entre los niveles de severidad disponibles.
• Codigo 511: alguna instancia de los niveles de severidad no esta extendiendo la instancia base u
otra instancia de los niveles de severidad.
• Codigo 512: no se ha podido encontrar un modulo de nivel de severidad.
• Codigo 513: no se ha podido cargar alguna instancia de nivel de severidad.
• Codigos de error de los modulos de analizadores de lenguajes de programacion:
• Codigo 60: no se ha podido encontrar un modulo de analizador.
• Codigo 61: no se ha podido cargar un modulo de analizador.
• Codigo 62: no se ha podido cargar la instancia de la clase abstracta del analizador.
• Codigo 63: alguna instancia del analizador del lenguaje de programacion no esta extendiendo la
instancia de la clase abstracta de los analizadores de lenguajes de programacion.
• Codigo 64: alguno de los callbacks definidos en el archivo de reglas no se han podido ejecutar
correctamente.
• Codigo 65: ninguno de los callbacks definidos en el archivo de reglas se han podido ejecutar
correctamente.
• Codigo 66: el analizador de lenguaje de programacion no ha finalizado con exito el analisis del
fichero de codigo.
• Codigo 67: la ejecucion del analizador no ha finalizado correctamente.
• Otros codigos de error:
• Codigo 1001: se hace uso de alguna palabra clave reservada en algun elemento en el que no esta
permitido.
• Codigo 1002: se ha indicado un tipo de analisis en el fichero de reglas no valido (solo el analisis
static o dynamic estan permitidos para especificarse en el fichero de reglas).
75