
Técnicas de explotación de ChakraCore
CVE-2016-7190 [0] es un desbordamiento de montón en la función Array.map() de ChakraCore que permite sobrescribir memoria adyacente. La idea principal para obtener acceso arbitrario de lectura/escritura es primero asignar una serie de Arrays de enteros JavaScript consecutivos y explotar el desbordamiento para manipular el tamaño de un array. Luego, aprovechamos este array para cambiar la dirección base de un Uint8Array a cualquier dirección que queramos leer/escribir. La razón de este paso adicional es que el desbordamiento está limitado a 2 veces el tamaño del array de enteros desbordado.
Esta vulnerabilidad es muy adecuada para probar diferentes estrategias de explotación porque puede reintroducirse fácilmente en versiones actuales de ChakraCore (ver undo-cve-2016-7190.patch).
En este ejemplo, secuestramos el flujo de control sobrescribiendo el puntero vtable de un objeto Uint8Array de C++ a una dirección de memoria controlada, e invocamos una función de los objetos Uint8Array que resulta en la invocación de una función virtual.
La Integridad del Flujo de Control (CFI) es una técnica de defensa para mitigar ataques de secuestro del flujo de control. La idea general de CFI es calcular un grafo de flujo de control (CFG) de una aplicación durante el tiempo de compilación, y luego instrumentar la aplicación con comprobaciones en tiempo de ejecución para asegurar que el flujo de control no se desvíe del CFG calculado estáticamente durante la ejecución. Sin embargo, si una aplicación, como un navegador web, soporta generación dinámica de código, el CFG debe ser extensible durante la ejecución. Esto impone varios desafíos:
Durante el tiempo en que realizamos nuestra investigación, nos centramos en el último desafío. La idea principal es manipular la entrada (datos) del compilador just-in-time (JIT). Como resultado, el compilador JIT generaría código malicioso que luego se integraría en el contexto actual—e incluso se endurecería con CFI. De manera concurrente a nuestra investigación, theori [4] mostró cómo eludir CFI manipulando la salida del compilador JIT. Este ataque se mitiga verificando la integridad de la salida mediante un checksum. Esto no puede detener nuestro ataque porque manipulamos la entrada del compilador JIT. SIN EMBARGO, al externalizar la compilación JIT a otro proceso (conocido como Arbitrary Code Guard [3]) ambos ataques se mitigan.
Demostramos que un atacante puede explotar un ataque solo de datos contra el compilador JIT para generar código nativo arbitrario. En particular, modificamos la representación intermedia (IR) del compilador JIT para inyectar instrucciones controladas por el atacante. Cuando el compilador JIT genera el código nativo basado en el IR modificado, generará código nativo controlado por el atacante.
Entre el momento en que la investigación se realizó originalmente y ahora, el IR de ChakraCore cambió. No hicimos el esfuerzo de portar el ataque, por lo tanto, para experimentar con este ataque, utilice los siguientes binarios.
Durante el análisis de ACG [3] notamos, entre otros [1,2], que los datos globales de solo lectura, almacenados en la sección .mrdata, deben ajustarse para integrar el código generado dinámicamente. Esto se realiza mediante la función LdrProtectMrdata() que utiliza un candado para hacerlo seguro para subprocesos. Sin embargo, la sección .mrdata se reasigna primero como escribible antes de adquirir el candado. Curiosamente, la sección .mrdata contiene la dirección base y el tamaño de la sección .mrdata que luego se usa para reasignar esta sección nuevamente como solo lectura.
Un atacante puede explotar esto para crear una primitiva de ataque que permita reasignar memoria de solo lectura como escribible. Por lo tanto, el atacante ejecuta los siguientes pasos:
La consecuencia de este ataque es que los datos previamente confiables, es decir, datos asignados como solo lectura, se vuelven no confiables. En nuestra prueba de concepto, explotamos esta primitiva para eludir Control-flow Guard (CFGuard) cambiando el puntero a la función de verificación de CFGuard que se guarda en _guard_dispatch_icall_fptr. El efecto se puede observar adjuntando el depurador y cambiando la variable bypass_cfguard.
[0] https://bugs.chromium.org/p/project-zero/issues/detail?id=923
[1] http://alex-ionescu.com/publications/euskalhack/euskalhack2017-cfg.pdf
[2] https://sites.google.com/site/bingsunsec/dataonlyattack
[3] https://blogs.windows.com/msedgedev/2017/02/23/mitigating-arbitrary-native-code-execution/
[4] http://theori.io/research/chakra-jit-cfg-bypass