Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
Pixel_GPU_Exploit — Exploit del kernel de Android 14 para Pixel7/8 Pro | Kitploit
Herramientas/GitHubGitHub/0x36/pixel_gpu_exploit
Seguridad AndroidEscalada de PrivilegiosForensia de MemoriaAnálisis de VulnerabilidadesExplotaciónAprendizaje y EducaciónExplotación de Binarios
GitHub0x36/pixel_gpu_exploit

Pixel_GPU_Exploit

Exploit del kernel de Android 14 para Pixel7/8 Pro

Ver Repositorio
55788hace 2 añosRevisado por Kitploit

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

Mali GPU Kernel LPE

Este artículo proporciona un análisis en profundidad de dos vulnerabilidades del kernel en la GPU Mali, alcanzables desde el sandbox de aplicaciones predeterminado, que identifiqué y reporté de forma independiente a Google. Incluye un exploit del kernel que logra capacidades arbitrarias de lectura/escritura del kernel. En consecuencia, deshabilita SELinux y eleva los privilegios a root en los modelos Google Pixel 7 y 8 Pro con las siguientes versiones de Android 14:

  • Pixel 8 Pro: google/husky/husky:14/UD1A.231105.004/11010374:user/release-keys
  • Pixel 7 Pro: google/cheetah/cheetah:14/UP1A.231105.003/11010452:user/release-keys
  • Pixel 7 Pro: google/cheetah/cheetah:14/UP1A.231005.007/10754064:user/release-keys
  • Pixel 7: google/panther/panther:14/UP1A.231105.003/11010452:user/release-keys (por m4b4 (Marcel))

Vulnerabilidades

Este exploit aprovecha dos vulnerabilidades: un desbordamiento de enteros resultante de un parche incompleto en el comando ioctl gpu_pixel_handle_buffer_liveness_update_ioctl, y una fuga de información en los búferes de mensajes del Timeline Stream.

Subdesbordamiento de búfer en gpu_pixel_handle_buffer_liveness_update_ioctl() debido a una corrección incorrecta del desbordamiento de enteros

Google abordó un desbordamiento de enteros en el comando ioctl gpu_pixel_handle_buffer_liveness_update_ioctl en este commit. Al principio, cuando reporté este problema, pensé que el error era causado por un problema en el parche descrito anteriormente. Tras revisar el informe, me di cuenta de que mi análisis de la vulnerabilidad era inexacto. A pesar de mi suposición inicial de que el parche estaba incompleto, en realidad resuelve y previene un subdesbordamiento en el cálculo. Esto me llevó a sospechar que el cambio no se había aplicado en las compilaciones de producción. Sin embargo, aunque puedo provocar un subdesbordamiento en el cálculo, no es posible provocar un desbordamiento. Esto sugiere que el comando ioctl se ha corregido parcialmente, aunque no con el parche mostrado anteriormente. Al examinar IDA se reveló que otra corrección incompleta se incluyó en las versiones de producción, y este parche no está presente en ninguna rama git del módulo del kernel de la GPU Mali.

Esta vulnerabilidad se descubrió por primera vez en la última versión de Android y se reportó el 19 de noviembre de 2023. Google me informó posteriormente de que ya la habían identificado internamente y le habían asignado el CVE-2023-48409 en el Boletín de seguridad de Android de diciembre, etiquetándola como un problema duplicado.

Aunque pude verificar que el error había sido identificado internamente meses antes de mi informe (según la fecha del commit, alrededor del 30 de agosto), sigue habiendo confusión. En concreto, es extraño que los niveles de parche de seguridad (SPL) de octubre y noviembre de los dispositivos más recientes siguieran afectados por esta vulnerabilidad —no he investigado versiones anteriores a estas. Por lo tanto, no puedo determinar de manera concluyente si realmente se trataba de un problema duplicado y si el parche correspondiente estaba efectivamente programado para diciembre antes de mi envío, o si hubo un descuido al abordar esta vulnerabilidad.

De todos modos, lo que hace poderosa esta vulnerabilidad es lo siguiente:

  • El búfer info.live_ranges está completamente controlado por el usuario.
  • Los valores que se desbordan son entrada controlada por el usuario; por lo tanto, podemos desbordar el cálculo para que el puntero info.live_ranges pueda estar en un desplazamiento arbitrario anterior al comienzo de la dirección del kernel buff.
  • El tamaño de la asignación también es entrada controlada por el usuario, lo que permite solicitar una asignación de memoria de cualquier asignador de slabs de propósito general.

