ASLR y cómo un programa puede realmente llamar a sus funciones

4

Estoy estudiando técnicas de protección y tengo una duda sobre cómo funciona ASLR para un programa en un entorno Windows.

Por lo que sé, ASLR funciona aleatorizando parte de la dirección de base de datos de imágenes al cargar el módulo en la memoria, de modo que las vulnerabilidades no puedan depender de direcciones codificadas.

Mi pregunta es: supongamos que un programa utiliza el módulo "kernel32.dll" y lo carga en su espacio de direcciones. Probablemente hay instrucciones CALL dentro de ese programa como

...
CALL some_address_inside_kernel32.dll
...
...

¿cómo sabe el programa a dónde saltar si las direcciones de la función kernel32.dll ya no son confiables? ¿Interviene el cargador en este proceso?

    
pregunta Marco A. 18.01.2013 - 18:17
fuente

2 respuestas

6

No llamas a funciones dentro del kernel. El kernel reside en otro nivel de privilegio; Sus páginas de memoria no son accesibles desde el código normal. Para saltar al código del kernel, el código de la aplicación realiza una llamada al sistema que implica el uso de una puerta específica que maneja la escalada temporal de privilegios. En un sistema x86 de 32 bits que ejecuta Linux, esto se hace con int 0x80 : una interrupción activada por software. El llamante proporciona los parámetros de llamada del sistema en algunos registros de CPU específicos; en particular, el registro %eax contiene el identificador simbólico para la llamada del sistema que la aplicación desea realizar. El manejador de interrupciones (dentro del kernel) mira los registros de la CPU para saber qué quiere la aplicación que haga el kernel (si la llamada al sistema concedida es otro problema).

No hay ASLR para llamadas al sistema; Este no es un concepto relevante aquí. ASLR es para DLL . Una DLL es una parte del código de aplicación que se carga en el espacio de direcciones de la aplicación y es accesible para la aplicación bajo sus privilegios normales; en particular, el código de la aplicación puede "saltar" al código DLL con un opcode de "salto" normal (una llamada de función no es más que un salto glorificado).

Dado que la dirección real en la que se cargará la DLL es conocida solo cuando la DLL está realmente cargada, el código de la aplicación sigue convenciones especiales para que sus códigos de salto se puedan ajustar dinámicamente para que apunten a donde quiera que se cargue la DLL. Este ajuste dinámico lo realiza el enlazador dinámico . Este enlazador utiliza tablas de reubicación para realizar su trabajo: una entrada en dicha tabla describe un código de operación que debe ajustarse, y el nombre de la función que el código de operación intenta alcanzar. Cuando se carga la DLL (mediante el enlazador dinámico), se conoce el emplazamiento en la memoria de esa función, y se ajustan los códigos de operación de salto descritos en las tablas de reubicación.

Como puede ver, todo el concepto de DLL permite que la DLL se cargue en un emplazamiento arbitrario en la RAM; ese emplazamiento puede diferir en ejecuciones sucesivas de la aplicación. El emplazamiento real depende de dónde haya un "agujero" de memoria libre lo suficientemente grande para realizar la carga, por lo que puede cambiar dependiendo de lo que haga la aplicación, la cantidad de memoria que asigne previamente, etc. La carga de DLL "naturalmente" implica direcciones de carga no fijas. ASLR es solo voluntario de mover DLL: el vinculador dinámico elige una carga aleatoria (gratuita) dirección a propósito . Esto es (casi) completamente transparente para las aplicaciones.

    
respondido por el Thomas Pornin 18.01.2013 - 18:58
fuente
3
  

¿cómo sabe el programa a dónde saltar si las direcciones de la función kernel32.dll ya no son confiables? ¿Interviene el cargador en este proceso?

El oso le ha dado la descripción general de ASLR. Sin embargo, hay una serie de puntos específicos de Windows que debe eliminar:

  • En realidad, los archivos DLL han siempre tenido que hacer frente a la reubicación. Contienen una dirección base preferida dentro del espacio de direcciones, al punto que un paso de optimización anterior en el desarrollo de aplicaciones win32 como vuelva a ajustar sus DLL porque el costo de reubicar muchas DLL en el lanzamiento de la aplicación se desaceleró, ya que cargar muchas DLL causó muchas colisiones.
  • Esto contrasta con el enfoque PIC de Unix, consulte wikipedia .
  • Cuando se carga una DLL, permanece en la memoria hasta que se cierra el último manejador. Dado que sería ineficiente asignar una DLL a una ubicación de dirección única por proceso, esto significa que una vez cargadas, las DLL comunes del sistema, como kernel32.dll , están presentes en la misma dirección en todas las aplicaciones. Puede leer más sobre los aspectos internos de ASLR en las excelentes respuestas aquí . Como tal, aunque puede ser difícil predecir las direcciones en kernel32.dll en los reinicios, una vez que se encuentra para un inicio dado, la dirección no cambia.
  • En la arquitectura de Windows, kernel32.dll es de hecho una biblioteca auxiliar de modo de usuario. Como dice Thomas con razón, se necesitan llamadas al sistema para llegar al modo kernel. De hecho, ocurren en ntdll.dll (Sí, otra DLL) que envuelve varias funciones con prefijo Nt en llamadas al sistema, las empaqueta y las enciende en el núcleo.
  • Los controladores del kernel se cargan de forma no determinista: el kernel tiene ASLR. Consulte este hilo osr y esta presentación . Como lo entiendo, las ventanas managar bootean ntoskrnl y hal.dll en ubicaciones al azar para ti. Los controladores desde Server 2008 ciertamente siempre se cargan de manera no determinista.
respondido por el user2213 19.01.2013 - 00:42
fuente

Lea otras preguntas en las etiquetas