Hace algunos años, uno de los ataques más habituales consistía en aprovechar un error de corrupción de memoria como un buffer overflow para inyectar código (normalmente shellcode) y desviar el flujo de control hacia ese código. Sin embargo, con la adopción generalizada de DEP (Data Execution Prevention) las páginas de memoria que contienen datos como el heap y el stack se marcan como no ejecutables, lo que deja inviables este tipo de ataques. En consecuencia, los ataques de reutilización de código en todas sus variantes (RILC, ROP, JOP, COOP) se convierten en la forma más utilizada para explotar errores de memoria, puesto que se basan en encadenar pequeños trozos de código (gadgets) del propio programa para conseguir el mismo objetivo. Las técnicas de aleatorización, entre las que se incluyen ASLR y otras técnicas recientes desarrolladas por la comunidad, complican este tipo de ataques haciendo que el atacante no pueda averiguar la localización de los gadgets. No obstante, una de las formas de sobrepasar defensas basadas en secretos como estas es mediante vulnerabilidades de information disclosure, por lo que muchos investigadores se han enfocado en combatir dichas vulnerabilidades.
Investigadores de la universidades de Vrije en Amsterdam, Ruhr de Bochum y del instituto tecnológico Stevens han presentado en EuroS&P (European Sysmposium on Security and Privacy) un estudio en el que demuestran que un nuevo tipo de ataque de reutilización de código, al que han llamado Position-Independent ROP (PIROP), se puede llevar a cabo de forma práctica sin necesidad de information disclosure para superar ASLR, basándose en la posición relativa en lugar de la absoluta de los gadgets en memoria. El ataque que proponen se puede dividir conceptualmente en 4 pasos:
- 1. Stack massaging: El primer paso consiste en ejecutar el programa variando las entradas para buscar datos y punteros a código masajeables en el stack que puedan proporcionar gadgets que ejecuten operaciones críticas como llamadas al sistema o stack pivoting.
- 2. Parcheo de punteros a código: Para poder realizar algunas operaciones necesarias o evitar que la ejecución de determinados gadgets dificulte la explotación, los punteros a código se parchean en el ROP payload para que apunten a distintas ubicaciones de código. Parcheando solo algunos bits específicos (los bits menos significativos) en los punteros a código usando una primitiva de escritura relativa (un buffer overflow no lineal como el ejemplo de la imagen) se pueden redirigir los punteros a una ubicación relativa al objetivo original, de forma que se garantice que el ROP payload permanece independiente de la aleatorización mientras se expande el conjunto de gadgets usables.
- 3. Parcheo de operandos: En el mejor de los casos, los datos sobre los que operan los gadgets se pueden establecer directamente controlando los valores de entrada del programa. Sin embargo no siempre es posible o puede que el payload también contenga punteros a datos aleatorizados, por lo que al igual que en el paso anterior, se puede aprovechar una primitiva de escritura relativa.
- 4. Ejecución del ROP payload: Finalmente, una vez que el payload se ha preparado es necesario dirigir el flujo de control para su ejecución, por ejemplo sobrescribiendo el byte menos significativo de una dirección de retorno en el stack mediante un buffer overflow.
Para validar la viabilidad de esta nueva técnica construyen exploits para Firefox y Asterisk y evalúan su efectividad en presencia de defensas contra information disclosure, ASLR y otras técnicas fine-grained de aleatorización (por ejemplo, a nivel de función), demostrando que puede superar las implementaciones más comunes de ASLR y debilitar considerablemente las defensas más avanzadas sin necesidad de information disclosure.