Esta vulnerabilidad comparte similitudes con la vulnerabilidad de subdesbordamiento de búfer DeCxt::RasterizeScaleBiasData() que encontré y exploté en el kernel de iOS 15 en 2022.

Fuga de punteros del kernel en los búferes de mensajes del Timeline Stream

La GPU Mali implementa un timeline stream personalizado diseñado para recopilar información, serializarla y, posteriormente, escribirla en un búfer circular siguiendo un formato específico. Los usuarios pueden invocar el comando ioctl kbase_api_tlstream_acquire para obtener un descriptor de archivo que les permita leer de este búfer circular. El formato de los mensajes es el siguiente:

  • Un encabezado de paquete
  • Un identificador de mensaje
  • Un búfer de mensaje serializado, cuyo contenido específico depende del identificador de mensaje.

Por ejemplo, la función __kbase_tlstream_tl_kbase_kcpuqueue_enqueue_fence_wait serializa los punteros del kernel kbase_kcpu_command_queue y dma_fence en el búfer de mensaje, lo que resulta en la fuga de punteros del kernel a procesos de espacio de usuario.```c void __kbase_tlstream_tl_kbase_kcpuqueue_enqueue_fence_wait( struct kbase_tlstream *stream, const void *kcpu_queue, const void *fence ) { const u32 msg_id = KBASE_TL_KBASE_KCPUQUEUE_ENQUEUE_FENCE_WAIT; const size_t msg_size = sizeof(msg_id) + sizeof(u64) + sizeof(kcpu_queue) + sizeof(fence) ; char *buffer; unsigned long acq_flags; size_t pos = 0;

root@kitploit:~
buffer = kbase_tlstream_msgbuf_acquire(stream, msg_size, &acq_flags);

pos = kbasep_serialize_bytes(buffer, pos, &msg_id, sizeof(msg_id));
pos = kbasep_serialize_timestamp(buffer, pos);
pos = kbasep_serialize_bytes(buffer,
	pos, &kcpu_queue, sizeof(kcpu_queue));
pos = kbasep_serialize_bytes(buffer,
	pos, &fence, sizeof(fence));

kbase_tlstream_msgbuf_release(stream, acq_flags);

}

root@kitploit:~
El exploit de prueba de concepto filtra la dirección del objeto `kbase_kcpu_command_queue` monitoreando el id de mensaje `KBASE_TL_KBASE_NEW_KCPUQUEUE`, que es despachado por la función `kbasep_kcpu_queue_new` cada vez que se asigna un nuevo objeto de cola kcpu.

Google me informó que la vulnerabilidad fue reportada en marzo de 2023 y se le asignó  [CVE-2023-26083](https://source.android.com/docs/security/bulletin/2023-07-01) en su boletín de seguridad. No obstante, pude replicar el problema en los últimos dispositivos Pixel con los Niveles de Parche de Seguridad (SPL) de octubre y noviembre, lo que indica que la corrección no se había aplicado correctamente o no se había aplicado en absoluto. Posteriormente, Google abordó rápidamente el problema en el Boletín de Actualización de Seguridad de diciembre sin ofrecer crédito, y más tarde me informó que el problema se consideraba un duplicado. Sin embargo, la justificación para etiquetar este problema como duplicado sigue siendo cuestionable.

## Explotación
---
Así que tengo dos vulnerabilidades interesantes. La primera ofrece una potente capacidad para modificar el contenido de cualquier dirección del kernel alineada a 16 bytes que se encuentre antes de la dirección ~buff~ asignada. La segunda vulnerabilidad proporciona pistas sobre las posibles ubicaciones de objetos dentro de la memoria del kernel.

### Notas sobre los valores buffer_count y live_ranges_count
Con control total sobre los campos `buffer_count` y `live_ranges_count`, tengo la flexibilidad de seleccionar la slab objetivo y el desplazamiento preciso al que pretendo escribir. Sin embargo, seleccionar valores para `buffer_count` y `live_ranges_count` requiere una consideración cuidadosa debido a varias restricciones y factores:
- Ambos valores están relacionados, y el desbordamiento solo ocurrirá si se eluden todas las comprobaciones recientemente introducidas.
- El requisito de que el desplazamiento negativo esté alineado a 16 bytes restringe la capacidad de escribir en cualquier ubicación elegida. Sin embargo, esto generalmente no es un obstáculo significativo.
- Optar por un desplazamiento más grande hace que se escriba una gran cantidad de datos en áreas de memoria que pueden no ser los objetivos previstos. Por ejemplo, si el tamaño de asignación se desborda a `0x3004`, el puntero `live_ranges` se establecería a `-0x4000` bytes del espacio asignado del objeto `buff`. La función `copy_from_user` escribiría entonces `0x7004` bytes, según el cálculo de `update->live_ranges_count` multiplicado por 4. En consecuencia, esta operación daría como resultado que datos controlados por el usuario sobrescriban el área de memoria entre el puntero `live_ranges` y la asignación de `buff`. Por lo tanto, es esencial asegurarse cuidadosamente de que ningún objeto crítico del sistema dentro de ese rango sea sobrescrito accidentalmente. Dado que la operación implica una llamada a `copy_from_user`, uno podría considerar provocar un `EFAULT` desmapeando deliberadamente la región de memoria no deseada después del búfer de origen del usuario para evitar que se escriban datos en ubicaciones sensibles. Sin embargo, este enfoque es ineficaz, porque si la función `raw_copy_from_user` falla, pondrá a cero los bytes restantes en el búfer de destino del kernel. Este comportamiento se implementa para garantizar que, en caso de una copia parcial debida a un error, el resto del búfer del kernel no contenga datos no inicializados.```c
static inline __must_check unsigned long
_copy_from_user(void *to, const void __user *from, unsigned long n)
{
	unsigned long res = n;
	might_fault();
	if (!should_fail_usercopy() && likely(access_ok(from, n))) {
		instrument_copy_from_user(to, from, n);
		res = raw_copy_from_user(to, from, n);
	}
	if (unlikely(res))
		memset(to + (n - res), 0, res);
	return res;
}

