Hola y bienvenidos a todos los lectores.
Como todos los años participo de Ekoparty Conference e intento absorber lo mas que pueda en conocimiento de los speaker y de personas que voy conociendo en el camino. Por esta razón, muchas veces me pierdo de divertidos retos y termino resolviéndolos después de tiempo.
Perdiendo la oportunidad de algún tipo de recompensa jeje. Pero bueno lo importante es resolverlo, aun así, se gana experiencia.
Entonces para ya comenzar con lo importante que es resolver el reto de este año, primero pasaremos por los requisitos impuestos por Blue Frost Security Labs en Ekoparty 2022.
Requisitos:
1. Solo se aceptarán soluciones de Python sin bibliotecas externas
2. El objetivo es ejecutar la Calculadora de Windows (calc.exe)
3. La solución debería funcionar en Windows 10 o Windows 11
4. La continuación del proceso es deseable (no obligatoria)
Descarga de la aplicación:
https://static.bluefrostsecurity.de/files/lab/bfs-eko2022.zip
Protecciones
Las protecciones de un binario son aplicadas para evitar que una posible explotación sea de manera trivial, aumentando la dificultad de explotación.Utilizamos la herramienta de winhecksec.exe, el cual nos muestra que el binario tiene habilitado la protección ASLR, DEP y GS que no se visualiza, pero reversando se puede validar.

| ASLR | Cambia las direcciones de memoria en cada ejecución |
| DEP | Evita ejecutar shellcode en el Stack |
| GS | Detecta desbordamientos que sobrescriben el retorno |
Análisis
Lo primero es lo primero, así que vamos a ejecutar el binario y vemos que levanta un servicio socket en algún puerto.
Utilizamos Process Hacker y en la pestaña Network filtramos por el nombre del binario y vemos que el servicio corre en el 31415. Esta es la manera mas fácil de identificar el puerto. Otra forma seria reversando y ver los parámetros de la función listen(), pero para que nos vamos a complicar.

Ya con esto creamos la primera estructura del script para que se comunique con el servicio. Este lo utilizaremos mas adelante en el análisis dinámico, por ahora vamos a ir reconociendo el flujo de forma estática.
Viendo el código identificamos primeramente la función main(), donde aquí se ejecutan las siguientes acciones que levantan el servicio y realiza algunas comprobaciones de los datos enviados.

Lo mas importante de aquí es cmp_msg_hello() que se encarga de comparar los bytes recibidos con una constante llamada "Hello", si es así sigue el camino correcto al send() para responder con un "Hi" mediante la conexión socket y poder llegar a la función packet_filter().

Ahora vemos las rutinas de packet_filter(), es aquí donde se encuentran las vulnerabilidades y donde hay que pensar.

Cuando entra a la función se ejecuta una especie de memset() sobre el buffer de virtualAlloc(), modificando los bytes existentes por unos definidos por el binario (5050505050… y CF58585858…) los que tienen un propósito.

El segundo recv() recibe una especie de cabecera de paquete que será utilizado para distintas validaciones como tamaño, cookie y el tipo.
1. size valida que la cabecera sea menor o igual a 11 bytes de tamaño
2. cookie valida que se envié la cadena Eko2022
3. type valida que se envié el carácter "T"
4. Integer Overflow es un valor que pertenece a la cabecera llamado size y debe ser menor a 0x0F00 que en decimal es 3840
Aquí podemos ver que existe una comparación con signo, el cual nos permite explotar la vulnerabilidad de Integer Overflow enviando un valor negativo que pronto será interpretado como positivo.
Entonces si en el valor size de la cabecera enviamos 0xFFFF esto será transformado a 0x0000FFFF el cual es negativo y menor a 0x0F00 logrando la evasión de comparación.

Cuando se logra la evasión entramos a llama otro recv() pero con un tamaño que controlamos, en este caso seria el word del valor negativo 0x0000FFFF y los bytes serán copiados a un buffer ubicado en el heap.
Seguido se hace una llamada a la función renombrada como copy_data_heap_to_stack() con los argumentos.
| len | Es el tamaño devuelto por el recv() |
| buf | Es el buffer de heap |
| cmdLine | Es un buffer de stack de 3840 bytes |

Dentro de copy_data_heap_to_stack() tenemos una rutina que realiza una copia byte a byte desde el buffer heap al buffer stack, mediante un ciclo for() utilizando como size el 0xFFFF, provocando un desbordamiento de buffer de stack. Ahora lo importante es poder controlar registros o variables de stack que nos permitan seguir con la ejecución, para eso es necesario enviar bytes de tamaño controlado para poder modificar el valor del tipo y poder llegar a winExec().

Antes de llamar a winExec() se realiza un desplazamiento dentro del buffer y esos bytes son movidos al registro rax el que pisa la dirección de winExec(), por lo tanto no se podrá utilizar la función para ejecutar código. Por ultimo se realiza la llamada.

Si recuerdan en la función memset() se modifican los bytes del buffer con 5050505050 y CF58585858. Ahora con el desplazamiento dentro del buffer mas 7 bytes, nos posicionamos justo en los 5050505050. Entonces cuando se realiza la llamada a winExec() llegamos a los 5050505050, los que se transforman en "pop rax" permitiendo hacer pop al stack y tomar el control del retorno que serian nuestras A's con iret.
Para esto veremos los pop rax y validamos con la información del stack.

Con el siguiente código se comprueba los explicado anteriormente.