
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.
| 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 |
./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.
| 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 |
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