Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
CVE-2026-64468 — Unprivilegierter Proof-of-Concept und x86_64 lokale Privilegieneskalation für einen Linux-Kernel-Binder-Use-after-Free (CVE-2026-64468), mit KASAN-Labor und verwundbarer/behobener Differenzialanalyse. | Kitploit
Tools/GitHubGitHub/aramosf/cve-2026-64468
Privilege EscalationExploit-FrameworksSchwachstellenanalyseExploitationLernen & BildungBinary-Exploitation
GitHubaramosf/cve-2026-64468

CVE-2026-64468

Unprivilegierter Proof-of-Concept und x86_64 lokale Privilegieneskalation für einen Linux-Kernel-Binder-Use-after-Free (CVE-2026-64468), mit KASAN-Labor und verwundbarer/behobener Differenzialanalyse.

Repository anzeigen
1vor 15 TagenNoch nicht geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

CVE-2026-64468 — Use-after-free über die Prozesslebensdauer im Linux-Kernel-Binder binder_free_transaction()

Live-QEMU/KVM-Lauf: nicht gepatchter verwundbarer Kernel meldet den Use-after-free, gepatchter Kernel nicht

Dieses Repository enthält, für den durch den Upstream-Commit f223d27a546c1e1f48d38fd67760e78f068fe8c4 behobenen Binder-Use-after-free im Linux-Kernel:

  • binder_chain_64468.c — ein Proof of Concept ohne Privilegien, der den Fehler erreicht und den Kernel ihn nachweisen lässt, mit einem KASAN-Labor und einem verwundbaren/gepatchten Differenzial (lab/, run.sh, verify.sh).
  • exploit.c — eine eigenständige lokale Privilegienerweiterung für x86_64. Sie kompiliert mit gcc -O2 -pthread -o exploit exploit.c, läuft als normaler Benutzer und endet in einer Root-Shell.
  • demo/ — ein Labor, das eine echte Debian-13-Userland auf einem nicht gepatchten Kernel bootet, sodass der Exploit vom eigenen gcc des Zielsystems kompiliert und auf der Maschine ausgeführt werden kann, die er dann übernimmt.

Alles läuft als normaler Benutzer (uid/gid 1000, keine Capabilities, keine Namespaces) gegen unveränderte Upstream-Kernel ohne Patches jeglicher Art.

Warnung

Dieser Code setzt bewusst Kernel-Objektlebensdauern einem Wettlauf aus und übernimmt anschließend die Kontrollflusssteuerung des Kernels. Ein verlorener Wettlauf beschädigt den Kernel-Heap-Zustand und kann die Maschine zum Absturz oder Hängen bringen. Führen Sie ihn nur in einer isolierten, wegwerfbaren VM aus, die Ihnen gehört. Führen Sie ihn nicht auf einem Host aus.

Status und Umfang

Schwachstelle

binder_free_transaction() liest den Zielprozess aus der Transaktion unter t->lock aus, gibt diese Sperre frei und erwirbt anschließend die innere Sperre des Ziels:```c spin_lock(&t->lock); target_proc = t->to_proc; spin_unlock(&t->lock);

root@kitploit:~
if (target_proc) {
	binder_inner_proc_lock(target_proc);   /* use after free */
root@kitploit:~
Nichts hält `target_proc` über diese Lücke hinweg am Leben. Ein Prozess, der parallel
abgebaut wird, kann dazwischen `binder_proc_dec_tmpref() -> kfree()` erreichen, sodass
die Sperre auf freigegebenem Speicher erworben wird. Der Upstream-Fix pinnt `t->to_thread`,
während `t->lock` noch gehalten wird, wodurch der besitzende Prozess am Leben bleibt, bis
die innere Sperre verwendet und freigegeben wurde.

Die Schwachstelle wurde von **Alice Ryhl** gemeldet und von **Carlos
Llamas** behoben, beide von Google. Der
[ursprüngliche Bericht](https://lore.kernel.org/all/[email protected]/)
enthält die zugehörige KASAN-Ablaufverfolgung.

### Erreichen des verwundbaren Zugriffs

Der einzige Aufrufer, der einen *fremden* `to_proc` erreichen kann, ist
`binder_send_failed_reply()`, und dieser geht nur dann zu `t->from_parent`, wenn
`t->from` `NULL` ist:```c
	target_thread = binder_get_txn_from_and_acq_inner(t);
	if (target_thread) { ...; binder_free_transaction(t); return; }
	next = t->from_parent;
	binder_free_transaction(t);
	t = next;

from_parent wird an genau einer Stelle zugewiesen, und die Zuweisung wird einige Zeilen zuvor durch die Bad-Transaction-Stack-Prüfung von Binder abgesichert: Ein Thread darf eine synchrone Transaktion nur senden, während sein Stack-Top eine Transaktion ist, die er empfängt. Daher sind für jedes Glied dieser Kette der Sender des Kindes und der Empfänger des Elternteils derselbe Thread.