Teniendo esto en cuenta, debemos seleccionar cuidadosamente el objeto a sobrescribir y los datos a escribir.

Elegir el objeto correcto para sobrescribir

Como estoy atascado con esta desafortunada comprobación, mi estrategia es identificar un objeto que, si se anula, no produzca ningún resultado no deseado. Pero, antes de llegar a eso, hay otro problema que resolver. ¿Recuerdas que en la última parte dije que puedo elegir cualquier tamaño de asignación y, por tanto, cualquier caché de slab de propósito general para atender mi búfer de asignación? Eso no es correcto, porque es debido a copy_from_user de nuevo. Es por la mitigación CONFIG_HARDENED_USERCOPY. Prohíbe especificar un tamaño que no coincida con el tamaño de la caché de slab correspondiente donde se encuentra el búfer de destino del kernel (en este caso) de un objeto del heap. Determina si la página del búfer es una página slab y, si es así, recupera el kmem_cache->size correspondiente y determina si el tamaño proporcionado por el usuario no lo excederá; de lo contrario, el kernel simplemente se bloquea debido a la discrepancia de tamaño. En otras palabras, no puedo apuntar a objetos que pertenezcan al asignador de propósito general, PERO aún puedo apuntar a objetos que tengan tamaños grandes (es decir, aquellos atendidos directamente por el asignador de páginas).

Lo primero que me vino a la mente fue usar la técnica de pipe_buffer, que es una técnica muy elegante para obtener primitivas arbitrarias de lectura/escritura. No entraré en detalle sobre la técnica, pero se anima a los lectores a leer este fantástico blog de Interrupt Labs. Al construir un objeto pipe, el objeto pipe_buffer se crea inicialmente en un array de 16 elementos; sin embargo, el tamaño del array se puede ajustar usando fcntl(F_SETPIPE_SZ). Por lo tanto, la asignación del array pipe_buffer se puede ajustar para que sea atendida por el asignador de páginas, lo que lo convierte en un objeto objetivo perfecto para atacar. Después de seleccionar el objeto pipe_buffer como candidato objetivo, el siguiente paso para lograr r/w del kernel es sobrescribir su contenido con la vulnerabilidad de underflow, lo que me permitirá leer/escribir desde/hacia cualquier ubicación de memoria cuya página esté sobrescribiendo el campo pipe_buffer->page. Como la vulnerabilidad me permite escribir datos arbitrarios, puedo controlar todo el contenido de 'pipe_buffer', incluido su campo page, y para hacerlo, necesito asignar el array pipe_buffer antes del objeto vulnerable kbuff y tienen que estar uno al lado del otro.

