CVE-2017-13253: Desbordamiento de búfer en varios servicios DRM de Android

CVE 2017 13253 Desbordamiento de bufer en varios servicios DRM de Android Como parte de nuestra investigación de plataforma en Zimperium zLabs, recientemente revelamos una vulnerabilidad de desbordamiento de búfer que afecta a varios servicios DRM de Android para Google. Google lo clasificó como de gravedad alta, lo designó como CVE-2017-13253 y lo parchó en el Actualización de seguridad de marzo.

En esta publicación de blog, cubriremos los detalles de la vulnerabilidad. Primero, repasaremos la información de fondo relevante, desde los mecanismos generales de Android hasta los mecanismos específicos relacionados con la vulnerabilidad. Nos centraremos en el Proyecto Treble recientemente presentado y en lo que realmente significan sus cambios. Luego, analizaremos la vulnerabilidad y su impacto. Veremos cómo, debido a otras fallas en algunos dispositivos, esta vulnerabilidad podría explotarse para lograr privilegios de root. Por último, hablaremos sobre el origen de la vulnerabilidad y cómo se podría haber evitado. Veremos cómo a pesar de la afirmación de Google de que Project Treble beneficia a la seguridad, es posible que hiciera lo contrario.

Tenga en cuenta que en esta publicación pretendo cubrir una gran cantidad de información básica relacionada con esta vulnerabilidad. Si ya tiene experiencia con Binder y libbinder, puede omitir la primera sección y comenzar con la información más específica de la vulnerabilidad en sí.

Carpeta y seguridad de Android

Un modelo de seguridad muy común utilizado en muchos sistemas operativos (incluido Android) gira en torno a la comunicación entre procesos (IPC). El código no confiable que se ejecuta en un proceso sin privilegios puede comunicarse con procesos (servicios) privilegiados y pedirles que realicen acciones muy específicas que permite el sistema operativo. Este modelo se basa en los servicios (y en el propio mecanismo IPC) para validar correctamente cada entrada enviada desde el proceso sin privilegios. A su vez, esto significa que los errores en esos servicios, especialmente en la parte de validación de entrada, pueden conducir fácilmente a vulnerabilidades.

Por ejemplo, Vulnerabilidades recientes de iOS encontradas por Rani Idan aquí mismo en Zimperium confiar en este método. Los errores en la validación de entrada de IPC permiten que un atacante ejecute código con permisos más altos desde una aplicación sin privilegios.

En el caso de Android, el mecanismo IPC se llama Aglutinante. La seguridad de los servicios de Android Binder es, naturalmente, muy interesante para la investigación de vulnerabilidades. Binder tiene muchas funciones útiles, por ejemplo, permite que los procesos transfieran objetos complejos entre sí, como descriptores de archivos o referencias a otros servicios de Binder. Para mantener la simplicidad y el buen rendimiento, Binder limita cada transacción a un tamaño máximo de 1 MB. En los casos en que los procesos necesitan transferir grandes cantidades de datos, pueden usar la memoria compartida para compartir rápidamente los datos entre ellos.

Biblioteca C++ de Binder

Biblioteca Binder de Android (encuadernador) proporciona muchas abstracciones para el código C++ que se basa en Binder. Le permite llamar a métodos de instancias remotas de clases de C++ casi como si no residieran en otro proceso.

Cada objeto que utiliza este mecanismo implementa algunas clases en una estructura predefinida:

  • Una clase de interfaz que define los métodos del objeto que deben poder llamarse a través de Binder. Con el prefijo «I».
  • Una clase del «lado del cliente» a cargo de serializar la entrada y deserializar la salida. Con el prefijo «Bp».
  • Una clase del «lado del servidor» a cargo de deserializar la entrada y serializar la salida. Con el prefijo «Bn».

Eventualmente, cuando se usa el objeto, casi siempre se usa el tipo de interfaz. Esto le permite tratar el objeto de la misma manera ya sea que esté en el mismo proceso o en uno diferente.

1670075468 994 CVE 2017 13253 Desbordamiento de bufer en varios servicios DRM de Android

Ejemplo del uso de libbinder en la interfaz ICrypto