Das hat eine scharfe Konsequenz. binder_thread_release() durchläuft den Stack des sterbenden Threads mit gehaltenem proc->inner_lock und schreibt beide``` iteration j : [holds child->lock ] child->from = NULL ; unlock child->lock spin_lock(parent->lock) iteration j+1 : [holds parent->lock] parent->to_proc = NULL

root@kitploit:~
in **aufeinanderfolgenden Iterationen eines einzelnen Durchlaufs**, jeweils unter der jeweiligen
`t->lock`. Ein Walker erfährt, dass `child->from == NULL` ist, erst, wenn der Durchlauf
`child->lock` freigegeben hat, und benötigt `parent->lock` für seine eigene Momentaufnahme. Die gesamte
Gelegenheit ist also die Lücke zwischen dem `spin_unlock(&child->lock)` dieses Durchlaufs und seinem
`spin_lock(&parent->lock)` — ein paar Anweisungen. Der Freigabe-Durchlauf hält eine
Spinlock und kann dort nicht unterbrochen werden; nur ein Interrupt kann ihn verzögern.

Deshalb ist der Wettlauf eng und sowohl der Proof of Concept als auch der
Exploit sind probabilistisch.

### Was der Proof of Concept aufbaut

`binder_chain_64468.c` konstruiert die kürzeste Kette, die den
verwundbaren Zugriff erreicht, sodass die Arbeit des Walkers innerhalb dieser Lücke so klein ist, wie Binder
es zulässt — zwei `t->lock`-Erwerbungen und ein `kfree()`, ohne fremde
`inner_proc_lock`, ohne `wake_up` und ohne Antwortzustellung:```
  B thread i   --e2 (sync, code 0x4442414b)-->  P thread Y_i
  P thread Y_i --e1 (sync, code 0x54414c4c)-->  B thread i   (nested target)

  B thread i   stack: [ e2 outgoing , e1 incoming (top) ]
  P thread Y_i stack: [ e2 incoming , e1 outgoing (top) ]

P gibt daraufhin seinen Binder-fd auf, sodass binder_deferred_release() die Y_1..Y_K freigibt und schließlich den binder_proc freigibt, während jeder B-Thread gleichzeitig BINDER_THREAD_EXIT ausführt:``` binder_thread_release(B_i) nulls e1->to_proc (own proc) and e2->from binder_send_failed_reply(e1) e1->from == NULL once Y_i was released -> binder_free_transaction(e1) target_proc already NULL, kfree(e1) -> binder_free_transaction(e2) target_proc == P <-- vulnerable access

root@kitploit:~
Ein dritter Prozess ist der Binder-Context-Manager, der nur dazu dient, die
Handles auszugeben, die die anderen beiden benötigen. `exploit.c` verwendet exakt diese Konstruktion erneut.

### Zwei unabhängige Bedingungen

Ein KASAN-Bericht benötigt beides:

* **den verwundbaren Zugriff** — der Walker muss `parent->lock` innerhalb des
  schmalen Fensters oben übernehmen, sodass er ein noch lebendes `to_proc` als Schnappschuss erfasst; und
* **das Free-Landing innerhalb des Fensters** — der Walker muss dann seine CPU
  zwischen dem Freigeben von `t->lock` und dem Übernehmen der inneren Sperre des Opfers verlieren und von ihr fernbleiben,
  bis die verzögerte Freigabe abgeschlossen ist und das `binder_proc` freigegeben wurde.

Die erste Bedingung hat ihr eigenes Orakel, das kein KASAN benötigt: Binder gibt```
binder: binder_free_proc: Unexpected outstanding_txns -1

wann immer es passiert, weil der Walker und binder_thread_release() dann beide denselben Zähler für eine Transaktion dekrementieren. Beachte, dass dies keine verwundbare/gefixt-Differenz für sich ist — der Fix stoppt das Free, nicht das zweite Dekrement — daher erscheint es auf beiden Kerneln. Es wird hier nur verwendet, um den verwundbaren Codepfad zu zeigen, der ausgeführt wird.

Vom Use-after-free zu Root

Der Proof of Concept endet bei einem 4-Byte-Dekrement von freigegebenem Speicher. Um daraus uid 0 zu machen, braucht es vier Dinge, und keines davon stammt aus dem Bug selbst: Der Bug leakt nichts.

1. Ein gemeinsamer Cache

struct binder_proc ist 648 Bytes groß und wird mit einem einfachen GFP_KERNEL kzalloc allokiert — nicht __GFP_ACCOUNT. Es landet daher in kmalloc-1k, zusammen mit jeder anderen nicht abgerechneten Allokation dieser Größe, und ist nicht hinter kmalloc-cg-* isoliert. Genau diese Tatsache macht das Objekt überhaupt reclaimbar.

2. Eine Reclaim-Pumpe, die mithält

Der Moment des kfree() ist aus dem Userspace nicht beobachtbar, und ebenso wenig der Moment, in dem der Walker das Objekt erneut berührt, daher gibt es nichts, woran man sich zeitlich orientieren könnte. Der Spray läuft daher als Pumpe: Er allokiert und gibt kmalloc-1k-Objekte kontinuierlich frei, von der CPU, die das verzögerte Release ausgeführt hat, solange die Walker laufen.