Posicionando los objetos pipe_buffer y buff de forma adyacente

Rocié la memoria del kernel con muchos objetos kbase_kcpu_command_queue seguidos de un montón de arrays pipe_buffer. No puedo usar solo los arrays pipe_buffer como fuente principal para el spray debido a la limitación impuesta por pipe_max_size. Por lo tanto, decidí comenzar el spray con el objeto kbase_kcpu_command_queue. Elegir el objeto kbase_kcpu_command_queue fue por dos razones: su tamaño de asignación es 0x38C8, por lo que lo maneja el asignador de páginas, y puedo obtener determinísticamente su dirección de kernel usando el bug de fuga de información del kernel, lo que lo convierte en un buen objeto para hacer spray y también en un buen objeto objetivo (como veremos en la siguiente sección).

Como se mencionó antes, usé fcntl(F_SETPIPE_SZ) para aumentar el tamaño de la asignación del array pipe_buffer para que pueda ser atendida por el asignador de páginas. Para ser más específico, elegí que el tamaño de asignación fuera de ==0x4000 bytes (4 * PAGE_SIZE)== para ser coherente con las asignaciones de kbase_kcpu_command_queue.

Obteniendo una dirección de struct page

Para usar correctamente pipe_buffer, se requiere una dirección de página. Poder identificar la dirección de kernel de un objeto kbase_kcpu_command_queue que puedo crear y destruir deliberadamente lo convierte en un buen candidato para usar, y encontrar su struct page correspondiente se puede lograr usando virt_to_page.

Contenido para escribir en el pipe_buffer

Entonces, el objeto pipe_buffer es el siguiente:```c struct pipe_buffer { struct page *page; unsigned int offset, len; const struct pipe_buf_operations *ops; unsigned int flags; unsigned long private; };

root@kitploit:~
Como se mencionó anteriormente, el campo `page` debe incluir una dirección de página válida. Los campos `offset` y `len` no deben exceder `PAGE_SIZE`; de lo contrario, la tubería incrementará los contadores head/tail, lo que resulta en el uso de un nuevo objeto `pipe_buffer` y la pérdida de control sobre el pipe buffer falso.
Además, `flags` debe ser `PIPE_BUF_FLAG_CAN_MERGE` para que las siguientes llamadas a `pipe_write`, en lugar de incrementar ciegamente el contador head y usar el siguiente pipe buffer, primero comprueben si hay espacio en el `pipe_buffer` actual que pueda alojar la solicitud de escritura; si lo hay, simplemente añadirán datos al mismo pipe buffer a partir del valor almacenado en el campo `len`.
Para evitar que el dispositivo falle en `pipe_buf_confirm`, que es invocada por `pipe_write` y `pipe_read`’, el puntero `ops` también debe ser una dirección de kernel válida con un campo `ops->confirm` establecido a _NULL_. Simplemente puedo usar un desplazamiento dentro del objeto filtrado `kbase_kcpu_command_queue` que sea NULL y que no cambiará bajo ninguna circunstancia.

### Elegir el valor de offset óptimo para el underflow
Si bien los tamaños de asignación de `buff` ,`kbase_kcpu_command_queue` y `pipe_buffer` son de ~0x4000~ bytes, elegí hacer underflow del buffer con **0x8000** bytes. ¿por qué ? 

Echemos un breve vistazo a cómo se actualizan los `pipe_buffers` durante las operaciones de lectura y escritura. Supongamos que podemos dar forma al `pipe_buffer` para que se vea así:```c
struct pipe_buffer {
	.page = virt_to_page(addr),
	.offset =  0,
	.len = 0x40,
	.ops = kcpu_addr + 0x50,
	.flags = PIPE_BUF_FLAG_CAN_MERGE,
	unsigned long private = 0
};