La parte del código del «lado del servidor» tradicionalmente se encuentra dentro del servicio privilegiado (aunque en algunos casos los roles se invierten), por lo que generalmente se encarga de validar la entrada. El código de validación puede comenzar en la clase Bn* y continuar a lo largo de los métodos llamados posteriormente. Esta es obviamente la parte más interesante para la investigación de vulnerabilidades.

La interfaz ICrypto y el método de descifrado

Después de cubrir Binder en general, echemos un vistazo a la implementación específica relevante para la vulnerabilidad. los servidor mediadrm El servicio (que, como era de esperar, está a cargo de los medios DRM) proporciona una interfaz para un Cripto objeto, la interfaz se llama entonces ICrypto. Tenga en cuenta que el objeto se cambió recientemente a CryptoHal, entraremos en eso más tarde. El propósito general de esta interfaz es permitir que las aplicaciones sin privilegios descifren datos DRM que requieren mayores privilegios para descifrar, como el acceso a la TEE. Los detalles del cifrado en sí no están dentro del alcance de esta publicación de blog, ya que nuevamente, estamos más interesados ​​en la validación de entrada.

ICrypto tiene múltiples métodos, pero sin duda el más importante (en torno al cual giran todos los demás) es descifrar.

1670075469 474 CVE 2017 13253 Desbordamiento de bufer en varios servicios DRM de Android

firma de descifrado (fuente)

Una de las primeras cosas notables en la firma de decrypt es cuán compleja es la entrada. Desde nuestro punto de vista, esto es muy interesante. La entrada compleja conduce a un código de validación complejo (cada parámetro se transfiere a través de Binder y debe verificarse) que puede ser susceptible a vulnerabilidades.

Veamos algunos de los parámetros:

[wpsm_comparison_table id=”3″ class=””]

(Para obtener más información sobre algunos de los parámetros del lado de Java de nivel superior de la API, consulte MediaCodec.CryptoInfo)

Ahora echemos un vistazo más de cerca a los tipos de parámetros de origen y destino:

1670075469 781 CVE 2017 13253 Desbordamiento de bufer en varios servicios DRM de Android

(fuente)

Los miembros de la estructura relevantes aquí son mHeapSeqNum y ambos mSharedMemory miembros (el resto de DestinationBuffer es en caso de que el destino no se almacene como memoria compartida, un caso que no es relevante para esta vulnerabilidad). El nombre montón se usa aquí para referirse a la memoria compartida real (que es para lo que ejecuta mmap). mHeapSeqNum es un identificador para una memoria como esa, que se compartió previamente usando un método de ICrypto llamado establecer montón. Ambos miembros de mSharedMemory solo representan el desplazamiento y el tamaño de un búfer dentro del montón. Esto significa que aunque mHeapSeqNum está dentro de la estructura de origen, en realidad es relevante para ambos.

1670075470 388 CVE 2017 13253 Desbordamiento de bufer en varios servicios DRM de Android

Ejemplo de parámetros para una ejecución de descifrado con datos claros

Es interesante notar que algunas partes de cómo se estructuran los parámetros son un poco raras. mSharedMemory es una IMemoria, que en realidad está conectado a su propio montón y se supone que representa un búfer dentro de él, pero este montón se ignora y el desplazamiento y el tamaño se usan para el montón mHeapSeqNum. También existe la presencia de mHeapSeqNum en la estructura de origen, pero está relacionado tanto con el origen como con el destino. Todo esto es consecuencia de los cambios recientes en este código, realizados como parte de una importante remodelación del marco de trabajo de Android llamada Project Treble.

Proyecto triple

Proyecto triple se introdujo como parte de Android 8.0; su objetivo principal es facilitar las actualizaciones del sistema al crear una separación clara entre AOSP y el proveedor. Google también afirma que Project Treble beneficia la seguridad de Android al agregar más aislamientos.

Para un servicio como mediadrmserver, Project Treble significa separación en múltiples procesos. El código a cargo del descifrado pertenece al proveedor, por lo que se separa en múltiples procesos de proveedores llamados HAL, cada uno a cargo de su propio esquema DRM. El rol de mediadrmserver ahora se reduce a transferir datos entre la aplicación y el proceso HAL para el esquema DRM relevante. La comunicación entre mediadrmserver y las HAL también se realiza a través de Binder, pero en un dominio diferente y utilizando el formato de una biblioteca diferente: bibliotecario. El cambio mencionado anteriormente de Crypto a CryptoHal se debe a que ahora es una clase diferente cuyo único propósito es convertir los datos al formato de libhwbinder y pasarlos a HAL.