System-V-Nachrichten werden dafür verwendet. alloc_msg() ist ein einfaches, nicht abgerechnetes kmalloc eines 48-Byte-Headers plus Payload, also ist eine 976-Byte-Nachricht eine 1024-Byte-Allokation; das Kontingent gilt pro Queue und nicht pro uid; und msgrcv() gibt synchron frei. Gemessener Durchsatz: ~198.000 Allokationen pro Sekunde, null Fehlschläge.

add_key/user_key_payload wurde zuerst versucht und ist eine Falle. Seine Payload wird gegen ein Byte-Kontingent pro uid angerechnet (kernel.keys.maxbytes, standardmäßig 20000), das erst freigegeben wird, wenn der Key-Garbage-Collector den Schlüssel zerstört, daher erschöpft eine enge Allokieren/Freigeben-Schleife es in Millisekunden: gemessen 27.151 erfolgreiche Allokationen gegen 2.121.009 Fehlschläge — 98,7 % des Sprays tun stillschweigend nichts, was genau wie ein Spray aussieht, das den Slot nie gewinnt. KEYCTL_INVALIDATE machte es schlimmer (4.775 Erfolge), weil es GC-Arbeit in die Queue stellt.

3. Was das reclaimte Objekt enthalten muss

Der Walker berührt vier Felder des freigegebenen binder_proc (Offsets gemessen mit pahole auf dem Ziel-Build):

Mit outstanding_txns == 1 und is_frozen == 1 erreicht das Dekrement null und der Walker ruft wake_up_interruptible_all(&proc->freeze_wait) auf. __wake_up_common berechnet dann curr = head.next - 24 und ruft *(head.next - 8) auf: ein Funktionszeiger, der von dort gelesen wird, wohin head.next zeigt, was ein Wert ist, den das reclaimte Objekt liefert.

4. Zwei Adressen, über einen Seitenkanal

Dieser Zeiger muss Speicher erreichen, den der Angreifer kontrolliert, an einer Kernel- Adresse, und der Bug leakt nichts. Beide Adressen stammen stattdessen aus dem Prefetch-Timing — demselben Kanal wie KASLD (Brendan Coles, MIT), dessen Implementierung dies ableitet:

  • Kernel-Text. Ein Prefetch einer gemappten Kernel-Adresse löst sich im Page-Table- Walk auf und wird messbar schneller abgeschlossen als einer einer ungemappten Adresse, selbst wenn der Zugriff nie architektonisch sichtbar wird. Das Scannen der 2-MiB-Slots des Textbereichs zeigt das Image als eine Folge schneller Slots; sein erster Slot ist _text.

  • Die direkte Abbildung. Mit CONFIG_RANDOMIZE_MEMORY wird die direkte Abbildung in 1-GiB-Einheiten randomisiert, daher muss auch sie lokalisiert werden. Anders als der Text deckt sie den gesamten RAM ab, also ist sie die längste zusammenhängende Folge gemappter Slots. Zwei Verfeinerungen waren nötig, um sie nutzbar zu machen:

    • ein einzelner Prefetch trennt Gemapptes von Ungemapptem unter KVM nur um ~4 Zyklen, was das Rauschen nicht übersteht, daher misst jede Stichprobe einen Stapel von 400 Prefetches (gemessen 311 vs. 523 Zyklen — trennbar);
    • ein einzelner Scan ist auf einem umkämpften Host nicht vertrauenswürdig — 10/10 korrekt mit einem Gast zur Zeit, 3/5 mit fünf Gästen gleichzeitig — daher wird der Scan fünfmal ausgeführt und eine Mehrheit ist erforderlich. Ohne Konsens meldet der Exploit einen Fehlschlag und stoppt, statt auf eine unverifizierte Adresse zu feuern.

    Womit der Lauf zuverlässig beginnt, ist nicht page_offset_base selbst, sondern der erste Slot, den der Kernel mit einer 1-GiB-Seite mappen könnte — derjenige, der physische 4 GiB abdeckt. Darunter erzwingen das PCI-Loch und Firmware-Reservierungen 2-MiB-Seiten, deren längerer Walk hier nicht von Ungemapptem trennbar ist. Dieser Slot ist genau das, was der Spray braucht, daher wird er verwendet.

Dann wird ~60 % des physischen Speichers mit Kopien einer einzigen handgefertigten 4-KiB- Seite gefüllt, sodass ein fester Offset von diesem Anker durch die handgefertigte Seite hinterlegt ist, egal wie das Layout letztlich aussieht.