Si bien el bug permite controlar arbitrariamente el contenido de este objeto, solo lo hace una vez porque el objeto con underflow se libera inmediatamente después de que termina la llamada ioctl. Esto en realidad plantea un problema porque necesito actualizar manualmente el objeto pipe_buffer para que vuelva a ser utilizable, ya que cada operación de lectura/escritura del pipe:

  • El campo .page no se actualiza; permanece igual, y cuando el búfer está vacío, se libera, lo cual no quiero que suceda porque el campo .ops no está configurado correctamente.
  • Debido a que el pipe_buffer actualiza el campo .offset en una operación de lectura, no puedo leer la misma región de memoria nuevamente.
  • Los datos escritos en el pipe_buffer se añadirán al búfer a partir del valor .len (asumiendo que la bandera PIPE_BUF_FLAG_CAN_MERGE está activada) y .len se actualiza en consecuencia. Es decir, no podemos escribir datos en la misma dirección exacta dos veces.

Como resultado, a menos que actualice correctamente el pipe_buffer después de cada operación de lectura o escritura, no puedo leer y escribir desde/hacia el mismo pipe al mismo tiempo. Es por eso que el underflow con 0x8000 bytes es mucho más práctico, porque en lugar de sobrescribir un único pipe_buffer, sobrescribiré dos instancias distintas de pipe_buffer de dos objetos de pipe distintos: una se considerará para operaciones de lectura y la otra para operaciones de escritura.```c #define PIPE_BUF_FLAG_CAN_MERGE 0x10 /* can merge buffers */

pipe_read = (struct pipe_buffer *)( ptr); pipe_read->page = virt_to_page(ta->kcpu_kaddr); pipe_read->offset = 0; pipe_read->len = 0xfff; pipe_read->ops = (const void *)(ta->kcpu_kaddr + 0x50); pipe_read->flags = PIPE_BUF_FLAG_CAN_MERGE; pipe_read->private = 0;

pipe_write = (struct pipe_buffer )( ptr + 0x4000); pipe_write->page = virt_to_page(ta->kcpu_kaddr); pipe_write->offset = 0; pipe_write->len = 0; / This is the starting position of the pipe_write */ pipe_write->ops = (const void *)(ta->kcpu_kaddr + 0x50); pipe_write->flags = PIPE_BUF_FLAG_CAN_MERGE; pipe_write->private = 0;

root@kitploit:~
El `pipe_read` es un búfer de pipe falso que se usará para leer datos de la página objetivo comenzando en `.offset = 0` hasta `0xfff` bytes, mientras que `pipe_write` es un `pipe_buffer` falso que se usará para escribir datos comenzando desde `.len = 0` hasta `0xfff` bytes.
También es muy importante mencionar nuevamente que escribir más de `PAGE_SIZE` bytes hará que el pipe incremente el contador head, usando por lo tanto un `pipe_buffer` recién asignado y perdiendo el control sobre nuestro `pipe_write` falso. Por otro lado, vaciar (leer `0xfff` datos de) el búfer `fake_read` le indica al kernel que libere la página real llamando a `ops→release`, lo que provoca que el kernel se bloquee porque todavía no tengo una dirección de texto del kernel.
Aunque logré separar las operaciones de lectura y escritura del pipe para que realizar una escritura en un extremo del pipe no interfiera con el otro búfer de pipe y viceversa, todavía no he resuelto el problema principal: ¿cómo actualizar el búfer de pipe de manera fiable? La respuesta obvia que me vino a la mente fue simplemente repetir el proceso de spray una y otra vez después de cada llamada de lectura o escritura del pipe. Y esto no tiene sentido porque habría tenido un impacto significativo en la fiabilidad del exploit. En la siguiente sección, dividiré el objetivo en dos subobjetivos: para comenzar, me centraré solo en el campo `.page`, y luego en los campos `.len/.offset`.

### Modificando el campo pipe_buffer→page
Para mi sorpresa, no tengo ni necesito actualizar `.page` en absoluto, porque puedo sobrescribir `pipe_buffer→page` para que apunte a la dirección de página del `kbase_kcpu_command_queue` filtrado. Por lo tanto, **todo lo que necesito hacer es liberar el objeto `kbase_kcpu_command_queue` y superponerlo con un nuevo objeto `pipe_buffer`. ¡Sí! Ahora tengo un `pipe_buffer→page` que apunta a un objeto `pipe_buffer` legítimo.
Reemplazar `kbase_kcpu_command_queue` con `pipe_buffer` nos da la capacidad de manipular un búfer de pipe legítimo sin tener que actualizar regularmente el campo `.page`. Sin embargo, todavía tengo que lidiar con los campos `.len` y `.offset`.

### Modificando los campos len/offset de pipe_buffer
Como mencioné anteriormente, realizar lecturas/escrituras en el pipe actualiza los campos `.len` y `.offset`, haciendo que las operaciones de lectura/escritura posteriores en la misma página sean inutilizables, incluso si se realizan a través de los dos pipes distintos. Aquí hay otro truco: **¡existe una técnica para leer/escribir datos sin siquiera tocar los campos `.len/.offset`!**. Y es posible lograrlo provocando una falta en las llamadas `copy_page_from_iter` y `copy_page_to_iter` en `pipe_read/write`! Sí, igual que `copy_to/from_user`, `copy_page_to/from_iter` copia datos desde/hacia el espacio de usuario que se pasa a través de la estructura `iov_iter`, y puede provocar una falta.

Para continuar con el ejemplo anterior, si deseamos escribir 8 bytes de datos en una dirección, el tamaño del búfer de espacio de usuario proporcionado debe ser 8, seguido de un área de memoria no mapeada o no legible, y luego pasar `9` como argumento de tamaño a la llamada al sistema `write`, indicando la cantidad de datos que queremos escribir. Esta operación escribirá 8 bytes y fallará en el _noveno_ porque encuentra una ubicación de memoria no mapeada/no legible. Como resultado, los datos se han escrito efectivamente en el búfer del kernel de destino y el campo `.len` no ha sido modificado. La función del kernel `pipe_write` simplemente retornará sin actualizar el campo `buf->len`.```c
		if ((buf->flags & PIPE_BUF_FLAG_CAN_MERGE) &&
		    offset + chars <= PAGE_SIZE) {
			ret = pipe_buf_confirm(pipe, buf);
			if (ret)
				goto out;

			ret = copy_page_from_iter(buf->page, offset, chars, from);
			if (unlikely(ret < chars)) {
				ret = -EFAULT;
				goto out;
			}

			buf->len += ret;
			if (!iov_iter_count(from))
				goto out;
		}

