¿Usar cadena de formato para controlar el flujo de un proceso?

6

Hoy estaba limpiando un código siguiendo el consejo de alguien que conozco, y me informaron sobre el potencial de un pirata informático para controlar el flujo del proceso mediante cadenas de formato, en particular la opción "% n".

No estoy completamente seguro, tanto conceptual como prácticamente, de cómo se logrará esto.

¿Tengo razón al pensar que tiene algo que ver con sobrescribir las direcciones de retorno? Suponiendo que sepa dónde estoy escribiendo,% n todavía solo me da el número de caracteres, ¿así que seguramente la prevención de desbordamiento de búfer haría esto casi imposible? ¿O hay una manera de mover el número 0xbbff a la ubicación de memoria 0xb7001122 con una cadena como 0xb7001122 0xbbff %n ?

¡Hay relativamente poco al respecto en línea! Estoy bastante confundido!

    
pregunta Richard 30.11.2012 - 22:50
fuente

2 respuestas

6

Para entender lo que está pasando, el punto importante es que en C, las funciones no "saben" cuántos argumentos les dio la persona que llama. Para las funciones normales, el compilador detecta usos no válidos. Por ejemplo, si escribes esto:

#include <string.h>
/* ... */
    memcpy(a, b);

luego el compilador se quejará en voz alta y se negará a finalizar la compilación: el encabezado incluido <string.h> declara la función memcpy() tomando tres parámetros (dos punteros y un entero del tipo size_t ). Si el compilador ve una llamada a memcpy() con solo dos parámetros, la falta de coincidencia lo hará gritar.

Sin embargo, algunas funciones toman una cantidad variable de parámetros, por ejemplo, %código%. Para estas funciones, el compilador no puede hacer mucho chequeo. printf() usa como primer argumento una "cadena de formato" cuyo contenido indica a printf() cuántos otros argumentos debería encontrar y su tipo esperado. Cuando la cadena de formato es una constante literal, los compiladores inteligentes pueden hacer algunas verificaciones adicionales (con printf() , esto se hace con " format "attribute , una extensión específica de gcc). Pero no puede hacer nada cuando la cadena de formato se obtiene en tiempo de ejecución. Imagina, por ejemplo, el siguiente código:

printf(s);

donde gcc es una cadena que el atacante puede elegir (por ejemplo, algunos datos enviados a través de la red o un parámetro de invocación). El programador simplemente asumió que la cadena era "simple" (solo caracteres alfanuméricos) pero el atacante puede poner signos " s " en ella, que % interpreta al observar parámetros adicionales. No hay parámetros adicionales, por lo que printf() realmente mirará las ranuras de memoria donde los parámetros adicionales habrían sido , si hubiera habido alguno. Estas ranuras son "más bajas en la pila" (técnicamente, en direcciones más altas, ya que las pilas "crecen hacia abajo" en la mayoría de las arquitecturas); allí se encuentran las variables locales, la dirección de retorno de la función y otros elementos de la pila para la función actual (y su interlocutor, y su interlocutor, etc.)

Al utilizar los especificadores printf() , el atacante puede leer todas estas ranuras ( %u imprimirá debidamente su contenido), y eso ya es malo. Con el parámetro printf() , los poderes del atacante incluso se mejoran: %n toma el siguiente parámetro, lo interpreta como un valor de puntero, lo sigue y escribe en la dirección apuntada a la cantidad actual de caracteres hasta el momento (la cantidad de caracteres impresos por %n hasta el printf() ). Si la llamada %n defectuosa ocurre en una función donde hay una variable local que también está bajo el control del atacante (por ejemplo, un valor entero simple), entonces el atacante puede hacer un printf() para usar esa variable local como puntero, y escribir datos donde apunta. En pocas palabras, esto le da al atacante la capacidad de escribir los bytes que quiera, donde quiera que quiera en la memoria RAM. En ese momento, estás bastante condenado.

El código correcto habría sido:

printf("%s", s);

que luego forzó a %n a imprimir la cadena controlada por el atacante "como está", sin hacer nada especial con los caracteres " printf() ".

Esta vulnerabilidad % resalta el hecho de que C es un lenguaje bastante peligroso: no hay controles de límites, no hay controles de tipo fuerte ... puede hacer que el programa vea las ranuras de memoria como si fueran parámetros, mientras que no lo son, y también interpretar los patrones de bytes como si fueran punteros, que no lo son. Los programadores competentes de C escribirán su código para dar al compilador la mayor información posible, a fin de detectar errores (los agujeros de seguridad son solo errores de programación con un efecto que puede torcerse para ventaja del atacante). Pero la competencia es un recurso escaso, e incluso los mejores programadores cometen errores ocasionalmente.

    
respondido por el Thomas Pornin 01.12.2012 - 18:41
fuente
2

El proceso de explotación de la cadena de formato depende de cómo se utiliza la cadena de formato en la aplicación vulnerable. Por ejemplo, printf () es solo lectura de memoria, lo que puede permitir al atacante leer una dirección ASLR'ed o Valor canario que podría usarse en la conjugación con un desbordamiento de búfer para obtener el control del proceso. Si la aplicación utiliza sprintf () , la aplicación está escribiendo en la memoria comenzando en una dirección base específica. Al proporcionar demasiadas opciones de cadena de formato como %n , el atacante puede sobrescribir la memoria fuera de los límites de la dirección de destino. Si el destino es una variable local declarada en la pila, entonces es posible que el atacante corrompa el marco de la pila y obtenga el control del puntero de instrucción.

Estos ataques y muchos más están cubiertos en Explotación de software: cómo romper el código y Hacking: El arte de la explotación .

    
respondido por el rook 01.12.2012 - 00:09
fuente

Lea otras preguntas en las etiquetas