Die Kette```

freeze_wait.head.next -> entry1 (in the sprayed page)

entry1.func = mov 0x28(%rdi),%rdi ; mov 0x18(%rdi),%rax ; jmp *0x58(%rax) RDI <- entry1+0x28 = &cred RAX <- cred+0x18 jmp *(RAX+0x58) = commit_creds -> commit_creds(cred)

entry2.func = mov $-1,%rax ; ret __wake_up_common breaks out on ret < 0

root@kitploit:~
No stack pivot und kein `iretq`: Der gekaperte Thread kehrt normal aus seinem `ioctl()` zurück und befindet sich einfach wieder im Userspace, als root.

### Die gefälschten Credentials und der Bug, der sie verschwendet hätte

Das Dispatcher-Gadget bezieht sein `RAX` aus `cred+0x18`, und `cred+0x18` ist
`euid`/`egid`, also ist direkt nach der Chain `euid` die untere Hälfte eines Kernel-Zeigers. Das ist kosmetisch. Was nicht kosmetisch ist: `prepare_creds()` — das **jeder spätere `fork()` und `execve()` aufruft** — dereferenziert drei Felder ohne NULL-Prüfung:```c
	get_group_info(new->group_info);        /* refcount_inc(&gi->usage) */
	get_uid(new->user);                     /* refcount_inc(&u->__count) */
	new->ucounts = get_ucounts(new->ucounts);

Eine gefälschte Cred, die NULL lässt, ergibt uid 0 und stürzt die Maschine dann beim ersten execve ab — das heißt, der Exploit würde Erfolg melden und die Box sofort zerstören. Die Kette zeigt daher user, ucounts und group_info auf die echten Kernel-Globals root_user, init_ucounts und init_groups, deren Adressen von derselben _text-Basis stammen. Mit diesen gesetzt hält der gerootete Thread CAP_SETUID über init_user_ns, ruft also setresuid(0,0,0) auf, und der Kernel installiert eine saubere, kernel-allozierte Root-Cred über der gefälschten. Erst danach wird irgendetwas anderes getan.

Privileg wird an den Elternprozess durch eine setuid-root-Kopie der Exploit-Binärdatei übergeben, die sich selbst entfernt, bevor sie die Shell ausführt, sodass die Root-Shell im Hauptprozess auf einem sauberen Terminal läuft und nichts Setuid zurückbleibt.

Welche Systeme betroffen sind

uname -r reicht nicht. Hersteller-Kernel portieren Binder-Fixes routinemäßig zurück, ohne auf die entsprechende Mainline-Version zu wechseln; prüfen Sie den Quellcode oder das Paket-Changelog.

Der Fix ist mit Cc: stable getaggt, sodass stabile und Hersteller-Zweige Backports erhalten.

Wo der Bug ist und wo er erreichbar ist

Das sind unterschiedliche Fragen, und die zweite ist die, die die Auswirkung entscheidet.

Der verwundbare Code wird überall dort kompiliert, wo CONFIG_ANDROID_BINDER_IPC gesetzt ist. Das schließt die allgemeinen Distributionen ein — aber auf allen untersuchten ist der Treiber ein Modul, das standardmäßig nicht geladen wird, und selbst wenn es geladen wird, registriert init_binder_device() das Misc-Device, ohne miscdev.mode zu setzen, sodass devtmpfs /dev/binder als 0600 root:root erstellt. Auf Android ist es ueventd, das es auf 0666 öffnet, was genau der Grund ist, warum der Bug dort relevant ist und hier größtenteils nicht.

Konfigurationen, gelesen aus den ausgelieferten Kernel-Paketen der Distributionen selbst:

Die praktische Gefährdung auf einer Desktop-Distribution ist daher indirekt: alles, was binder lädt und öffnet — Waydroid, Anbox, ein Android- Emulator oder Container-Runtime — führt genau die Android-Erreichbarkeit wieder ein auf einer Maschine, deren Kernel den Bug immer noch hat.

Was jede Härtungsoption den Exploit kostet

Diese betreffen nicht die Verwundbarkeit; sie betreffen diesen Exploit.

Validierte Ziele

Beide Kernel sind unveränderte Upstream-Bäume. Nichts ist gepatcht.

CONFIG_KASAN_GENERIC im ersten Labor ist ein Detektor, kein Aktivator: der Race ist ohne es identisch. CONFIG_PREEMPT ist eine echte Voraussetzung und das, was Android ausliefert.

Die Architektur ist kein Faktor für den Bug — es ist ein Lifetime-Fehler in architekturunabhängigem C. Sie ist sehr wohl ein Faktor für den Exploit: die Gadgets, der Prefetch-Kanal und das Direct-Map-Layout sind alle x86_64.

Bauen und Ausführen

Voraussetzungen: clang, lld, make, cpio, gzip, qemu-system-x86_64, docker (nur zum Zusammenbauen des Debian-Rootfs), ein lokaler Klon des Linux-Git- Baums und gcc.

Der Exploit, auf einem System, das bereits verwundbar ist```sh

gcc -O2 -pthread -o exploit exploit.c ./exploit

root@kitploit:~
Für einen anderen Kernel als den in `demo/` extrahiere zuerst dessen Offsets:```sh
./mkoffsets.sh /path/to/vmlinux > offsets.h
gcc -O2 -pthread -DEXPLOIT_OFFSETS='"offsets.h"' -o exploit exploit.c

Tunables, alle optional, alle aus der Umgebung gelesen: CVE64468_SECONDS, CVE64468_THREADS, CVE64468_SPRAY_PERCENT, CVE64468_CALL_OFFSET_MB, CVE64468_KASLR_ATTEMPTS, CVE64468_DELAY_MAX_US, CVE64468_DELAY_STEP_US, CVE64468_STAGGER_US, CVE64468_VERBOSE, CVE64468_SHELL.