Lo mismo ocurre con las operaciones de lectura; si queremos leer 8 bytes, hacemos que el noveno byte del búfer no sea legible y luego simplemente afirmamos que queremos leer 9 bytes; los datos se copiarán al búfer del usuario sin cambiar el campo .offset. Como resultado, podemos realizar operaciones ilimitadas de lectura/escritura en cualquier dirección de memoria del kernel sin tener que pasar repetidamente por el proceso de spray.

Obtener root

Ahora que tengo una primitiva sólida de lectura/escritura arbitraria, revisé todas las struct page en el array VMEMMAP_START para determinar la dirección de inicio del texto del kernel utilizando la técnica descrita en la entrada de blog de Interrupt Labs. Entonces me di cuenta de que init_task está anulada en Android November Security Updates, así que usé kthreadd_task en su lugar. Tener la dirección del kernel de kthreadd_task me permitió recorrer la lista task->tasks y obtener la dirección del kernel de mi propia tarea current, para luego poner a cero la estructura cred y obtener privilegios de root.

Más tarde, me di cuenta de que escanear todas las direcciones de página era innecesario porque ya tenía la dirección de texto del kernel de anon_pipe_buf_ops a partir de un objeto pipe_buffer. Con esta información, pude deducir la dirección base del texto del kernel, eludiendo KASLR de manera efectiva.

Deshabilitar SELinux

El exploit también deshabilita SELinux; con la dirección base del texto del kernel, solo necesito encontrar la ubicación de la estructura global selinux_state y luego poner a cero el valor .enforcing.

Prueba de concepto

