
Exploit LPE del kernel per CVE-2023-6931 su Xiaomi Pad 6 (pipa), che concatena un overflow di perf read_size per ottenere i privilegi di root e disabilita SELinux tramite la corruzione data-only di netlink_sock sk_destruct.
La versione finale dell'exploit utilizzata sul dispositivo, raccolta separatamente in questa directory.
Dispositivo Xiaomi Pad 6 / pipa / SM8250
Sistema HyperOS OS2.0.20.0.UMZCNXM
Kernel 4.19.157-perf-g240e1d0a5f52
Punto di partenza adb shell (dominio shell)
Risultato uid0 una tantum + SELinux permissive globale (perso al riavvio)
Vedi l'intestazione del file exploit.c, riassunto di seguito.
1) perf group read_size overflow -> primitiva di incremento u64 fuori limite
2) incremento di 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) migrazione dello stack nel payload skb dentro 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 disattivazione di SELinux avviene tramite pura riscrittura di dati: fake+0x248 punta a selinux_state, l'atomic_dec di __sk_destruct decrementa enforcing da 1 a 0 nel segmento del bit meno significativo di quella parola.
./build.sh # output nella directory corrente come exploit
CC=aarch64-linux-gnu-gcc ./build.sh /tmp/exploit
Riga di comando usata dallo script di guida .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 &'
Significato dei parametri:
--selinux-off disattiva SELinux (percorso di pura riscrittura dati), con falsificazione di cred
--layout-interleave disposizione interlacciata della sequenza di allocazione, per far cadere l'incremento OOB su sk_destruct dell'oggetto bersaglio
--tsec-sid 1 sid/osid del contesto di sicurezza del cred falsificato (si usa 1 quando non si riesce a ottenere il sid del dominio reale)
--alarm-us 5000000 ITIMER_REAL di auto-risveglio, un tentativo bloccato esce dopo 5 secondi
Prerequisiti e limitazioni di esecuzione:
shell può raggiungere perf_event e netlink_route, il dominio applicativo no, quindi è necessario avviare da adb shell;perf_event_mlock_kb = 516, non rieseguire nello stesso boot, se l'istanza precedente non termina non rilascia gli eventi, una riesecuzione creerà solo da 4000 a 6000 eventi;kill -9 per eliminare la vecchia istanza e riprovare, altrimenti si conta un solo tentativo valido per avvio;pause(), root e permissive valgono solo per questo avvio.--edl, --edl2), per entrare in modo deterministico in EDL dalla finestra di root; questa directory raccoglie solo la versione finale utilizzata.Questa directory contiene solo la prima fase. L'intera catena è in realtà composta da due segmenti, il punto di raccordo è il canale di comandi root lasciato dalla prima fase.
Prima fase exploit_selinux8 escalation kernel + disattivazione SELinux, poi si ferma sul canale di comandi root
prodotto: uid0 + permissive, canale = FIFO /data/local/tmp/root_in|root_out
(inoltre unix socket astratto rootshell / rootshell2, e modalità --root-client PORT)
Seconda fase stage2-frida/ avvia frida-server come root attraverso il canale sopra, inietta in Settings
prodotto: richiesta di binding costruita e inviata nel processo applicativo (firma TEE)
Al termine della prima fase viene scritto ROOTSHELL_OK in /data/local/tmp/root_proof.txt come prova dell'esecuzione dei comandi. Successivamente qualsiasi azione root dall'host consiste nello scrivere una riga di comando nella FIFO, ad esempio quella che avvia frida-server nella seconda 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 non può essere omesso: dopo la disattivazione di SELinux, se frida-server gira nel dominio kernel non ottiene l'Android Context e PackageManager va in NPE.
Dipendenze esterne (non incluse in questa directory): frida-server 16.7.19 (54 MB, in .project/tools/ext/frida/fs16, prefisso sha256 4eebf1fbc66ff54aba9a9124) e il relativo client python (.project/tools/ext/fridavenv16, frida 16.7.19). Si usa la 16.x invece della 17.x perché l'agent della 17 non ha il Java bridge.
La catena di chiamata completa è orchestrata da .project/scripts/drive_selinux_off.sh: dopo aver colpito la finestra di root la prima azione è bash .project/scripts/exp_forge_send.sh, e ottenendo code 0 il ciclo si interrompe.
exploit_selinux8 è l'originale consegnato, i checksum sono in SHA256SUMS. Ricostruendo dai sorgenti di questa directory si ottiene un binario diverso:
Ricostruzione dai sorgenti di questa directory 1.853.744 B f8bb900bf4e197190d56bfc1437bcc9a025e49640a7fa2a78ff0f7fb0a6e7939
Ricostruzione dai sorgenti originali 1.853.752 B 96c7c3637682a2d5869ab4b6a732688a252f17f5e604b1695bc8fcc022113b3f
Quello effettivamente eseguito 1.853.720 B 279837d2ee028fbbb520120180156446ad8dc3286b723e28de300b544be2a21a
La differenza è stata localizzata simbolo per simbolo, e ha un'unica origine: due modalità in più nella tabella dei parametri della riga di comando.
--edl frame 2 chiama __qcom_scm_set_dload_mode(dev, 1)
--edl2 frame 2 scrive direttamente il dload cookie
In altre parole, la versione consegnata è quella compilata prima dell'aggiunta della modalità EDL, mentre il sorgente attuale è quello successivo (.project/work/exploit_edl1, exploit_edl2 sono compilati da esso). Il ramo EDL si attiva solo passando --edl o --edl2, edl_mode è 0 per default, e l'unica riga rilevante nella costruzione del payload è edl_mode ? 1ULL : rt(OFF_INIT_CRED), quindi con la riga di comando finale --selinux-off i due si comportano allo stesso modo. La differenza è un incremento funzionale, non una correzione o un cambiamento di comportamento; il binario è allegato come originale, il sorgente conserva la versione completa.
Elaborato con .project/tools/strip_dev_comments.py, rimuovendo 124 blocchi di commenti del processo di sviluppo (marcatori di iterazione di versione come v96, v127, e toni di debug in cinese come "vecchio valore/misurato/rieseguito/saltato"), mantenendo 313 commenti strutturali. Il confronto del risultato del preprocessing differisce solo per righe vuote, i token del codice sono completamente identici. Nei log di esecuzione rimangono testi di stampa con prefisso vNNN:, perché fanno parte dell'output del programma e non dei commenti.
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.
| File | Descrizione |
|---|
exploit.c | Codice sorgente. I commenti del processo di sviluppo sono stati rimossi, mantenendo i commenti strutturali come la descrizione della catena nell'intestazione, la tabella degli offset e il layout delle strutture |
text_syms.h | Tabella dei simboli .text generata dal dump di kallsyms del dispositivo, necessaria per la compilazione |
ro_syms.h | Come sopra, tabella dei simboli in sola lettura, necessaria per la compilazione |
exploit_selinux8 | Il binario effettivamente eseguito sul dispositivo, identico a quello usato dallo script di guida |
build.sh | Script di build |
SHA256SUMS | Checksum dei file sopra elencati |
| File | Descrizione |
|---|
exp_forge_send.sh | Esegue il push e avvia frida-server 16.7.19, assicura che Settings sia in esecuzione, ottiene il pid, esegue l'agent |
frida_run.py | Wrapper del client frida, supporta connessione diretta con FRIDA_PID e avvio automatico con FRIDA_SPAWN (aggirando am/pm) |
forge_send.js | Copia getNonce nel processo Settings, firma applyBind con il vecchio insieme di campi e lo invia |
| Elemento di confronto | Risultato |
|---|
| Simboli con dimensione diversa | Solo main (shipped 17.744 B, ricostruito 18.004 B, differenza 260 B ≈ 65 istruzioni) |
| Gli altri 1.503 simboli | Dimensioni identiche, nessuna aggiunta o rimozione |
| Simboli presenti solo nella ricostruzione | Uno: edl_mode |
| Tabella dei parametri della riga di comando | shipped 85, ricostruito 87, quelli in più sono esattamente --edl e --edl2 |
Numero di istruzioni dell'intero .text | 126.533 e 126.597, differenza di 64, coerente con la differenza di main |