1670075470 992 CVE 2017 13253 Desbordamiento de bufer en varios servicios DRM de Android

La figura anterior muestra por qué Google afirma que Project Treble beneficia la seguridad. Los permisos están separados en diferentes procesos (cada HAL solo puede comunicarse con su propio controlador) y las aplicaciones que no son de confianza ya no interactúan directamente con el proceso de privilegios elevados.

Tenga en cuenta que a partir de Android 8.1, la separación sigue siendo opcional y depende del proveedor. En Nexus 5X, por ejemplo, las HAL están todas dentro del proceso mediadrmserver. Los datos aún se convierten al formato HAL pero no se transfieren a otro proceso.

Complementos criptográficos

Anteriormente mencioné diferentes esquemas DRM, en la terminología de Android, el controlador para cada esquema DRM se denomina complemento, o Complemento criptográfico en nuestro caso concreto. los el proveedor está a cargo de proporcionar estos complementos, pero hay un código útil en AOSP que el proveedor puede usar. Por ejemplo, AOSP contiene una implementación completa de código abierto de un complemento para el esquema ClearKey DRM. Por lo general, los dispositivos tendrían el complemento ClearKey de código abierto y un complemento Widevine de código cerrado (ese es el caso de los dispositivos Nexus/Pixel, por ejemplo).

El problema con los cambios de Project Treble antes mencionados es que ahora los complementos reciben datos en formato HAL. Para simplificar la transición sin necesidad de actualizar cada complemento para admitir este nuevo formato, se agregó una implementación predeterminada de Crypto Plugin a AOSP para el uso de los proveedores. Esta implementación convierte los datos del formato HAL al formato heredado y los pasa al código del complemento original. Idealmente, esta solución debería ser solo temporal hasta que se actualicen los complementos, porque de lo contrario nos quedaremos con conversiones de formato redundantes (hacia y desde HAL).

1670075470 863 CVE 2017 13253 Desbordamiento de bufer en varios servicios DRM de Android

El flujo de conversiones de formato de datos

Buscando en el código fuente

Después de cubrir el proceso general del método de descifrado de ICrypto, veamos de cerca el código de validación para los búferes de memoria compartida. Como habrás adivinado (ya que estamos hablando de un desbordamiento de búfer), esta es el área donde se encontró la vulnerabilidad.

Como se mencionó anteriormente, la validación generalmente comienza en las clases Bn*, en nuestro caso, BnCrypto, el «lado del servidor» de la interfaz ICrypto.

1670075471 309 CVE 2017 13253 Desbordamiento de bufer en varios servicios DRM de Android

Parte del código de validación de BnCrypto (fuente)
  • Primero, el código verifica que la suma de los tamaños de las submuestras sea válida y no se desborde. Recuerde que es el tamaño de los datos a copiar.
  • También comprueba que esta suma coincida tamaño totalotro parámetro pasado por Binder que es bastante redundante (ya puede saber el tamaño total por la suma de las submuestras, el código verifica específicamente que este sea el caso).
  • La siguiente verificación es que el tamaño de los datos no exceda el tamaño del búfer de origen.
  • Por último, comprueba que el tamaño de los datos más el desplazamiento no excedan el búfer de origen.

A partir de ahí, CryptoHal convierte los datos en formato HAL y los envía al complemento correspondiente; no hay ningún código de validación interesante aquí.

A continuación, la implementación predeterminada de Crypto Plugin (que puede o no estar en un proceso diferente) vuelve a convertir los datos al formato heredado y continúa validándolos.

1670075472 308 CVE 2017 13253 Desbordamiento de bufer en varios servicios DRM de Android

Parte del código de validación predeterminado de Crypto Plugin (fuente)