La prueba de concepto que acompaña al informe fue probada en dispositivos Pixel 7 y 8 Pro que ejecutan Android 14 con los ASB de octubre y noviembre, logrando una tasa de éxito de casi el 100%. También es importante mencionar que el exploit no funcionará directamente en otros dispositivos debido al uso de algunos offsets hardcodeados. Para añadir soporte a un nuevo dispositivo, se deben proporcionar los siguientes datos:

  • offset de kthreadd_task desde la dirección base del kernel.
  • offset de selinux_state desde la dirección base del kernel.
  • offsets de estructura de task_struct->cred , task_struct->pid y task_struct->tasks.
  • offset de anon_pipe_buf_ops desde la dirección base del kernel.

Compilación

Para compilar el exploit como un binario independiente, use el siguiente comando y luego use adb shell para ejecutarlo:```sh $ aarch64-linux-androidXX-clang++ -static-libstdc++ -w -Wno-c++11-narrowing -DUSE_STANDALONE -o poc poc.cpp -llog $ adb push poc /data/local/tmp/ $ adb shell /data/local/tmp/poc

root@kitploit:~
También puedes ejecutar el exploit a través de una aplicación de Android Studio incrustando este directorio en ella, y asegúrate de deshabilitar las advertencias innecesarias de C++ añadiendo `-w -Wno-c++11-narrowing` al archivo cmake.

### Demo```shell
$ adb logcat  |grep -i EXPLOIT
11-28 16:04:12.500  7989  7989 E EXPLOIT : [+] Target device: 'google/husky/husky:14/UD1A.231105.004/11010374:user/release-keys' 0xa9027bfdd10203ff 0xa90467faa9036ffc
11-28 16:04:15.563  7989  7989 E EXPLOIT : [+] Got the kcpu_id (0) kernel address = 0xffffff8901390000  from context (0x0)
11-28 16:04:18.441  7989  7989 E EXPLOIT : [+] Got the kcpu_id (255) kernel address = 0xffffff89b0bf8000  from context (0xff)
11-28 16:04:18.442  7989  7989 E EXPLOIT : [+] Found corrupted pipe with size 0xfff
11-28 16:04:18.442  7989  7989 E EXPLOIT : [+] SUCCESS! we have a fake pipe_buffer (0)!
11-28 16:04:18.444  7989  7989 E EXPLOIT : 10 00 39 01 89 FF FF FF  10 00 39 01 89 FF FF FF  | ..9.......9.....
11-28 16:04:18.444  7989  7989 E EXPLOIT : 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  | ................
11-28 16:04:18.444  7989  7989 E EXPLOIT : 00 B0 CD 12 C0 FF FF FF  00 00 00 00 00 00 00 00  | ................
11-28 16:04:18.444  7989  7989 E EXPLOIT : 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  | ................
11-28 16:04:18.445  7989  7989 E EXPLOIT : [+] Freeing kcpu_id = 0 (0xffffff8901390000)
11-28 16:04:18.446  7989  7989 E EXPLOIT : [+] Allocating 61 pipes with 256 slots
11-28 16:04:18.462  7989  7989 E EXPLOIT : [+] Successfully overlapped the kcpuqueue object with a pipe buffer
11-28 16:04:18.463  7989  7989 E EXPLOIT : 40 AB BA 26 FE FF FF FF  00 00 00 00 30 00 00 00  | @..&........0...
11-28 16:04:18.463  7989  7989 E EXPLOIT : 70 37 8D F1 DA FF FF FF  10 00 00 00 00 00 00 00  | p7..............
11-28 16:04:18.463  7989  7989 E EXPLOIT : 00 00 00 00 00 00 00 00                           | ........
11-28 16:04:18.463  7989  7989 E EXPLOIT : [+] pipe_buffer {.page = 0xfffffffe26baab40, .offset = 0x0, .len = 0x30, ops = 0xffffffdaf18d3770}
11-28 16:04:18.463  7989  7989 E EXPLOIT : [+] kernel base = 0xffffffdaf0010000, kthreadd_task = 0xffffff8002da3780 selinux_state = 0xffffffdaf28a3168
11-28 16:04:20.097  7989  7989 E EXPLOIT : [+] Found our own task struct 0xffffff88416c5c80
11-28 16:04:20.097  7989  7989 E EXPLOIT : [+] Successfully got root: getuid() = 0 getgid() = 0
11-28 16:04:20.097  7989  7989 E EXPLOIT : [+] Successfully disabled SELinux
11-28 16:04:20.102  7989  7989 E EXPLOIT : [+] Cleanup  ... OK
Descargar herramienta