Das Memory-Safety-Labor```sh

LINUX_GIT=/path/to/linux ./lab/build.sh # x86_64 LINUX_GIT=/path/to/linux TARGET_ARCH=arm64 ./lab/build.sh # arm64 ./run.sh vulnerable ./verify.sh 600 16

root@kitploit:~
The script creates two detached worktrees at the commits above, refuses to run
if either worktree is dirty, verifies that the vulnerable tree lacks the fix and
the fixed tree carries it, builds both kernels, and packs the proof of concept
into an initramfs.

### The exploitation laboratory```sh
./demo/build-kernel.sh     # vulnerable kernel, KASAN off, hardening on
./demo/build-rootfs.sh     # Debian 13 userland + gcc, as an initramfs
./demo/run-demo.sh         # boot it; this is what the recording shows
./demo/verify.sh logs/     # unattended reliability run, one guest per trial

demo/run-demo.sh bootet den Gast und übergibt die Konsole an uid 1000, der exploit.c mit dem eigenen gcc des Gasts kompiliert und ausführt.

Kernel-Images, Worktrees, Rootfs-Bäume und Initramfs sind Labor-Artefakte und werden nicht versioniert.

Reale Läufe

Speichersicherheit, verwundbar versus gepatcht

docs/example-output.txt ist das echte Transkript des verwundbaren Kernels, einschließlich des KASAN-Berichts; docs/patched-negative-output.txt ist die Kontrolle des gepatchten Kernels; docs/e2e-results.json ist das maschinenlesbare Ergebnis.

