
Proof-of-Concept-Exploit für CVE-2026-31431, eine lokale Privilegieneskalation im Linux-Kernel über splice() und manipulierte Socket-Optionen, die einen beliebigen Schreibzugriff auf den Kernel-Speicher ermöglicht, um setuid-Binärdateien zu überschreiben.
copyFail.py ist ein Proof-of-Concept-Exploit (PoC), der eine lokale Privilegienausweitung im Linux-Kernel demonstriert. Durch den Missbrauch des Systemaufrufs splice() in Kombination mit manipulierten Socket-Optionen erlangt der Exploit die Fähigkeit, beliebigen Kernel-Speicher zu korrumpieren. Er nutzt diese Fähigkeit, um eine beliebige setuid-Binärdatei auf der Festplatte mit einer eigenen ELF-Datei zu überschreiben und führt anschließend die manipulierte Binärdatei aus, um erhöhte Privilegien zu erlangen.
Angriffstyp: Lokale Privilegienausweitung (LPE) Auswirkung: Vollständiger Root-Shell-Zugriff (bei Zielsetzung auf setuid-Root-Binärdateien) Komplexität: Gering (ein einzelnes Skript, keine externen Abhängigkeiten)
Verwendung:
./copyFail_cleaned.py [target_binary] # Standard: /usr/bin/su
Das ursprüngliche PoC hatte /usr/bin/su fest verdrahtet, aber die zugrunde liegende Kernel-Schwachstelle betrifft jede Datei — der Angreifer kann jede beliebige setuid-Binärdatei überschreiben. Die bereinigte Version akzeptiert ein optionales Befehlszeilenargument zur Angabe des Ziels, wodurch dieses Verhalten explizit wird.
Der Linux-Kernel stellt einen Systemaufruf splice() bereit, der Daten zwischen zwei Dateideskriptoren vollständig innerhalb des Kernels kopiert — ohne die Daten zuvor in den Userspace zu kopieren. Dies wird als "Zero-Copy-I/O" bezeichnet und dient der Leistungssteigerung in Hochdurchsatz-Anwendungen wie Webservern und Reverse-Proxys.
Die Schwachstelle entsteht, weil der Kernel den internen Zustand bestimmter Socket-Strukturen nicht ordnungsgemäß validiert, wenn splice() auf PACKET-Sockets angewendet wird, die mit nicht unterstützten protokollspezifischen Socket-Optionen konfiguriert wurden. Durch sorgfältiges Gestalten dieser Optionen kann ein Angreifer Kernel-Zeiger korrumpieren und dann splice() verwenden, um Kernel-Speicherschreibvorgänge auf beliebige Speicherorte umzuleiten.
Einfach ausgedrückt: Der Kernel hat eine Abkürzung zum internen Verschieben von Daten, und dieser Exploit bringt diese Abkürzung dazu, Daten an die falsche Stelle zu schreiben — konkret in die In-Memory-Kopie einer System-Binärdatei.
Der Exploit folgt einer klaren Abfolge von Schritten:
Der Exploit erstellt einen rohen PACKET-Socket (AF_PACKET / SOCK_RAW) und bindet ihn an absichtlich fehlerhafte Adressen. Anschließend ruft er setsockopt() mit SOL_PNIO (Protokollebene 279) auf, einer Solaris-Level-Konstante, die Linux nicht erkennt.
Warum das wichtig ist: Wenn der Kernel in setsockopt() auf eine nicht unterstützte Protokollebene stößt, fällt er auf einen generischen Handler zurück, der die Daten nicht ordnungsgemäß validiert. Dies hinterlässt Kernel-Speicher in einem inkonsistenten, ausnutzbaren Zustand.
Nach dem Aufbau einer Socket-Verbindung über accept() sendet der Exploit manipulierte Ancillary- (Kontroll-) Nachrichten über sendmsg(). Diese Nachrichten haben absichtlich nicht übereinstimmende Längen-Header — einige geben an, kürzer zu sein, als sie sind, andere geben an, länger zu sein.
Warum das wichtig ist: Der Kontrollnachrichten-Parser des Kernels verwendet diese Längen für Zeigerarithmetik. Nicht übereinstimmende Längen führen dazu, dass der Parser über die beabsichtigten Grenzen hinaus liest oder schreibt, wodurch benachbarte Kernel-Strukturen korrumpiert werden — insbesondere Zeiger, denen splice() später folgen wird.
Der Exploit erstellt eine Pipe und ruft splice() zweimal auf:
/usr/bin/su in die Pipe.Warum das wichtig ist: Der splice()-Aufruf folgt den in Schritt 2 platzierten korrumpierten Kernel-Zeigern, wodurch er Daten an eine vom Angreifer gewählte Kernel-Speicheradresse schreibt, anstatt in den Socket-Puffer. Dies verleiht dem Exploit eine beliebige Kernel-Speicherschreib-Primitive.
Die Nutzlast des Exploits ist eine 160-Byte-ELF-Datei (siehe unten). Jeder Aufruf der Exploit-Funktion schreibt genau 4 Bytes. Die Hauptschleife läuft also 40 Mal (160 / 4 = 40), wobei jedes Mal:
Nach allen 40 Iterationen wurden die ersten 160 Bytes von /usr/bin/su überschrieben.
Schließlich führt der Exploit os.system("su") aus. Der Kernel lädt das modifizierte /usr/bin/su aus seinem Page-Cache (der nun den Shellcode des Angreifers anstelle der echten su-Binärdatei enthält), und der Shellcode wird ausgeführt.
Die eingebettete Nutzlast dekomprimiert zu einer 160-Byte-x86-64-ELF-Datei, die diesen Shellcode enthält:
; Versuch von Syscall 105 (execveat) — möglicherweise auf älteren Kerneln nicht verfügbar
xor eax, eax
xor edi, edi
mov al, 0x69 ; Syscall 105
syscall
; Fallback: Syscall 59 (execve) — der zuverlässige Pfad
lea rdi, [rip+0xf] ; RDI = Zeiger auf "/bin/sh"
xor esi, esi ; RSI = NULL (envp)
push 0x3b ; Syscall-Nummer 59
pop eax
cdq ; RDX = NULL (argv)
syscall
; Sauber beenden
xor edi, edi
push 0x3c ; Syscall 60 (exit)
pop eax
syscall
; Datensektion: "/bin/sh\0\0\0"
Was es tut: Startet /bin/sh ohne Argumente und erbt dabei die Privilegien des Prozesses, der su ausgeführt hat. Wenn der Exploit als Root läuft (oder mit Capabilities, die su Root-Zugriff gewähren), ist die resultierende Shell eine Root-Shell.
Was es NICHT tut:
Dies ist eine einfache Einmal-Privilegienausweitung, konsistent mit einem Forschungs-PoC.
copyFail.py
├── hex_to_bytes() — Hex-String-Decoder
├── exploit_splice() — Kern-Exploit (Socket-Einrichtung + Korruption + splice)
│ ├── Phase 1: PACKET-Socket erstellen, binden, setsockopt (SOL_PNIO)
│ ├── Phase 2: accept()-Verbindung
│ ├── Phase 3: sendmsg() mit manipulierten Ancillary-Nachrichten
│ ├── Phase 4: pipe() + splice() zur Korruption des Kernel-Speichers
│ └── Phase 5: recv()-Versuch (verfestigt die Korruption)
└── Hauptschleife:
├── /usr/bin/su öffnen (nur lesen)
├── Eingebettete Nutzlast dekomprimieren (zlib → 160-Byte-ELF)
├── Schleife: 4 Bytes pro Iteration injizieren (insgesamt 40 Iterationen)
└── Manipuliertes su ausführen → Root-Shell
Der Exploit öffnet /usr/bin/su mit O_RDONLY (nur lesen). Er benötigt keinen Schreibzugriff, weil die Korruption über den Kernel-Page-Cache erfolgt — die In-Memory-Kopie der Datei, die der Kernel verwendet, bevor Änderungen auf die Festplatte geschrieben werden. Der splice()-basierte Schreibvorgang umgeht normale Dateiberechtigungen vollständig und geht direkt in den Kernel-Speicher.
/usr/bin/su-Binärdatei (Hash mit dem Paketmanager vergleichen)setsockopt()-Aufrufe mit unbekannten Protokollebenensplice()-Systemaufrufe auf nicht regulären Dateideskriptorensysctl verwenden, um die Erstellung von AF_PACKET-Sockets durch nicht privilegierte Benutzer einzuschränkencopyFail.py (wie verteilt)copyFail_cleaned.py (gleiches Verzeichnis)net/packet/, fs/splice.c, net/core/sock.cDieser Bericht dient ausschließlich der Sicherheitsforschung und defensiven Analyse.
| Konstante | Wert | Zweck im Exploit |
|---|
AF_PACKET | 17 | Rohe PACKET-Socket-Familie |
SOCK_RAW | 3 | Roher Socket-Typ |
SOL_PNIO | 279 | Nicht unterstützte Protokollebene (Solaris) |
MSG_DONTWAIT | 0x400 | Nicht blockierendes sendmsg-Flag |