Analizador de Vulnerabilidades Mediante Análisis Dinámico

88

Transcript of Analizador de Vulnerabilidades Mediante Análisis Dinámico

Page 1: Analizador de Vulnerabilidades Mediante Análisis Dinámico
Page 2: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 3: Analizador de Vulnerabilidades Mediante Análisis Dinámico

“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

Page 4: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 5: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 6: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 7: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 8: Analizador de Vulnerabilidades Mediante Análisis Dinámico

INDICE

B. Codigos de Error en BOA 73

VII

Page 9: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 10: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 11: Analizador de Vulnerabilidades Mediante Análisis Dinámico

Indice de algoritmos

1. CGF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14

2. AFL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

3. Algoritmo Genetico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40

X

Page 12: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 13: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 14: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 15: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 16: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 17: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 18: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 19: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 20: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 21: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 22: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 23: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 24: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 25: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 26: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 27: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 28: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 29: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 30: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 31: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 32: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 33: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 34: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 35: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 36: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 37: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 38: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 39: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 40: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 41: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 42: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 43: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 44: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 45: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 46: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 47: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 48: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 49: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 50: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 51: Analizador de Vulnerabilidades Mediante Análisis Dinámico

5. DISENO

Figura 5.2: Estructura basica del ADN

Genotipo

Fenotipo

Ambiente

+ =

Figura 5.3: Relacion entre genotipo y fenotipo

38

Page 52: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 53: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 54: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 55: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 56: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 57: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 58: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 59: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 60: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 61: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 62: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 63: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 64: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 65: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 66: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 67: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 68: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 69: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 70: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 71: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 72: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 73: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 74: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 75: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 76: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 77: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 78: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 79: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 80: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 81: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 82: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 83: Analizador de Vulnerabilidades Mediante Análisis Dinámico

Anexos

70

Page 84: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 85: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 86: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 87: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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

Page 88: Analizador de Vulnerabilidades Mediante Análisis Dinámico

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