Una nota al margen sobre este código: lo encuentro un poco desordenado. Hay múltiples variables de «destino» y «fuente», fuenteBase y destBase son en realidad exactamente lo mismo (el montón) y no hay ningún comentario para ayudarlo. Como mencioné anteriormente, esta parte es completamente nueva y solo se agregó en Android 8.0, por lo que tiene sentido. Aún así, tengo una ligera sospecha de que este desorden condujo a la vulnerabilidad, ya que hace que sea mucho más difícil mirar todo el código de validación y ver si falta algo.

  • La primera verificación aquí es que la suma de las compensaciones y el tamaño del búfer no exceda el tamaño del almacenamiento dinámico. fuenteBase es el montón mientras fuente ahora es lo que antes era source.mSharedMemory. Si está confundido por las dos compensaciones, recuerde que mSharedMemory contenía una compensación y también hay un parámetro de compensación diferente para el método de descifrado.
  • La otra verificación es similar, pero se realiza en el búfer de destino. búfer de destino es lo que era destination.mSharedMemory y destBase es el mismo montón que fuenteBase. Esta vez el parámetro de compensación no está involucrado.

Eventualmente, cada búfer se simplifica en solo un puntero a la memoria; el desplazamiento ahora es parte del puntero mientras que se omite el tamaño del búfer. Para determinar el tamaño de los datos, el complemento utiliza la matriz subSamples.

1670075472 570 CVE 2017 13253 Desbordamiento de bufer en varios servicios DRM de Android

El código del complemento ClearKey para cuando los datos no están encriptados (fuente)

El código anterior muestra la parte final para ayudar a comprender el flujo. Como se mencionó anteriormente, cuando los datos no están encriptados, simplemente se copian de un lugar a otro.

Por ahora, he dado suficiente información para que, en teoría, puedas detectar la vulnerabilidad. Si quiere probar y hacer eso, puede regresar y seguir leyendo el código. Desde mi experiencia, es muy difícil en este tipo de publicaciones de blog brindar suficiente información para detectarlo y mantenerlo como un desafío real (especialmente desde el punto de vista de alguien que ya lo encontró), así que no se asuste incluso si no puedo detectarlo (¿o tal vez fue demasiado fácil?).

la vulnerabilidad

El problema es que no hay verificación de que la cantidad de datos que se copian no supere el búfer de destino. Solo hay una verificación similar para el búfer de origen (la tercera verificación de BnCrypto verifica eso y la siguiente verificación incluso agrega la compensación adicional en consideración). La única verificación relacionada con el búfer de destino es la segunda verificación del complemento criptográfico predeterminado (que asegura que el búfer se encuentre dentro del montón y no lo exceda), pero eso simplemente no es suficiente.

Echemos un vistazo a un ejemplo. Digamos que el tamaño de los datos a copiar es 0x1000. Dado que este tamaño está representado por la matriz de submuestras, tendremos una entrada en esa matriz con 0x1000 bytes claros (y 0 bytes cifrados). El montón también tendrá 0x1000 bytes y el búfer de origen apuntará al montón completo (compensación = 0, tamaño = 0x1000). El búfer de destino es donde se pone interesante. Digamos que el desplazamiento es 0x800 y el tamaño es 0x800. Esto todavía cabe dentro del montón, por lo que pasa la verificación del complemento criptográfico predeterminado. En este caso, habrá un desbordamiento; Se escribirían 0x800 bytes después del montón.

1670075473 267 CVE 2017 13253 Desbordamiento de bufer en varios servicios DRM de Android

Prueba de concepto

1670075473 893 CVE 2017 13253 Desbordamiento de bufer en varios servicios DRM de Android

El código para el ejemplo mencionado anteriormente que desencadena la vulnerabilidad (se puede encontrar un enlace a la PoC completa al final de la publicación del blog)

Nota: Base de memoria Los objetos son la implementación de la interfaz libbinder de IMemory. Este es un ejemplo en el que se utiliza la capacidad de Binder de transferir referencias a otros objetos de Binder. Este también es un ejemplo en el que se invierten los roles de Binder. El proceso privilegiado es el “lado del cliente”, por lo que solicita información a través de Binder y se encarga de validarla.

El efecto de la vulnerabilidad.

Esta vulnerabilidad permite al atacante sobrescribir la memoria en el proceso de destino con datos arbitrarios. Dado que se trata de un desbordamiento en el nivel de las páginas de memoria, actualmente no hay mitigación para evitarlo (como si hubiera un control controlado de pila para un desbordamiento de pila). Todavía está restringido por el hecho de que los datos deben comenzar dentro de la memoria compartida, debido a la verificación del Crypto Plugin predeterminado. Esto significa que solo se puede sobrescribir la memoria que se encuentra directamente después de la memoria compartida. Además, normalmente muchas áreas en la memoria no están asignadas o no se pueden escribir, por lo que intentar escribir allí resultará en una falla de segmentación.