Die gemeldete Aufrufkette stimmt exakt mit dem Upstream-Bericht überein:``` BUG: KASAN: slab-use-after-free in queued_spin_lock_slowpath+0x62/0x6a0 Read of size 4 at addr ffff888100282270 by task cve64468-B/91 CPU: 6 UID: 1000 PID: 91 Comm: cve64468-B Not tainted 7.2.0-rc1+ #2 PREEMPT _raw_spin_lock+0x55/0x60 binder_free_transaction+0x80/0x1f0 binder_send_failed_reply+0x98/0x340 binder_thread_release+0x528/0x5e0 binder_ioctl+0x1ca/0xeb0 __x64_sys_ioctl+0x8c9/0xcf0

Allocated by task 93: binder_open+0xb5/0x7b0

Freed by task 9: kfree+0x113/0x310 binder_deferred_func+0x104b/0x1180 process_scheduled_works+0x69f/0xbb0

root@kitploit:~
`UID: 1000` ist die eigene unprivilegierte Identität des Proof of Concept: das Opfer
`binder_proc` wird von `binder_open()` allokiert, von der verzögerten Binder-Workqueue freigegeben und vom Walker nach der Freigabe gelesen.

Aufgezeichneter Lauf, 2026-08-16, `./verify.sh 1200 16 3` — drei gleichzeitige QEMU/KVM-Gäste pro Variante, je 10 vCPUs, 16 Threads, 1200 s pro Variante, **keine Kernel-Patches auf beiden Seiten**:

| | Verwundbar `114a116aaa5f` | Behoben `f223d27a546c` |
| --- | --- | --- |
| Versuche | 158.384 | 158.471 |
| Walks | 2.534.144 | 2.535.536 |
| Setup-Fehler | 0 | 0 |
| Verwundbarer Zugriff (`Unexpected outstanding_txns -1`) | 1.127 | 934 |
| **KASAN `slab-use-after-free`** | **8** | **0** |

Das ist der Unterschied: dieselbe Arbeitslast, dieselbe Versuchszahl bis auf
0,06 %, und der Use-after-Free tritt nur auf dem ungepatchten verwundbaren Kernel auf.

Der verwundbare Zugriff erscheint auf *beiden* Kerneln, und das ist erwartet — der Fix
verhindert, dass der Prozess innerhalb des Fensters freigegeben wird, nicht die zweite
Dekrementierung von `outstanding_txns`. Deshalb wird diese Zeile nur als billiges
Orakel und nie als Unterscheidungskriterium verwendet.

### Privilege Escalation

![Live-QEMU/KVM-Lauf: Debian-13-Gast, unprivilegierter Benutzer kompiliert exploit.c mit dem eigenen gcc des Gasts und endet an einer Root-Eingabeaufforderung](https://assets.kitploit.com/production/public/readmes/54229/da1e333d7516671ffab64e10d89604ee2a8043de1c48306abc03aaeee88d0d62/205a1253ab216ae4b62a65f761fffb9f0329bcbf9e4feb38563647f68ad2c5e0-display-v1.webp)

`docs/lpe-output.txt` ist ein echtes Transkript aus einem Demonstrationsgast, und
`docs/lpe-demo.cast` ist die vollständige, unbearbeitete Asciinema-Aufnahme, aus der die obige Animation gerendert wurde (`asciinema play docs/lpe-demo.cast` spielt sie in ihrer Gesamtheit ab). Die Animation blendet den langen Renn-Mittelteil dieser Aufnahme aus — die serielle Konsole des Gasts streamt Binder-Debug für das gesamte ~24-minütige Rennen, was sich zu zig Megabyte scrollendem Log rendern würde — und behält die Debian-Präambel und die Root-Shell; die eigene Zeile des Exploits `hit after 26661 attempts ... in 1437s` gibt genau an, was ausgeblendet wurde. Beides sind Einzelläufe: Ein Gast, der innerhalb seines Budgets nicht gewinnt, fährt herunter, und die Aufnahme wird einfach wiederholt, statt zu einem Sieg geschnitten zu werden.

Siehe *Zuverlässigkeit* unten für die gemessene Erfolgsrate.

## Zuverlässigkeit

Das Rennen ist in beiden Punkten probabilistisch, daher ist ein fehlgeschlagener Lauf das erwartete Verhalten — manchmal — und kein defekter Exploit.

### Speichersicherheit

Abgeleitete Raten auf dem verwundbaren Kernel, aus dem obigen Lauf:

| Menge | Wert |
| --- | --- |
| Versuchsrate | ~44 Versuche/s pro Gast, ~132/s über drei |
| Verwundbarer Zugriff | 7,1e-3 pro Versuch |
| Freigabe, die innerhalb des Fensters landet, gegeben der Zugriff | 7,1e-3 |
| KASAN-Bericht | ~1 pro 20.000 Versuche, d. h. grob einer alle 2,5 Minuten bei dieser Rate |

### Privilege Escalation

Jeder Gast ist ein unabhängiger Versuch: sein eigenes KASLR, seine eigene Direct-Map-Randomisierung, und ein Gast, der gewinnt, hört auf zu rasen. Die Zahl ist daher eine **Erfolgsrate pro Boot**, nicht pro Versuch.

Aufgezeichnete Kampagne, 2026-08-16 23:32 UTC, `GUESTS=6 CPUS=8 MEMORY_MB=9216 ./demo/verify.sh`
— sechs gleichzeitige QEMU/KVM-Gäste, ungepatchtes `114a116aaa5f`, Debian-13-Userland,
60 Minuten Budget jeweils, Exploit im Gast kompiliert, als uid 1000 gestartet:

| | |
| --- | --- |
| Gäste, die uid 0 erreichten | **2 von 6** |
| Zeit bis Root | 623 s und 1.344 s |
| Versuche beim gewinnenden Rennen | 13.839 und 31.125 |
| Versuche jedes Gasts, der nicht gewann | ~95.000 über die vollen 3.600 s |
| Gesamtversuche über die Kampagne | 427.676 (6,8 M Stack-Walks) |
| Beobachtete verwundbare Zugriffe (`Unexpected outstanding_txns -1`) | 256 |
| **Kernel-Abstürze, Oopses oder Panics** | **0**, in 9 Gaststunden |

Zwei Dinge sind aus dieser Tabelle lesenswert.

**Im Wesentlichen wurde jedes gewonnene Rennen zu Root.** Das KASAN-Labor misst die
Wahrscheinlichkeit, dass die Freigabe innerhalb des Fensters landet, gegeben den verwundbaren Zugriff,
bei 7,1e-3. Angewandt auf die hier beobachteten 256 Zugriffe sagt das ~1,8
Use-after-Frees über die Kampagne voraus — und 2 Roots wurden erzielt. Die Rückgewinnung,
die Adressermittlung und die Kette sind nicht der Engpass; das Rennen ist es.

**Nichts ist abgestürzt.** Kein Gast nahm in neun Gaststunden einen Oops, einschließlich der
vier, die nie gewannen. Entweder feuert die Kette gegen eine korrekt platzierte, gesprayte
Seite, oder sie feuert überhaupt nie — wofür die Mehrheitsentscheidungs-Verweigerung in
Stufe 2 da ist.

Diese Verweigerung feuert in der Praxis. Das gleichzeitige Booten von sechs Gästen auf einem bereits ausgelasteten Host erzeugte einen, der bei Stufe 2 aufgab mit```
[*] direct map not found; refusing to fire at an unverified address

und ohne Rennen beendet. Das ist das beabsichtigte Verhalten: Ein verschwendeter Boot ist das korrekte Ergebnis, wenn der Timing-Kanal keinen Konsens erreichen kann, und es ist viel besser als die Alternative, die Kette auf eine Adresse zu feuern, die nie bestätigt wurde.

docs/lpe-results.json enthält die maschinenlesbare Form, einschließlich der SHA-256 des exakt verwendeten Kernels und Initramfs.

Warum mehrere Gäste und warum ein überbuchter Host

Das gleichzeitige Ausführen der Gäste dient nicht nur der Parallelität. Auf einem ausgelasteten Host plant KVM die Gast-vCPUs um, und genau das ist die Verzögerung, die die zweite Bedingung benötigt: Der Walker muss seine CPU zwischen dem Freigeben von t->lock und dem Ergreifen der inneren Sperre des Opfers verlieren. Ein einzelner Gast auf einem im Leerlauf befindlichen Host wurde mit etwa einem Zwanzigstel der anfälligen Zugriffsrate von drei gleichzeitigen Gästen gemessen.

