
Kernel-LPE-Exploit für CVE-2023-6931 auf dem Xiaomi Pad 6 (pipa), der einen perf-read_size-Overflow verkettet, um Root zu erlangen und SELinux über eine datenbasierte netlink_sock-sk_destruct-Korruption zu deaktivieren.
Die endgültig auf dem Gerät verwendete Version des Exploits, separat in diesem Verzeichnis zusammengestellt.
Gerät Xiaomi Pad 6 / pipa / SM8250
System HyperOS OS2.0.20.0.UMZCNXM
Kernel 4.19.157-perf-g240e1d0a5f52
Start adb shell (shell-Domain)
Ergebnis einmalig uid0 + globales SELinux permissive (nach Neustart ungültig)
Siehe Kopf von exploit.c, Zusammenfassung wie folgt.
1) perf group read_size overflow -> Out-of-Bounds u64 Selbstinkrement-Primitiv
2) Selbstinkrement 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) Stack-Migration in die skb-Nutzlast innerhalb von fake (pt_regs1 @ +960, pt_regs2 @ +1120)
5) restore_tramp -> eret EL1h -> commit_creds(&init_cred)
6) restore_no_kpti -> eret EL0 -> post_root
Die SELinux-Deaktivierung erfolgt über reine Datenmanipulation: fake+0x248 zeigt auf selinux_state, das atomic_dec in __sk_destruct dekrementiert enforcing im niederwertigsten Bitfeld dieses Worts von 1 auf 0.
./build.sh # Ausgabe nach exploit in diesem Verzeichnis
CC=aarch64-linux-gnu-gcc ./build.sh /tmp/exploit
Die vom Treiberskript .project/scripts/drive_selinux_off.sh verwendete Kommandozeile:
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 &'
Bedeutung der Parameter:
--selinux-off SELinux deaktivieren (reiner Datenmanipulationspfad), gleichzeitig mit cred-Fälschung
--layout-interleave Allokationssequenz verschachtelt anordnen, damit das OOB-Selbstinkrement auf sk_destruct des Zielobjekts trifft
--tsec-sid 1 Sicherheitskontext sid/osid des gefälschten cred (1 verwenden, wenn die echte Domain-sid nicht ermittelbar ist)
--alarm-us 5000000 Selbstweckendes ITIMER_REAL, hängende Versuche beenden sich nach 5 Sekunden
Voraussetzungen und Einschränkungen:
shell-Domain kann auf perf_event und netlink_route zugreifen, die App-Domain nicht, daher muss von adb shell aus gestartet werden;perf_event_mlock_kb = 516; nicht mehrfach im selben Boot ausführen, da die vorherige Instanz die Events nicht freigibt, wenn sie nicht beendet wird, und ein erneuter Lauf nur 4000 bis 6000 erstellt;kill -9 bereinigen und neu starten, andernfalls gilt ein effektiver Versuch pro Boot;pause(), root und permissive gelten nur für diesen Start.--edl, --edl2), um deterministisch aus dem root-Fenster in den EDL zu gelangen; dieses Verzeichnis enthält nur die endgültig verwendete Version.Dieses Verzeichnis enthält nur die erste Phase. Die gesamte Kette besteht tatsächlich aus zwei Abschnitten, deren Verbindungspunkt der von der ersten Phase hinterlassene root-Kommando-Kanal ist.
Erste Phase exploit_selinux8 Kernel-Privilegieneskalation + SELinux-Deaktivierung, dann Halt am root-Kommando-Kanal
Ergebnis: uid0 + permissive, Kanal = FIFO /data/local/tmp/root_in|root_out
(zusätzlich abstrakter Unix-Socket rootshell / rootshell2 sowie --root-client PORT-Modus)
Zweite Phase stage2-frida/ Über den obigen Kanal frida-server als root starten, Settings injizieren
Ergebnis: im App-Prozess konstruierte und abgesendete Bindungsanfrage (TEE-Signatur)
Am Ende der ersten Phase wird ROOTSHELL_OK in /data/local/tmp/root_proof.txt geschrieben als Nachweis für ausführbare Kommandos. Danach besteht jede root-Aktion vom Host aus dem Schreiben einer Kommandozeile in die FIFO, beispielsweise der Zeile zum Starten des frida-server in der zweiten Phase:
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 darf nicht weggelassen werden: Wenn frida-server nach Deaktivierung von SELinux in der Kernel-Domain läuft, erhält es keinen Android Context und PackageManager wirft eine NPE.
Externe Abhängigkeiten (nicht in diesem Verzeichnis enthalten): frida-server 16.7.19 (54 MB, unter .project/tools/ext/frida/fs16, sha256-Präfix 4eebf1fbc66ff54aba9a9124) und der zugehörige Python-Client (.project/tools/ext/fridavenv16, frida 16.7.19). 16.x statt 17.x, weil der Agent von 17 keine Java-Bridge enthält.
Die vollständige Aufrufkette wird von .project/scripts/drive_selinux_off.sh orchestriert: Nach Treffer des root-Fensters ist die erste Aktion bash .project/scripts/exp_forge_send.sh, und bei code 0 wird die Schleife beendet.
exploit_selinux8 ist das ausgelieferte Original, Prüfsummen siehe SHA256SUMS. Ein Neubau aus dem Quellcode dieses Verzeichnisses ergibt ein anderes Binary:
Neubau aus Quellcode dieses Verzeichnisses 1.853.744 B f8bb900bf4e197190d56bfc1437bcc9a025e49640a7fa2a78ff0f7fb0a6e7939
Neubau aus Originalquellcode 1.853.752 B 96c7c3637682a2d5869ab4b6a732688a252f17f5e604b1695bc8fcc022113b3f
Tatsächlich ausgeführtes Binary 1.853.720 B 279837d2ee028fbbb520120180156446ad8dc3286b723e28de300b544be2a21a
Die Unterschiede wurden Symbol für Symbol lokalisiert, die Ursache ist nur eine: zwei zusätzliche Modi in der Kommandozeilen-Parametertabelle.
--edl Frame 2 ruft __qcom_scm_set_dload_mode(dev, 1) auf
--edl2 Frame 2 schreibt direkt das dload-Cookie
Das heißt, die ausgelieferte Version wurde vor Hinzufügen des EDL-Modus kompiliert, der aktuelle Quellcode ist der danach (.project/work/exploit_edl1, exploit_edl2 wurden daraus gebaut). Der EDL-Zweig wird nur bei Übergabe von --edl oder --edl2 aktiv, edl_mode ist standardmäßig 0, und die einzige relevante Zeile in der Payload-Konstruktion ist edl_mode ? 1ULL : rt(OFF_INIT_CRED), sodass sich beide bei der endgültigen --selinux-off-Kommandozeile identisch verhalten. Die Differenz ist ein funktionaler Zuwachs, keine Fehlerbehebung oder Verhaltensänderung; das Binary wird als Original beigelegt, der Quellcode bleibt in der vollständigen Version erhalten.
Verarbeitet mit .project/tools/strip_dev_comments.py, 124 Entwicklungsprozess-Kommentarblöcke entfernt (Versionsiterationsmarker wie v96, v127 sowie chinesische Debug-Formulierungen wie „alter Wert/gemessen/erneut ausführen/überspringen"), 313 strukturelle Kommentare beibehalten. Der Vorverarbeitungsvergleich zeigt nur Unterschiede bei Leerzeilen, die Code-Token sind vollständig identisch. In den Laufzeitlogs verbleiben weiterhin Ausgabetexte mit vNNN:-Präfix, da diese Programmausgaben und keine Kommentare sind.
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.
| Datei | Beschreibung |
|---|
exploit.c | Quellcode. Entwicklungsprozess-Kommentare wurden entfernt; strukturelle Kommentare wie die Kettenbeschreibung im Dateikopf, Offset-Tabellen und Struct-Layouts bleiben erhalten |
text_syms.h | Aus dem kallsyms-Dump des Geräts generierte .text-Symboltabelle, zum Kompilieren erforderlich |
ro_syms.h | Wie oben, Symboltabelle für schreibgeschützte Daten, zum Kompilieren erforderlich |
exploit_selinux8 | Das tatsächlich auf dem Gerät ausgeführte Binary, identisch mit dem vom Treiberskript verwendeten |
build.sh | Build-Skript |
SHA256SUMS | Prüfsummen der oben genannten Dateien |
| Datei | Beschreibung |
|---|
exp_forge_send.sh | frida-server 16.7.19 pushen und starten, sicherstellen dass Settings läuft, pid ermitteln, Agent ausführen |
frida_run.py | frida-Client-Wrapper, unterstützt FRIDA_PID-Direktverbindung und FRIDA_SPAWN-Selbststart (umgeht am/pm) |
forge_send.js | Im Settings-Prozess getNonce kopieren, applyBind mit altem Feldsatz signieren und absenden |
| Vergleichspunkt | Ergebnis |
|---|
| Symbole mit abweichender Funktionsgröße | Nur main (shipped 17.744 B, Neubau 18.004 B, Differenz 260 B ≈ 65 Instruktionen) |
| Übrige 1.503 Funktionen | Größen vollständig identisch, keine Zu- oder Abnahmen |
| Nur im Neubau vorhandene Symbole | edl_mode (eines) |
| Kommandozeilen-Parametertabelle | shipped 85, Neubau 87, die zusätzlichen sind genau --edl und --edl2 |
Instruktionszahl des gesamten .text | 126.533 vs. 126.597, Differenz 64, konsistent mit dem Unterschied in main |