Los procesos afectados dependen de la implementación del proveedor. Si el proveedor no separa las HAL en diferentes procesos, el servidor mediadrm se verá afectado. Si el proveedor los separa, todos los servicios HAL para un complemento criptográfico que utiliza el código predeterminado del complemento criptográfico se ven afectados. Dado que el código predeterminado de Crypto Plugin solo deja un puntero al búfer de destino, y el tamaño solo está determinado por las submuestras, el código del proveedor no tiene forma de saber que recibió datos mal formados. Esto significa que no importa qué tan bien escrita esté la parte del proveedor, seguirá siendo vulnerable.

Posible impacto

Digamos que un atacante logra explotar esta vulnerabilidad para elevar los privilegios a los privilegios del servicio vulnerable, así que veamos qué puede lograr. Tenga en cuenta que esta parte es principalmente especulativa. No he escrito un exploit, pero tengo algunas ideas sobre cómo esta vulnerabilidad podría usarse teóricamente para alcanzar todos los privilegios de root.

Aquí es donde entran en juego las reglas de SELinux de Android; aunque el servicio vulnerable tiene más permisos, SELinux aún los limita fuertemente. Aún así, incluso después de las limitaciones, nos queda un permiso muy interesante: acceso completo al dispositivo TEE.

Entonces, ¿qué puede hacer con acceso completo al TEE? La excelente investigación de Gal Beniamini muestra que muchos dispositivos no pueden revocar correctamente los viejos trustlets TEE vulnerables. Esto significa que si ataca un dispositivo que tiene un trustlet vulnerable antiguo, podría usar el acceso al dispositivo TEE, cargar el trustlet y explotarlo en ejecución de código en el TEE. Más que eso, Gal Benimaini también demostró en el pasado cómo en los dispositivos basados ​​en Qualcomm la ejecución del código TEE podría conducir a privilegios de root.

1670075474 568 CVE 2017 13253 Desbordamiento de bufer en varios servicios DRM de Android

Posible flujo de ataque hasta los privilegios de root

El origen de la vulnerabilidad

Ya mencioné varias veces cómo Project Treble provocó modificaciones importantes en esta área del código. Probablemente no le sorprenda saber que estos cambios en realidad introdujeron esta vulnerabilidad (antes de los cambios, el búfer de destino ni siquiera podía establecerse en este formato).

Obviamente, no puede culpar únicamente a la refactorización principal por hacer que el código sea vulnerable, ya que eso implicaría que la refactorización del código no debería ocurrir, lo cual no es cierto. La cuestión es que, como ya he señalado, varias partes de este código son desordenadas o redundantes. Si bien esto en sí mismo no necesariamente hace que un código sea vulnerable, sí aumenta las posibilidades de que eso suceda, ya que hace que el código sea más difícil de revisar (algunas partes del código me llevaron bastante tiempo para comprenderlas en comparación con la complejidad de lo que realmente significan). hacer). Entonces, si bien las vulnerabilidades a veces pueden ser muy difíciles de detectar, el código desordenado o redundante suele ser más fácil de detectar. Sé que es más fácil criticar un mal diseño de código desde el punto de vista de un revisor que escribir un buen código, pero sigo pensando que hay algunas partes que deberían mejorarse.

Conclusión

Google afirma que Project Treble beneficia la seguridad de Android, pero en este ejemplo en realidad hizo lo contrario. Project Treble no es necesariamente malo en sí mismo, el problema clave aquí es que la implementación no se manejó muy bien.

Se puede encontrar el código fuente completo para el PoC que desencadena la vulnerabilidad en GitHubjunto con alguna información adicional.

Cronología

  • 20.12.2017 – Vulnerabilidad descubierta
  • 28.12.2017 – Detalles de vulnerabilidad + PoC enviado a Google
  • 29.12.2017 – Respuesta inicial de Google
  • 05.03.2018 – Parches distribuidos por Google

Me gustaría agradecer a Google por su respuesta rápida y profesional, Simone Margaritelli (@evilsocket), Nikias Bassen (@pimskeks) y el resto del Zimperium zLabs equipo.

Deja un comentario