Buenas a todos, en este post me gustaría hablaros de fuzzing de binarios ya que es una técnica muy utilizada en la explotación de binarios. En este caso aprenderemos a usar la herramienta de fuzzing AFL.
Peeero como no me gustaría utilizar un binario ya preparado que sabemos 100% que explotará, ya que están pensados para ello, utilizaremos un caso real, en este caso es una vulnerabilidad muy conocida ya que afecta a uno de los programas más populares a día de hoy que es 7zip.
Que mejor manera que aprender a usar una herramienta en un caso real XD, dicho esto, empezemos
La vulnerabilidad CVE-2024-11477
La vulnerabilidad CVE-2024-11477, tal y como se muestra en la imagen, es una vulnerabilidad RCE que se produce al hacer un Integer Underflow.
Dicho Integer Underflow se produce solamente cuando descomprimimos un fichero comprimido con el algoritmo ZStandard, de ahí que su CVSS sea de un 7.8 (Que a pesar de ser bastante alta, no estamos hablando de un Score de 9.0 o 10.0). No obstante, nos servirá de ejemplo para aprender a usar AFL más adelante.
Un Integer Underflow es una vulnerabilidad que, ha diferencia de un Overflow normal, se produce cuando alcanzamos el valor mínimo que tiene una variable y seguímos restando de manera indefinida.
Dicho de otra manera, imaginad que tenemos una variable «n» que no puede ser negativa. Si nosotros vamos restando, llegaremos a 0. Aquí la pregunta es: ¿Qué pasaría si seguimos restando? Exacto aquí se produce el problema XD. De repente, ese valor no pasa a ser -1, si no que pasa a ser el valor más alto que puede tener esa variable.
Claro, para que se de esta vulnerabilidad, necesitamos que se cumpla otra condición mas y es que esa variable «n», se utilice en un buffer. Un ejemplo de pseudocódigo vulnerable sería el siguiente:
function iu_vuln(var:user_value):
var:n = user_value // n cannot be negative
var:buffer[255]
do:
n = n - CONST_MAX_SIZE // OOOPS
memcopy(buffer, n, sizeof(buffer)) // buffer, offset, sizeof
while: n <= 0
end function
Esto podría ser un código plenamente funcional y a simple vista parece que no sea vulnerable, pero… ¿Qué pasaría si en user_value nos inventáramos un número cuya resta nunca va a dar 0? Uno piensa, bueno para eso está el «<=» ¿No?. Pues siento decirte que ese «menor que» no te va a servir para nada XD Porque en el momento que «Imaginemos que «n» vale 2 y «CONST_MAX_SIZE» vale 3″ hagas la resta de, 2 – 3, aunque a términos matemáticos su resultado sea -1, para el software, QUE NO ACEPTA NEGATIVOS, ese -1 se convertirá en, por ejemplo, 3456423456 (Un número tan grande como quepa en «n», por lo que seguirá restando y copiando al buffer de manera indefinida.
Una vez entendido que es un Integer Underflow vamos a utilizar AFL para encontrar esta vulnerabilidad
¿Qué es AFL?
Durante el post, he estado hablando todo el rato de AFL pero ¿Qué es realmente AFL?
AFL o por sus siglas (American Fuzzing Lop) es una herramienta de fuzzing capaz de encontrar stack smashing dentro de un binario.
AFL es considerado un Smart Fuzzer ya que esta herramienta es capaz de a partir de una o varias muestras funcionales, ir permutando y modificando dichas muestras de manera automática hasta encontrar uno o varios stacksmashing.
No solamente hace eso si no que AFL tiene un compilador propio «afl-gcc» que permite a la herramienta crear «chivatos» dentro del código cuyo fin es avisar a la herramienta de que ha encontrado un «nuevo camino» dentro del ejecutable con esa nueva variante creada a partir de la muestra original.
En resumen, es una herramienta muy potente para realizar fuzzing en binarios. (Sería el equivalente a «dirb» en fuzzing web. Existen herramientas mejores pero todo el mundo conoce «dirb» XD)
Utilizando AFL contra 7zip
Instalando AFL
Instalar AFL es muy fácil, solamente tenemos que ejecutar el siguiente comando y ya está jeje
sudo apt install afl-g++
Con este comando instalará toda la suite de AFL (Software y compiladores)
Instalando GDB con esteroides
Lo siguiente que necesitaremos es GDB pero con esteroides XD. En este caso os doy dos opciones:
- GDB PEDA: https://github.com/longld/peda
- GDB GEF: https://github.com/hugsy/gef
Las dos opciones son igual de buenas, puede que a uno le guste más PEDA y a otros GEF XD. Yo en mi caso me he instalado GDB PEDA.
Si no tenéis GDB instalado, podéis instalarlo con:
sudo apt install gdb
Compilando 7zip
Lo que viene a continuación es descargarse el código de 7zip. En este caso, necesitamos la versión anterior al parche, que es la 24.06 por lo que accedéis a la siguiente URL:
https://github.com/ip7z/7zip/releases/download/24.06/7z2406-src.tar.xz
Una vez descomprimido, vamos a modificar el fichero «7zip_gcc.mak» que se encuentra en «CPP\7zip», en la Línea 41 descomentamos el DEBUG_BUILD = 1 y en la línea 51 cambiamos el O2 por O0.
Esto lo que hace es habilitar los símbolos de depuración y hacer que el código resultante no se optimice para facilitar la tarea de reversing.
Una vez realizado los cambios, vamos a la carpeta CPP/7zip/Bundles/Anon y ejecutamos el siguiente comando:
CC=afl-gcc CXX=afl-g++ make -f makefile.gcc
Con esto, lo que estamos haciendo es compilar 7zip con el compilador de AFL para poder facilitar el fuzzing.
Una vez compilado, el output se encuentra en la carpeta «_o»
OPCIONAL: Para verificar que el binario compilado tiene los símbolos de depuración podemos ejecutar el comando «file 7za»
Antes de lanzar el AFL, necesitaremos crear un fichero que, si pueda ser procesado por 7za. Como la vulnerabilidad está en ZStandard al momento de descomprimirse, crearemos un 7zip utilizando dicho algoritmo de compresión. Ese fichero lo colocaremos en una carpeta, que previamente crearemos, llamada «testcases/».
Una vez hecho esto, queda ejecutar por fin AFL XD
afl-fuzz -S 7zipfuzzer -i testcases -o output -t 10000 -P exploit ./7za x @@
Lo que hace cada parámetro es lo siguiente:
- -S: Crea un proyecto llamado 7zipfuzzer
- -i: Carpeta donde se encuentran los testcases/
- -o: Carpeta donde se guardarán los resultados del AFL.
- -t: Indica un timeout para cuando se producen bucles infinitos dentro del binario.
- -P: Esto define el comportamiento de AFL, en este caso se centra mas en buscar crasheos.
El último parámetro es la ejecución del binario con @@ para indicar donde se colocan los testcases. Ejecutamos el comando y a esperar XD
IMPORTANTE: Si os da un fallo a la hora de ejecutar afl-fuzz, ejecutar el siguiente comando: sudo afl-system-config
TIP: En este caso, sabemos que tarde o pronto va a crashear pero en el caso de que seamos bug bountiers, es recomendable ejecutar varias instancias de AFL Fuzz con el fin de agilizar el proceso.
En unos minutos veremos ya el primer crasheo de la aplicación. ¡Genial!
Una vez hemos conseguido crashear la aplicación vamos a ver como y porque ha crasheado y para eso usaremos AFLTriage
AFLTriage es una herramienta que nos da información de donde, como y cuando ha petado la aplicación generando un reporte en txt a partir de los resultados del AFL Fuzz.
Esta herramienta se encuentra en el siguiente link: https://github.com/quic/AFLTriage
Una vez instalada, ejecutaremos el siguiente comando
afltriage -i output/7zipfuzzer/crashes -o reports -t 1000 ./7za x @@
Como podéis observar el comando es muy parecido al de afl-fuzz XD
Si abrimos el txt generado por AFLTriage veremos lo siguiente:
Si hacemos zoom fijaos donde crashea…
¡Perfecto! Estamos viendo que crashea justo al momento de descomprimir un 7zip con compresión ZStandard y si nos fijamos, lo primero que muestra el Stacktrace del crasheo (Sería la última información antes del crasheo) es que se rompe en la línea COPY_CHUNKS
Vamos a analizar que es COPY_CHUNKS abriendo el código fuente con Visual Studio Code y en el buscador de ficheros buscamos por el nombre que he mencionado antes.
Vaya, según la línea marcada vemos que se esta restando una variable len menos un valor fijo llamado COPY_CHUNK_SIZE. ¿Esto os suena de algo?
Efectivamente, es parecido al ejemplo que os puse de Integer Underflow, en este caso el do…while seguirá haciendo loop hasta que len sea igual a 0 o menor de zero peeeero, si len no soporta valores negativos… ¿Qué pasaría? Efectivamente si len no llega nunca a 0 y su resultado da un valor negativo, se generará un número muchísimo mas grande positivo por lo que el bucle nunca llegará a acabarse XD
Vamos a analizar este crasheo con GDB-Peda para ver a lo que me refiero, para ello ejecutamos el siguiente comando:
gdb --args ./7za x ./output/7zipfuzzer/crashes/id:000000,sig:11,src:000132+000144,time:224168,execs:112391,op:splice,rep:1
Esto lo que está haciendo es básicamente ejecutar GDB con el input que ha conseguido crashear el binario y al escribir en la consola «r» tendremos lo siguiente:
Como podeis ver lo que pasa es que se está intentando escribir un valor dentro$ rdx en la dirección de memoria que esta almacenada en $rax
Si hacemos un vmmap de la dirección del valor que hay en $rax fijaos que muestra:
¿Entendéis porque está crasheando? Efectivamente, está intentando escribir en un sitio dentro de la memoria que tiene únicamente permisos de lectura por lo que esa es la causa de porque crashea.
Si hacemos un vmmap para ver cómo está distribuida la memoria dentro de la aplicación fijaos el gran salto que ha hecho:
Nosotros seriamos todo lo marcado por 7za más el heap y tal vez mapped pero es que ha saltado de ahí y seguramente ha ido sobrescribiendo hasta llegar a un segmento de memoria de solo lectura porque si XD
Ahora bien, todo esto es muy bonito, pero… ¿Esto es realmente explotable?
Para ello utilizaremos otra herramienta, pero esta vez es una extensión de GDB llamada «exploitable»
La podéis descargar desde aquí: https://github.com/jfoote/exploitable
Una vez descargada ejecutáis el Python desde gdb con el comando source y al ejecutarla os aparecerá el siguiente output:
Esta extensión lo que hace es analizar el stacktrace que ha provocado el crash así como verificar si el stack pointer es correcto, en este caso nos indica que es posiblemente EXPLOTABLE por que se está escribiendo en un sitio donde no tocaría XD
Por último, quedaría el desarrollo del exploit pero en este caso no vamos a crearlo debido a la complejidad de este XD es por eso que lo dejaremos por aquí.
Como curiosidad, vamos a ver como han arreglado esta vulnerabilidad
Como podemos ver han cambiado el tipo de valor de byte a unsigned para aumentar el tamaño que puede almacenar esa variable y además han incluido un if que al parecer lo que hace es que si sym se excede de un número máximo, entonces da un SZ_ERROR_DATA evitando la vuln.
En este momento los bug-bounters se encargarían de verificar el parche tirando el mismo payload pero en una nueva versión pero esto os lo dejo a vosotros a ver si conseguís una vulnerabilidad nueva 😛
Conclusiones
Como conclusión podemos decir que el uso de herramientas como AFL Fuzzing facilitan mucho la búsqueda de Seg. Faults dentro de la aplicación que pueden llevar a un Stack Smashing.
También se ha demostrado que nunca puedes confiar en el input del usuario porque te la puede liar XD
Aunque no haya exploit público, al parecer esta vulnerabilidad está siendo explotada por lo que es recomendable que actualiceis el 7zip si aún no lo habéis hecho jeje
¡Muchas gracias por leer el post y nos vemos en el siguiente!