Es gibt jedoch eine Obergrenze. Bei acht Gästen mit je 8 vCPUs auf einem Host mit 32 Threads, mit ~5 GiB Direct-Map-Spray pro Gast, ging der Host in den Swap über und drei der acht Gäste machten überhaupt keine Fortschritte. Sechs ist die ausgelieferte Standardeinstellung.

Die optionalen Preemption-Helfer-Threads im Gast wurden gemessen und kosteten etwa das Dreifache der Versuchsrate, ohne die Trefferquote zu verbessern, und werden nicht verwendet.

Ein Umgebungsfaktor erwies sich als wichtiger als erwartet: binders eigene Debug-Ausgabe. Mit binder.debug_mask auf dem Standardwert sendet der Treiber während des Rennens eine große Menge an ratenbegrenztem pr_info-Verkehr, und der dadurch erzeugte Druck auf printk und Console-Lock verlängert genau das Preemption-Fenster, das die zweite Bedingung benötigt. Das Stummschalten mit binder.debug_mask=0 für eine aufgeräumtere Konsole – das Naheliegende für eine Aufnahme – senkte messbar die Trefferquote im Test: Gäste liefen weit über die Versuchszahlen beider Gewinner hinaus, ohne einen Treffer. demo/run-demo.sh belässt binder debug daher auf dem Standardwert, und eine ruhige Konsole ist ein explizites Opt-in. Dies ist eine Eigenschaft des Labors, nicht des Exploits – aber es ist eine gute Veranschaulichung dafür, wie sehr dieses Rennen von systemweitem Timing-Jitter abhängt und nicht von etwas, das der Exploit selbst kontrolliert.

Sicherheit

  • Beide Gäste sind Wegwerf-Initramfs-Images. Es wird nichts auf die Gast- oder Host-Festplatte geschrieben.
  • Der Proof of Concept öffnet nur /dev/binder, sendet Binder-Transaktionen und beendet Binder-Threads. Er installiert nichts und hinterlässt nichts.
  • Der Exploit schreibt eine Datei: eine setuid-root-Kopie seiner selbst, die verwendet wird, um Privilegien vom gewinnenden Thread an den Elternprozess zu übergeben. Er entfernt diese Kopie vor dem Ausführen der Shell per unlink, sodass nach dem Lauf nichts Setuid überlebt.
  • Ein verlorenes Rennen kann den Gast zum Absturz (Panic) oder Hängen bringen; das Memory-Safety-Labor bootet mit panic=1 oops=panic, sodass ein Lauf endet, anstatt mit beschädigtem Zustand fortzufahren. Starte immer von einem sauberen Boot neu.

Danksagungen

  • Exploit-Autor: A. Ramos <[email protected]> (Twitter: @aramosf)
  • Schwachstellenentdeckung und -meldung: Alice Ryhl, Google
  • Upstream-Fix: Carlos Llamas, Google
  • Prefetch-KASLR-Seitenkanal: abgeleitet von KASLD, Copyright (c) 2019 Brendan Coles, MIT-lizenziert. Die Direct-Map-Variante ist hier neue Arbeit.

Suche nach öffentlichen Exploits

Geprüft am 2026-08-16 mit SearchSploit (lokale Exploit-DB-Kopie) und Websuche nach CVE-2026-64468, binder_free_transaction und f223d27a546c. Es wurde kein öffentlicher Exploit oder Proof of Concept für diese CVE gefunden; SearchSploit liefert nur nicht verwandte, ältere Android-Binder-Einträge. Dies ist eine punktuelle Prüfung, keine dauerhafte Garantie.

