
Exploit LPE del kernel para CVE-2023-6931 en Xiaomi Pad 6 (pipa), encadenando un desbordamiento de perf read_size hasta root y deshabilitando SELinux mediante corrupción data-only de netlink_sock sk_destruct.
La versión final del exploit utilizada en el dispositivo, organizada por separado en este directorio.
Dispositivo Xiaomi Pad 6 / pipa / SM8250
Sistema HyperOS OS2.0.20.0.UMZCNXM
Kernel 4.19.157-perf-g240e1d0a5f52
Punto de inicio adb shell (dominio shell)
Resultado uid0 en un solo paso + SELinux permissive global (se pierde al reiniciar)
Ver el encabezado del archivo exploit.c, resumen a continuación.
1) perf group read_size overflow -> primitiva de incremento u64 fuera de límites
2) incremento de victim netlink_sock->sk_destruct -> netlink_sock_destruct_work (+0x28e8)
3) close(victim) -> sk_destruct(work) -> container_of(work, netlink_sock, 0x440)
-> fake = victim-0x440 -> sk_free(fake) -> __sk_destruct(&fake->sk_rcu)
-> fake->sk_destruct @ fake+704 = pivot(mov sp, x19)
4) migración de pila al payload skb dentro de fake (pt_regs1 @ +960, pt_regs2 @ +1120)
5) restore_tramp -> eret EL1h -> commit_creds(&init_cred)
6) restore_no_kpti -> eret EL0 -> post_root
La desactivación de SELinux se realiza mediante escritura pura de datos: fake+0x248 apunta a selinux_state, y el atomic_dec de __sk_destruct reduce el enforcing del segmento de bits menos significativo de esa palabra de 1 a 0.
./build.sh # salida a exploit en este directorio
CC=aarch64-linux-gnu-gcc ./build.sh /tmp/exploit
Línea de comandos utilizada por el script controlador .project/scripts/drive_selinux_off.sh:
adb push exploit_selinux8 /data/local/tmp/exploit_selinux8
adb shell 'chmod 755 /data/local/tmp/exploit_selinux8'
adb shell 'cd /data/local/tmp && setsid nohup /data/local/tmp/exploit_selinux8 \
--selinux-off --layout-interleave --tsec-sid 1 --alarm-us 5000000 \
> /data/local/tmp/run.out 2>&1 < /dev/null &'
Significado de los parámetros:
--selinux-off Desactiva SELinux (ruta de escritura pura de datos), incluye falsificación de cred
--layout-interleave Distribución intercalada de la secuencia de asignación, para que el incremento OOB caiga en el sk_destruct del objeto objetivo
--tsec-sid 1 sid/osid del contexto de seguridad del cred falsificado (se usa 1 cuando no se puede obtener el sid del dominio real)
--alarm-us 5000000 ITIMER_REAL de auto-despertar, la tentativa bloqueada sale después de 5 segundos
Requisitos previos y limitaciones de ejecución:
shell puede acceder a perf_event y netlink_route, el dominio de aplicación no, por lo que debe iniciarse desde adb shell;perf_event_mlock_kb = 516, no ejecutar repetidamente en el mismo boot, si la instancia anterior no sale no libera los eventos, una reejecución solo creará entre 4000 y 6000;kill -9 para eliminar la instancia antigua y volver a intentarlo, de lo contrario se cuenta como un intento válido por arranque;pause(), root y permissive solo son válidos para este arranque.--edl, --edl2), utilizadas para entrar de forma determinista en EDL desde la ventana de root; este directorio solo incluye la versión final utilizada.Este directorio solo contiene la primera fase. La cadena completa en realidad consta de dos segmentos, y el punto de conexión es el canal de comandos root que deja la primera fase.
Primera fase exploit_selinux8 Escalada de privilegios en el kernel + desactivación de SELinux, luego se detiene en el canal de comandos root
Producto: uid0 + permissive, canal = FIFO /data/local/tmp/root_in|root_out
(también hay socket unix abstracto rootshell / rootshell2, y modo --root-client PORT)
Segunda fase stage2-frida/ A través del canal anterior inicia frida-server como root, inyecta en Settings
Producto: solicitud de vinculación construida y emitida dentro del proceso de la aplicación (firma TEE)
Al finalizar la primera fase se escribe ROOTSHELL_OK en /data/local/tmp/root_proof.txt como credencial de comandos ejecutables. Después, cualquier acción root desde el host consiste en escribir una línea de comando en el FIFO, por ejemplo la que inicia frida-server en la segunda fase:
adb shell "echo 'killall -9 frida-server 2>/dev/null; rm -f /data/local/tmp/frida.log; \
setsid runcon u:r:shell:s0 /data/local/tmp/frida-server16 -D \
</dev/null >/data/local/tmp/frida.log 2>&1 & echo FRIDA_STARTED' > /data/local/tmp/root_in"
runcon u:r:shell:s0 no se puede omitir: si frida-server se ejecuta en el dominio kernel después de desactivar SELinux, no puede obtener el Android Context y PackageManager provocará un NPE.
Dependencias externas (no incluidas en este directorio): frida-server 16.7.19 (54 MB, ubicado en .project/tools/ext/frida/fs16, prefijo sha256 4eebf1fbc66ff54aba9a9124) y el cliente python correspondiente (.project/tools/ext/fridavenv16, frida 16.7.19). Se usa 16.x en lugar de 17.x porque el agent de 17 no tiene Java bridge.
La cadena de llamadas completa está orquestada por .project/scripts/drive_selinux_off.sh: la primera acción tras alcanzar la ventana de root es bash .project/scripts/exp_forge_send.sh, y al obtener code 0 se detiene el bucle.
exploit_selinux8 es el original entregado, las sumas de verificación están en SHA256SUMS. Reconstruir con el código fuente de este directorio produce otro binario:
Reconstrucción con fuente de este directorio 1,853,744 B f8bb900bf4e197190d56bfc1437bcc9a025e49640a7fa2a78ff0f7fb0a6e7939
Reconstrucción con fuente original 1,853,752 B 96c7c3637682a2d5869ab4b6a732688a252f17f5e604b1695bc8fcc022113b3f
La versión realmente ejecutada 1,853,720 B 279837d2ee028fbbb520120180156446ad8dc3286b723e28de300b544be2a21a
La diferencia se ha localizado símbolo por símbolo, y su origen es único: hay dos modos adicionales en la tabla de parámetros de línea de comandos.
--edl frame2 llama a __qcom_scm_set_dload_mode(dev, 1)
--edl2 frame2 escribe directamente la cookie dload
Es decir, la versión entregada fue compilada antes de añadir el modo EDL, y el código fuente actual es posterior a la adición (.project/work/exploit_edl1, exploit_edl2 se compilan a partir de él). La rama EDL solo se activa al pasar --edl o --edl2, edl_mode es 0 por defecto, y la única línea relevante en la construcción del payload es edl_mode ? 1ULL : rt(OFF_INIT_CRED), por lo que al usar la línea de comandos final --selinux-off ambos se comportan igual. La diferencia es una adición funcional, no una corrección ni un cambio de comportamiento; el binario se adjunta como original y el código fuente conserva la versión completa.
Se procesó con .project/tools/strip_dev_comments.py, eliminando 124 bloques de comentarios del proceso de desarrollo (marcas de iteración de versión como v96, v127, y tono de depuración en chino como "valor antiguo/medido real/reejecutar/omitir"), conservando 313 comentarios estructurales. La comparación del resultado del preprocesamiento solo difiere en líneas vacías, los tokens de código son completamente idénticos. Los registros de ejecución aún llevan texto impreso con prefijo vNNN:, porque son salida del programa y no comentarios.
idk fr what files are really needed, if you want to you might very need a LLM to understand the whole exp and write yours.
| Archivo | Descripción |
|---|
exploit.c | Código fuente. Se han eliminado los comentarios del proceso de desarrollo, conservando los comentarios estructurales como la descripción de la cadena en el encabezado, la tabla de offsets y el diseño de estructuras |
text_syms.h | Tabla de símbolos .text generada a partir del volcado de kallsyms del dispositivo, necesaria para compilar |
ro_syms.h | Igual que el anterior, tabla de símbolos de datos de solo lectura, necesaria para compilar |
exploit_selinux8 | El binario que realmente se ejecuta en el dispositivo, idéntico al utilizado por el script controlador |
build.sh | Script de compilación |
SHA256SUMS | Sumas de verificación de los archivos anteriores |
| Archivo | Descripción |
|---|
exp_forge_send.sh | Envía e inicia frida-server 16.7.19, asegura que Settings esté en ejecución, obtiene el pid, ejecuta el agent |
frida_run.py | Envoltorio del cliente frida, soporta conexión directa con FRIDA_PID y auto-inicio con FRIDA_SPAWN (evitando am/pm) |
forge_send.js | Dentro del proceso Settings copia getNonce, firma applyBind con el conjunto de campos antiguo y lo emite |
| Elemento de comparación | Resultado |
|---|
| Símbolos con tamaño de función diferente | Solo main (shipped 17,744 B, reconstrucción 18,004 B, diferencia 260 B ≈ 65 instrucciones) |
| Los otros 1,503 funciones | Tamaño idéntico, sin aumentos ni reducciones |
| Símbolos que solo aparecen en la reconstrucción | Uno: edl_mode |
| Tabla de parámetros de línea de comandos | shipped 85, reconstrucción 87, los adicionales son precisamente --edl y --edl2 |
Número de instrucciones de todo el segmento .text | 126,533 y 126,597, diferencia de 64, consistente con la diferencia de main |