2. // WHO AM I
#define speaker Antonio Morales Maldonado
#define job Security Researcher at
#define twitter @nosoynadiemas
using namespace RootedCON;
int main(int argc, char* argv[]){
2
3.
4.
5. ● Podéis pegaros una cabezadita sin problema (siempre que no ronquéis en
exceso)
Aviso
5
No seré yo quien os lo
recrimine (que soy andaluz)
10. ● Se trata de un Use-After-Free que afecta al memory pool de Pro-FTPd
● Fue reportado el 21 de Enero de 2020
● El fallo permite corromper el memory pool de ProFTPd a través de la
interrupción de una transferencia activa
● La gravedad del fallo aumenta dado que:
○ Es explotable (se puede obtener una primitiva de escritura)
○ La corrupción del memory pool deriva en otras vulnerabilidades
(como un OOB-Write o un OOB-Read)
Motivación: CVE-2020-9273
10
12. ● Se conoce como memory pool al uso de “pools” (colección de
recursos) para la gestión de memoría dinámica.
● Se utiliza con el objetivo de reducir la fragmentación de las funciones
nativas (malloc, new) u obtener mejoras en la organización de la
memoria (ej. jerarquía).
12
Motivación: CVE-2020-9273
13. ● Así que, ¿por qué no crear mi propio memory pool?
13
Motivación: CVE-2020-9273
15. La implementación de ProFTPd está
basada en la de Apache HTTP
server, y se estructura de forma
jerárquica (de mayor a menor vida
útil).
Internamente, cada pool se organiza
como una lista enlazada de recursos
y dichos recursos son liberados de
forma automática cuando se
destruye el pool.
15
Motivación: CVE-2020-9273
16. ● Con el objetivo de reducir el número de “allocations” (malloc) ProFTPd
reserva bloques de memoria de gran tamaño que se van añadiendo a la
lista enlazada del pool.
● Cada vez que se realiza una llamada a pcalloc intenta satisfacerla con la
memoria disponible en el ultimo elemento de la lista. Si se requiere más
memoria que la disponible en el ultimo bloque, entonces hay que añadir un
nuevo bloque como último elemento.
• next
• first_avail
Memory
pool • next
• first_avail
Memory
• next
• first_avail
Memory
16
Motivación: CVE-2020-9273
17. ● Asimismo, se mantiene una lista de bloques libres para reducir el numero
de “deallocations” (free). De este modo solo es necesario reservar memoria
nueva cuando la lista de bloques libres se queda vacía.
● Pero, qué sucedería si la función new_block (encargada de devolver
un nuevo bloque) nos devolviera un bloque que no está vacío…
block_freelist • next
• first_avail
Memory
• next
• first_avail
Memory
17
Motivación: CVE-2020-9273
18. ● La función “new_block” nos
devuelve como vacío un bloque
que ya se encuentra en el pool,
corrompiendo la lista del pool
• next
• first_avail
Memory
pool • next
• first_avail
Memory
• next
• first_avail
MemoryMemory
18
Motivación: CVE-2020-9273
19. ● Aquí podemos apreciar como dicho pool está totalmente corrupto:
19
Motivación: CVE-2020-9273
20. ● Aquí podemos apreciar como dicho pool está totalmente corrupto:
20
Motivación: CVE-2020-9273
21. ● Este tipo de errores son difíciles de detectar utilizando ASAN, dado
que ASAN no cuenta con “interceptors” para las funciones de gestión
de memoria del pool (ej. pcalloc)
● Por tanto, solo podremos detectar fallos derivados de estos (a menos
que implementemos nuestros propios interceptors)
● En este caso se detectó un OOB write y un Null dereference durante el
proceso de fuzzing (ambos provenientes del mismo UAF)
21
Motivación: CVE-2020-9273
25. ● Se trata de un OOB-Read que afecta a la función pure_strcmp de
Pure-FTPd
● Fue reportado el 24 de Febrero de 2020
● El fallo se produce dado que dicha función da por supuesto que la
longitud de ambos strings siempre será igual, aunque dicha premisa
es totalmente falsa.
25
Motivación: CVE-2020-9365
31. ● El problema de encontrar todos los posibles bugs para un input de un
tamaño dado, se puede modelar como un problema de búsqueda.
● Razonando un poco acerca de la complejidad del problema, para un
archivo de entrada de tamaño 1KB, si quisiéramos probar todas las
posibles combinaciones de bytes
256 ^ 1024 posibles combinaciones
● Por tanto, resultará imposible realizar una búsqueda por fuerza bruta
sobre todo el posible espacio de búsqueda
31
Complejidad del problema
32. ● El problema de encontrar todos los posibles bugs para un input de un
tamaño dado, se puede modelar como un problema de búsqueda.
● Razonando un poco acerca de la complejidad del problema, para un
archivo de entrada de tamaño 1KB, si quisiéramos probar todas las
posibles combinaciones de bytes
256 ^ 1024 posibles combinaciones
● Por tanto, resultará imposible realizar una búsqueda por fuerza bruta
sobre todo el posible espacio de búsqueda
32
Complejidad del problema
34. ● Los fuzzers como AFL o Libfuzzer son de tipo evolutivo: un proceso
iterativo que evoluciona los inputs de entrada con el tiempo
● La estrategia de selección de posibles candidatos puede ser muy
variada y puede conducir a diferentes resultados
34
Mutation Scheduling
37. Fuzzing evolutivo ≈ Recorrido de un árbol
● El proceso evolutivo de fuzzing se puede equiparar al proceso de
recorrido sobre un árbol
● El nodo padre representa nuestro archivo de entrada inicial, y los
hijos que cuelgan de cada uno de ellos son el resultado de las distintas
mutaciones sucesivas.
● Sin embargo, el problema de “path explosion” nos impide recorrer
todos y cada uno de estos paths (crecimiento exponencial)
● Es por tanto necesario aplicar una estrategia de selección heurística
que ofrezca un equilibrio entre tiempo/resultados
37
39. ● AFL asigna una mayor cantidad de esfuerzo a aquellos inputs situados
a mayor profundidad, con el objetivo de obtener inputs con mayor
cantidad de mutaciones.
39
AFL
40. ● Implementado por Marcel Böhme (Monash University, Australia), se
trata de una extension de AFL que introduce el concepto de power
schedule
● Dichos power schedules gestionan la cantidad de esfuerzo dedicado
en la generación de nuevos inputs a lo largo del tiempo de fuzzing
● Es importante hacer notar que dichos power schedules solo tienen
influencia sobre las etapas de mutaciones no deterministas de AFL
(havoc).
40
AFLFast
41. ● Entre los power schedules implementados por AFLFast Podemos
encontrar:
○ Constante: Asignación de energía constante para cada nivel
○ Lineal: Incremento de tiempo lineal para
○ Exponencial: Incrementa de forma exponencial el tiempo de
espera de aquellos
● Dicho de otro modo, un esquema “exponencial” será más similar a una
búsqueda en anchura mientras que un esquema “lineal” será más
incisivo y profundizará mucho más en el árbol.
41
AFLFast
43. ● Podemos encontrar implementado todos estos power schedules en
AFL++
● AFL++ es un fork de AFL que se encuentra en desarrollo activo y que
incluye gran cantidad de añadidos sobre la versión de AFL original
● Para hacer uso de los power schedules en AFL++ basta con hacer uso
de la opción “-p [schedule]”
● Entre los schedules incluidos podemos encontrar: exploit, coe, fast,
explore, quad o lin
43
AFL++
44.
45. ● Se trata de un proceso heurístico, donde se emplean mutaciones no
deterministas, por lo que no será posible obtener conclusiones que
funcionen para todo software
● La elección de cada uno de los enfoques responde a:
○ A un método de ensayo/error
○ Experiencia práctica del humano
● Es en este punto donde se entremezclan el arte y la ciencia
45
Aviso
46. ● Se trata de un proceso heurístico, donde se emplean mutaciones no
deterministas, por lo que no será posible obtener conclusiones que
funcionen para todo software
● La elección de cada uno de los enfoques responde a:
○ A un método de ensayo/error
○ Experiencia práctica del humano
● Es en este punto donde se entremezclan el arte y la ciencia
46
Aviso
51. ● Al uso de mutaciones personalizadas (custom mutators) en base al
dominio de nuestros inputs
● Por ejemplo, podemos crear mutaciones en base a una gramática
dada.
51
¿Qué es realmente?
52. ● Al uso de mutaciones personalizadas (custom mutators) en base al
dominio de nuestros inputs
● Por ejemplo, podemos crear mutaciones en base a una gramática
dada.
52
¿Qué es realmente?
53. ● Cuando analicemos un software con una arquitectura en pipeline
▪ No confundir con el paralelismo de instrucciones
en los procesadores
▪ Nos referimos a una arquitectura de software “en-
cadena” donde el output de cada uno los módulos
es el input del siguiente módulo.
53
¿Cuándo es útil utilizar structure-aware-fuzzing?
54. ● Cuando analicemos un software con una arquitectura en pipeline
Visión general de la arquitectura de GCC
54
¿Cuándo es útil utilizar structure-aware-fuzzing?
55. ● Cuando analicemos un software con una arquitectura en pipeline
55
¿Cuándo es útil utilizar structure-aware-fuzzing?
56. ● Cuando analicemos un software con una arquitectura en pipeline
▪ En estos casos, un error en el análisis léxico puede provocar que no se
avance a la fase de análisis sintáctico.
▪ O un error en la sintaxis de nuestro input, provocará que no sea
evaluado por el analizador semántico.
56
¿Cuándo es útil utilizar structure-aware-fuzzing?
57. ● Software con gran uso de variables / estados globales
57
¿Cuándo es útil utilizar structure-aware-fuzzing?
58. ● Software con gran uso de variables / estados globales
▪ Tiende a conducir a software con
baja modularidad
▪ Las variables con gran alcance
(scope) suelen incrementar la
complejidad de un potencial análisis
dado que hay más código que puede
modificar el valor de las mismas
58
¿Cuándo es útil utilizar structure-aware-fuzzing?
59. ● Software con gran uso de variables / estados globales
▪ Tiende a conducir a software con
baja modularidad
▪ Las variables con gran alcance
(scope) suelen incrementar la
complejidad de un potencial análisis
dado que hay más código que puede
modificar el valor de las mismas
59
¿Cuándo es útil utilizar structure-aware-fuzzing?
60. ● Tengamos que cumplir restricciones complejas
▪ Por ejemplo restricciones complejas en el orden de envío de los datos o
en los tiempos de ejecución
▪ Un ejemplo de esto serían algunos protocolos de red o algunas APIs,
donde es crucial el orden de la secuencia de peticiones
60
¿Cuándo es útil utilizar structure-aware-fuzzing?
887175 e515cd 3b479f 6ae6fb 1a76138e
61. ● Tengamos que cumplir restricciones complejas
61
¿Cuándo es útil utilizar structure-aware-fuzzing?
62. Un ejemplo de Structure-Aware-Fuzzing bien utilizado
https://github.com/googleprojectzero/fuzzilli
62
¿Cuándo es útil utilizar structure-aware-fuzzing?
63. ● Cuando tengamos restricciones sencillas en el código (como
checksums)
● Para dichos casos se antoja mucho más sencillo eliminarlos del código
y después volver a re-calcularlo para dicho input
63
¿Cuándo no es tan útil utilizar structure-aware-fuzzing?
64. ● Cuando tengamos un software altamente modular
● En dicho caso puede ser preferible descomponer el software en
módulos más simples, y analizar dichos módulos por separado.
64
¿Cuándo no es tan útil utilizar structure-aware-fuzzing?
70. 70
Protocol buffers: Es un formato creado por Google que permite serializar y
representar información estructurada (archivos .proto)
Un paso más allá
71. 71
Un paso más allá
Al igual que en libFuzzer, podemos incluir
libprotobuf-mutator en nuestro custom-
mutator de AFL++.
De este modo, es posible utilizar archivos
.proto para definir la estructura de
nuestro formato.
72. ● Independientemente del caso en el
que lo utilicemos, siempre que
hagamos uso de custom mutators
estaremos acotando nuestros
inputs y por tanto corremos el
riesgo de perder casos límites
● Al final todo se reduce a priorizar
entre profundidad y anchura del
espacio de búsqueda
72
Structure-aware-fuzzing
76. • Edge coverage:
• LibFuzzer (método nativo de LLVM SanitizerCoverage)
• AFL (almacenado en un bitmap de 64kb)
• Complete path coverage
• All possible states
Edge Coverage
Complete path
coverage
All possible
states
76
Otros tipos de cobertura de código
78. • Edge coverage
• Complete path coverage:
• Requiere que todos los pasibles paths de ejecución
sean cubiertos
• Nos encontramos con el problema de los loops en el
código
• All possible states
Edge Coverage
Complete path
coverage
All possible
states
78
Otros tipos de cobertura de código
80. • Edge coverage
• Complete path coverage
• All possible states:
• Todas las posibles combinaciones de variables han sido
probadas
• Cada posible estado en memoria ha sido probado
Edge Coverage
Complete path
coverage
All possible
states
80
Otros tipos de cobertura de código
81. Edge Coverage
Complete path
coverage
All possible
states
INITIAL
CORPUS
FUZZER
● Idealmente, nuestro conjunto
inicial de archivos debería de
cubrir todas las lineas de código y
una cantidad importante de
execution paths (todas las
funcionalidades del programa).
● El proposito de nuestro fuzzer es
encontrar execution paths
“extraños” y estados del
programa que se salgan de lo
previsto.
Escenario de comienzo ideal
83. Necesitaremos utilizar otros métodos de feedback
para guiar a nuestro fuzzer a encontrar nuevos paths
de ejecución…
83
84. ● FuzzFactory es una extension de AFL que
evoluciona el concepto de coverague-guided
fuzzing en el de domain-specific-guided
fuzzing.
● Introduce el concepto de waypoints: inputs
que no incrementan la cobertura de código
de nuestro fuzzer, pero que maximizan
alguna otra métrica dentro de nuestro
dominio.
https://github.com/rohanpadhye/FuzzFactory
84
FuzzFactory: Domain-Specific-guided fuzzing
85. Algunos ejemplos de domain-specific-feedback:
● Inputs que maximizan la longitud de los “execution paths”
85
FuzzFactory: Domain-Specific-guided fuzzing
887175 e515cd 3b479f 6ae6fb 1a76138e
86. Algunos ejemplos de domain-specific-feedback:
● Inputs que maximizan las reservas de memoría dinámica
86
FuzzFactory: Domain-Specific-guided fuzzing
87. Algunos ejemplos de domain-specific-feedback:
● Inputs que minimizan el numero de “asserts” negativos (evitan que el
programa termine de forma prematura con un estado de error)
87
FuzzFactory: Domain-Specific-guided fuzzing
89. 1. Cuando trabajamos con heurísticas siempre existe un factor de
aleatoriedad, por lo que los resultados variaran para un mismo input.
2. Cualquier aproximación que nos permita profundizar en el espacio de
búsqueda, tendrá un costo de perdida de casos límites (anchura), y a la
inversa
3. El factor humano siempre marcará la diferencia a la hora de obtener
resultados.
89
Conclusión