Tool herunterladen
StatusBehauptung
BestätigtDie Schwachstelle ist real, von einem Prozess ohne Privilegien erreichbar, und der Upstream-Fix beseitigt sie.
DemonstriertDie verwundbare Dereferenzierung eines sterbenden binder_proc wird auf dem nicht gepatchten Kernel natürlich und wiederholt erreicht, auf dem gepatchten Kernel nie.
DemonstriertDer vollständige, von KASAN gemeldete Use-after-free auf dem nicht gepatchten Kernel.
DemonstriertRückgewinnung des freigegebenen binder_proc mit angreiferkontrollierten Bytes, Übernahme der Kernel-Kontrollflusssteuerung und Privilegienerweiterung auf uid 0 von einem Benutzer ohne Privilegien, auf x86_64.
Nicht behauptetJegliches Ergebnis auf einem bestimmten Herstellergerät oder Android-Gerät. Nur die unten aufgeführten Upstream-Kernel wurden auf x86_64 getestet.
Nicht behauptetDass der mitgelieferte Exploit unverändert gegen einen Distributionskernel funktioniert. Siehe Welche Systeme betroffen sind: Er benötigt pro Kernel spezifische Offsets, und auf jeder untersuchten Allzweck-Distribution ist das Binder-Gerät für einen Benutzer ohne Privilegien ohnehin nicht erreichbar.
OffsetFeldWas der Walker tut
108int outstanding_txnsdekrementiert es
113bool is_frozenliest es
120wait_queue_head_t freeze_waitdurchläuft es, wenn outstanding_txns == 0 && is_frozen
624spinlock_t inner_locknimmt und gibt es frei
ZustandCommitAnmerkungen
Eingeführte Abstammunga370003cc301Benannt durch das Upstream-Fixes:-Tag
Als verwundbar validiert114a116aaa5fDirekter Eltern-Commit des Fixes; trägt den benachbarten CVE-2026-64469-Fix, sodass das Paar CVE-2026-64468 allein isoliert
Korrigierte Mainlinef223d27a546cDer getestete Fix
DistributionKernelANDROID_BINDER_IPCDeviceSLAB_BUCKETSRANDOM_KMALLOC_CACHESUnprivilegiert erreichbar?
Debian 13 (trixie)6.12.101mANDROID_BINDER_DEVICES="binder", BINDERFS ausyausNein — Modul nicht geladen; /dev/binder ist 0600
Debian 12 (bookworm)6.1.0mANDROID_BINDER_DEVICES="binder", BINDERFS ausn/a (vor 6.11)n/a (vor 6.6)Nein — dasselbe
Ubuntu 24.04 LTS6.8.0mBINDERFS=m, ANDROID_BINDER_DEVICES=""n/a (vor 6.11)yNein — benötigt Root für mount -t binder
Ubuntu 22.04 LTS5.15.0mBINDERFS=m, ANDROID_BINDER_DEVICES=""n/an/aNein — dasselbe
Android (AOSP / Hersteller)6.1, 6.6, 6.12 GKIy/dev/binder, /dev/hwbinder, /dev/vndbinder——Ja — der Treiber ist die Plattform-IPC und weltweit zugänglich
OptionWirkung hier
CONFIG_SLAB_BUCKETS (6.11+)Tödlich für dieses Reclaim. Es isoliert msg_msg in eigene kmalloc-Buckets, sodass die Pumpe nie im Slot von binder_proc landen kann. Debian 13 setzt es. Eine weitere nicht berücksichtigte 1-KiB-Allokation müsste gefunden werden. Kernel 6.6, den die interessierenden Android-Geräte ausführen, stammt vollständig von vorher.
CONFIG_RANDOM_KMALLOC_CACHES (6.6+)Teilt kmalloc-1k in mehrere Caches nach Aufrufstelle auf, sodass die Pumpe denselben treffen muss; eine 1-zu-16-Abgabe auf das Reclaim, keine Wand. Ubuntu setzt es, Debian nicht.
Page-Table-Isolation (nopti nicht verwendet)Tödlich für die Adressermittlung. Prefetch kann Kernel-Text mit aktivem PTI nicht sehen, und der Exploit erkennt dies und stoppt. PTI ist auf jeder obigen Distribution kompiliert, aber die CPU entscheidet, ob es aktiv ist: Es ist aus auf Hardware, die nicht von Meltdown betroffen ist, wo diese Ergebnisse gemessen wurden.
CONFIG_SLAB_FREELIST_RANDOM, ..._HARDENEDIm Labor aktiviert, wie Distributionen sie ausliefern. Keine messbare Wirkung: Die Pumpe sagt die Freelist-Reihenfolge nicht voraus, sie allokiert einfach sehr viele Objekte.
KASLR (RANDOMIZE_BASE, RANDOMIZE_MEMORY)Aktiviert. Durch die Prefetch-Stufen besiegt; kein nokaslr.
Per-Kernel-OffsetsDie Kette benötigt commit_creds, drei Cred-bezogene Globals und zwei Gadgets als Offsets von _text. mkoffsets.sh extrahiert sie aus einem Ziel-vmlinux; ohne sie feuert der Exploit auf falsche Adressen. Dies ist eine Eigenschaft jedes Kernel-Exploits pro Build, keine Verteidigung.
Memory-Safety-Labor (lab/)Exploitation-Labor (demo/)
Kernel-Version7.2.0-rc1+7.2.0-rc1+
Verwundbarer Commit114a116aaa5f0295376cdf12da743c5bce3b20cederselbe
Fix-Commitf223d27a546c1e1f48d38fd67760e78f068fe8c4— (Exploitation wird nur auf dem verwundbaren Kernel gemessen)
Architekturx86_64 (KVM) und arm64 (TCG)x86_64 (KVM)
CompilerUbuntu clang 21.1.8 / LLD 21.1.8derselbe
KASANan — es ist der Detektoraus — es ändert das Slab-Layout und würde jedes Reclaim unrepresentativ machen
Slab-Härtung—SLAB_FREELIST_RANDOM, SLAB_FREELIST_HARDENED an; SLAB_BUCKETS, RANDOM_KMALLOC_CACHES aus
KASLR—RANDOMIZE_BASE, RANDOMIZE_MEMORY an
Userlandminimale InitramfsDebian GNU/Linux 13 (trixie), mit dem eigenen gcc der Distribution
Startidentitätuid 1000, gid 1000, keine Capabilities, keine Namespacesdieselbe
Boot-Kommandozeileconsole=ttyS0 rdinit=/init panic=1 oops=panic kasan_multi_shot=1console=ttyS0 loglevel=4 rdinit=/init — kein nopti, kein nokaslr, kein mitigations=off